diff --git a/pkg/vm_service/test/evaluate_optimized_out_variable_test.dart b/pkg/vm_service/test/evaluate_optimized_out_variable_test.dart new file mode 100644 index 00000000000..30f5f4c4428 --- /dev/null +++ b/pkg/vm_service/test/evaluate_optimized_out_variable_test.dart @@ -0,0 +1,85 @@ +// Copyright (c) 2024, 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://dartbug.com/53996. + +import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart'; + +import 'common/service_test_common.dart'; +import 'common/test_helper.dart'; + +// AUTOGENERATED START +// +// Update these constants by running: +// +// dart pkg/vm_service/test/update_line_numbers.dart +// +const LINE_A = 27; +// AUTOGENERATED END + +bool debug = false; + +bool bar(int i) { + if (i == 2) { + if (debug) { + print('woke up'); // LINE_A + } + return true; + } + return false; +} + +void foo() { + final List data = [1, 2, 3]; + for (int i in data) { + if (bar(i)) { + break; + } + } +} + +void testeeMain() { + // Trigger optimization of [foo]. + for (int i = 0; i < 20; i++) { + foo(); + } + debug = true; + foo(); +} + +final tests = [ + hasPausedAtStart, + setBreakpointAtLine(LINE_A), + resumeIsolate, + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_A), + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + try { + await service.evaluateInFrame( + isolateId, + 1, + 'data.length', + ); + fail('Expected evaluateInFrame to throw an RPCError'); + } on RPCError catch (e) { + expect(e.code, RPCErrorKind.kExpressionCompilationError.code); + expect(e.message, 'Expression compilation error'); + } + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'evaluate_optimized_out_variable_test.dart', + testeeConcurrent: testeeMain, + pauseOnStart: true, + extraArgs: const [ + '--deterministic', + '--prune-dead-locals', + '--optimization-counter-threshold=10', + ], + ); diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc index 40ae71a74d2..cb2149dd79c 100644 --- a/runtime/vm/debugger.cc +++ b/runtime/vm/debugger.cc @@ -1111,7 +1111,8 @@ TypeArgumentsPtr ActivationFrame::BuildParameters( type_arguments_available = true; type_arguments ^= value.ptr(); } else if (!name.Equals(Symbols::This()) && - !IsSyntheticVariableName(name)) { + !IsSyntheticVariableName(name) && + value.ptr() != Object::optimized_out().ptr()) { if (IsPrivateVariableName(name)) { name = Symbols::New(Thread::Current(), String::ScrubName(name)); } diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index f2f07be9b21..17003bb98a2 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -4945,6 +4945,12 @@ ObjectPtr Instance::EvaluateCompiledExpression( const auto& eval_function = Function::Cast(result); +#if defined(DEBUG) + for (intptr_t i = 0; i < arguments.Length(); ++i) { + ASSERT(arguments.At(i) != Object::optimized_out().ptr()); + } +#endif // defined(DEBUG) + auto& all_arguments = Array::Handle(zone, arguments.ptr()); if (!eval_function.is_static()) { // `this` may be optimized out (e.g. not accessible from breakpoint due to