diff --git a/CHANGELOG.md b/CHANGELOG.md index f012c60b9f57..8c0c7986367b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 3.2.6 - + +This is a patch release that: +- Fixes an issue that causes Flutter apps to freeze when breakpoints + are added to multiple isolates at the same time + It also Fixes an issue that causes Flutter apps to crash during hot + reload + (issue [54699][]). + +[#54699] - https://github.com/dart-lang/sdk/issues/54699 + ## 3.2.5 - 2024-01-17 This is a patch release that: diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc index 779992ba5fc6..34d151fed37c 100644 --- a/runtime/vm/debugger.cc +++ b/runtime/vm/debugger.cc @@ -411,9 +411,9 @@ static bool IsImplicitFunction(const Function& func) { return false; } -bool GroupDebugger::HasCodeBreakpointInFunction(const Function& func) { - auto thread = Thread::Current(); - ReadRwLocker sl(thread, code_breakpoints_lock()); +bool GroupDebugger::HasCodeBreakpointInFunctionUnsafe(const Function& func) { + DEBUG_ASSERT(code_breakpoints_lock()->IsCurrentThreadReader() || + Thread::Current()->IsInStoppedMutatorsScope()); CodeBreakpoint* cbpt = code_breakpoints_; while (cbpt != nullptr) { if (func.ptr() == cbpt->function()) { @@ -424,9 +424,20 @@ bool GroupDebugger::HasCodeBreakpointInFunction(const Function& func) { return false; } +bool GroupDebugger::HasCodeBreakpointInFunction(const Function& func) { + auto thread = Thread::Current(); + // Don't need to worry about the lock if mutators are stopped. + if (thread->IsInStoppedMutatorsScope()) { + return HasCodeBreakpointInFunctionUnsafe(func); + } else { + SafepointReadRwLocker sl(thread, code_breakpoints_lock()); + return HasCodeBreakpointInFunctionUnsafe(func); + } +} + bool GroupDebugger::HasBreakpointInCode(const Code& code) { auto thread = Thread::Current(); - ReadRwLocker sl(thread, code_breakpoints_lock()); + SafepointReadRwLocker sl(thread, code_breakpoints_lock()); CodeBreakpoint* cbpt = code_breakpoints_; while (cbpt != nullptr) { if (code.ptr() == cbpt->code_) { @@ -1372,9 +1383,9 @@ BreakpointLocation* CodeBreakpoint::FindBreakpointForDebugger( GroupDebugger::GroupDebugger(IsolateGroup* isolate_group) : isolate_group_(isolate_group), - code_breakpoints_lock_(new RwLock()), + code_breakpoints_lock_(new SafepointRwLock()), code_breakpoints_(nullptr), - breakpoint_locations_lock_(new RwLock()), + breakpoint_locations_lock_(new SafepointRwLock()), single_stepping_set_lock_(new RwLock()), needs_breakpoint_cleanup_(false) {} @@ -1420,8 +1431,8 @@ void Debugger::Shutdown() { return; } { - WriteRwLocker sl(Thread::Current(), - group_debugger()->breakpoint_locations_lock()); + SafepointWriteRwLocker sl(Thread::Current(), + group_debugger()->breakpoint_locations_lock()); while (breakpoint_locations_ != nullptr) { BreakpointLocation* loc = breakpoint_locations_; group_debugger()->UnlinkCodeBreakpoints(loc); @@ -1502,7 +1513,6 @@ void Debugger::DeoptimizeWorld() { #if defined(DART_PRECOMPILED_RUNTIME) UNREACHABLE(); #else - NoBackgroundCompilerScope no_bg_compiler(Thread::Current()); if (FLAG_trace_deoptimization) { THR_Print("Deopt for debugger\n"); } @@ -1524,8 +1534,6 @@ void Debugger::DeoptimizeWorld() { const intptr_t num_classes = class_table.NumCids(); const intptr_t num_tlc_classes = class_table.NumTopLevelCids(); - // TODO(dartbug.com/36097): Need to stop other mutators running in same IG - // before deoptimizing the world. SafepointWriteRwLocker ml(thread, isolate_group->program_lock()); for (intptr_t i = 1; i < num_classes + num_tlc_classes; i++) { const intptr_t cid = @@ -1583,8 +1591,32 @@ void Debugger::DeoptimizeWorld() { #endif // defined(DART_PRECOMPILED_RUNTIME) } -void Debugger::NotifySingleStepping(bool value) const { - isolate_->set_single_step(value); +void Debugger::RunWithStoppedDeoptimizedWorld(std::function fun) { +#if !defined(DART_PRECOMPILED_RUNTIME) + // RELOAD_OPERATION_SCOPE is used here because is is guaranteed that + // isolates at reload safepoints hold no safepoint locks. + RELOAD_OPERATION_SCOPE(Thread::Current()); + group_debugger()->isolate_group()->RunWithStoppedMutators([&]() { + DeoptimizeWorld(); + fun(); + }); +#endif +} + +void Debugger::NotifySingleStepping(bool value) { + if (value) { + // Setting breakpoint requires unoptimized code, make sure we stop all + // isolates to prevent racing reoptimization. + RunWithStoppedDeoptimizedWorld([&] { + isolate_->set_single_step(value); + // Ensure other isolates in the isolate group keep + // unoptimized code unoptimized, won't attempt to optimize it. + group_debugger()->RegisterSingleSteppingDebugger(Thread::Current(), this); + }); + } else { + isolate_->set_single_step(value); + group_debugger()->UnregisterSingleSteppingDebugger(Thread::Current(), this); + } } static ActivationFrame* CollectDartFrame(uword pc, @@ -2144,8 +2176,11 @@ bool BreakpointLocation::EnsureIsResolved(const Function& target_function, return true; } -void GroupDebugger::MakeCodeBreakpointAt(const Function& func, - BreakpointLocation* loc) { +void GroupDebugger::MakeCodeBreakpointAtUnsafe(const Function& func, + BreakpointLocation* loc) { + DEBUG_ASSERT(Thread::Current()->IsInStoppedMutatorsScope() || + code_breakpoints_lock()->IsCurrentThreadWriter()); + ASSERT(loc->token_pos().IsReal()); ASSERT((loc != nullptr) && loc->IsResolved()); ASSERT(!func.HasOptimizedCode()); @@ -2171,7 +2206,6 @@ void GroupDebugger::MakeCodeBreakpointAt(const Function& func, } uword lowest_pc = code.PayloadStart() + lowest_pc_offset; - WriteRwLocker sl(Thread::Current(), code_breakpoints_lock()); CodeBreakpoint* code_bpt = GetCodeBreakpoint(lowest_pc); if (code_bpt == nullptr) { // No code breakpoint for this code exists; create one. @@ -2200,6 +2234,17 @@ void GroupDebugger::MakeCodeBreakpointAt(const Function& func, } } +void GroupDebugger::MakeCodeBreakpointAt(const Function& func, + BreakpointLocation* loc) { + auto thread = Thread::Current(); + if (thread->IsInStoppedMutatorsScope()) { + MakeCodeBreakpointAtUnsafe(func, loc); + } else { + SafepointWriteRwLocker sl(thread, code_breakpoints_lock()); + MakeCodeBreakpointAtUnsafe(func, loc); + } +} + void Debugger::FindCompiledFunctions( const GrowableHandlePtrArray& scripts, TokenPosition start_pos, @@ -2550,10 +2595,22 @@ BreakpointLocation* Debugger::SetBreakpoint( // have already been compiled. We can resolve the breakpoint now. // If requested_column is larger than zero, [token_pos, last_token_pos] // governs one single line of code. - DeoptimizeWorld(); - BreakpointLocation* loc = - SetCodeBreakpoints(scripts, token_pos, last_token_pos, requested_line, - requested_column, exact_token_pos, code_functions); + TokenPosition exact_token_pos = TokenPosition::kNoSource; +#if !defined(DART_PRECOMPILED_RUNTIME) + if (token_pos != last_token_pos && requested_column >= 0) { + exact_token_pos = + FindExactTokenPosition(script, token_pos, requested_column); + } +#endif // !defined(DART_PRECOMPILED_RUNTIME) + BreakpointLocation* loc = nullptr; + // Ensure that code stays deoptimized (and background compiler disabled) + // until we have installed the breakpoint (at which point the compiler + // will not try to optimize it anymore). + RunWithStoppedDeoptimizedWorld([&] { + loc = SetCodeBreakpoints(scripts, token_pos, last_token_pos, + requested_line, requested_column, + exact_token_pos, code_functions); + }); if (loc != nullptr) { return loc; } @@ -2594,7 +2651,7 @@ BreakpointLocation* Debugger::SetBreakpoint( // associated with the breakpoint location loc. void GroupDebugger::SyncBreakpointLocation(BreakpointLocation* loc) { bool any_enabled = loc->AnyEnabled(); - WriteRwLocker sl(Thread::Current(), code_breakpoints_lock()); + SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock()); CodeBreakpoint* cbpt = code_breakpoints_; while (cbpt != nullptr) { if (cbpt->HasBreakpointLocation(loc)) { @@ -2973,7 +3030,7 @@ void Debugger::Pause(ServiceEvent* event) { } void GroupDebugger::Pause() { - WriteRwLocker sl(Thread::Current(), code_breakpoints_lock()); + SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock()); if (needs_breakpoint_cleanup_) { RemoveUnlinkedCodeBreakpoints(); } @@ -2981,7 +3038,6 @@ void GroupDebugger::Pause() { void Debugger::EnterSingleStepMode() { ResetSteppingFramePointer(); - DeoptimizeWorld(); NotifySingleStepping(true); } @@ -3005,14 +3061,12 @@ void Debugger::HandleSteppingRequest(bool skip_next_step /* = false */) { // the isolate has been interrupted, but can happen in other cases // as well. We need to deoptimize the world in case we are about // to call an optimized function. - DeoptimizeWorld(); NotifySingleStepping(true); skip_next_step_ = skip_next_step; if (FLAG_verbose_debug) { OS::PrintErr("HandleSteppingRequest - kStepInto\n"); } } else if (resume_action_ == kStepOver) { - DeoptimizeWorld(); NotifySingleStepping(true); skip_next_step_ = skip_next_step; SetSyncSteppingFramePointer(stack_trace_); @@ -3037,7 +3091,6 @@ void Debugger::HandleSteppingRequest(bool skip_next_step /* = false */) { } // Fall through to synchronous stepping. - DeoptimizeWorld(); NotifySingleStepping(true); // Find topmost caller that is debuggable. for (intptr_t i = 1; i < stack_trace_->Length(); i++) { @@ -3318,30 +3371,45 @@ bool Debugger::IsDebuggable(const Function& func) { void GroupDebugger::RegisterSingleSteppingDebugger(Thread* thread, const Debugger* debugger) { - ASSERT(single_stepping_set_lock()->IsCurrentThreadWriter()); + WriteRwLocker sl(Thread::Current(), single_stepping_set_lock()); single_stepping_set_.Insert(debugger); } void GroupDebugger::UnregisterSingleSteppingDebugger(Thread* thread, const Debugger* debugger) { - ASSERT(single_stepping_set_lock()->IsCurrentThreadWriter()); + WriteRwLocker sl(Thread::Current(), single_stepping_set_lock()); single_stepping_set_.Remove(debugger); } -bool GroupDebugger::HasBreakpoint(Thread* thread, const Function& function) { - { - ReadRwLocker(thread, breakpoint_locations_lock()); - // Check if function has any breakpoints. - String& url = String::Handle(thread->zone()); - for (intptr_t i = 0; i < breakpoint_locations_.length(); i++) { - BreakpointLocation* location = breakpoint_locations_.At(i); - url = location->url(); - if (FunctionOverlaps(function, url, location->token_pos(), - location->end_token_pos())) { - return true; - } +bool GroupDebugger::HasBreakpointUnsafe(Thread* thread, + const Function& function) { + DEBUG_ASSERT(thread->IsInStoppedMutatorsScope() || + breakpoint_locations_lock()->IsCurrentThreadReader()); + // Check if function has any breakpoints. + String& url = String::Handle(thread->zone()); + for (intptr_t i = 0; i < breakpoint_locations_.length(); i++) { + BreakpointLocation* location = breakpoint_locations_.At(i); + url = location->url(); + if (FunctionOverlaps(function, url, location->token_pos(), + location->end_token_pos())) { + return true; } } + return false; +} + +bool GroupDebugger::HasBreakpoint(Thread* thread, const Function& function) { + bool hasBreakpoint = false; + // Don't need to worry about the lock if mutators are stopped. + if (thread->IsInStoppedMutatorsScope()) { + hasBreakpoint = HasBreakpointUnsafe(thread, function); + } else { + SafepointReadRwLocker sl(thread, breakpoint_locations_lock()); + hasBreakpoint = HasBreakpointUnsafe(thread, function); + } + if (hasBreakpoint) { + return true; + } // TODO(aam): do we have to iterate over both code breakpoints and // breakpoint locations? Wouldn't be sufficient to iterate over only @@ -3366,7 +3434,6 @@ bool GroupDebugger::IsDebugging(Thread* thread, const Function& function) { void Debugger::set_resume_action(ResumeAction resume_action) { auto thread = Thread::Current(); - WriteRwLocker sl(thread, group_debugger()->single_stepping_set_lock()); if (resume_action == kContinue) { group_debugger()->UnregisterSingleSteppingDebugger(thread, this); } else { @@ -3518,8 +3585,8 @@ ErrorPtr Debugger::PauseBreakpoint() { BreakpointLocation* bpt_location = nullptr; const char* cbpt_tostring = nullptr; { - ReadRwLocker cbl(Thread::Current(), - group_debugger()->code_breakpoints_lock()); + SafepointReadRwLocker cbl(Thread::Current(), + group_debugger()->code_breakpoints_lock()); CodeBreakpoint* cbpt = nullptr; bpt_location = group_debugger()->GetBreakpointLocationFor( this, top_frame->pc(), &cbpt); @@ -3767,7 +3834,7 @@ void Debugger::NotifyDoneLoading() { // TODO(hausner): Could potentially make this faster by checking // whether the call target at pc is a debugger stub. bool GroupDebugger::HasActiveBreakpoint(uword pc) { - ReadRwLocker sl(Thread::Current(), code_breakpoints_lock()); + SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock()); CodeBreakpoint* cbpt = GetCodeBreakpoint(pc); return (cbpt != nullptr) && (cbpt->IsEnabled()); } @@ -3788,7 +3855,7 @@ BreakpointLocation* GroupDebugger::GetBreakpointLocationFor( uword breakpoint_address, CodeBreakpoint** pcbpt) { ASSERT(pcbpt != nullptr); - ReadRwLocker sl(Thread::Current(), code_breakpoints_lock()); + SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock()); *pcbpt = code_breakpoints_; while (*pcbpt != nullptr) { if ((*pcbpt)->pc() == breakpoint_address) { @@ -3808,7 +3875,7 @@ void GroupDebugger::RegisterCodeBreakpoint(CodeBreakpoint* cbpt) { } CodePtr GroupDebugger::GetPatchedStubAddress(uword breakpoint_address) { - ReadRwLocker sl(Thread::Current(), code_breakpoints_lock()); + SafepointReadRwLocker sl(Thread::Current(), code_breakpoints_lock()); CodeBreakpoint* cbpt = GetCodeBreakpoint(breakpoint_address); if (cbpt != nullptr) { return cbpt->OrigStubAddress(); @@ -3818,8 +3885,8 @@ CodePtr GroupDebugger::GetPatchedStubAddress(uword breakpoint_address) { } bool Debugger::SetBreakpointState(Breakpoint* bpt, bool enable) { - WriteRwLocker sl(Thread::Current(), - group_debugger()->breakpoint_locations_lock()); + SafepointWriteRwLocker sl(Thread::Current(), + group_debugger()->breakpoint_locations_lock()); if (bpt->is_enabled() != enable) { if (FLAG_verbose_debug) { OS::PrintErr("Setting breakpoint %" Pd " to state: %s\n", bpt->id(), @@ -3835,8 +3902,8 @@ bool Debugger::SetBreakpointState(Breakpoint* bpt, bool enable) { // Remove and delete the source breakpoint bpt and its associated // code breakpoints. void Debugger::RemoveBreakpoint(intptr_t bp_id) { - WriteRwLocker sl(Thread::Current(), - group_debugger()->breakpoint_locations_lock()); + SafepointWriteRwLocker sl(Thread::Current(), + group_debugger()->breakpoint_locations_lock()); if (RemoveBreakpointFromTheList(bp_id, &breakpoint_locations_)) { return; } @@ -3911,7 +3978,8 @@ bool Debugger::RemoveBreakpointFromTheList(intptr_t bp_id, } void GroupDebugger::RegisterBreakpointLocation(BreakpointLocation* location) { - ASSERT(breakpoint_locations_lock()->IsCurrentThreadWriter()); + DEBUG_ASSERT(breakpoint_locations_lock()->IsCurrentThreadWriter() || + Thread::Current()->IsInStoppedMutatorsScope()); breakpoint_locations_.Add(location); } @@ -3931,7 +3999,7 @@ void GroupDebugger::UnregisterBreakpointLocation(BreakpointLocation* location) { // should be hit before it gets deleted. void GroupDebugger::UnlinkCodeBreakpoints(BreakpointLocation* bpt_location) { ASSERT(bpt_location != nullptr); - WriteRwLocker sl(Thread::Current(), code_breakpoints_lock()); + SafepointWriteRwLocker sl(Thread::Current(), code_breakpoints_lock()); CodeBreakpoint* curr_bpt = code_breakpoints_; while (curr_bpt != nullptr) { if (curr_bpt->FindAndDeleteBreakpointLocation(bpt_location)) { @@ -3945,7 +4013,8 @@ void GroupDebugger::UnlinkCodeBreakpoints(BreakpointLocation* bpt_location) { // Remove and delete unlinked code breakpoints, i.e. breakpoints that // are not associated with a breakpoint location. void GroupDebugger::RemoveUnlinkedCodeBreakpoints() { - ASSERT(code_breakpoints_lock()->IsCurrentThreadWriter()); + DEBUG_ASSERT(code_breakpoints_lock()->IsCurrentThreadWriter() || + Thread::Current()->IsInStoppedMutatorsScope()); CodeBreakpoint* prev_bpt = nullptr; CodeBreakpoint* curr_bpt = code_breakpoints_; while (curr_bpt != nullptr) { @@ -4070,15 +4139,27 @@ BreakpointLocation* Debugger::GetLatentBreakpoint(const String& url, return loc; } -void Debugger::RegisterBreakpointLocation(BreakpointLocation* loc) { - WriteRwLocker sl(Thread::Current(), - group_debugger()->breakpoint_locations_lock()); +void Debugger::RegisterBreakpointLocationUnsafe(BreakpointLocation* loc) { + DEBUG_ASSERT( + group_debugger()->breakpoint_locations_lock()->IsCurrentThreadWriter() || + Thread::Current()->IsInStoppedMutatorsScope()); ASSERT(loc->next() == nullptr); loc->set_next(breakpoint_locations_); breakpoint_locations_ = loc; group_debugger()->RegisterBreakpointLocation(loc); } +void Debugger::RegisterBreakpointLocation(BreakpointLocation* loc) { + auto thread = Thread::Current(); + if (thread->IsInStoppedMutatorsScope()) { + RegisterBreakpointLocationUnsafe(loc); + } else { + SafepointWriteRwLocker sl(thread, + group_debugger()->breakpoint_locations_lock()); + RegisterBreakpointLocationUnsafe(loc); + } +} + #endif // !PRODUCT } // namespace dart diff --git a/runtime/vm/debugger.h b/runtime/vm/debugger.h index 355b55c2280c..02c76faca328 100644 --- a/runtime/vm/debugger.h +++ b/runtime/vm/debugger.h @@ -574,6 +574,8 @@ class GroupDebugger { explicit GroupDebugger(IsolateGroup* isolate_group); ~GroupDebugger(); + void MakeCodeBreakpointAtUnsafe(const Function& func, + BreakpointLocation* bpt); void MakeCodeBreakpointAt(const Function& func, BreakpointLocation* bpt); // Returns [nullptr] if no breakpoint exists for the given address. @@ -592,6 +594,7 @@ class GroupDebugger { // Returns true if the call at address pc is patched to point to // a debugger stub. bool HasActiveBreakpoint(uword pc); + bool HasCodeBreakpointInFunctionUnsafe(const Function& func); bool HasCodeBreakpointInFunction(const Function& func); bool HasCodeBreakpointInCode(const Code& code); @@ -609,9 +612,11 @@ class GroupDebugger { void VisitObjectPointers(ObjectPointerVisitor* visitor); - RwLock* code_breakpoints_lock() { return code_breakpoints_lock_.get(); } + SafepointRwLock* code_breakpoints_lock() { + return code_breakpoints_lock_.get(); + } - RwLock* breakpoint_locations_lock() { + SafepointRwLock* breakpoint_locations_lock() { return breakpoint_locations_lock_.get(); } @@ -623,19 +628,22 @@ class GroupDebugger { // Returns [true] if there is at least one breakpoint set in function or code. // Checks for both user-defined and internal temporary breakpoints. + bool HasBreakpointUnsafe(Thread* thread, const Function& function); bool HasBreakpoint(Thread* thread, const Function& function); bool IsDebugging(Thread* thread, const Function& function); + IsolateGroup* isolate_group() { return isolate_group_; } + private: IsolateGroup* isolate_group_; - std::unique_ptr code_breakpoints_lock_; + std::unique_ptr code_breakpoints_lock_; CodeBreakpoint* code_breakpoints_; // Secondary list of all breakpoint_locations_(primary is in Debugger class). // This list is kept in sync with all the lists in Isolate Debuggers and is // used to quickly scan BreakpointLocations when new Function is compiled. - std::unique_ptr breakpoint_locations_lock_; + std::unique_ptr breakpoint_locations_lock_; MallocGrowableArray breakpoint_locations_; std::unique_ptr single_stepping_set_lock_; @@ -793,7 +801,8 @@ class Debugger { TokenPosition last_token_pos, Function* best_fit); void DeoptimizeWorld(); - void NotifySingleStepping(bool value) const; + void RunWithStoppedDeoptimizedWorld(std::function fun); + void NotifySingleStepping(bool value); BreakpointLocation* SetCodeBreakpoints( const GrowableHandlePtrArray& scripts, TokenPosition token_pos, @@ -820,6 +829,7 @@ class Debugger { BreakpointLocation* GetLatentBreakpoint(const String& url, intptr_t line, intptr_t column); + void RegisterBreakpointLocationUnsafe(BreakpointLocation* loc); void RegisterBreakpointLocation(BreakpointLocation* bpt); BreakpointLocation* GetResolvedBreakpointLocation( const String& script_url, diff --git a/runtime/vm/debugger_api_impl_test.cc b/runtime/vm/debugger_api_impl_test.cc index a98ce211c8f4..ff601ae32ab9 100644 --- a/runtime/vm/debugger_api_impl_test.cc +++ b/runtime/vm/debugger_api_impl_test.cc @@ -157,6 +157,15 @@ Dart_Handle Dart_SetBreakpoint(Dart_Handle script_url_in, return Dart_NewInteger(bpt->id()); } +Dart_Handle Dart_RemoveBreakpoint(Dart_Handle breakpoint_id_in) { + DARTSCOPE(Thread::Current()); + Isolate* I = T->isolate(); + CHECK_DEBUGGER(I); + UNWRAP_AND_CHECK_PARAM(Integer, breakpoint_id, breakpoint_id_in); + I->debugger()->RemoveBreakpoint(breakpoint_id.AsInt64Value()); + return Api::Success(); +} + Dart_Handle Dart_EvaluateStaticExpr(Dart_Handle lib_handle, Dart_Handle expr_in) { DARTSCOPE(Thread::Current()); diff --git a/runtime/vm/debugger_api_impl_test.h b/runtime/vm/debugger_api_impl_test.h index 66ca912cb445..18eff22f4b4f 100644 --- a/runtime/vm/debugger_api_impl_test.h +++ b/runtime/vm/debugger_api_impl_test.h @@ -82,6 +82,12 @@ Dart_Handle Dart_GetLibraryDebuggable(intptr_t library_id, bool* is_debuggable); */ Dart_Handle Dart_SetLibraryDebuggable(intptr_t library_id, bool is_debuggable); +/** + * Remove breakpoint with provided id. + * + * Requires there to be a current isolate. + */ +Dart_Handle Dart_RemoveBreakpoint(Dart_Handle breakpoint_id); /** * Sets a breakpoint at line \line_number in \script_url, or the closest * following line (within the same function) where a breakpoint can be set. diff --git a/runtime/vm/heap/safepoint_test.cc b/runtime/vm/heap/safepoint_test.cc index 0f08b45166f4..e432c4957bda 100644 --- a/runtime/vm/heap/safepoint_test.cc +++ b/runtime/vm/heap/safepoint_test.cc @@ -387,7 +387,7 @@ class CheckinTask : public StateMachineTask { // Test that mutators will not check-in to "deopt safepoint operations" at // at places where the mutator cannot depot (which is indicated by the -// Thread::runtime_call_kind_ value). +// [Thread::runtime_call_deopt_ability_] value). #if !defined(PRODUCT) ISOLATE_UNIT_TEST_CASE(SafepointOperation_SafepointPointTest) { auto isolate_group = thread->isolate_group(); diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc index 5d1af8fee83b..4a7aaffcd68a 100644 --- a/runtime/vm/object_test.cc +++ b/runtime/vm/object_test.cc @@ -5816,6 +5816,202 @@ TEST_CASE(FunctionWithBreakpointNotInlined) { } } +void SetBreakpoint(Dart_NativeArguments args) { + // Refers to the DeoptimizeFramesWhenSettingBreakpoint function below. + const int kBreakpointLine = 8; + + // This will force deoptimization of functions on stack. + // Function on stack has to be optimized, since we want to trigger debuggers + // on-stack deoptimization flow when we set a breakpoint. + Dart_Handle result = + Dart_SetBreakpoint(NewString(TestCase::url()), kBreakpointLine); + EXPECT_VALID(result); +} + +static Dart_NativeFunction SetBreakpointResolver(Dart_Handle name, + int argument_count, + bool* auto_setup_scope) { + ASSERT(auto_setup_scope != nullptr); + *auto_setup_scope = true; + const char* cstr = nullptr; + Dart_Handle result = Dart_StringToCString(name, &cstr); + EXPECT_VALID(result); + EXPECT_STREQ(cstr, "setBreakpoint"); + return &SetBreakpoint; +} + +TEST_CASE(DeoptimizeFramesWhenSettingBreakpoint) { + const char* kOriginalScript = "test() {}"; + + Dart_Handle lib = TestCase::LoadTestScript(kOriginalScript, nullptr); + EXPECT_VALID(lib); + Dart_SetNativeResolver(lib, &SetBreakpointResolver, nullptr); + + // Get unoptimized code for functions so they can be optimized. + Dart_Handle result = Dart_Invoke(lib, NewString("test"), 0, nullptr); + EXPECT_VALID(result); + + // Launch second isolate so that running with stopped mutators during + // deoptimizattion requests a safepoint. + Dart_Isolate parent = Dart_CurrentIsolate(); + Dart_ExitIsolate(); + char* error = nullptr; + Dart_Isolate child = Dart_CreateIsolateInGroup(parent, "child", + /*shutdown_callback=*/nullptr, + /*cleanup_callback=*/nullptr, + /*peer=*/nullptr, &error); + EXPECT_NE(nullptr, child); + EXPECT_EQ(nullptr, error); + Dart_ExitIsolate(); + Dart_EnterIsolate(parent); + + const char* kReloadScript = + R"( + @pragma("vm:external-name", "setBreakpoint") + external setBreakpoint(); + baz() {} + test() { + if (true) { + setBreakpoint(); + } else { + baz(); // this line gets a breakpoint + } + } + )"; + lib = TestCase::ReloadTestScript(kReloadScript); + EXPECT_VALID(lib); + + { + TransitionNativeToVM transition(thread); + const String& name = String::Handle(String::New(TestCase::url())); + const Library& vmlib = + Library::Handle(Library::LookupLibrary(thread, name)); + EXPECT(!vmlib.IsNull()); + Function& func_test = Function::Handle(GetFunction(vmlib, "test")); + Compiler::EnsureUnoptimizedCode(thread, func_test); + Compiler::CompileOptimizedFunction(thread, func_test); + func_test.set_unoptimized_code(Code::Handle(Code::null())); + } + + result = Dart_Invoke(lib, NewString("test"), 0, nullptr); + EXPECT_VALID(result); + + // Make sure child isolate finishes. + Dart_ExitIsolate(); + Dart_EnterIsolate(child); + { + bool result = + Dart_RunLoopAsync(/*errors_are_fatal=*/true, + /*on_error_port=*/0, /*on_exit_port=*/0, &error); + EXPECT_EQ(true, result); + } + EXPECT_EQ(nullptr, error); + Dart_EnterIsolate(parent); +} + +class ToggleBreakpointTask : public ThreadPool::Task { + public: + ToggleBreakpointTask(IsolateGroup* isolate_group, + Dart_Isolate isolate, + std::atomic* done) + : isolate_group_(isolate_group), isolate_(isolate), done_(done) {} + virtual void Run() { + Dart_EnterIsolate(isolate_); + Dart_EnterScope(); + const int kBreakpointLine = 5; // in the dart script below + Thread* t = Thread::Current(); + for (intptr_t i = 0; i < 1000; i++) { + Dart_Handle result = + Dart_SetBreakpoint(NewString(TestCase::url()), kBreakpointLine); + EXPECT_VALID(result); + int64_t breakpoint_id; + { + TransitionNativeToVM transition(t); + Integer& breakpoint_id_handle = Integer::Handle(); + breakpoint_id_handle ^= Api::UnwrapHandle(result); + breakpoint_id = breakpoint_id_handle.AsInt64Value(); + } + result = Dart_RemoveBreakpoint(Dart_NewInteger(breakpoint_id)); + EXPECT_VALID(result); + } + Dart_ExitScope(); + Dart_ExitIsolate(); + *done_ = true; + } + + private: + IsolateGroup* isolate_group_; + Dart_Isolate isolate_; + std::atomic* done_; +}; + +TEST_CASE(DartAPI_BreakpointLockRace) { + const char* kScriptChars = + "class A {\n" + " a() {\n" + " }\n" + " b() {\n" + " a();\n" // This is line 5. + " }\n" + "}\n" + "test() {\n" + " new A().b();\n" + "}"; + // Create a test library and Load up a test script in it. + Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, nullptr); + EXPECT_VALID(lib); + + // Run function A.b one time. + Dart_Handle result = Dart_Invoke(lib, NewString("test"), 0, nullptr); + EXPECT_VALID(result); + + // Launch second isolate so that running with stopped mutators during + // deoptimizattion requests a safepoint. + Dart_Isolate parent = Dart_CurrentIsolate(); + Dart_ExitIsolate(); + char* error = nullptr; + Dart_Isolate child = Dart_CreateIsolateInGroup(parent, "child", + /*shutdown_callback=*/nullptr, + /*cleanup_callback=*/nullptr, + /*peer=*/nullptr, &error); + EXPECT_NE(nullptr, child); + EXPECT_EQ(nullptr, error); + Dart_ExitIsolate(); + Dart_EnterIsolate(parent); + + // Run function A.b one time. + std::atomic done = false; + Dart::thread_pool()->Run(IsolateGroup::Current(), child, + &done); + + while (!done) { + ReloadParticipationScope allow_reload(thread); + { + TransitionNativeToVM transition(thread); + const String& name = String::Handle(String::New(TestCase::url())); + const Library& vmlib = + Library::Handle(Library::LookupLibrary(thread, name)); + EXPECT(!vmlib.IsNull()); + const Class& class_a = Class::Handle( + vmlib.LookupClass(String::Handle(Symbols::New(thread, "A")))); + Function& func_b = Function::Handle(GetFunction(class_a, "b")); + func_b.CanBeInlined(); + } + } + + // Make sure child isolate finishes. + Dart_ExitIsolate(); + Dart_EnterIsolate(child); + { + bool result = + Dart_RunLoopAsync(/*errors_are_fatal=*/true, + /*on_error_port=*/0, /*on_exit_port=*/0, &error); + EXPECT_EQ(true, result); + } + EXPECT_EQ(nullptr, error); + Dart_EnterIsolate(parent); +} + ISOLATE_UNIT_TEST_CASE(SpecialClassesHaveEmptyArrays) { ObjectStore* object_store = IsolateGroup::Current()->object_store(); Class& cls = Class::Handle();