Chromium Code Reviews| Index: runtime/vm/debugger.cc |
| diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc |
| index 53bcba2632e17b1e2eb42aee4b611cb66b6ad185..2bd64fab0fffb859b7a755a34b51608fed527771 100644 |
| --- a/runtime/vm/debugger.cc |
| +++ b/runtime/vm/debugger.cc |
| @@ -26,6 +26,7 @@ |
| #include "vm/service_isolate.h" |
| #include "vm/service.h" |
| #include "vm/stack_frame.h" |
| +#include "vm/stack_trace.h" |
| #include "vm/stub_code.h" |
| #include "vm/symbols.h" |
| #include "vm/thread_interrupter.h" |
| @@ -249,13 +250,15 @@ ActivationFrame::ActivationFrame(uword pc, |
| uword sp, |
| const Code& code, |
| const Array& deopt_frame, |
| - intptr_t deopt_frame_offset) |
| + intptr_t deopt_frame_offset, |
| + ActivationFrame::Kind kind) |
| : pc_(pc), |
| fp_(fp), |
| sp_(sp), |
| ctx_(Context::ZoneHandle()), |
| code_(Code::ZoneHandle(code.raw())), |
| function_(Function::ZoneHandle(code.function())), |
|
rmacnak
2017/01/26 18:05:57
Doesn't mutate, replace slot with an function.
Cutch
2017/01/31 23:45:30
Not entirely sure I understand your comment here.
|
| + live_frame_(kind == kRegular), |
| token_pos_initialized_(false), |
| token_pos_(TokenPosition::kNoSource), |
| try_index_(-1), |
| @@ -264,12 +267,35 @@ ActivationFrame::ActivationFrame(uword pc, |
| context_level_(-1), |
| deopt_frame_(Array::ZoneHandle(deopt_frame.raw())), |
| deopt_frame_offset_(deopt_frame_offset), |
| + kind_(kind), |
| vars_initialized_(false), |
| var_descriptors_(LocalVarDescriptors::ZoneHandle()), |
| desc_indices_(8), |
| pc_desc_(PcDescriptors::ZoneHandle()) {} |
| +ActivationFrame::ActivationFrame(Kind kind) |
| + : pc_(0), |
| + fp_(0), |
| + sp_(0), |
| + ctx_(Context::ZoneHandle()), |
| + code_(Code::ZoneHandle()), |
| + function_(Function::ZoneHandle()), |
| + live_frame_(kind == kRegular), |
| + token_pos_initialized_(false), |
| + token_pos_(TokenPosition::kNoSource), |
| + try_index_(-1), |
| + line_number_(-1), |
| + column_number_(-1), |
| + context_level_(-1), |
| + deopt_frame_(Array::ZoneHandle()), |
| + deopt_frame_offset_(0), |
| + kind_(kind), |
| + vars_initialized_(false), |
| + var_descriptors_(LocalVarDescriptors::ZoneHandle()), |
| + desc_indices_(8), |
| + pc_desc_(PcDescriptors::ZoneHandle()) {} |
| + |
| bool Debugger::NeedsIsolateEvents() { |
| return ((isolate_ != Dart::vm_isolate()) && |
| !ServiceIsolate::IsServiceIsolateDescendant(isolate_) && |
| @@ -318,12 +344,11 @@ RawError* Debugger::PauseRequest(ServiceEvent::EventKind kind) { |
| if (trace->Length() > 0) { |
| event.set_top_frame(trace->FrameAt(0)); |
| } |
| - ASSERT(stack_trace_ == NULL); |
| - stack_trace_ = trace; |
| + CacheStackTraces(trace, CollectAsyncCausalStackTrace()); |
| resume_action_ = kContinue; |
| Pause(&event); |
| HandleSteppingRequest(trace); |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| // If any error occurred while in the debug message loop, return it here. |
| const Error& error = Error::Handle(Thread::Current()->sticky_error()); |
| @@ -740,7 +765,7 @@ void ActivationFrame::GetDescIndices() { |
| GetVarDescriptors(); |
| TokenPosition activation_token_pos = TokenPos(); |
| - if (!activation_token_pos.IsDebugPause()) { |
| + if (!activation_token_pos.IsDebugPause() || !live_frame_) { |
| // We don't have a token position for this frame, so can't determine |
| // which variables are visible. |
| vars_initialized_ = true; |
| @@ -1086,8 +1111,22 @@ const char* ActivationFrame::ToCString() { |
| void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) { |
| + if (kind_ == kRegular) { |
| + PrintToJSONObjectRegular(jsobj, full); |
| + } else if (kind_ == kAsyncCausal) { |
| + PrintToJSONObjectAsyncCausal(jsobj, full); |
| + } else if (kind_ == kAsyncSuspensionMarker) { |
| + PrintToJSONObjectAsyncSuspensionMarker(jsobj, full); |
| + } else { |
| + UNIMPLEMENTED(); |
| + } |
| +} |
| + |
| + |
| +void ActivationFrame::PrintToJSONObjectRegular(JSONObject* jsobj, bool full) { |
| const Script& script = Script::Handle(SourceScript()); |
| jsobj->AddProperty("type", "Frame"); |
| + jsobj->AddProperty("kind", KindToCString(kind_)); |
| TokenPosition pos = TokenPos(); |
| if (pos.IsSynthetic()) { |
| pos = pos.FromSynthetic(); |
| @@ -1129,6 +1168,36 @@ void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) { |
| } |
| } |
| + |
| +void ActivationFrame::PrintToJSONObjectAsyncCausal(JSONObject* jsobj, |
| + bool full) { |
| + jsobj->AddProperty("type", "Frame"); |
| + jsobj->AddProperty("kind", KindToCString(kind_)); |
| + const Script& script = Script::Handle(SourceScript()); |
| + TokenPosition pos = TokenPos(); |
| + if (pos.IsSynthetic()) { |
| + pos = pos.FromSynthetic(); |
| + } |
| + jsobj->AddLocation(script, pos); |
| + jsobj->AddProperty("function", function(), !full); |
| + jsobj->AddProperty("code", code()); |
| + if (full) { |
| + // TODO(cutch): The old "full" script usage no longer fits |
| + // in the world where we pass the script as part of the |
| + // location. |
| + jsobj->AddProperty("script", script, !full); |
| + } |
| +} |
| + |
| + |
| +void ActivationFrame::PrintToJSONObjectAsyncSuspensionMarker(JSONObject* jsobj, |
| + bool full) { |
| + jsobj->AddProperty("type", "Frame"); |
| + jsobj->AddProperty("kind", KindToCString(kind_)); |
| + jsobj->AddProperty("marker", "AsynchronousSuspension"); |
| +} |
| + |
| + |
| static bool IsFunctionVisible(const Function& function) { |
| return FLAG_show_invisible_frames || function.is_visible(); |
| } |
| @@ -1141,6 +1210,19 @@ void DebuggerStackTrace::AddActivation(ActivationFrame* frame) { |
| } |
| +void DebuggerStackTrace::AddMarker(ActivationFrame::Kind marker) { |
| + ASSERT((marker >= ActivationFrame::kAsyncSuspensionMarker) && |
| + (marker <= ActivationFrame::kAsyncSuspensionMarker)); |
| + trace_.Add(new ActivationFrame(marker)); |
| +} |
| + |
| + |
| +void DebuggerStackTrace::AddAsyncCausalFrame(uword pc, const Code& code) { |
| + trace_.Add(new ActivationFrame(pc, 0, 0, code, Array::Handle(), 0, |
| + ActivationFrame::kAsyncCausal)); |
| +} |
| + |
| + |
| const uint8_t kSafepointKind = RawPcDescriptors::kIcCall | |
| RawPcDescriptors::kUnoptStaticCall | |
| RawPcDescriptors::kRuntimeCall; |
| @@ -1281,6 +1363,7 @@ Debugger::~Debugger() { |
| ASSERT(breakpoint_locations_ == NULL); |
| ASSERT(code_breakpoints_ == NULL); |
| ASSERT(stack_trace_ == NULL); |
| + ASSERT(async_causal_stack_trace_ == NULL); |
| ASSERT(obj_cache_ == NULL); |
| ASSERT(synthetic_async_breakpoint_ == NULL); |
| } |
| @@ -1519,33 +1602,111 @@ DebuggerStackTrace* Debugger::CollectStackTrace() { |
| } |
| if (frame->IsDartFrame()) { |
| code = frame->LookupDartCode(); |
| - if (code.is_optimized() && !FLAG_precompiled_runtime) { |
| - deopt_frame = DeoptimizeToArray(thread, frame, code); |
| - for (InlinedFunctionsIterator it(code, frame->pc()); !it.Done(); |
| - it.Advance()) { |
| + AppendCodeFrames(thread, isolate, zone, stack_trace, frame, &code, |
| + &inlined_code, &deopt_frame); |
| + } |
| + } |
| + return stack_trace; |
| +} |
| + |
| +void Debugger::AppendCodeFrames(Thread* thread, |
| + Isolate* isolate, |
| + Zone* zone, |
| + DebuggerStackTrace* stack_trace, |
| + StackFrame* frame, |
| + Code* code, |
| + Code* inlined_code, |
| + Array* deopt_frame) { |
| + if (code->is_optimized() && !FLAG_precompiled_runtime) { |
| + *deopt_frame = DeoptimizeToArray(thread, frame, *code); |
| + for (InlinedFunctionsIterator it(*code, frame->pc()); !it.Done(); |
| + it.Advance()) { |
| + *inlined_code = it.code(); |
| + if (FLAG_trace_debugger_stacktrace) { |
| + const Function& function = |
| + Function::Handle(zone, inlined_code->function()); |
| + ASSERT(!function.IsNull()); |
| + OS::PrintErr("CollectStackTrace: visiting inlined function: %s\n", |
| + function.ToFullyQualifiedCString()); |
| + } |
| + intptr_t deopt_frame_offset = it.GetDeoptFpOffset(); |
| + stack_trace->AddActivation(CollectDartFrame(isolate, it.pc(), frame, |
| + *inlined_code, *deopt_frame, |
| + deopt_frame_offset)); |
| + } |
| + } else { |
| + stack_trace->AddActivation(CollectDartFrame( |
| + isolate, frame->pc(), frame, *code, Object::null_array(), 0)); |
| + } |
| +} |
| + |
| + |
| +DebuggerStackTrace* Debugger::CollectAsyncCausalStackTrace() { |
| + if (!FLAG_sane_async_stacks) { |
| + return NULL; |
| + } |
| + Thread* thread = Thread::Current(); |
| + Zone* zone = thread->zone(); |
| + Isolate* isolate = thread->isolate(); |
| + DebuggerStackTrace* stack_trace = new DebuggerStackTrace(8); |
| + StackFrameIterator iterator(false); |
| + Code& code = Code::Handle(zone); |
| + Smi& offset = Smi::Handle(); |
| + Code& inlined_code = Code::Handle(zone); |
| + Array& deopt_frame = Array::Handle(zone); |
| + |
| + Function& async_function = Function::Handle(zone); |
| + Array& async_code_array = Array::Handle(zone); |
| + Array& async_pc_offset_array = Array::Handle(zone); |
| + const intptr_t async_stack_trace_length = |
| + StackTraceUtils::ExtractAsyncStackTraceInfo( |
| + thread, &async_function, &async_code_array, &async_pc_offset_array); |
| + |
| + if (async_function.IsNull()) { |
| + return NULL; |
| + } |
| + |
| + // Append the top frame from the stack trace. This is the active |
| + // asynchronous function. We truncate the remainder of the synchronous |
| + // stack trace because it contains activations that are part of the |
| + // asynchronous dispatch mechanisms. |
| + StackFrame* frame = iterator.NextFrame(); |
| + while ((frame != NULL) && !frame->IsDartFrame()) { |
| + frame = iterator.NextFrame(); |
| + } |
| + ASSERT(frame != NULL); |
| + ASSERT(frame->IsDartFrame()); |
| + code = frame->LookupDartCode(); |
| + AppendCodeFrames(thread, isolate, zone, stack_trace, frame, &code, |
| + &inlined_code, &deopt_frame); |
| + |
| + // Now we append the asynchronous causal stack trace. These are not active |
| + // frames but a historical record of how this asynchronous function was |
| + // activated. |
| + for (intptr_t i = 0; i < async_stack_trace_length; i++) { |
| + if (async_code_array.At(i) == Code::null()) { |
| + break; |
| + } |
| + if (async_code_array.At(i) == |
| + StubCode::AsynchronousGapMarker_entry()->code()) { |
| + stack_trace->AddMarker(ActivationFrame::kAsyncSuspensionMarker); |
| + } else { |
| + code = Code::RawCast(async_code_array.At(i)); |
| + offset = Smi::RawCast(async_pc_offset_array.At(i)); |
| + uword pc = code.PayloadStart() + offset.Value(); |
| + if (code.is_optimized()) { |
| + for (InlinedFunctionsIterator it(code, pc); !it.Done(); it.Advance()) { |
| inlined_code = it.code(); |
| - if (FLAG_trace_debugger_stacktrace) { |
| - const Function& function = |
| - Function::Handle(zone, inlined_code.function()); |
| - ASSERT(!function.IsNull()); |
| - OS::PrintErr("CollectStackTrace: visiting inlined function: %s\n", |
| - function.ToFullyQualifiedCString()); |
| - } |
| - intptr_t deopt_frame_offset = it.GetDeoptFpOffset(); |
| - stack_trace->AddActivation(CollectDartFrame(isolate, it.pc(), frame, |
| - inlined_code, deopt_frame, |
| - deopt_frame_offset)); |
| + stack_trace->AddAsyncCausalFrame(pc, inlined_code); |
| } |
| } else { |
| - stack_trace->AddActivation(CollectDartFrame( |
| - isolate, frame->pc(), frame, code, Object::null_array(), 0)); |
| + stack_trace->AddAsyncCausalFrame(pc, code); |
| } |
| } |
| } |
| return stack_trace; |
| } |
| - |
| ActivationFrame* Debugger::TopDartFrame() const { |
| StackFrameIterator iterator(false); |
| StackFrame* frame = iterator.NextFrame(); |
| @@ -1563,10 +1724,23 @@ DebuggerStackTrace* Debugger::StackTrace() { |
| return (stack_trace_ != NULL) ? stack_trace_ : CollectStackTrace(); |
| } |
| + |
| DebuggerStackTrace* Debugger::CurrentStackTrace() { |
| return CollectStackTrace(); |
| } |
| + |
| +DebuggerStackTrace* Debugger::AsyncCausalStackTrace() { |
| + return (async_causal_stack_trace_ != NULL) ? async_causal_stack_trace_ |
| + : CollectAsyncCausalStackTrace(); |
| +} |
| + |
| + |
| +DebuggerStackTrace* Debugger::CurrentAsyncCausalStackTrace() { |
| + return CollectAsyncCausalStackTrace(); |
| +} |
| + |
| + |
| DebuggerStackTrace* Debugger::StackTraceFrom(const class StackTrace& ex_trace) { |
| DebuggerStackTrace* stack_trace = new DebuggerStackTrace(8); |
| Function& function = Function::Handle(); |
| @@ -1667,11 +1841,10 @@ void Debugger::PauseException(const Instance& exc) { |
| if (stack_trace->Length() > 0) { |
| event.set_top_frame(stack_trace->FrameAt(0)); |
| } |
| - ASSERT(stack_trace_ == NULL); |
| - stack_trace_ = stack_trace; |
| + CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace()); |
| Pause(&event); |
| HandleSteppingRequest(stack_trace_); // we may get a rewind request |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| } |
| @@ -2668,6 +2841,21 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
| } |
| +void Debugger::CacheStackTraces(DebuggerStackTrace* stack_trace, |
| + DebuggerStackTrace* async_causal_stack_trace) { |
| + ASSERT(stack_trace_ == NULL); |
| + stack_trace_ = stack_trace; |
| + ASSERT(async_causal_stack_trace_ == NULL); |
| + async_causal_stack_trace_ = async_causal_stack_trace; |
| +} |
| + |
| + |
| +void Debugger::ClearCachedStackTraces() { |
| + stack_trace_ = NULL; |
| + async_causal_stack_trace_ = NULL; |
| +} |
| + |
| + |
| static intptr_t FindNextRewindFrameIndex(DebuggerStackTrace* stack, |
| intptr_t frame_index) { |
| for (intptr_t i = frame_index + 1; i < stack->Length(); i++) { |
| @@ -2796,7 +2984,7 @@ void Debugger::RewindToFrame(intptr_t frame_index) { |
| void Debugger::RewindToUnoptimizedFrame(StackFrame* frame, const Code& code) { |
| // We will be jumping out of the debugger rather than exiting this |
| // function, so prepare the debugger state. |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| resume_action_ = kContinue; |
| resume_frame_index_ = -1; |
| EnterSingleStepMode(); |
| @@ -2828,7 +3016,7 @@ void Debugger::RewindToOptimizedFrame(StackFrame* frame, |
| // We will be jumping out of the debugger rather than exiting this |
| // function, so prepare the debugger state. |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| resume_action_ = kContinue; |
| resume_frame_index_ = -1; |
| EnterSingleStepMode(); |
| @@ -2996,8 +3184,8 @@ RawError* Debugger::PauseStepping() { |
| frame->TokenPos().ToCString()); |
| } |
| - ASSERT(stack_trace_ == NULL); |
| - stack_trace_ = CollectStackTrace(); |
| + |
| + CacheStackTraces(CollectStackTrace(), CollectAsyncCausalStackTrace()); |
| // If this step callback is part of stepping over an await statement, |
| // we saved the synthetic async breakpoint in PauseBreakpoint. We report |
| // that we are paused at that breakpoint and then delete it after continuing. |
| @@ -3007,7 +3195,7 @@ RawError* Debugger::PauseStepping() { |
| synthetic_async_breakpoint_ = NULL; |
| } |
| HandleSteppingRequest(stack_trace_); |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| // If any error occurred while in the debug message loop, return it here. |
| const Error& error = Error::Handle(Thread::Current()->sticky_error()); |
| @@ -3030,50 +3218,7 @@ RawError* Debugger::PauseBreakpoint() { |
| CodeBreakpoint* cbpt = GetCodeBreakpoint(top_frame->pc()); |
| ASSERT(cbpt != NULL); |
| - BreakpointLocation* bpt_location = cbpt->bpt_location_; |
| - Breakpoint* bpt_hit = NULL; |
| - |
| - // There may be more than one applicable breakpoint at this location, but we |
| - // will report only one as reached. If there is a single-shot breakpoint, we |
| - // favor it; then a closure-specific breakpoint ; then an general breakpoint. |
| - if (bpt_location != NULL) { |
| - Breakpoint* bpt = bpt_location->breakpoints(); |
| - while (bpt != NULL) { |
| - if (bpt->IsSingleShot()) { |
| - bpt_hit = bpt; |
| - break; |
| - } |
| - bpt = bpt->next(); |
| - } |
| - |
| - if (bpt_hit == NULL) { |
| - bpt = bpt_location->breakpoints(); |
| - while (bpt != NULL) { |
| - if (bpt->IsPerClosure()) { |
| - Object& closure = Object::Handle(top_frame->GetClosure()); |
| - ASSERT(closure.IsInstance()); |
| - ASSERT(Instance::Cast(closure).IsClosure()); |
| - if (closure.raw() == bpt->closure()) { |
| - bpt_hit = bpt; |
| - break; |
| - } |
| - } |
| - bpt = bpt->next(); |
| - } |
| - } |
| - |
| - if (bpt_hit == NULL) { |
| - bpt = bpt_location->breakpoints(); |
| - while (bpt != NULL) { |
| - if (bpt->IsRepeated()) { |
| - bpt_hit = bpt; |
| - break; |
| - } |
| - bpt = bpt->next(); |
| - } |
| - } |
| - } |
| - |
| + Breakpoint* bpt_hit = FindHitBreakpoint(cbpt->bpt_location_, top_frame); |
| if (bpt_hit == NULL) { |
| return Error::null(); |
| } |
| @@ -3081,8 +3226,7 @@ RawError* Debugger::PauseBreakpoint() { |
| if (bpt_hit->is_synthetic_async()) { |
| DebuggerStackTrace* stack_trace = CollectStackTrace(); |
| ASSERT(stack_trace->Length() > 0); |
| - ASSERT(stack_trace_ == NULL); |
| - stack_trace_ = stack_trace; |
| + CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace()); |
| // Hit a synthetic async breakpoint. |
| if (FLAG_verbose_debug) { |
| @@ -3104,7 +3248,7 @@ RawError* Debugger::PauseBreakpoint() { |
| // When we single step from a user breakpoint, our next stepping |
| // point will be at the exact same pc. Skip it. |
| HandleSteppingRequest(stack_trace_, true /* skip next step */); |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| return Error::null(); |
| } |
| @@ -3117,13 +3261,12 @@ RawError* Debugger::PauseBreakpoint() { |
| cbpt->token_pos().ToCString(), top_frame->pc()); |
| } |
| - ASSERT(stack_trace_ == NULL); |
| - stack_trace_ = stack_trace; |
| + CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace()); |
| SignalPausedEvent(top_frame, bpt_hit); |
| // When we single step from a user breakpoint, our next stepping |
| // point will be at the exact same pc. Skip it. |
| HandleSteppingRequest(stack_trace_, true /* skip next step */); |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| if (cbpt->IsInternal()) { |
| RemoveInternalBreakpoints(); |
| } |
| @@ -3135,6 +3278,50 @@ RawError* Debugger::PauseBreakpoint() { |
| } |
| +Breakpoint* Debugger::FindHitBreakpoint(BreakpointLocation* location, |
| + ActivationFrame* top_frame) { |
| + if (location == NULL) { |
| + return NULL; |
| + } |
| + // There may be more than one applicable breakpoint at this location, but we |
| + // will report only one as reached. ; then ; then an general breakpoint. |
|
rmacnak
2017/01/26 18:05:57
Copy-paste error in comment.
Cutch
2017/01/31 23:45:30
Done.
|
| + |
| + // First check for a single-shot breakpoint. |
| + Breakpoint* bpt = location->breakpoints(); |
| + while (bpt != NULL) { |
| + if (bpt->IsSingleShot()) { |
| + return bpt; |
| + } |
| + bpt = bpt->next(); |
| + } |
| + |
| + // Now check for a closure-specific breakpoint. |
| + bpt = location->breakpoints(); |
| + while (bpt != NULL) { |
| + if (bpt->IsPerClosure()) { |
| + Object& closure = Object::Handle(top_frame->GetClosure()); |
| + ASSERT(closure.IsInstance()); |
| + ASSERT(Instance::Cast(closure).IsClosure()); |
| + if (closure.raw() == bpt->closure()) { |
| + return bpt; |
| + } |
| + } |
| + bpt = bpt->next(); |
| + } |
| + |
| + // Finally, check for a general breakpoint. |
| + bpt = location->breakpoints(); |
| + while (bpt != NULL) { |
| + if (bpt->IsRepeated()) { |
| + return bpt; |
| + } |
| + bpt = bpt->next(); |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| + |
| void Debugger::PauseDeveloper(const String& msg) { |
| // We ignore this breakpoint when the VM is executing code invoked |
| // by the debugger to evaluate variables values, or when we see a nested |
| @@ -3145,9 +3332,7 @@ void Debugger::PauseDeveloper(const String& msg) { |
| DebuggerStackTrace* stack_trace = CollectStackTrace(); |
| ASSERT(stack_trace->Length() > 0); |
| - ASSERT(stack_trace_ == NULL); |
| - stack_trace_ = stack_trace; |
| - |
| + CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace()); |
| // TODO(johnmccutchan): Send |msg| to Observatory. |
| // We are in the native call to Developer_debugger. the developer |
| @@ -3155,8 +3340,7 @@ void Debugger::PauseDeveloper(const String& msg) { |
| // this, we continue execution until the call exits (step out). |
| SetResumeAction(kStepOut); |
| HandleSteppingRequest(stack_trace_); |
| - |
| - stack_trace_ = NULL; |
| + ClearCachedStackTraces(); |
| } |