[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:
Martin Kustermann
2026-02-13 11:56:53 -08:00
committed by Commit Queue
parent c4f7afa401
commit b87dd83013
3 changed files with 135 additions and 22 deletions
+8 -8
View File
@@ -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);
}
+60 -14
View File
@@ -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> {}