[CFE] Fix crash in expression compilation when evaluating inside extension type with type parameter
E.g. expression evaluation threw when serializing when trying to
evaluate inside `write` on
`extension type EnumSet<T extends Enum>(int _bits)` from the analyzer.
Change-Id: I4738b37c5431f11b11abff0e8d7183514a66b5dd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459441
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/base/incremental_compiler.dart b/pkg/front_end/lib/src/base/incremental_compiler.dart
index 1cdc458..b41bfe7 100644
--- a/pkg/front_end/lib/src/base/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/base/incremental_compiler.dart
@@ -1836,6 +1836,7 @@
LibraryBuilder libraryBuilder = compilationUnit.libraryBuilder;
List<VariableDeclarationImpl> extraKnownVariables = [];
String? usedMethodName = methodName;
+ Substitution? substitution;
if (scriptUri != null && offset != TreeNode.noOffset) {
Uri? scriptUriAsUri = Uri.tryParse(scriptUri);
if (scriptUriAsUri != null) {
@@ -1876,23 +1877,11 @@
);
}
- Map<TypeParameter, TypeParameterType> substitutionMap = {};
- Map<String, TypeParameter> typeDefinitionNamesMap = {};
- for (TypeParameter typeDefinition in typeDefinitions) {
- if (typeDefinition.name != null) {
- typeDefinitionNamesMap[typeDefinition.name!] = typeDefinition;
- }
- }
- for (TypeParameter typeParameter in foundScope.typeParameters) {
- TypeParameter? match = typeDefinitionNamesMap[typeParameter.name];
- if (match != null) {
- substitutionMap[typeParameter] = new TypeParameterType(
- match,
- match.computeNullabilityFromBound(),
+ substitution =
+ _calculateExpressionEvaluationTypeParameterSubstitution(
+ typeDefinitions,
+ foundScope.typeParameters,
);
- }
- }
- Substitution substitution = Substitution.fromMap(substitutionMap);
final bool alwaysInlineConstants = lastGoodKernelTarget
.backendTarget
@@ -2034,7 +2023,19 @@
// If we setup the extensionType (and later the
// `extensionThis`) we should also set the type correctly
// (at least in a non-static setting).
- usedDefinitions[syntheticThisName] = positionals.first.type;
+ if (substitution == null || substitution.isEmpty) {
+ // Re-do substitutions if the old one is empty - in case the
+ // finding of scope didn't find the right thing (e.g.
+ // sometimes the VM claims to be on the offset for a method
+ // name while having data as if it is inside the method).
+ substitution =
+ _calculateExpressionEvaluationTypeParameterSubstitution(
+ typeDefinitions,
+ subBuilder.invokeTarget?.function?.typeParameters,
+ );
+ }
+ usedDefinitions[syntheticThisName] = substitution
+ .substituteType(positionals.first.type);
}
isStatic = false;
}
@@ -2268,6 +2269,32 @@
}
// Coverage-ignore(suite): Not run.
+ Substitution _calculateExpressionEvaluationTypeParameterSubstitution(
+ List<TypeParameter> typeDefinitions,
+ List<TypeParameter>? typeParameters,
+ ) {
+ Map<TypeParameter, TypeParameterType> substitutionMap = {};
+ Map<String, TypeParameter> typeDefinitionNamesMap = {};
+ for (TypeParameter typeDefinition in typeDefinitions) {
+ if (typeDefinition.name != null) {
+ typeDefinitionNamesMap[typeDefinition.name!] = typeDefinition;
+ }
+ }
+ if (typeParameters != null) {
+ for (TypeParameter typeParameter in typeParameters) {
+ TypeParameter? match = typeDefinitionNamesMap[typeParameter.name];
+ if (match != null) {
+ substitutionMap[typeParameter] = new TypeParameterType(
+ match,
+ match.computeNullabilityFromBound(),
+ );
+ }
+ }
+ }
+ return Substitution.fromMap(substitutionMap);
+ }
+
+ // Coverage-ignore(suite): Not run.
bool _packagesEqual(Package? a, Package? b) {
if (a == null || b == null) return false;
if (a.name != b.name) return false;
diff --git a/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml
new file mode 100644
index 0000000..64636d3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml
@@ -0,0 +1,43 @@
+# Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Definition, offset, method etc extracted by starting the VM with
+# `-DDFE_VERBOSE=true`, e.g.
+# ```
+# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service inputFile.dart
+# ```
+# and then issuing the expression compilation.
+#
+# Inside an extension type with type parameters - shouldn't crash on
+# serialization.
+
+sources:
+ main.dart: |
+ import "dart:developer";
+
+ extension type Foo<T extends Enum>(int foo) {
+ void bar(T constant) {
+ debugger();
+ print(constant.index);
+ }
+ }
+
+ enum Bar { bar, baz }
+
+ void main() {
+ var x = Foo<Bar>(0);
+ x.bar(Bar.bar);
+ }
+
+definitions: ["#this", "constant"]
+definition_types: ["dart:core", "_Smi", "1", "0", "file:///usr/local/google/home/jensj/code/dart-sdk/sdk/t.dart", "Bar", "1", "0"]
+type_definitions: ["T"]
+type_bounds: ["dart:core", "Enum", "1", "0"]
+type_defaults: ["dart:core", "Enum", "1", "0"]
+method: "Foo.bar"
+static: true
+offset: 105
+scriptUri: main.dart
+expression: |
+ constant.index
diff --git a/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml.expect b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml.expect
new file mode 100644
index 0000000..2ec96a3
--- /dev/null
+++ b/pkg/front_end/testcases/expression/extension_type_with_type_parameter.expression.yaml.expect
@@ -0,0 +1,4 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr<T extends dart.core::Enum>(lowered #lib1::Foo<#lib2::debugExpr::T>% /* erasure=dart.core::int, declared=! */ #this, #lib2::debugExpr::T constant) → dynamic
+ return constant.{dart.core::Enum::index}{dart.core::int};
diff --git a/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml
new file mode 100644
index 0000000..81893f1
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml
@@ -0,0 +1,35 @@
+# Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# https://github.com/dart-lang/sdk/issues/56911
+
+sources: |
+ void main() {
+ List<ExtensionType> list = [new ExtensionType(0)];
+ list.forEach((ExtensionType input) {
+ print(input.value);
+ });
+ }
+
+ extension type ExtensionType._(String s) {
+ ExtensionType(int i) : this._("$i");
+ int get value => s.codeUnitAt(0);
+ }
+
+definitions: ["#this"]
+# String
+definition_types: ["dart:core", "_OneByteString", "1", "0"]
+type_definitions: []
+type_bounds: []
+type_defaults: []
+method: "ExtensionType.value"
+static: true
+offset: 231 # at the 'value' of 'int get value => s.codeUnitAt(0);' line.
+scriptUri: main.dart
+expression: |
+ () {
+ s;
+ s.codeUnitAt(0);
+ value;
+ }()
diff --git a/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml.expect
new file mode 100644
index 0000000..4aaaffe
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_03.expression.yaml.expect
@@ -0,0 +1,8 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr(lowered #lib1::ExtensionType% /* erasure=dart.core::String, declared=! */ #this) → dynamic
+ return (() → Null {
+ #this as{Unchecked} dart.core::String;
+ (#this as{Unchecked} dart.core::String).{dart.core::String::codeUnitAt}(0){(dart.core::int) → dart.core::int};
+ #lib1::ExtensionType|get#value(#this);
+ })(){() → Null};
diff --git a/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml
new file mode 100644
index 0000000..621f37f
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# https://github.com/dart-lang/sdk/issues/56911
+
+sources: |
+ void main() {
+ List<ExtensionType> list = [new ExtensionType("0")];
+ list.forEach((ExtensionType input) {
+ print(input.value);
+ });
+ }
+
+ extension type ExtensionType<E extends String>(E e) {
+ int get value => e.codeUnitAt(0);
+ }
+
+definitions: ["#this"]
+# String
+definition_types: ["dart:core", "_OneByteString", "1", "0"]
+type_definitions: ["E"]
+type_bounds: ["dart:core", "String", "1", "0"]
+type_defaults: ["dart:core", "String", "1", "0"]
+method: "ExtensionType.value"
+static: true
+offset: 205 # at the 'value' of 'int get value => s.codeUnitAt(0);' line.
+scriptUri: main.dart
+expression: |
+ () {
+ s;
+ s.codeUnitAt(0);
+ value;
+ }()
diff --git a/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml.expect b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml.expect
new file mode 100644
index 0000000..14b3383
--- /dev/null
+++ b/pkg/front_end/testcases/expression/issue_56911_04.expression.yaml.expect
@@ -0,0 +1,8 @@
+Errors: {
+}
+method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr<E extends dart.core::String>(lowered #lib1::ExtensionType<#lib2::debugExpr::E>% /* erasure=#lib2::debugExpr::E, declared=! */ #this) → dynamic
+ return (() → Null {
+ #this{dynamic}.s;
+ #this{dynamic}.s{dynamic}.codeUnitAt(0);
+ #lib1::ExtensionType|get#value<#lib2::debugExpr::E>(#this);
+ })(){() → Null};