Skip to content

Commit a0397d6

Browse files
jensjohaCommit Queue
authored andcommitted
[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>
1 parent 8db070e commit a0397d6

7 files changed

+176
-17
lines changed

pkg/front_end/lib/src/base/incremental_compiler.dart

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,7 @@ class IncrementalCompiler implements IncrementalKernelGenerator {
18361836
LibraryBuilder libraryBuilder = compilationUnit.libraryBuilder;
18371837
List<VariableDeclarationImpl> extraKnownVariables = [];
18381838
String? usedMethodName = methodName;
1839+
Substitution? substitution;
18391840
if (scriptUri != null && offset != TreeNode.noOffset) {
18401841
Uri? scriptUriAsUri = Uri.tryParse(scriptUri);
18411842
if (scriptUriAsUri != null) {
@@ -1876,23 +1877,11 @@ class IncrementalCompiler implements IncrementalKernelGenerator {
18761877
);
18771878
}
18781879

1879-
Map<TypeParameter, TypeParameterType> substitutionMap = {};
1880-
Map<String, TypeParameter> typeDefinitionNamesMap = {};
1881-
for (TypeParameter typeDefinition in typeDefinitions) {
1882-
if (typeDefinition.name != null) {
1883-
typeDefinitionNamesMap[typeDefinition.name!] = typeDefinition;
1884-
}
1885-
}
1886-
for (TypeParameter typeParameter in foundScope.typeParameters) {
1887-
TypeParameter? match = typeDefinitionNamesMap[typeParameter.name];
1888-
if (match != null) {
1889-
substitutionMap[typeParameter] = new TypeParameterType(
1890-
match,
1891-
match.computeNullabilityFromBound(),
1880+
substitution =
1881+
_calculateExpressionEvaluationTypeParameterSubstitution(
1882+
typeDefinitions,
1883+
foundScope.typeParameters,
18921884
);
1893-
}
1894-
}
1895-
Substitution substitution = Substitution.fromMap(substitutionMap);
18961885

18971886
final bool alwaysInlineConstants = lastGoodKernelTarget
18981887
.backendTarget
@@ -2034,7 +2023,19 @@ class IncrementalCompiler implements IncrementalKernelGenerator {
20342023
// If we setup the extensionType (and later the
20352024
// `extensionThis`) we should also set the type correctly
20362025
// (at least in a non-static setting).
2037-
usedDefinitions[syntheticThisName] = positionals.first.type;
2026+
if (substitution == null || substitution.isEmpty) {
2027+
// Re-do substitutions if the old one is empty - in case the
2028+
// finding of scope didn't find the right thing (e.g.
2029+
// sometimes the VM claims to be on the offset for a method
2030+
// name while having data as if it is inside the method).
2031+
substitution =
2032+
_calculateExpressionEvaluationTypeParameterSubstitution(
2033+
typeDefinitions,
2034+
subBuilder.invokeTarget?.function?.typeParameters,
2035+
);
2036+
}
2037+
usedDefinitions[syntheticThisName] = substitution
2038+
.substituteType(positionals.first.type);
20382039
}
20392040
isStatic = false;
20402041
}
@@ -2267,6 +2268,32 @@ class IncrementalCompiler implements IncrementalKernelGenerator {
22672268
});
22682269
}
22692270

2271+
// Coverage-ignore(suite): Not run.
2272+
Substitution _calculateExpressionEvaluationTypeParameterSubstitution(
2273+
List<TypeParameter> typeDefinitions,
2274+
List<TypeParameter>? typeParameters,
2275+
) {
2276+
Map<TypeParameter, TypeParameterType> substitutionMap = {};
2277+
Map<String, TypeParameter> typeDefinitionNamesMap = {};
2278+
for (TypeParameter typeDefinition in typeDefinitions) {
2279+
if (typeDefinition.name != null) {
2280+
typeDefinitionNamesMap[typeDefinition.name!] = typeDefinition;
2281+
}
2282+
}
2283+
if (typeParameters != null) {
2284+
for (TypeParameter typeParameter in typeParameters) {
2285+
TypeParameter? match = typeDefinitionNamesMap[typeParameter.name];
2286+
if (match != null) {
2287+
substitutionMap[typeParameter] = new TypeParameterType(
2288+
match,
2289+
match.computeNullabilityFromBound(),
2290+
);
2291+
}
2292+
}
2293+
}
2294+
return Substitution.fromMap(substitutionMap);
2295+
}
2296+
22702297
// Coverage-ignore(suite): Not run.
22712298
bool _packagesEqual(Package? a, Package? b) {
22722299
if (a == null || b == null) return false;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
5+
# Definition, offset, method etc extracted by starting the VM with
6+
# `-DDFE_VERBOSE=true`, e.g.
7+
# ```
8+
# out/ReleaseX64/dart -DDFE_VERBOSE=true --enable-vm-service inputFile.dart
9+
# ```
10+
# and then issuing the expression compilation.
11+
#
12+
# Inside an extension type with type parameters - shouldn't crash on
13+
# serialization.
14+
15+
sources:
16+
main.dart: |
17+
import "dart:developer";
18+
19+
extension type Foo<T extends Enum>(int foo) {
20+
void bar(T constant) {
21+
debugger();
22+
print(constant.index);
23+
}
24+
}
25+
26+
enum Bar { bar, baz }
27+
28+
void main() {
29+
var x = Foo<Bar>(0);
30+
x.bar(Bar.bar);
31+
}
32+
33+
definitions: ["#this", "constant"]
34+
definition_types: ["dart:core", "_Smi", "1", "0", "file:///usr/local/google/home/jensj/code/dart-sdk/sdk/t.dart", "Bar", "1", "0"]
35+
type_definitions: ["T"]
36+
type_bounds: ["dart:core", "Enum", "1", "0"]
37+
type_defaults: ["dart:core", "Enum", "1", "0"]
38+
method: "Foo.bar"
39+
static: true
40+
offset: 105
41+
scriptUri: main.dart
42+
expression: |
43+
constant.index
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Errors: {
2+
}
3+
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
4+
return constant.{dart.core::Enum::index}{dart.core::int};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
5+
# https://github.com/dart-lang/sdk/issues/56911
6+
7+
sources: |
8+
void main() {
9+
List<ExtensionType> list = [new ExtensionType(0)];
10+
list.forEach((ExtensionType input) {
11+
print(input.value);
12+
});
13+
}
14+
15+
extension type ExtensionType._(String s) {
16+
ExtensionType(int i) : this._("$i");
17+
int get value => s.codeUnitAt(0);
18+
}
19+
20+
definitions: ["#this"]
21+
# String
22+
definition_types: ["dart:core", "_OneByteString", "1", "0"]
23+
type_definitions: []
24+
type_bounds: []
25+
type_defaults: []
26+
method: "ExtensionType.value"
27+
static: true
28+
offset: 231 # at the 'value' of 'int get value => s.codeUnitAt(0);' line.
29+
scriptUri: main.dart
30+
expression: |
31+
() {
32+
s;
33+
s.codeUnitAt(0);
34+
value;
35+
}()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Errors: {
2+
}
3+
method /* from org-dartlang-debug:synthetic_debug_expression */ debugExpr(lowered #lib1::ExtensionType% /* erasure=dart.core::String, declared=! */ #this) → dynamic
4+
return (() → Null {
5+
#this as{Unchecked} dart.core::String;
6+
(#this as{Unchecked} dart.core::String).{dart.core::String::codeUnitAt}(0){(dart.core::int) → dart.core::int};
7+
#lib1::ExtensionType|get#value(#this);
8+
})(){() → Null};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
5+
# https://github.com/dart-lang/sdk/issues/56911
6+
7+
sources: |
8+
void main() {
9+
List<ExtensionType> list = [new ExtensionType("0")];
10+
list.forEach((ExtensionType input) {
11+
print(input.value);
12+
});
13+
}
14+
15+
extension type ExtensionType<E extends String>(E e) {
16+
int get value => e.codeUnitAt(0);
17+
}
18+
19+
definitions: ["#this"]
20+
# String
21+
definition_types: ["dart:core", "_OneByteString", "1", "0"]
22+
type_definitions: ["E"]
23+
type_bounds: ["dart:core", "String", "1", "0"]
24+
type_defaults: ["dart:core", "String", "1", "0"]
25+
method: "ExtensionType.value"
26+
static: true
27+
offset: 205 # at the 'value' of 'int get value => s.codeUnitAt(0);' line.
28+
scriptUri: main.dart
29+
expression: |
30+
() {
31+
s;
32+
s.codeUnitAt(0);
33+
value;
34+
}()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Errors: {
2+
}
3+
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
4+
return (() → Null {
5+
#this{dynamic}.s;
6+
#this{dynamic}.s{dynamic}.codeUnitAt(0);
7+
#lib1::ExtensionType|get#value<#lib2::debugExpr::E>(#this);
8+
})(){() → Null};

0 commit comments

Comments
 (0)