From 30b0796c8cc146a4d152f38053ea3b7972db2c06 Mon Sep 17 00:00:00 2001 From: Derek Xu Date: Tue, 4 Jun 2024 15:06:28 +0000 Subject: [PATCH] [VM/Debugger] Fix bug where breakpoints set in compiled field initializers do not resolve immediately TEST=verified that pkg/vm_service/test/breakpoint_resolves_immediately_in_compiled_field_initializer_test.dart fails without the changes in this CL and passes with them, verified that none of the existing debugger tests got broken by this CL Change-Id: I6acb5576a80e5d633b012c866fe90bf13d2c1ba6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/369162 Commit-Queue: Derek Xu Reviewed-by: Alexander Aprelev --- ...ly_in_compiled_field_initializer_test.dart | 65 +++++++++++++++++++ runtime/vm/debugger.cc | 34 ++++++++-- 2 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 pkg/vm_service/test/breakpoint_resolves_immediately_in_compiled_field_initializer_test.dart diff --git a/pkg/vm_service/test/breakpoint_resolves_immediately_in_compiled_field_initializer_test.dart b/pkg/vm_service/test/breakpoint_resolves_immediately_in_compiled_field_initializer_test.dart new file mode 100644 index 00000000000..05742a46a73 --- /dev/null +++ b/pkg/vm_service/test/breakpoint_resolves_immediately_in_compiled_field_initializer_test.dart @@ -0,0 +1,65 @@ +// 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. + +import 'dart:developer' show debugger; + +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 = 28; +const LINE_B = 34; +// AUTOGENERATED END + +int getTwo() => 3; + +int getThree() => 3; + +class C { + static int x = getTwo() + getThree(); // LINE_A +} + +Future testeeMain() async { + final y = C.x; + print(y); + debugger(); // LINE_B +} + +final tests = [ + // Ensure that the main isolate has stopped at the [debugger] statement at the + // end of [testeeMain]. + hasStoppedAtBreakpoint, + stoppedAtLine(LINE_B), + (VmService service, IsolateRef isolateRef) async { + final isolateId = isolateRef.id!; + final isolate = await service.getIsolate(isolateId); + final rootLib = await service.getObject( + isolateId, + isolate.rootLib!.id!, + ) as Library; + final scriptId = rootLib.scripts![0].id!; + + // Add a breakpoint at the initializer of `C.x`. + final breakpoint = await service.addBreakpoint(isolateId, scriptId, LINE_A); + // It is guaranteed that the initializer of `C.x` has been compiled at this + // point, because `C.x` was already used to initialize `y`, so we ensure + // that the newly set breakpoint has been resolved immediately. + expect(breakpoint.resolved, true); + }, +]; + +void main([args = const []]) => runIsolateTests( + args, + tests, + 'breakpoint_resolves_immediately_in_compiled_field_initializer_test.dart', + testeeConcurrent: testeeMain, + ); diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc index d7ec91e3f5c..e57cbc00019 100644 --- a/runtime/vm/debugger.cc +++ b/runtime/vm/debugger.cc @@ -2302,8 +2302,10 @@ void Debugger::FindCompiledFunctions( }); Class& cls = Class::Handle(zone); - Function& function = Function::Handle(zone); Array& functions = Array::Handle(zone); + Function& function = Function::Handle(zone); + Array& fields = Array::Handle(zone); + Field& field = Field::Handle(zone); const ClassTable& class_table = *isolate_->group()->class_table(); const intptr_t num_classes = class_table.NumCids(); @@ -2346,6 +2348,28 @@ void Debugger::FindCompiledFunctions( } } } + fields = cls.fields(); + if (!fields.IsNull()) { + const intptr_t num_fields = fields.Length(); + for (intptr_t pos = 0; pos < num_fields; pos++) { + field ^= fields.At(pos); + ASSERT(!field.IsNull()); + if (field.Script() != script.ptr()) { + continue; + } + if (!field.has_nontrivial_initializer()) { + continue; + } + function = field.EnsureInitializerFunction(); + ASSERT(!function.IsNull()); + if (function.is_debuggable() && function.HasCode() && + function.token_pos() == start_pos && + function.end_token_pos() == end_pos && + function.script() == script.ptr()) { + code_function_list->Add(function); + } + } + } } } } @@ -2476,11 +2500,8 @@ bool Debugger::FindBestFit(const Script& script, } } } - // If none of the functions in the class contain token_pos, then we - // check if it falls within a function literal initializer of a field - // that has not been initialized yet. If the field (and hence the - // function literal initializer) has already been initialized, then - // it would have been found above in the object store as a closure. + // If none of the functions in the class contain token_pos, then we check + // if it falls within a function literal initializer of a field. fields = cls.fields(); if (!fields.IsNull()) { const intptr_t num_fields = fields.Length(); @@ -2501,6 +2522,7 @@ bool Debugger::FindBestFit(const Script& script, end = field.end_token_pos(); if (token_pos.IsWithin(start, end) || start.IsWithin(token_pos, last_token_pos)) { + *best_fit = field.InitializerFunction(); return true; } }