FixCrash. Fix crashes on empty YAML nodes in pubspec.

When a pubspec.yaml file contains empty items in a list (e.g., in
`assets` or `workspace`) or empty keys in a map, the YAML parser
produces nodes with `null` values. Accessing these with `valueOrThrow`
caused a `TypeError: type 'Null' is not a subtype of type 'Object' in
type cast`.

This CL:
- Replaces `valueOrThrow` with `value` in `flutter_validator.dart` and `workspace_validator.dart`, allowing the existing type checks to handle the `null` case and report proper diagnostics instead of crashing.
- Handles null keys gracefully in `dependency_validator.dart`.
- Adds reproduction tests for all three validators.

Change-Id: I54638ffdc1c00c1fb44028da1a466337bc3dfd32
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/493620
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov
2026-04-08 10:00:39 -07:00
committed by Commit Queue
parent 384f76e596
commit 16854dc720
7 changed files with 52 additions and 6 deletions
@@ -120,7 +120,7 @@ void dependencyValidator(PubspecValidationContext ctx) {
ctx.reportErrorForNode(
packageName,
diag.unnecessaryDevDependency.withArguments(
package: packageName.valueOrThrow.toString(),
package: packageName.value.toString(),
),
);
}
@@ -5,7 +5,6 @@
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/pubspec/pubspec_validator.dart';
import 'package:analyzer/src/util/yaml.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
@@ -34,7 +33,7 @@ void flutterValidator(PubspecValidationContext ctx) {
for (var assetField in assetsField.nodes) {
if (assetField is YamlScalar) {
var entry = assetField.valueOrThrow;
Object? entry = assetField.value;
if (entry is! String) {
ctx.reportErrorForNode(assetField, diag.assetNotStringOrMap);
return;
@@ -48,7 +47,7 @@ void flutterValidator(PubspecValidationContext ctx) {
} else if (pathField is! YamlScalar) {
ctx.reportErrorForNode(pathField, diag.assetPathNotString);
} else {
var entry = pathField.valueOrThrow;
Object? entry = pathField.value;
if (entry is! String) {
ctx.reportErrorForNode(pathField, diag.assetNotString);
return;
@@ -4,7 +4,6 @@
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/pubspec/pubspec_validator.dart';
import 'package:analyzer/src/util/yaml.dart';
import 'package:glob/glob.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
@@ -22,7 +21,7 @@ void workspaceValidator(PubspecValidationContext ctx) {
for (var directoryField in workspaceField.nodes) {
if (directoryField is YamlScalar) {
var entry = directoryField.valueOrThrow;
Object? entry = directoryField.value;
if (entry is! String) {
ctx.reportErrorForNode(directoryField, diag.workspaceValueNotString);
return;
@@ -39,6 +39,18 @@ flutter:
''');
}
test_assetNotString_error_null() {
assertErrors(
'''
name: sample
flutter:
assets:
-
''',
[diag.assetNotStringOrMap],
);
}
test_assetNotString_noError() {
newFile('/sample/assets/my_icon.png', '');
assertNoErrors('''
@@ -27,6 +27,18 @@ flutter:
);
}
test_pathIsNull() {
assertErrors(
'''
name: sample
flutter:
assets:
- path:
''',
[diag.assetNotString],
);
}
test_pathIsString() {
newFile('/sample/assets/my_icon.png', '');
assertNoErrors('''
@@ -28,6 +28,19 @@ dev_dependencies:
);
}
test_unnecessaryDevDependency_error_null() {
assertErrors(
'''
name: sample
dependencies:
null: any
dev_dependencies:
null: any
''',
[diag.unnecessaryDevDependency],
);
}
test_unnecessaryDevDependency_noError() {
assertNoErrors('''
name: sample
@@ -105,6 +105,17 @@ workspace:
);
}
test_workspaceValueIsNull() {
assertErrors(
'''
name: sample
workspace:
-
''',
[diag.workspaceValueNotString],
);
}
test_workspaceValueIsString() {
newFolder('/sample/package1');
assertNoErrors('''