From eb4619d1399f456fde6f3fb6085c861acfaf6388 Mon Sep 17 00:00:00 2001 From: Nicholas Shahan Date: Mon, 28 Jul 2025 16:16:06 -0700 Subject: [PATCH] [ddc] Add hot reload checks for instance methods Handles errors and type changes on a hot reload when call sites that were statically valid are retained and run after the reload. Change-Id: I8bebbd7bc7acc97f55ff930e8f456f99146fbf21 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/440082 Reviewed-by: Mark Zhou Commit-Queue: Nicholas Shahan Reviewed-by: Nate Biggs --- .../lib/src/kernel/compiler_new.dart | 52 ++++++++++++++----- .../private/ddc_runtime/operations.dart | 31 ++++++----- .../config.json | 5 ++ .../main.0.dart | 30 +++++++++++ .../main.1.dart | 44 ++++++++++++++++ .../config.json | 6 +++ .../main.0.dart | 30 +++++++++++ .../main.1.dart | 44 ++++++++++++++++ .../config.json | 5 ++ .../main.0.dart | 30 +++++++++++ .../main.1.dart | 48 +++++++++++++++++ .../config.json | 6 +++ .../main.0.dart | 31 +++++++++++ .../main.1.dart | 49 +++++++++++++++++ .../config.json | 5 ++ .../main.0.dart | 30 +++++++++++ .../main.1.dart | 48 +++++++++++++++++ .../config.json | 6 +++ .../main.0.dart | 30 +++++++++++ .../main.1.dart | 48 +++++++++++++++++ .../change_instance_format5_ddc/config.json | 5 ++ .../change_instance_format5_ddc/main.0.dart | 33 ++++++++++++ .../change_instance_format5_ddc/main.1.dart | 50 ++++++++++++++++++ .../change_instance_format5_vm/config.json | 6 +++ .../main.0.dart | 0 .../main.1.dart | 0 26 files changed, 646 insertions(+), 26 deletions(-) create mode 100644 tests/hot_reload/call_deleted_instance_method_ddc/config.json create mode 100644 tests/hot_reload/call_deleted_instance_method_ddc/main.0.dart create mode 100644 tests/hot_reload/call_deleted_instance_method_ddc/main.1.dart create mode 100644 tests/hot_reload/call_deleted_instance_method_vm/config.json create mode 100644 tests/hot_reload/call_deleted_instance_method_vm/main.0.dart create mode 100644 tests/hot_reload/call_deleted_instance_method_vm/main.1.dart create mode 100644 tests/hot_reload/call_instance_method_parameters_change_ddc/config.json create mode 100644 tests/hot_reload/call_instance_method_parameters_change_ddc/main.0.dart create mode 100644 tests/hot_reload/call_instance_method_parameters_change_ddc/main.1.dart create mode 100644 tests/hot_reload/call_instance_method_parameters_change_vm/config.json create mode 100644 tests/hot_reload/call_instance_method_parameters_change_vm/main.0.dart create mode 100644 tests/hot_reload/call_instance_method_parameters_change_vm/main.1.dart create mode 100644 tests/hot_reload/call_instance_method_return_change_ddc/config.json create mode 100644 tests/hot_reload/call_instance_method_return_change_ddc/main.0.dart create mode 100644 tests/hot_reload/call_instance_method_return_change_ddc/main.1.dart create mode 100644 tests/hot_reload/call_instance_method_return_change_vm/config.json create mode 100644 tests/hot_reload/call_instance_method_return_change_vm/main.0.dart create mode 100644 tests/hot_reload/call_instance_method_return_change_vm/main.1.dart create mode 100644 tests/hot_reload/change_instance_format5_ddc/config.json create mode 100644 tests/hot_reload/change_instance_format5_ddc/main.0.dart create mode 100644 tests/hot_reload/change_instance_format5_ddc/main.1.dart create mode 100644 tests/hot_reload/change_instance_format5_vm/config.json rename tests/hot_reload/{change_instance_format5 => change_instance_format5_vm}/main.0.dart (100%) rename tests/hot_reload/{change_instance_format5 => change_instance_format5_vm}/main.1.dart (100%) diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart index 364097da3df..1b7a7f0c95d 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart @@ -6551,6 +6551,19 @@ class LibraryCompiler extends ComputeOnceConstantVisitor // Otherwise generate this as a normal typed method call. var jsName = _emitMemberName(name, member: target); var invocation = js.call('#.#(#)', [jsReceiver, jsName, jsArguments]); + if (_shouldRewriteInvocationWithHotReloadChecks(target)) { + var checkedInvocation = _rewriteInvocationWithHotReloadChecks( + jsReceiver, + jsName, + target, + node.arguments, + node.getStaticType(_staticTypeContext), + _nodeStart(node), + ); + // As an optimization, avoid extra checks when the invocation code was + // compiled in the same generation that it is running. + return _emitHotReloadSafeInvocation(invocation, checkedInvocation); + } return _isNullCheckableJsInterop(node.interfaceTarget) ? _wrapWithJsInteropNullCheck(invocation) : invocation; @@ -7636,6 +7649,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor ..sourceInformation = _nodeStart(node); if (_shouldRewriteInvocationWithHotReloadChecks(target)) { var checkedCall = _rewriteInvocationWithHotReloadChecks( + fn.receiver, + fn.selector, target, node.arguments, node.getStaticType(_staticTypeContext), @@ -7678,6 +7693,10 @@ class LibraryCompiler extends ComputeOnceConstantVisitor /// time to include checks to preserve soundness in the presence of hot /// reloads at runtime. /// + /// The compiled JavaScript [receiver] and [selector] should be passed so that + /// they can be reused for the validated invocation after the checks have + /// passed. + /// /// The checks are similar to the those performed when making a dynamic call. /// The [arguments] are checked for the correct shape and runtime types. /// Additionally after the invocation, the returned value is checked against @@ -7689,7 +7708,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor /// The resulting expression for the validated call site will receive the /// [originalCallSiteSourceLocation]. js_ast.Expression _rewriteInvocationWithHotReloadChecks( - Member target, + js_ast.Expression receiver, + js_ast.Expression selector, + Procedure target, Arguments arguments, DartType expectedReturnType, SourceLocation? originalCallSiteSourceLocation, @@ -7738,8 +7759,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor // still valid. var checkResult = _emitScopedId('\$result'); _letVariables!.add(checkResult); - var jsTarget = _emitStaticTarget(target); - var jsTypeArguments = [ + var typeArguments = [ // TODO(nshahan): Remove this check if we stop rewriting calls to SDK // functions. if (_reifyGenericFunction(target)) @@ -7748,9 +7768,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor var correctnessCheck = _runtimeCall( 'hotReloadCorrectnessChecks(#, #, #, #, #)', [ - jsTarget.receiver, - jsTarget.selector, - js_ast.ArrayInitializer(jsTypeArguments), + receiver, + selector, + js_ast.ArrayInitializer(typeArguments), js_ast.ArrayInitializer(hoistedPositionalVariables), hoistedNamedVariables.isEmpty ? js_ast.LiteralNull() @@ -7766,14 +7786,18 @@ class LibraryCompiler extends ComputeOnceConstantVisitor : js_ast.Binary(',', letAssignments, checkAssignment); // Create a new invocation of the original target but passing all the // arguments via their let variables. - var validatedCallSite = js_ast.Call(jsTarget, [ - ...jsTypeArguments, - ...hoistedPositionalVariables, - if (hoistedNamedVariables.isNotEmpty) - js_ast.ObjectInitializer([ - for (var e in hoistedNamedVariables.entries) - js_ast.Property(js.string(e.key), e.value), - ]), + var validatedCallSite = js.call('#.#(#)', [ + receiver, + selector, + [ + ...typeArguments, + ...hoistedPositionalVariables, + if (hoistedNamedVariables.isNotEmpty) + js_ast.ObjectInitializer([ + for (var e in hoistedNamedVariables.entries) + js_ast.Property(js.string(e.key), e.value), + ]), + ], ])..sourceInformation = originalCallSiteSourceLocation; // Cast the result of the checked call or the value returned from a // `NoSuchMethod` invocation. diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart index 9136db63d44..1a2cae1e912 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart @@ -744,13 +744,15 @@ Object? hotReloadCorrectnessChecks( ), ); } - var functionType = JS( - '', - '#[#][#]', - receiver, - name, - JS_GET_NAME(JsGetName.SIGNATURE_NAME), - ); + var functionType = _jsInstanceOf(receiver, Object) + ? getMethodType(receiver, name) + : JS( + '', + '#[#][#]', + receiver, + name, + JS_GET_NAME(JsGetName.SIGNATURE_NAME), + ); if (functionType == null) { // Allow JavaScript interop calls without checking arguments. // TODO(nshahan): Potentially we should be checking arguments to static @@ -1546,7 +1548,7 @@ bool isStateBearingSymbol(property) => JS( /// copies the members of [classDeclaration] and its prototype's properties to /// the existing class. Existing members not prefixed by a special identifier /// are replaced (see [isStateBearingSymbol]). -declareClass(library, classIdentifier, classDeclaration) { +declareClass(Object library, Object classIdentifier, Object classDeclaration) { var originalClass = JS('!', '#.#', library, classIdentifier); if (JS('!', '# === void 0', originalClass)) { JS('', '#.# = #', library, classIdentifier, classDeclaration); @@ -1559,6 +1561,8 @@ declareClass(library, classIdentifier, classDeclaration) { !isStateBearingSymbol(property), originalClassProto, ); + // Reconcile instance members. + deleteClassMembers(originalClassProto, newClassProto); copyProperties(originalClassProto, newClassProto, copyWhen: copyWhenProto); var copyWhen = (property) => JS( '!', @@ -1566,6 +1570,7 @@ declareClass(library, classIdentifier, classDeclaration) { !isStateBearingSymbol(property), originalClass, ); + // Reconcile static members. deleteClassMembers(originalClass, classDeclaration); copyProperties(originalClass, classDeclaration, copyWhen: copyWhen); } @@ -1579,10 +1584,12 @@ declareClass(library, classIdentifier, classDeclaration) { /// /// Called from generated code. void deleteClassMembers(Object oldClass, Object newClass) { - for (var name in getOwnNamesAndSymbols(oldClass)) { - if (JS('', '#.#', newClass, name) == null && - !isStateBearingSymbol(name)) { - JS('', 'delete #.#', oldClass, name); + var oldClassNamesAndSymbols = getOwnNamesAndSymbols(oldClass); + var newClassNamesAndSymbols = getOwnNamesAndSymbols(newClass); + for (var property in oldClassNamesAndSymbols) { + if (JS('', '!#.includes(#)', newClassNamesAndSymbols, property) && + !isStateBearingSymbol(property)) { + JS('', 'delete #.#', oldClass, property); } } } diff --git a/tests/hot_reload/call_deleted_instance_method_ddc/config.json b/tests/hot_reload/call_deleted_instance_method_ddc/config.json new file mode 100644 index 00000000000..6bd1b0002af --- /dev/null +++ b/tests/hot_reload/call_deleted_instance_method_ddc/config.json @@ -0,0 +1,5 @@ +{ + "exclude": [ + "vm" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/call_deleted_instance_method_ddc/main.0.dart b/tests/hot_reload/call_deleted_instance_method_ddc/main.0.dart new file mode 100644 index 00000000000..1f6436a3a70 --- /dev/null +++ b/tests/hot_reload/call_deleted_instance_method_ddc/main.0.dart @@ -0,0 +1,30 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C { + String deleted() { + return 'hello'; + } +} + +helper() { + c = C(); + retained = () => c!.deleted(); + return retained(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains('deleted'), + ); +} diff --git a/tests/hot_reload/call_deleted_instance_method_ddc/main.1.dart b/tests/hot_reload/call_deleted_instance_method_ddc/main.1.dart new file mode 100644 index 00000000000..1bc62b0d26e --- /dev/null +++ b/tests/hot_reload/call_deleted_instance_method_ddc/main.1.dart @@ -0,0 +1,44 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C {} + +helper() { + return retained(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains('deleted'), + ); +} + +/** DIFF **/ +/* + var retained; + C? c; + +-class C { +- String deleted() { +- return 'hello'; +- } +-} ++class C {} + + helper() { +- c = C(); +- retained = () => c!.deleted(); + return retained(); + } + +*/ diff --git a/tests/hot_reload/call_deleted_instance_method_vm/config.json b/tests/hot_reload/call_deleted_instance_method_vm/config.json new file mode 100644 index 00000000000..2e7ceb47217 --- /dev/null +++ b/tests/hot_reload/call_deleted_instance_method_vm/config.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "chrome", + "d8" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/call_deleted_instance_method_vm/main.0.dart b/tests/hot_reload/call_deleted_instance_method_vm/main.0.dart new file mode 100644 index 00000000000..e1b31bbd9f7 --- /dev/null +++ b/tests/hot_reload/call_deleted_instance_method_vm/main.0.dart @@ -0,0 +1,30 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C { + String deleted() { + return 'hello'; + } +} + +helper() { + c = C(); + retained = () => c!.deleted(); + return retained(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains('Lookup failed: deleted in @methods in C'), + ); +} diff --git a/tests/hot_reload/call_deleted_instance_method_vm/main.1.dart b/tests/hot_reload/call_deleted_instance_method_vm/main.1.dart new file mode 100644 index 00000000000..d504adc081d --- /dev/null +++ b/tests/hot_reload/call_deleted_instance_method_vm/main.1.dart @@ -0,0 +1,44 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C {} + +helper() { + return retained(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains('Lookup failed: deleted in @methods in C'), + ); +} + +/** DIFF **/ +/* + var retained; + C? c; + +-class C { +- String deleted() { +- return 'hello'; +- } +-} ++class C {} + + helper() { +- c = C(); +- retained = () => c!.deleted(); + return retained(); + } + +*/ diff --git a/tests/hot_reload/call_instance_method_parameters_change_ddc/config.json b/tests/hot_reload/call_instance_method_parameters_change_ddc/config.json new file mode 100644 index 00000000000..6bd1b0002af --- /dev/null +++ b/tests/hot_reload/call_instance_method_parameters_change_ddc/config.json @@ -0,0 +1,5 @@ +{ + "exclude": [ + "vm" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/call_instance_method_parameters_change_ddc/main.0.dart b/tests/hot_reload/call_instance_method_parameters_change_ddc/main.0.dart new file mode 100644 index 00000000000..bc9e12f17a4 --- /dev/null +++ b/tests/hot_reload/call_instance_method_parameters_change_ddc/main.0.dart @@ -0,0 +1,30 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C { + int parametersChange(int i) { + return i + 10; + } +} + +helper() { + c = C(); + retained = () => c!.parametersChange(32); + return retained!(); +} + +Future main() async { + Expect.equals(42, helper()); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains("'int' is not a subtype of type 'String'"), + ); +} diff --git a/tests/hot_reload/call_instance_method_parameters_change_ddc/main.1.dart b/tests/hot_reload/call_instance_method_parameters_change_ddc/main.1.dart new file mode 100644 index 00000000000..1c6a2ebbf0b --- /dev/null +++ b/tests/hot_reload/call_instance_method_parameters_change_ddc/main.1.dart @@ -0,0 +1,48 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C { + int parametersChange(String s) { + return s.length; + } +} + +helper() { + return retained!(); +} + +Future main() async { + Expect.equals(42, helper()); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains("'int' is not a subtype of type 'String'"), + ); +} + +/** DIFF **/ +/* + C? c; + + class C { +- int parametersChange(int i) { +- return i + 10; ++ int parametersChange(String s) { ++ return s.length; + } + } + + helper() { +- c = C(); +- retained = () => c!.parametersChange(32); + return retained!(); + } + +*/ diff --git a/tests/hot_reload/call_instance_method_parameters_change_vm/config.json b/tests/hot_reload/call_instance_method_parameters_change_vm/config.json new file mode 100644 index 00000000000..2e7ceb47217 --- /dev/null +++ b/tests/hot_reload/call_instance_method_parameters_change_vm/config.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "chrome", + "d8" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/call_instance_method_parameters_change_vm/main.0.dart b/tests/hot_reload/call_instance_method_parameters_change_vm/main.0.dart new file mode 100644 index 00000000000..206aa30d1bd --- /dev/null +++ b/tests/hot_reload/call_instance_method_parameters_change_vm/main.0.dart @@ -0,0 +1,31 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C { + int parametersChange(int i) { + return i + 10; + } +} + +helper() { + c = C(); + retained = () => c!.parametersChange(32); + return retained!(); +} + +Future main() async { + Expect.equals(42, helper()); + await hotReload(); + Expect.throws( + helper, + (error) => + '$error'.contains("Class 'int' has no instance getter 'length'."), + ); +} diff --git a/tests/hot_reload/call_instance_method_parameters_change_vm/main.1.dart b/tests/hot_reload/call_instance_method_parameters_change_vm/main.1.dart new file mode 100644 index 00000000000..122c4ffe031 --- /dev/null +++ b/tests/hot_reload/call_instance_method_parameters_change_vm/main.1.dart @@ -0,0 +1,49 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +var retained; +C? c; + +class C { + int parametersChange(String s) { + return s.length; + } +} + +helper() { + return retained!(); +} + +Future main() async { + Expect.equals(42, helper()); + await hotReload(); + Expect.throws( + helper, + (error) => + '$error'.contains("Class 'int' has no instance getter 'length'."), + ); +} + +/** DIFF **/ +/* + C? c; + + class C { +- int parametersChange(int i) { +- return i + 10; ++ int parametersChange(String s) { ++ return s.length; + } + } + + helper() { +- c = C(); +- retained = () => c!.parametersChange(32); + return retained!(); + } + +*/ diff --git a/tests/hot_reload/call_instance_method_return_change_ddc/config.json b/tests/hot_reload/call_instance_method_return_change_ddc/config.json new file mode 100644 index 00000000000..6bd1b0002af --- /dev/null +++ b/tests/hot_reload/call_instance_method_return_change_ddc/config.json @@ -0,0 +1,5 @@ +{ + "exclude": [ + "vm" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/call_instance_method_return_change_ddc/main.0.dart b/tests/hot_reload/call_instance_method_return_change_ddc/main.0.dart new file mode 100644 index 00000000000..a6229451444 --- /dev/null +++ b/tests/hot_reload/call_instance_method_return_change_ddc/main.0.dart @@ -0,0 +1,30 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +int Function()? retained; +C? c; + +class C { + String returnChange() { + return 'hello'; + } +} + +helper() { + c = C(); + retained = () => c!.returnChange().length; + return retained!(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains("'double' is not a subtype of type 'String'"), + ); +} diff --git a/tests/hot_reload/call_instance_method_return_change_ddc/main.1.dart b/tests/hot_reload/call_instance_method_return_change_ddc/main.1.dart new file mode 100644 index 00000000000..2404affd124 --- /dev/null +++ b/tests/hot_reload/call_instance_method_return_change_ddc/main.1.dart @@ -0,0 +1,48 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +int Function()? retained; +C? c; + +class C { + double returnChange() { + return 3.14; + } +} + +helper() { + return retained!(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains("'double' is not a subtype of type 'String'"), + ); +} + +/** DIFF **/ +/* + C? c; + + class C { +- String returnChange() { +- return 'hello'; ++ double returnChange() { ++ return 3.14; + } + } + + helper() { +- c = C(); +- retained = () => c!.returnChange().length; + return retained!(); + } + +*/ diff --git a/tests/hot_reload/call_instance_method_return_change_vm/config.json b/tests/hot_reload/call_instance_method_return_change_vm/config.json new file mode 100644 index 00000000000..2e7ceb47217 --- /dev/null +++ b/tests/hot_reload/call_instance_method_return_change_vm/config.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "chrome", + "d8" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/call_instance_method_return_change_vm/main.0.dart b/tests/hot_reload/call_instance_method_return_change_vm/main.0.dart new file mode 100644 index 00000000000..1c986450b2c --- /dev/null +++ b/tests/hot_reload/call_instance_method_return_change_vm/main.0.dart @@ -0,0 +1,30 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +int Function()? retained; +C? c; + +class C { + String returnChange() { + return 'hello'; + } +} + +helper() { + c = C(); + retained = () => c!.returnChange().length; + return retained!(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains("'length'"), + ); +} diff --git a/tests/hot_reload/call_instance_method_return_change_vm/main.1.dart b/tests/hot_reload/call_instance_method_return_change_vm/main.1.dart new file mode 100644 index 00000000000..893ac896ff8 --- /dev/null +++ b/tests/hot_reload/call_instance_method_return_change_vm/main.1.dart @@ -0,0 +1,48 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +int Function()? retained; +C? c; + +class C { + double returnChange() { + return 3.14; + } +} + +helper() { + return retained!(); +} + +Future main() async { + helper(); + await hotReload(); + Expect.throws( + helper, + (error) => '$error'.contains("'length'"), + ); +} + +/** DIFF **/ +/* + C? c; + + class C { +- String returnChange() { +- return 'hello'; ++ double returnChange() { ++ return 3.14; + } + } + + helper() { +- c = C(); +- retained = () => c!.returnChange().length; + return retained!(); + } + +*/ diff --git a/tests/hot_reload/change_instance_format5_ddc/config.json b/tests/hot_reload/change_instance_format5_ddc/config.json new file mode 100644 index 00000000000..6bd1b0002af --- /dev/null +++ b/tests/hot_reload/change_instance_format5_ddc/config.json @@ -0,0 +1,5 @@ +{ + "exclude": [ + "vm" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/change_instance_format5_ddc/main.0.dart b/tests/hot_reload/change_instance_format5_ddc/main.0.dart new file mode 100644 index 00000000000..8af1829a6b6 --- /dev/null +++ b/tests/hot_reload/change_instance_format5_ddc/main.0.dart @@ -0,0 +1,33 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +// Adapted from: +// https://github.com/dart-lang/sdk/blob/1a486499bf73ee5b007abbe522b94869a1f36d02/runtime/vm/isolate_reload_test.cc#L3985 + +// Tests reload succeeds when instance format changes. +// Change: Bar {a, b}, Foo : Bar {c:42} -> Bar {c:42}, Foo : Bar {} +// Validate: c keeps the value in the retained Foo object. + +class Bar { + var a; + var b; +} + +class Foo extends Bar { + var c; +} + +var f; + +Future main() async { + f = Foo(); + f.c = 42; + Expect.equals(42, f.c); + await hotReload(); + + Expect.equals(null, f.c); +} diff --git a/tests/hot_reload/change_instance_format5_ddc/main.1.dart b/tests/hot_reload/change_instance_format5_ddc/main.1.dart new file mode 100644 index 00000000000..ab5225529f1 --- /dev/null +++ b/tests/hot_reload/change_instance_format5_ddc/main.1.dart @@ -0,0 +1,50 @@ +// 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'; +import 'package:reload_test/reload_test_utils.dart'; + +// Adapted from: +// https://github.com/dart-lang/sdk/blob/1a486499bf73ee5b007abbe522b94869a1f36d02/runtime/vm/isolate_reload_test.cc#L3985 + +// Tests reload succeeds when instance format changes. +// Change: Bar {a, b}, Foo : Bar {c:42} -> Bar {c:42}, Foo : Bar {} +// Validate: c keeps the value in the retained Foo object. + +class Bar { + var c; +} + +class Foo extends Bar {} + +var f; + +Future main() async { + f = Foo(); + f.c = 42; + Expect.equals(42, f.c); + await hotReload(); + + Expect.equals(null, f.c); +} + +/** DIFF **/ +/* + // Validate: c keeps the value in the retained Foo object. + + class Bar { +- var a; +- var b; +-} +- +-class Foo extends Bar { + var c; + } + ++class Foo extends Bar {} ++ + var f; + + Future main() async { +*/ diff --git a/tests/hot_reload/change_instance_format5_vm/config.json b/tests/hot_reload/change_instance_format5_vm/config.json new file mode 100644 index 00000000000..4d383163ebe --- /dev/null +++ b/tests/hot_reload/change_instance_format5_vm/config.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "d8", + "chrome" + ] +} \ No newline at end of file diff --git a/tests/hot_reload/change_instance_format5/main.0.dart b/tests/hot_reload/change_instance_format5_vm/main.0.dart similarity index 100% rename from tests/hot_reload/change_instance_format5/main.0.dart rename to tests/hot_reload/change_instance_format5_vm/main.0.dart diff --git a/tests/hot_reload/change_instance_format5/main.1.dart b/tests/hot_reload/change_instance_format5_vm/main.1.dart similarity index 100% rename from tests/hot_reload/change_instance_format5/main.1.dart rename to tests/hot_reload/change_instance_format5_vm/main.1.dart