From d80fab4a8a6d630a6c4e55eac7032126b7035091 Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Tue, 7 Jan 2025 11:44:30 -0800 Subject: [PATCH] [dart2wasm] Fix dynamic switch casts. If the switch's expression has type 'dynamic' and all the case expressions have the same type, we compare them using '=='. This requires a cast to ensure all the types match for the dispatch to the '==' function. However, we don't check that the type of the switch expression matches the type of the case expressions. So the cast fails if they don't match. This adds a guard to ensure the types match before running through the case expressions. If the guard fails, we either jump to the default case or if there isn't one, we skip the switch entirely. Fixes: https://github.com/dart-lang/sdk/issues/59782 Change-Id: I12e81f98d1c2046ee47e8ca4371642fd40620636 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402460 Commit-Queue: Nate Biggs Reviewed-by: Martin Kustermann --- pkg/dart2wasm/lib/code_generator.dart | 24 +++++++++++++++++++++ tests/web/wasm/regress_59782_test.dart | 29 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/web/wasm/regress_59782_test.dart diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 0806f30770da..69315ab89e88 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -1399,6 +1399,14 @@ abstract class AstCodeGenerator b.local_set(switchValueNonNullableLocal); } + final dynamicTypeGuard = switchInfo.dynamicTypeGuard; + if (dynamicTypeGuard != null) { + final success = b.block(const [], [translator.topInfo.nonNullableType]); + dynamicTypeGuard(switchValueNonNullableLocal, success); + b.br(switchLabels[defaultCase] ?? doneLabel); + b.end(); + } + // Compare against all case values for (SwitchCase c in node.cases) { for (Expression exp in c.expressions) { @@ -4114,6 +4122,12 @@ class SwitchInfo { /// expression is nullable. late final w.ValueType nonNullableType; + /// Generates code that will br on [successLabel] if [switchExprLocal] has the + /// correct type for the case checks on this switch. Only set for switches + /// where the switch expression is dynamic. + void Function(w.Local switchExprLocal, w.Label successLabel)? + dynamicTypeGuard; + /// Generates code that compares value of a `case` expression with the /// `switch` expression's value. Calls [pushCaseExpr] once. late final void Function( @@ -4179,6 +4193,16 @@ class SwitchInfo { // add missing optional parameters. assert(equalsMemberSignature.inputs.length == 2); + dynamicTypeGuard = (switchExprLocal, successLabel) { + codeGen.b.local_get(switchExprLocal); + codeGen.b.br_on_cast( + successLabel, + switchExprLocal.type as w.RefType, + equalsMemberSignature.inputs[0] + .withNullability(switchExprLocal.type.nullable) as w.RefType); + codeGen.b.drop(); + }; + compare = (switchExprLocal, pushCaseExpr) { final caseExprType = pushCaseExpr(); translator.convertType( diff --git a/tests/web/wasm/regress_59782_test.dart b/tests/web/wasm/regress_59782_test.dart new file mode 100644 index 000000000000..890a6437bf23 --- /dev/null +++ b/tests/web/wasm/regress_59782_test.dart @@ -0,0 +1,29 @@ +// 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. + +import 'package:expect/expect.dart'; + +void main() { + String? result; + dynamic number = 42; + switch (number) { + case 'a': + result = 'a'; + case 'b': + result = 'b'; + default: + result = 'default'; + } + + Expect.equals(result, 'default'); + + result = null; + switch (number) { + case 'a': + result = 'a'; + case 'b': + result = 'b'; + } + Expect.isNull(result); +}