[dart2wasm] Fix bug in handling of forwarding stubs to fields
The CFE may insert forwarding stubs not only for procedures but also for fields that require covariance type checks on the setter values. This also fixes a bug where type parameter bounds weren't correctly type checked against (The VM has the same bug, see [1]) [0] https://github.com/dart-lang/sdk/issues/62664 Fixes https://github.com/dart-lang/sdk/issues/62662 Change-Id: I61c005a26bd393df3279fdb349304ccc1eec9c6c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/480200 Reviewed-by: Nate Biggs <natebiggs@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
Commit Queue
parent
c4f7afa401
commit
b87dd83013
@@ -245,11 +245,12 @@ abstract class AstCodeGenerator
|
||||
}
|
||||
final mayNeedToCheckTypes = translator.needToCheckTypesFor(member);
|
||||
if (mayNeedToCheckTypes) {
|
||||
for (int i = 0; i < typeParametersToTypeCheck.length; i++) {
|
||||
final typeParameter = typeParametersToTypeCheck[i];
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
final typeParameter = typeParameters[i];
|
||||
if (translator.needToCheckTypeParameter(typeParameter)) {
|
||||
final typeParameterToCheck = typeParametersToTypeCheck[i];
|
||||
_generateTypeArgumentBoundCheck(typeParameter.name!,
|
||||
typeLocals[typeParameter]!, typeParameter.bound);
|
||||
typeLocals[typeParameter]!, typeParameterToCheck);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,14 +346,13 @@ abstract class AstCodeGenerator
|
||||
|
||||
for (int i = 0; i < positional.length; i++) {
|
||||
final bool isRequired = i < memberFunction.requiredParameterCount;
|
||||
final typeToCheck = positionalToTypeCheck[i].type;
|
||||
final typeToCheck = positionalToTypeCheck[i];
|
||||
setupParamLocal(
|
||||
typeToCheck, positional[i], i, paramInfo.positional[i], isRequired);
|
||||
}
|
||||
for (var param in named) {
|
||||
final typeToCheck = identical(named, namedToTypeCheck)
|
||||
? param.type
|
||||
: namedToTypeCheck.singleWhere((n) => n.name == param.name).type;
|
||||
for (int i = 0; i < named.length; i++) {
|
||||
final param = named[i];
|
||||
final typeToCheck = namedToTypeCheck[i];
|
||||
setupParamLocal(typeToCheck, param, paramInfo.nameIndex[param.name]!,
|
||||
paramInfo.named[param.name], param.isRequired);
|
||||
}
|
||||
|
||||
@@ -1588,11 +1588,11 @@ class Translator with KernelNodes {
|
||||
|
||||
({
|
||||
List<TypeParameter> typeParameters,
|
||||
List<TypeParameter> typeParametersToTypeCheck,
|
||||
List<DartType> typeParametersToTypeCheck,
|
||||
List<VariableDeclaration> positional,
|
||||
List<VariableDeclaration> positionalToTypeCheck,
|
||||
List<DartType> positionalToTypeCheck,
|
||||
List<VariableDeclaration> named,
|
||||
List<VariableDeclaration> namedToTypeCheck
|
||||
List<DartType> namedToTypeCheck
|
||||
}) getParametersToCheck(Member member) {
|
||||
final memberFunction = member.function!;
|
||||
final List<TypeParameter> typeParameters = member is Constructor
|
||||
@@ -1608,26 +1608,72 @@ class Translator with KernelNodes {
|
||||
// This mirrors what the VM does in
|
||||
// - FlowGraphBuilder::BuildTypeArgumentTypeChecks
|
||||
// - FlowGraphBuilder::BuildArgumentTypeChecks
|
||||
Procedure? forwardingTarget;
|
||||
Member? procedureForwardingTarget;
|
||||
if (member is Procedure && member.isForwardingStub) {
|
||||
forwardingTarget = member.concreteForwardingStubTarget as Procedure?;
|
||||
final forwardingTarget = member.concreteForwardingStubTarget;
|
||||
if (forwardingTarget is Field) {
|
||||
assert(
|
||||
typeParameters.isEmpty && named.isEmpty && positional.length == 1);
|
||||
return (
|
||||
typeParameters: [],
|
||||
typeParametersToTypeCheck: [],
|
||||
positional: positional,
|
||||
positionalToTypeCheck: [forwardingTarget.type],
|
||||
named: named,
|
||||
namedToTypeCheck: [],
|
||||
);
|
||||
}
|
||||
procedureForwardingTarget = forwardingTarget as Procedure;
|
||||
}
|
||||
final List<TypeParameter> typeParametersToTypeCheck =
|
||||
forwardingTarget?.typeParameters ?? typeParameters;
|
||||
final List<VariableDeclaration> positionalToTypeCheck =
|
||||
forwardingTarget?.function.positionalParameters ?? positional;
|
||||
final List<VariableDeclaration> namedToTypeCheck =
|
||||
forwardingTarget?.function.namedParameters ?? named;
|
||||
return (
|
||||
typeParameters: typeParameters,
|
||||
typeParametersToTypeCheck: typeParametersToTypeCheck,
|
||||
typeParametersToTypeCheck: _typesFromTypeParameterBounds(
|
||||
procedureForwardingTarget?.function?.typeParameters ??
|
||||
typeParameters),
|
||||
positional: positional,
|
||||
positionalToTypeCheck: positionalToTypeCheck,
|
||||
positionalToTypeCheck: _typesFromPositionalParameters(
|
||||
procedureForwardingTarget?.function?.positionalParameters ??
|
||||
positional),
|
||||
named: named,
|
||||
namedToTypeCheck: namedToTypeCheck,
|
||||
namedToTypeCheck: _typeFromNamedParameters(
|
||||
named, procedureForwardingTarget?.function?.namedParameters ?? named),
|
||||
);
|
||||
}
|
||||
|
||||
List<DartType> _typesFromTypeParameterBounds(
|
||||
List<TypeParameter> typeParameters) {
|
||||
if (typeParameters.isEmpty) return const [];
|
||||
return [for (final param in typeParameters) param.bound];
|
||||
}
|
||||
|
||||
List<DartType> _typesFromPositionalParameters(
|
||||
List<VariableDeclaration> typeParameters) {
|
||||
if (typeParameters.isEmpty) return const [];
|
||||
return [for (final param in typeParameters) param.type];
|
||||
}
|
||||
|
||||
List<DartType> _typeFromNamedParameters(
|
||||
List<VariableDeclaration> namedOrder,
|
||||
List<VariableDeclaration> namedType,
|
||||
) {
|
||||
if (namedOrder.isEmpty) return const [];
|
||||
final namedTypes = <DartType>[];
|
||||
for (int i = 0; i < namedOrder.length; ++i) {
|
||||
final named = namedOrder[i];
|
||||
DartType? type;
|
||||
|
||||
for (int j = 0; j < namedType.length; ++j) {
|
||||
final other = namedType[j];
|
||||
if (named.name == other.name) {
|
||||
type = other.type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
namedTypes.add(type!);
|
||||
}
|
||||
return namedTypes;
|
||||
}
|
||||
|
||||
DispatchTable dispatchTableForTarget(Reference target) {
|
||||
assert(target.asMember.isInstanceMember);
|
||||
if (!isDynamicSubmodule) return dispatchTable;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2026, 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.
|
||||
|
||||
// Regression test for https://github.com/dart-lang/sdk/issues/62662
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
void main() {
|
||||
testFieldForwardingStub();
|
||||
testTypeParameterForwardingStub();
|
||||
testPositionalParameterForwardingStub();
|
||||
testNamedParameterForwardingStub();
|
||||
}
|
||||
|
||||
void testFieldForwardingStub() {
|
||||
final Box<Object> object = Sub();
|
||||
object.field = 1;
|
||||
Expect.throws<TypeError>(() => object.field = 'not an int');
|
||||
Expect.equals(1, object.field);
|
||||
}
|
||||
|
||||
void testTypeParameterForwardingStub() {
|
||||
final Box<Object> object = Sub();
|
||||
object.foo<List<int>>(<int>[1]);
|
||||
Expect.throws<TypeError>(() => object.foo<List<Object>>(<Object>['a']));
|
||||
}
|
||||
|
||||
void testPositionalParameterForwardingStub() {
|
||||
final Box<Object> object = Sub();
|
||||
object.bar(1);
|
||||
object.bar(2);
|
||||
Expect.throws<TypeError>(() => object.bar('a'));
|
||||
}
|
||||
|
||||
void testNamedParameterForwardingStub() {
|
||||
final Box<Object> object = Sub();
|
||||
object.baz();
|
||||
object.baz(w: Object());
|
||||
object.baz(x: -1);
|
||||
object.baz(y: Object());
|
||||
object.baz(z: 0);
|
||||
object.baz(w: Object(), x: 1);
|
||||
object.baz(x: 2, w: Object());
|
||||
object.baz(w: Object(), x: 3, y: Object(), z: 4);
|
||||
object.baz(z: 5, y: Object(), x: 6, w: Object());
|
||||
Expect.throws<TypeError>(() => object.baz(x: 'a'));
|
||||
Expect.throws<TypeError>(() => object.baz(z: 'a'));
|
||||
}
|
||||
|
||||
abstract class Box<T> {
|
||||
T? field;
|
||||
void foo<H extends List<T>>(H a);
|
||||
void bar(T a);
|
||||
void baz({T? z, Object? y, T? x, Object? w});
|
||||
}
|
||||
|
||||
class Base {
|
||||
int? field;
|
||||
void foo<H extends List<int>>(List<int> a) =>
|
||||
print('Base.foo<$H>(${1 + a[0]})');
|
||||
void bar(int a) => print('Base.bar($a)');
|
||||
void baz({Object? w, int? x, Object? y, int? z}) =>
|
||||
print('Base.baz({w: $w, x: $x, y: $y, z: $z})');
|
||||
}
|
||||
|
||||
class Sub extends Base implements Box<int> {}
|
||||
Reference in New Issue
Block a user