mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Ignore replacement characters from vswhere.exe output (#104284)
Flutter uses `vswhere.exe` to find Visual Studio installations and determine if they satisfy Flutter's requirements. However, `vswhere.exe`'s JSON output is known to contain bad UTF-8. This change ignores bad UTF-8 as long as they affect JSON properties that are either unused, or, used only for display purposes by Flutter. Fixes: https://github.com/flutter/flutter/issues/102451
This commit is contained in:
parent
ac29c11aec
commit
c29a7a2d22
@ -26,10 +26,14 @@ const Encoding utf8ForTesting = cnv.utf8;
|
||||
/// that aren't UTF-8 and we're not quite sure how this is happening.
|
||||
/// This tells people to report a bug when they see this.
|
||||
class Utf8Codec extends Encoding {
|
||||
const Utf8Codec();
|
||||
const Utf8Codec({this.reportErrors = true});
|
||||
|
||||
final bool reportErrors;
|
||||
|
||||
@override
|
||||
Converter<List<int>, String> get decoder => const Utf8Decoder();
|
||||
Converter<List<int>, String> get decoder => reportErrors
|
||||
? const Utf8Decoder()
|
||||
: const Utf8Decoder(reportErrors: false);
|
||||
|
||||
@override
|
||||
Converter<String, List<int>> get encoder => cnv.utf8.encoder;
|
||||
|
@ -47,12 +47,14 @@ class VisualStudio {
|
||||
|
||||
/// The name of the Visual Studio install.
|
||||
///
|
||||
/// For instance: "Visual Studio Community 2019".
|
||||
/// For instance: "Visual Studio Community 2019". This should only be used for
|
||||
/// display purposes.
|
||||
String? get displayName => _bestVisualStudioDetails?.displayName;
|
||||
|
||||
/// The user-friendly version number of the Visual Studio install.
|
||||
///
|
||||
/// For instance: "15.4.0".
|
||||
/// For instance: "15.4.0". This should only be used for display purposes.
|
||||
/// Logic based off the installation's version should use the `fullVersion`.
|
||||
String? get displayVersion => _bestVisualStudioDetails?.catalogDisplayVersion;
|
||||
|
||||
/// The directory where Visual Studio is installed.
|
||||
@ -282,12 +284,15 @@ class VisualStudio {
|
||||
'-utf8',
|
||||
'-latest',
|
||||
];
|
||||
// Ignore replacement characters as vswhere.exe is known to output them.
|
||||
// See: https://github.com/flutter/flutter/issues/102451
|
||||
const Encoding encoding = Utf8Codec(reportErrors: false);
|
||||
final RunResult whereResult = _processUtils.runSync(<String>[
|
||||
_vswherePath,
|
||||
...defaultArguments,
|
||||
...?additionalArguments,
|
||||
...requirementArguments,
|
||||
], encoding: utf8);
|
||||
], encoding: encoding);
|
||||
if (whereResult.exitCode == 0) {
|
||||
final List<Map<String, dynamic>> installations =
|
||||
(json.decode(whereResult.stdout) as List<dynamic>).cast<Map<String, dynamic>>();
|
||||
@ -416,17 +421,40 @@ class VswhereDetails {
|
||||
|
||||
return VswhereDetails(
|
||||
meetsRequirements: meetsRequirements,
|
||||
installationPath: details['installationPath'] as String?,
|
||||
displayName: details['displayName'] as String?,
|
||||
fullVersion: details['installationVersion'] as String?,
|
||||
isComplete: details['isComplete'] as bool?,
|
||||
isLaunchable: details['isLaunchable'] as bool?,
|
||||
isRebootRequired: details['isRebootRequired'] as bool?,
|
||||
isPrerelease: details['isPrerelease'] as bool?,
|
||||
|
||||
// Below are strings that must be well-formed without replacement characters.
|
||||
installationPath: _validateString(details['installationPath'] as String?),
|
||||
fullVersion: _validateString(details['installationVersion'] as String?),
|
||||
|
||||
// Below are strings that are used only for display purposes and are allowed to
|
||||
// contain replacement characters.
|
||||
displayName: details['displayName'] as String?,
|
||||
catalogDisplayVersion: catalog == null ? null : catalog['productDisplayVersion'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Verify JSON strings from vswhere.exe output are valid.
|
||||
///
|
||||
/// The output of vswhere.exe is known to output replacement characters.
|
||||
/// Use this to ensure values that must be well-formed are valid. Strings that
|
||||
/// are only used for display purposes should skip this check.
|
||||
/// See: https://github.com/flutter/flutter/issues/102451
|
||||
static String? _validateString(String? value) {
|
||||
if (value != null && value.contains('\u{FFFD}')) {
|
||||
throwToolExit(
|
||||
'Bad UTF-8 encoding (U+FFFD; REPLACEMENT CHARACTER) found in string: $value. '
|
||||
'The Flutter team would greatly appreciate if you could file a bug explaining '
|
||||
'exactly what you were doing when this happened:\n'
|
||||
'https://github.com/flutter/flutter/issues/new/choose\n');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Whether the installation satisfies the required workloads and minimum version.
|
||||
final bool meetsRequirements;
|
||||
|
||||
|
@ -864,6 +864,85 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
// The output of vswhere.exe is known to contain bad UTF8.
|
||||
// See: https://github.com/flutter/flutter/issues/102451
|
||||
group('Correctly handles bad UTF-8 from vswhere.exe output', () {
|
||||
late VisualStudioFixture fixture;
|
||||
late VisualStudio visualStudio;
|
||||
|
||||
setUp(() {
|
||||
fixture = setUpVisualStudio();
|
||||
visualStudio = fixture.visualStudio;
|
||||
});
|
||||
|
||||
testWithoutContext('Ignores unicode replacement char in unused properties', () {
|
||||
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
||||
..['unused'] = 'Bad UTF8 \u{FFFD}';
|
||||
|
||||
setMockCompatibleVisualStudioInstallation(
|
||||
response,
|
||||
fixture.fileSystem,
|
||||
fixture.processManager,
|
||||
);
|
||||
|
||||
expect(visualStudio.isInstalled, true);
|
||||
expect(visualStudio.isAtLeastMinimumVersion, true);
|
||||
expect(visualStudio.hasNecessaryComponents, true);
|
||||
expect(visualStudio.cmakePath, equals(cmakePath));
|
||||
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
||||
});
|
||||
|
||||
testWithoutContext('Throws ToolExit on bad UTF-8 in installationPath', () {
|
||||
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
||||
..['installationPath'] = '\u{FFFD}';
|
||||
|
||||
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
||||
|
||||
expect(() => visualStudio.isInstalled,
|
||||
throwsToolExit(message: 'Bad UTF-8 encoding (U+FFFD; REPLACEMENT CHARACTER) found in string'));
|
||||
});
|
||||
|
||||
testWithoutContext('Throws ToolExit on bad UTF-8 in installationVersion', () {
|
||||
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
||||
..['installationVersion'] = '\u{FFFD}';
|
||||
|
||||
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
||||
|
||||
expect(() => visualStudio.isInstalled,
|
||||
throwsToolExit(message: 'Bad UTF-8 encoding (U+FFFD; REPLACEMENT CHARACTER) found in string'));
|
||||
});
|
||||
|
||||
testWithoutContext('Ignores bad UTF-8 in displayName', () {
|
||||
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
||||
..['displayName'] = '\u{FFFD}';
|
||||
|
||||
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
||||
|
||||
expect(visualStudio.isInstalled, true);
|
||||
expect(visualStudio.isAtLeastMinimumVersion, true);
|
||||
expect(visualStudio.hasNecessaryComponents, true);
|
||||
expect(visualStudio.cmakePath, equals(cmakePath));
|
||||
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
||||
expect(visualStudio.displayName, equals('\u{FFFD}'));
|
||||
});
|
||||
|
||||
testWithoutContext("Ignores bad UTF-8 in catalog's productDisplayVersion", () {
|
||||
final Map<String, dynamic> catalog = Map<String, dynamic>.of(_defaultResponse['catalog'] as Map<String, dynamic>)
|
||||
..['productDisplayVersion'] = '\u{FFFD}';
|
||||
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
||||
..['catalog'] = catalog;
|
||||
|
||||
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
||||
|
||||
expect(visualStudio.isInstalled, true);
|
||||
expect(visualStudio.isAtLeastMinimumVersion, true);
|
||||
expect(visualStudio.hasNecessaryComponents, true);
|
||||
expect(visualStudio.cmakePath, equals(cmakePath));
|
||||
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
||||
expect(visualStudio.displayVersion, equals('\u{FFFD}'));
|
||||
});
|
||||
});
|
||||
|
||||
group(VswhereDetails, () {
|
||||
test('Accepts empty JSON', () {
|
||||
const bool meetsRequirements = true;
|
||||
|
Loading…
Reference in New Issue
Block a user