From b87dd83013d240f3f8a5d0a38bef32da676a2923 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Fri, 13 Feb 2026 11:56:53 -0800 Subject: [PATCH] [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 Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/code_generator.dart | 16 ++-- pkg/dart2wasm/lib/translator.dart | 74 +++++++++++++++---- .../forwarding_stubs_regress_62662_test.dart | 67 +++++++++++++++++ 3 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 tests/web/wasm/forwarding_stubs_regress_62662_test.dart diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index f44e8ceafc4..c3ecd365171 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -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); } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 2dd2cef5778..a19ef16ef8e 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -1588,11 +1588,11 @@ class Translator with KernelNodes { ({ List typeParameters, - List typeParametersToTypeCheck, + List typeParametersToTypeCheck, List positional, - List positionalToTypeCheck, + List positionalToTypeCheck, List named, - List namedToTypeCheck + List namedToTypeCheck }) getParametersToCheck(Member member) { final memberFunction = member.function!; final List 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 typeParametersToTypeCheck = - forwardingTarget?.typeParameters ?? typeParameters; - final List positionalToTypeCheck = - forwardingTarget?.function.positionalParameters ?? positional; - final List 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 _typesFromTypeParameterBounds( + List typeParameters) { + if (typeParameters.isEmpty) return const []; + return [for (final param in typeParameters) param.bound]; + } + + List _typesFromPositionalParameters( + List typeParameters) { + if (typeParameters.isEmpty) return const []; + return [for (final param in typeParameters) param.type]; + } + + List _typeFromNamedParameters( + List namedOrder, + List namedType, + ) { + if (namedOrder.isEmpty) return const []; + final namedTypes = []; + 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; diff --git a/tests/web/wasm/forwarding_stubs_regress_62662_test.dart b/tests/web/wasm/forwarding_stubs_regress_62662_test.dart new file mode 100644 index 00000000000..0c41f323827 --- /dev/null +++ b/tests/web/wasm/forwarding_stubs_regress_62662_test.dart @@ -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 = Sub(); + object.field = 1; + Expect.throws(() => object.field = 'not an int'); + Expect.equals(1, object.field); +} + +void testTypeParameterForwardingStub() { + final Box object = Sub(); + object.foo>([1]); + Expect.throws(() => object.foo>(['a'])); +} + +void testPositionalParameterForwardingStub() { + final Box object = Sub(); + object.bar(1); + object.bar(2); + Expect.throws(() => object.bar('a')); +} + +void testNamedParameterForwardingStub() { + final Box 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(() => object.baz(x: 'a')); + Expect.throws(() => object.baz(z: 'a')); +} + +abstract class Box { + T? field; + void foo>(H a); + void bar(T a); + void baz({T? z, Object? y, T? x, Object? w}); +} + +class Base { + int? field; + void foo>(List 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 {}