[flutter_tools] Don't crash on non-UTF-8 plugin pubspec.yaml (#188976)
Fixes https://github.com/flutter/flutter/issues/188970
`_pluginFromPackage` caught only `YamlException` around the plugin's
`pubspec.yaml` read, so a pubspec containing non-UTF-8 bytes threw an
uncaught `FileSystemException` and crashed the tool. This mirrors the
sibling pubspec-cache reader (added in #184528), catching
`FileSystemException` and skipping the unreadable package.
## Tests
Added a regression test in `plugins_test.dart` that corrupts a plugin's
`pubspec.yaml` with invalid UTF-8 and asserts the tool skips it instead
of crashing. Verified it fails without the fix
(`FileSystemException: Invalid UTF-8 byte`) and passes with it.
## Design note
`dart:io` surfaces UTF-8 decode failures as a generic
`FileSystemException` with no narrower subtype, so this catches
`FileSystemException`, consistent with the sibling `buildPubspecCache`.
Genuine I/O errors on an existing pubspec are therefore also skipped
rather than crashing, which matches existing behavior and is preferable
to failing every command over one unreadable transitive dependency.
diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart
index ca9dc32..1d52046 100644
--- a/packages/flutter_tools/lib/src/flutter_plugins.dart
+++ b/packages/flutter_tools/lib/src/flutter_plugins.dart
@@ -125,6 +125,9 @@
} on YamlException catch (err) {
globals.printTrace('Failed to parse plugin manifest for $name: $err');
// Do nothing, potentially not a plugin.
+ } on FileSystemException catch (err) {
+ globals.printTrace('Failed to read plugin manifest for $name: $err');
+ // Do nothing, potentially not a plugin.
}
}
if (pubspec == null) {
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index 4ac9a79..1d2e063 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -4,6 +4,7 @@
import 'dart:convert';
import 'dart:io';
+import 'dart:typed_data';
import 'package:file/file.dart';
import 'package:file/memory.dart';
@@ -483,6 +484,36 @@
);
testUsingContext(
+ 'does not crash when a plugin pubspec.yaml is not valid UTF-8',
+ () async {
+ // Regression test for https://github.com/flutter/flutter/issues/188970.
+ final List<Directory> pluginDirs = createFakePlugins(fs, <String>[
+ 'good_plugin',
+ 'bad_plugin',
+ ]);
+ // Write bytes that are not valid UTF-8, so readAsString throws a FileSystemException.
+ pluginDirs[1].childFile('pubspec.yaml').writeAsBytesSync(
+ Uint8List.fromList(<int>[0xff, 0xfe, 0xfd]),
+ );
+
+ // The tool must not crash when a plugin's pubspec.yaml cannot be read.
+ final Future<List<Plugin>> pluginsFuture = findPlugins(flutterProject);
+ await expectLater(pluginsFuture, completes);
+
+ // The unreadable plugin is skipped, but the readable one is still found.
+ final List<Plugin> plugins = await pluginsFuture;
+ final pluginNames = <String>[for (final Plugin plugin in plugins) plugin.name];
+ expect(pluginNames, contains('good_plugin'));
+ expect(pluginNames, isNot(contains('bad_plugin')));
+ },
+ overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => FakeProcessManager.any(),
+ Pub: ThrowingPub.new,
+ },
+ );
+
+ testUsingContext(
'Refreshing the plugin list updates .flutter-plugins-dependencies if the plugins changed',
() async {
// Refresh the plugin list (we have no plugins).