Index: runtime/vm/debugger.cc |
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc |
index 53bcba2632e17b1e2eb42aee4b611cb66b6ad185..8061270f5876c059c1550e3c0ce9f1369bcc698d 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" |
@@ -244,32 +245,6 @@ void CodeBreakpoint::VisitObjectPointers(ObjectPointerVisitor* visitor) { |
} |
-ActivationFrame::ActivationFrame(uword pc, |
- uword fp, |
- uword sp, |
- const Code& code, |
- const Array& deopt_frame, |
- intptr_t deopt_frame_offset) |
- : pc_(pc), |
- fp_(fp), |
- sp_(sp), |
- ctx_(Context::ZoneHandle()), |
- code_(Code::ZoneHandle(code.raw())), |
- function_(Function::ZoneHandle(code.function())), |
- 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.raw())), |
- deopt_frame_offset_(deopt_frame_offset), |
- 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 +293,12 @@ 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, CollectAsyncStackTrace(), |
+ CollectAsyncReturnCallStack()); |
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()); |
@@ -503,6 +478,86 @@ void Debugger::PrintSettingsToJSONObject(JSONObject* jsobj) const { |
} |
+ActivationFrame::ActivationFrame(uword pc, |
+ uword fp, |
+ uword sp, |
+ const Code& code, |
+ const Array& deopt_frame, |
+ 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())), |
+ suspended_frame_(false), |
+ 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.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()), |
+ suspended_frame_(true), |
+ 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()) {} |
+ |
+ |
+ActivationFrame::ActivationFrame(const Closure& async_activation) |
+ : pc_(0), |
+ fp_(0), |
+ sp_(0), |
+ ctx_(Context::ZoneHandle()), |
+ code_(Code::ZoneHandle()), |
+ function_(Function::ZoneHandle()), |
+ suspended_frame_(true), |
+ 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_(kAsyncLive), |
+ vars_initialized_(false), |
+ var_descriptors_(LocalVarDescriptors::ZoneHandle()), |
+ desc_indices_(8), |
+ pc_desc_(PcDescriptors::ZoneHandle()) { |
+ // Extract the function and the code from the asynchronous activation. |
+ function_ = async_activation.function(); |
+ code_ = function_.unoptimized_code(); |
+ ctx_ = async_activation.context(); |
+} |
+ |
+ |
RawString* ActivationFrame::QualifiedFunctionName() { |
return String::New(Debugger::QualifiedFunctionName(function())); |
} |
@@ -622,13 +677,13 @@ intptr_t ActivationFrame::ContextLevel() { |
// for the code position of the frame? For now say we are at context |
// level 0. |
TokenPos(); |
- if (token_pos_ == TokenPosition::kNoSource) { |
+ if (token_pos_.IsClassifying() || token_pos_.IsNoSource()) { |
// No PcDescriptor. |
return context_level_; |
} |
ASSERT(!pc_desc_.IsNull()); |
TokenPosition innermost_begin_pos = TokenPosition::kMinSource; |
- TokenPosition activation_token_pos = TokenPos(); |
+ TokenPosition activation_token_pos = TokenPos().FromSynthetic(); |
ASSERT(activation_token_pos.IsReal()); |
GetVarDescriptors(); |
intptr_t var_desc_len = var_descriptors_.Length(); |
@@ -676,6 +731,189 @@ const Context& ActivationFrame::GetSavedCurrentContext() { |
} |
+RawObject* ActivationFrame::GetAsyncCompleter() { |
+ if (!function_.IsAsyncClosure()) { |
+ return Object::null(); |
+ } |
+ GetVarDescriptors(); |
+ intptr_t var_desc_len = var_descriptors_.Length(); |
+ if (fp() == 0) { |
+ // Not actually on the stack. Pull it out of the closure's context. |
+ intptr_t var_desc_len = var_descriptors_.Length(); |
+ for (intptr_t i = 0; i < var_desc_len; i++) { |
+ RawLocalVarDescriptors::VarInfo var_info; |
+ var_descriptors_.GetInfo(i, &var_info); |
+ const int8_t kind = var_info.kind(); |
+ if (var_descriptors_.GetName(i) == Symbols::AsyncCompleter().raw()) { |
+ ASSERT(kind == RawLocalVarDescriptors::kContextVar); |
+ ASSERT(!ctx_.IsNull()); |
+ return ctx_.At(var_info.index()); |
+ } |
+ } |
+ } else { |
+ // On the stack. |
+ for (intptr_t i = 0; i < var_desc_len; i++) { |
+ RawLocalVarDescriptors::VarInfo var_info; |
+ var_descriptors_.GetInfo(i, &var_info); |
+ if (var_descriptors_.GetName(i) == Symbols::AsyncCompleter().raw()) { |
+ const int8_t kind = var_info.kind(); |
+ if (kind == RawLocalVarDescriptors::kStackVar) { |
+ return GetStackVar(var_info.index()); |
+ } else { |
+ ASSERT(kind == RawLocalVarDescriptors::kContextVar); |
+ return GetContextVar(var_info.scope_id, var_info.index()); |
+ } |
+ } |
+ } |
+ } |
+ return Object::null(); |
+} |
+ |
+ |
+RawObject* ActivationFrame::GetAsyncCompleterAwaiter(const Object& completer) { |
+ const Class& sync_completer_cls = Class::Handle(completer.clazz()); |
+ ASSERT(!sync_completer_cls.IsNull()); |
+ const Class& completer_cls = Class::Handle(sync_completer_cls.SuperClass()); |
+ const Field& future_field = |
+ Field::Handle(completer_cls.LookupInstanceFieldAllowPrivate( |
+ Symbols::CompleterFuture())); |
+ ASSERT(!future_field.IsNull()); |
+ Instance& future = Instance::Handle(); |
+ future ^= Instance::Cast(completer).GetField(future_field); |
+ ASSERT(!future.IsNull()); |
+ const Class& future_cls = Class::Handle(future.clazz()); |
+ ASSERT(!future_cls.IsNull()); |
+ const Field& awaiter_field = Field::Handle( |
+ future_cls.LookupInstanceFieldAllowPrivate(Symbols::_Awaiter())); |
+ ASSERT(!awaiter_field.IsNull()); |
+ return future.GetField(awaiter_field); |
+} |
+ |
+ |
+RawObject* ActivationFrame::GetAsyncStreamControllerStream() { |
+ if (!function_.IsAsyncGenClosure()) { |
+ return Object::null(); |
+ } |
+ GetVarDescriptors(); |
+ intptr_t var_desc_len = var_descriptors_.Length(); |
+ if (fp() == 0) { |
+ // Not actually on the stack. Pull it out of the closure's context. |
+ intptr_t var_desc_len = var_descriptors_.Length(); |
+ for (intptr_t i = 0; i < var_desc_len; i++) { |
+ RawLocalVarDescriptors::VarInfo var_info; |
+ var_descriptors_.GetInfo(i, &var_info); |
+ const int8_t kind = var_info.kind(); |
+ if (var_descriptors_.GetName(i) == Symbols::ControllerStream().raw()) { |
+ ASSERT(kind == RawLocalVarDescriptors::kContextVar); |
+ ASSERT(!ctx_.IsNull()); |
+ return ctx_.At(var_info.index()); |
+ } |
+ } |
+ } else { |
+ // On the stack. |
+ for (intptr_t i = 0; i < var_desc_len; i++) { |
+ RawLocalVarDescriptors::VarInfo var_info; |
+ var_descriptors_.GetInfo(i, &var_info); |
+ if (var_descriptors_.GetName(i) == Symbols::ControllerStream().raw()) { |
+ const int8_t kind = var_info.kind(); |
+ if (kind == RawLocalVarDescriptors::kStackVar) { |
+ return GetStackVar(var_info.index()); |
+ } else { |
+ ASSERT(kind == RawLocalVarDescriptors::kContextVar); |
+ return GetContextVar(var_info.scope_id, var_info.index()); |
+ } |
+ } |
+ } |
+ } |
+ return Object::null(); |
+} |
+ |
+ |
+RawObject* ActivationFrame::GetAsyncStreamControllerStreamAwaiter( |
+ const Object& stream) { |
+ const Class& stream_cls = Class::Handle(stream.clazz()); |
+ ASSERT(!stream_cls.IsNull()); |
+ const Class& stream_impl_cls = Class::Handle(stream_cls.SuperClass()); |
+ const Field& awaiter_field = Field::Handle( |
+ stream_impl_cls.LookupInstanceFieldAllowPrivate(Symbols::_Awaiter())); |
+ ASSERT(!awaiter_field.IsNull()); |
+ return Instance::Cast(stream).GetField(awaiter_field); |
+} |
+ |
+ |
+RawObject* ActivationFrame::GetAsyncAwaiter() { |
+ const Object& completer = Object::Handle(GetAsyncCompleter()); |
+ if (!completer.IsNull()) { |
+ return GetAsyncCompleterAwaiter(completer); |
+ } |
+ const Object& async_stream_controller_stream = |
+ Object::Handle(GetAsyncStreamControllerStream()); |
+ if (!async_stream_controller_stream.IsNull()) { |
+ return GetAsyncStreamControllerStreamAwaiter( |
+ async_stream_controller_stream); |
+ } |
+ return Object::null(); |
+} |
+ |
+ |
+void ActivationFrame::ExtractTokenPositionFromAsyncClosure() { |
+ // Attempt to determine the token position from the async closure. |
+ ASSERT(function_.IsAsyncGenClosure() || function_.IsAsyncClosure()); |
+ // This should only be called on frames that aren't active on the stack. |
+ ASSERT(fp() == 0); |
+ const Array& await_to_token_map = |
+ Array::Handle(code_.await_token_positions()); |
+ if (await_to_token_map.IsNull()) { |
+ OS::PrintErr("NO await_token_positions MAPPING\n"); |
+ // No mapping. |
+ return; |
+ } |
+ GetVarDescriptors(); |
+ GetPcDescriptors(); |
+ intptr_t var_desc_len = var_descriptors_.Length(); |
+ intptr_t await_jump_var = -1; |
+ for (intptr_t i = 0; i < var_desc_len; i++) { |
+ RawLocalVarDescriptors::VarInfo var_info; |
+ var_descriptors_.GetInfo(i, &var_info); |
+ const int8_t kind = var_info.kind(); |
+ if (var_descriptors_.GetName(i) == Symbols::AwaitJumpVar().raw()) { |
+ ASSERT(kind == RawLocalVarDescriptors::kContextVar); |
+ ASSERT(!ctx_.IsNull()); |
+ Object& await_jump_index = Object::Handle(ctx_.At(var_info.index())); |
+ ASSERT(await_jump_index.IsSmi()); |
+ await_jump_var = Smi::Cast(await_jump_index).Value(); |
+ OS::PrintErr("Extracted await var: %" Pd "\n", await_jump_var); |
+ } |
+ } |
+ if (await_jump_var < 0) { |
+ OS::PrintErr("await_jump_var < 0\n"); |
+ return; |
+ } |
+ ASSERT(await_jump_var < await_to_token_map.Length()); |
+ const Object& token_pos = |
+ Object::Handle(await_to_token_map.At(await_jump_var)); |
+ if (token_pos.IsNull()) { |
+ OS::PrintErr("token_pos was null\n"); |
+ return; |
+ } |
+ ASSERT(token_pos.IsSmi()); |
+ token_pos_ = TokenPosition(Smi::Cast(token_pos).Value()); |
+ token_pos_initialized_ = true; |
+ OS::PrintErr("async token position: %s\n", token_pos_.ToCString()); |
+// Now that we have the token position, we clear a bunch of frame state that |
+// will repopulated based on the token position. |
+// Clear the context. |
+#if 0 |
+ ctx_ ^= Object::null(); |
+ context_level_ = -1; |
+ // Clear variable descriptors. |
+ vars_initialized_ = false; |
+ var_descriptors_ ^= Object::null(); |
+ desc_indices_.Clear(); |
+#endif |
+} |
+ |
+ |
RawObject* ActivationFrame::GetAsyncOperation() { |
GetVarDescriptors(); |
intptr_t var_desc_len = var_descriptors_.Length(); |
@@ -740,7 +978,7 @@ void ActivationFrame::GetDescIndices() { |
GetVarDescriptors(); |
TokenPosition activation_token_pos = TokenPos(); |
- if (!activation_token_pos.IsDebugPause()) { |
+ if (!activation_token_pos.IsDebugPause() || suspended_frame_) { |
// We don't have a token position for this frame, so can't determine |
// which variables are visible. |
vars_initialized_ = true; |
@@ -871,6 +1109,35 @@ bool ActivationFrame::IsRewindable() const { |
} |
+bool ActivationFrame::IsCompleteOnAsyncReturn() { |
+ const class Library& async_library = Library::Handle(Library::AsyncLibrary()); |
+ ASSERT(!async_library.IsNull()); |
+ |
+ const Function& complete_on_async_return_function = |
+ Function::Handle(async_library.LookupFunctionAllowPrivate( |
+ Symbols::CompleterCompleteOnAsyncReturn())); |
+ ASSERT(!complete_on_async_return_function.IsNull()); |
+ return complete_on_async_return_function.raw() == function_.raw(); |
+} |
+ |
+ |
+bool ActivationFrame::IsInAsyncStarStreamController() { |
+ const class Library& async_library = Library::Handle(Library::AsyncLibrary()); |
+ ASSERT(!async_library.IsNull()); |
+ |
+ const Class& async_stream_controller = |
+ Class::Handle(async_library.LookupClassAllowPrivate( |
+ Symbols::_AsyncStarStreamController())); |
+ ASSERT(!async_stream_controller.IsNull()); |
+ return function_.Owner() == async_stream_controller.raw(); |
+} |
+ |
+ |
+bool ActivationFrame::IsInAsyncMachinery() { |
+ return IsCompleteOnAsyncReturn() || IsInAsyncStarStreamController(); |
+} |
+ |
+ |
void ActivationFrame::PrintContextMismatchError(intptr_t ctx_slot, |
intptr_t frame_ctx_level, |
intptr_t var_ctx_level) { |
@@ -1086,8 +1353,54 @@ const char* ActivationFrame::ToCString() { |
void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) { |
- const Script& script = Script::Handle(SourceScript()); |
+ if (kind_ == kRegular) { |
+ PrintToJSONObjectRegular(jsobj, full); |
+ } else { |
+ PrintToJSONObjectAsync(jsobj, full); |
+ } |
+} |
+ |
+ |
+void ActivationFrame::PrintToJSONObjectAsync(JSONObject* jsobj, bool full) { |
+ jsobj->AddProperty("type", "AsyncFrame"); |
+ jsobj->AddProperty("kind", KindToCString(kind_)); |
+ if (kind_ == kAsyncSuspensionMarker) { |
+ jsobj->AddProperty("marker", "AsynchronousSuspension"); |
+ } else if (kind_ == kAsyncHistorical) { |
+ PrintToJSONObjectHistoricalFrame(jsobj, full); |
+ } else { |
+ PrintToJSONObjectLiveFrame(jsobj, full); |
+ } |
+} |
+ |
+ |
+void ActivationFrame::PrintToJSONObjectRegular(JSONObject* jsobj, bool full) { |
jsobj->AddProperty("type", "Frame"); |
+ PrintToJSONObjectLiveFrame(jsobj, full); |
+} |
+ |
+ |
+void ActivationFrame::PrintToJSONObjectHistoricalFrame(JSONObject* jsobj, |
+ bool full) { |
+ 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::PrintToJSONObjectLiveFrame(JSONObject* jsobj, bool full) { |
+ const Script& script = Script::Handle(SourceScript()); |
TokenPosition pos = TokenPos(); |
if (pos.IsSynthetic()) { |
pos = pos.FromSynthetic(); |
@@ -1112,7 +1425,8 @@ void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) { |
TokenPosition visible_end_token_pos; |
VariableAt(v, &var_name, &declaration_token_pos, &visible_start_token_pos, |
&visible_end_token_pos, &var_value); |
- if (var_name.raw() != Symbols::AsyncOperation().raw()) { |
+ if ((var_name.raw() != Symbols::AsyncOperation().raw()) && |
+ (var_name.raw() != Symbols::AsyncCompleter().raw())) { |
JSONObject jsvar(&jsvars); |
jsvar.AddProperty("type", "BoundVariable"); |
var_name = String::ScrubName(var_name); |
@@ -1129,6 +1443,7 @@ void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) { |
} |
} |
+ |
static bool IsFunctionVisible(const Function& function) { |
return FLAG_show_invisible_frames || function.is_visible(); |
} |
@@ -1141,6 +1456,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::AddAsyncHistoricalFrame(uword pc, const Code& code) { |
+ trace_.Add(new ActivationFrame(pc, 0, 0, code, Array::Handle(), 0, |
+ ActivationFrame::kAsyncHistorical)); |
+} |
+ |
+ |
const uint8_t kSafepointKind = RawPcDescriptors::kIcCall | |
RawPcDescriptors::kUnoptStaticCall | |
RawPcDescriptors::kRuntimeCall; |
@@ -1229,6 +1557,15 @@ void CodeBreakpoint::Disable() { |
} |
+const char* CodeBreakpoint::ToCString() { |
+ Zone* zone = Thread::Current()->zone(); |
+ ASSERT(zone != NULL); |
+ return OS::SCreate(zone, "%s: %" Pd " (token %s)", |
+ String::Handle(SourceUrl()).ToCString(), LineNumber(), |
+ token_pos().ToCString()); |
+} |
+ |
+ |
RemoteObjectCache::RemoteObjectCache(intptr_t initial_size) { |
objs_ = |
&GrowableObjectArray::ZoneHandle(GrowableObjectArray::New(initial_size)); |
@@ -1268,7 +1605,11 @@ Debugger::Debugger() |
pause_event_(NULL), |
obj_cache_(NULL), |
stack_trace_(NULL), |
+ async_stack_trace_(NULL), |
+ async_return_call_stack_(NULL), |
stepping_fp_(0), |
+ top_frame_awaiter_(Object::null()), |
+ async_stepping_fp_(0), |
skip_next_step_(false), |
synthetic_async_breakpoint_(NULL), |
exc_pause_info_(kNoPauseOnExceptions) {} |
@@ -1281,6 +1622,7 @@ Debugger::~Debugger() { |
ASSERT(breakpoint_locations_ == NULL); |
ASSERT(code_breakpoints_ == NULL); |
ASSERT(stack_trace_ == NULL); |
+ ASSERT(async_stack_trace_ == NULL); |
ASSERT(obj_cache_ == NULL); |
ASSERT(synthetic_async_breakpoint_ == NULL); |
} |
@@ -1327,6 +1669,38 @@ static RawFunction* ResolveLibraryFunction(const Library& library, |
} |
+bool Debugger::SteppedForSyntheticAsyncBreakpoint() const { |
+ return synthetic_async_breakpoint_ != NULL; |
+} |
+ |
+ |
+RawError* Debugger::StepForSyntheticAsyncBreakpoint(Breakpoint* bpt) { |
+ ASSERT(bpt->is_synthetic_async()); |
+ |
+ TD_Print("ASYNC: Auto step-over\n"); |
+ CacheStackTraces(CollectStackTrace(), CollectAsyncStackTrace(), |
+ CollectAsyncReturnCallStack()); |
+ |
+ ASSERT(synthetic_async_breakpoint_ == NULL); |
+ synthetic_async_breakpoint_ = bpt; |
+ // We are at the entry of an async closure. |
+ // We issue a step over to resume at the point after the await statement. |
+ SetResumeAction(kStepOver); |
+ // When we single step from a breakpoint, our next stepping point will be at |
+ // the exact same pc. Skip it. |
+ HandleSteppingRequest(stack_trace_, true /* skip next step */); |
+ ClearCachedStackTraces(); |
+ return Error::null(); |
+} |
+ |
+ |
+void Debugger::CleanupSyntheticAsyncBreakpoint() { |
+ if (synthetic_async_breakpoint_ != NULL) { |
+ RemoveBreakpoint(synthetic_async_breakpoint_->id()); |
+ synthetic_async_breakpoint_ = NULL; |
+ } |
+} |
+ |
bool Debugger::SetupStepOverAsyncSuspension(const char** error) { |
ActivationFrame* top_frame = TopDartFrame(); |
if (!IsAtAsyncJump(top_frame)) { |
@@ -1352,6 +1726,30 @@ bool Debugger::SetupStepOverAsyncSuspension(const char** error) { |
} |
+// Returns the stepping frame pointer when stepping into a caller of |
+// the async activation generator functions. |
+uword Debugger::GetAsyncActivationGeneratorSteppingFramePointer() { |
+ StackFrameIterator iterator(false); |
+ // Scan until we hit the top Dart frame. |
+ StackFrame* frame = iterator.NextFrame(); |
+ while ((frame != NULL) && !frame->IsDartFrame()) { |
+ frame = iterator.NextFrame(); |
+ } |
+ if (frame != NULL) { |
+ frame = iterator.NextFrame(); |
+ if ((frame != NULL) && frame->IsDartFrame()) { |
+ // Check to see if we are in a callee of the async function. |
+ const Function& function = Function::Handle(frame->LookupDartFunction()); |
+ if (function.IsAsyncFunction() || function.IsAsyncGenerator()) { |
+ // We are, continue until this function has exited. |
+ return frame->fp(); |
+ } |
+ } |
+ } |
+ return 0; |
+} |
+ |
+ |
bool Debugger::SetResumeAction(ResumeAction action, |
intptr_t frame_index, |
const char** error) { |
@@ -1462,10 +1860,12 @@ ActivationFrame* Debugger::CollectDartFrame(Isolate* isolate, |
StackFrame* frame, |
const Code& code, |
const Array& deopt_frame, |
- intptr_t deopt_frame_offset) { |
+ intptr_t deopt_frame_offset, |
+ ActivationFrame::Kind kind) { |
ASSERT(code.ContainsInstructionAt(pc)); |
- ActivationFrame* activation = new ActivationFrame( |
- pc, frame->fp(), frame->sp(), code, deopt_frame, deopt_frame_offset); |
+ ActivationFrame* activation = |
+ new ActivationFrame(pc, frame->fp(), frame->sp(), code, deopt_frame, |
+ deopt_frame_offset, kind); |
if (FLAG_trace_debugger_stacktrace) { |
const Context& ctx = activation->GetSavedCurrentContext(); |
OS::PrintErr("\tUsing saved context: %s\n", ctx.ToCString()); |
@@ -1546,6 +1946,166 @@ DebuggerStackTrace* Debugger::CollectStackTrace() { |
} |
+DebuggerStackTrace* Debugger::CollectAsyncStackTrace() { |
+ if (!FLAG_sane_async_stacks) { |
+ return CollectStackTrace(); |
+ } |
+ 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(); |
+ Function& function = Function::Handle(zone); |
+ 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); |
+ |
+ for (StackFrame* frame = iterator.NextFrame(); frame != NULL; |
+ frame = iterator.NextFrame()) { |
+ ASSERT(frame->IsValid()); |
+ if (FLAG_trace_debugger_stacktrace) { |
+ OS::PrintErr("CollectStackTrace: visiting frame:\n\t%s\n", |
+ frame->ToCString()); |
+ } |
+ 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()) { |
+ 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(); |
+ ActivationFrame* activation_frame = CollectDartFrame( |
+ isolate, it.pc(), frame, inlined_code, deopt_frame, |
+ deopt_frame_offset, ActivationFrame::kAsyncLive); |
+ stack_trace->AddActivation(activation_frame); |
+ } |
+ } else { |
+ ActivationFrame* activation_frame = CollectDartFrame( |
+ isolate, frame->pc(), frame, code, Object::null_array(), 0, |
+ ActivationFrame::kAsyncLive); |
+ stack_trace->AddActivation(activation_frame); |
+ if (async_stack_trace_length > 0) { |
+ // Stop once we hit async closure. |
+ function = code.function(); |
+ if (function.parent_function() == async_function.raw()) { |
+ break; |
+ } |
+ } |
+ } |
+ } |
+ } |
+ ASSERT((async_stack_trace_length == 0) || |
+ (function.parent_function() == async_function.raw())); |
+ // Append the asynchronous stack trace. |
+ 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(); |
+ stack_trace->AddAsyncHistoricalFrame(pc, inlined_code); |
+ } |
+ } else { |
+ stack_trace->AddAsyncHistoricalFrame(pc, code); |
+ } |
+ } |
+ } |
+ return stack_trace; |
+} |
+ |
+ |
+DebuggerStackTrace* Debugger::CollectAsyncReturnCallStack() { |
+ 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); |
+ Code& inlined_code = Code::Handle(zone); |
+ Array& deopt_frame = Array::Handle(zone); |
+ Function& function = Function::Handle(zone); |
+ Closure& async_activation = Closure::Handle(zone); |
+ |
+ for (StackFrame* frame = iterator.NextFrame(); frame != NULL; |
+ frame = iterator.NextFrame()) { |
+ ASSERT(frame->IsValid()); |
+ if (FLAG_trace_debugger_stacktrace) { |
+ OS::PrintErr("CollectStackTrace: visiting frame:\n\t%s\n", |
+ frame->ToCString()); |
+ } |
+ if (frame->IsDartFrame()) { |
+ code = frame->LookupDartCode(); |
+ function = code.function(); |
+ if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) { |
+ ActivationFrame* activation = CollectDartFrame( |
+ isolate, frame->pc(), frame, code, Object::null_array(), 0, |
+ ActivationFrame::kAsyncLive); |
+ ASSERT(activation != NULL); |
+ stack_trace->AddActivation(activation); |
+ // Grab the awaiter. |
+ async_activation ^= activation->GetAsyncAwaiter(); |
+ break; |
+ } |
+ 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) { |
+ function = 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, ActivationFrame::kAsyncLive)); |
+ } |
+ } else { |
+ stack_trace->AddActivation(CollectDartFrame( |
+ isolate, frame->pc(), frame, code, Object::null_array(), 0, |
+ ActivationFrame::kAsyncLive)); |
+ } |
+ } |
+ } |
+ |
+ // Append the asynchronous return call stack. |
+ while (!async_activation.IsNull()) { |
+ ActivationFrame* activation = new ActivationFrame(async_activation); |
+ async_activation ^= activation->GetAsyncAwaiter(); |
+ activation->ExtractTokenPositionFromAsyncClosure(); |
+ stack_trace->AddActivation(activation); |
+ } |
+ |
+ return stack_trace; |
+} |
+ |
+ |
ActivationFrame* Debugger::TopDartFrame() const { |
StackFrameIterator iterator(false); |
StackFrame* frame = iterator.NextFrame(); |
@@ -1563,10 +2123,34 @@ DebuggerStackTrace* Debugger::StackTrace() { |
return (stack_trace_ != NULL) ? stack_trace_ : CollectStackTrace(); |
} |
+ |
DebuggerStackTrace* Debugger::CurrentStackTrace() { |
return CollectStackTrace(); |
} |
+ |
+DebuggerStackTrace* Debugger::AsyncStackTrace() { |
+ return (async_stack_trace_ != NULL) ? async_stack_trace_ |
+ : CollectAsyncStackTrace(); |
+} |
+ |
+ |
+DebuggerStackTrace* Debugger::CurrentAsyncStackTrace() { |
+ return CollectAsyncStackTrace(); |
+} |
+ |
+ |
+DebuggerStackTrace* Debugger::AsyncReturnStack() { |
+ return (async_return_call_stack_ != NULL) ? async_return_call_stack_ |
+ : CollectAsyncReturnCallStack(); |
+} |
+ |
+ |
+DebuggerStackTrace* Debugger::CurrentAsyncReturnStack() { |
+ return CollectAsyncReturnCallStack(); |
+} |
+ |
+ |
DebuggerStackTrace* Debugger::StackTraceFrom(const class StackTrace& ex_trace) { |
DebuggerStackTrace* stack_trace = new DebuggerStackTrace(8); |
Function& function = Function::Handle(); |
@@ -1659,6 +2243,8 @@ void Debugger::PauseException(const Instance& exc) { |
return; |
} |
DebuggerStackTrace* stack_trace = CollectStackTrace(); |
+ DebuggerStackTrace* async_stack_trace = CollectAsyncStackTrace(); |
+ DebuggerStackTrace* async_return_call_stack = CollectAsyncReturnCallStack(); |
if (!ShouldPauseOnException(stack_trace, exc)) { |
return; |
} |
@@ -1667,11 +2253,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, async_stack_trace, async_return_call_stack); |
Pause(&event); |
HandleSteppingRequest(stack_trace_); // we may get a rewind request |
- stack_trace_ = NULL; |
+ ClearCachedStackTraces(); |
} |
@@ -2544,6 +3129,7 @@ void Debugger::VisitObjectPointers(ObjectPointerVisitor* visitor) { |
cbpt->VisitObjectPointers(visitor); |
cbpt = cbpt->next(); |
} |
+ visitor->VisitPointer(reinterpret_cast<RawObject**>(&top_frame_awaiter_)); |
} |
@@ -2603,6 +3189,20 @@ void Debugger::Pause(ServiceEvent* event) { |
} |
+void Debugger::AsyncContinue() { |
+ SetResumeAction(kContinue); |
+ stepping_fp_ = 0; |
+ async_stepping_fp_ = 0; |
+ isolate_->set_single_step(false); |
+} |
+ |
+ |
+void Debugger::AsyncStepInto(const Closure& async_op) { |
+ SetBreakpointAtActivation(async_op, true); |
+ AsyncContinue(); |
+} |
+ |
+ |
void Debugger::EnterSingleStepMode() { |
stepping_fp_ = 0; |
DeoptimizeWorld(); |
@@ -2612,7 +3212,9 @@ void Debugger::EnterSingleStepMode() { |
void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
bool skip_next_step) { |
+ top_frame_awaiter_ = stack_trace->FrameAt(0)->GetAsyncAwaiter(); |
stepping_fp_ = 0; |
+ async_stepping_fp_ = 0; |
if (resume_action_ == kStepInto) { |
// When single stepping, we need to deoptimize because we might be |
// stepping into optimized code. This happens in particular if |
@@ -2622,6 +3224,10 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
DeoptimizeWorld(); |
isolate_->set_single_step(true); |
skip_next_step_ = skip_next_step; |
+ if (stack_trace->FrameAt(0)->function().IsAsyncClosure() || |
+ stack_trace->FrameAt(0)->function().IsAsyncGenClosure()) { |
+ async_stepping_fp_ = stack_trace->FrameAt(0)->fp(); |
+ } |
if (FLAG_verbose_debug) { |
OS::Print("HandleSteppingRequest- kStepInto\n"); |
} |
@@ -2631,10 +3237,29 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
skip_next_step_ = skip_next_step; |
ASSERT(stack_trace->Length() > 0); |
stepping_fp_ = stack_trace->FrameAt(0)->fp(); |
+ if (stack_trace->FrameAt(0)->function().IsAsyncClosure() || |
+ stack_trace->FrameAt(0)->function().IsAsyncGenClosure()) { |
+ async_stepping_fp_ = stack_trace->FrameAt(0)->fp(); |
+ } |
if (FLAG_verbose_debug) { |
OS::Print("HandleSteppingRequest- kStepOver %" Px "\n", stepping_fp_); |
} |
} else if (resume_action_ == kStepOut) { |
+ // Handle the async case first. |
+ if (stack_trace->FrameAt(0)->function().IsAsyncClosure() || |
+ stack_trace->FrameAt(0)->function().IsAsyncGenClosure()) { |
+ if (top_frame_awaiter_ != Object::null()) { |
+ const Object& async_op = Object::Handle(top_frame_awaiter_); |
+ ASSERT(async_op.IsClosure()); |
+ AsyncStepInto(Closure::Cast(async_op)); |
+ return; |
+ } else { |
+ // No awaiter. Just continue execution. |
+ AsyncContinue(); |
+ return; |
+ } |
+ } |
+ // Now the synchronous case. |
DeoptimizeWorld(); |
isolate_->set_single_step(true); |
// Find topmost caller that is debuggable. |
@@ -2680,6 +3305,25 @@ static intptr_t FindNextRewindFrameIndex(DebuggerStackTrace* stack, |
} |
+void Debugger::CacheStackTraces(DebuggerStackTrace* stack_trace, |
+ DebuggerStackTrace* async_stack_trace, |
+ DebuggerStackTrace* async_return_call_stack) { |
+ ASSERT(stack_trace_ == NULL); |
+ stack_trace_ = stack_trace; |
+ ASSERT(async_stack_trace_ == NULL); |
+ async_stack_trace_ = async_stack_trace; |
+ ASSERT(async_return_call_stack_ == NULL); |
+ async_return_call_stack_ = async_return_call_stack; |
+} |
+ |
+ |
+void Debugger::ClearCachedStackTraces() { |
+ stack_trace_ = NULL; |
+ async_stack_trace_ = NULL; |
+ async_return_call_stack_ = NULL; |
+} |
+ |
+ |
// Can the top frame be rewound? |
bool Debugger::CanRewindFrame(intptr_t frame_index, const char** error) const { |
// check rewind pc is found |
@@ -2796,7 +3440,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 +3472,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(); |
@@ -2904,6 +3548,7 @@ bool Debugger::IsDebuggable(const Function& func) { |
void Debugger::SignalPausedEvent(ActivationFrame* top_frame, Breakpoint* bpt) { |
resume_action_ = kContinue; |
stepping_fp_ = 0; |
+ async_stepping_fp_ = 0; |
isolate_->set_single_step(false); |
ASSERT(!IsPaused()); |
ASSERT(obj_cache_ == NULL); |
@@ -2953,14 +3598,44 @@ RawError* Debugger::PauseStepping() { |
return Error::null(); |
} |
+ ActivationFrame* frame = TopDartFrame(); |
+ ASSERT(frame != NULL); |
+ |
+ OS::PrintErr("top frame: %s\n", frame->ToCString()); |
+ OS::PrintErr("async_stepping_fp: %" Px "\n", async_stepping_fp_); |
+ OS::PrintErr("IsInAsyncMachinery: %d\n", frame->IsInAsyncMachinery()); |
+ |
+ // Check if the user has single stepped out of an asynchronous function |
+ // with an awaiter. If so, the next point of execution will be in the awaiter. |
+ if (async_stepping_fp_ != 0) { |
+ // We have either single stepped into async machinery or we have |
+ // stepped out of the function. |
+ const bool exited_async_function = |
+ (IsCalleeFrameOf(async_stepping_fp_, frame->fp()) && |
+ frame->IsInAsyncMachinery()) || |
+ IsCalleeFrameOf(frame->fp(), async_stepping_fp_); |
+ OS::PrintErr("exited_async_function = %d\n", exited_async_function); |
+ if (exited_async_function) { |
+ // We returned from the asynchronous function. |
+ if (top_frame_awaiter_ == Object::null()) { |
+ AsyncContinue(); |
+ return Error::null(); |
+ } else { |
+ ASSERT(top_frame_awaiter_ != Object::null()); |
+ const Object& async_op = Object::Handle(top_frame_awaiter_); |
+ ASSERT(async_op.IsClosure()); |
+ top_frame_awaiter_ = Object::null(); |
+ AsyncStepInto(Closure::Cast(async_op)); |
+ return Error::null(); |
+ } |
+ } |
+ } |
+ |
// Check whether we are in a Dart function that the user is |
// interested in. If we saved the frame pointer of a stack frame |
// the user is interested in, we ignore the single step if we are |
// in a callee of that frame. Note that we assume that the stack |
// grows towards lower addresses. |
- ActivationFrame* frame = TopDartFrame(); |
- ASSERT(frame != NULL); |
- |
if (stepping_fp_ != 0) { |
// There is an "interesting frame" set. Only pause at appropriate |
// locations in this frame. |
@@ -2974,6 +3649,16 @@ RawError* Debugger::PauseStepping() { |
// and let the user set the "interesting" frame again. |
stepping_fp_ = 0; |
} |
+ } else if (FLAG_sane_async_stacks && !SteppedForSyntheticAsyncBreakpoint()) { |
+ const uword possible_stepping_fp = |
+ GetAsyncActivationGeneratorSteppingFramePointer(); |
+ if (possible_stepping_fp > 0) { |
+ // We are currently executing in a callee of an async activation generator |
+ // function. We do not want the user to see any of this code, so we will |
+ // continue to step until we have exited the async activation generator. |
+ stepping_fp_ = possible_stepping_fp; |
+ return Error::null(); |
+ } |
} |
if (!frame->IsDebuggable()) { |
@@ -2996,18 +3681,14 @@ RawError* Debugger::PauseStepping() { |
frame->TokenPos().ToCString()); |
} |
- ASSERT(stack_trace_ == NULL); |
- stack_trace_ = CollectStackTrace(); |
- // 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. |
- SignalPausedEvent(frame, synthetic_async_breakpoint_); |
- if (synthetic_async_breakpoint_ != NULL) { |
- RemoveBreakpoint(synthetic_async_breakpoint_->id()); |
- synthetic_async_breakpoint_ = NULL; |
+ CacheStackTraces(CollectStackTrace(), CollectAsyncStackTrace(), |
+ CollectAsyncReturnCallStack()); |
+ if (SteppedForSyntheticAsyncBreakpoint()) { |
+ CleanupSyntheticAsyncBreakpoint(); |
} |
+ SignalPausedEvent(frame, 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()); |
@@ -3024,88 +3705,23 @@ RawError* Debugger::PauseBreakpoint() { |
return Error::null(); |
} |
DebuggerStackTrace* stack_trace = CollectStackTrace(); |
+ DebuggerStackTrace* async_stack_trace = CollectAsyncStackTrace(); |
+ DebuggerStackTrace* async_return_call_stack = CollectAsyncReturnCallStack(); |
ASSERT(stack_trace->Length() > 0); |
ActivationFrame* top_frame = stack_trace->FrameAt(0); |
ASSERT(top_frame != NULL); |
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(); |
} |
if (bpt_hit->is_synthetic_async()) { |
- DebuggerStackTrace* stack_trace = CollectStackTrace(); |
- ASSERT(stack_trace->Length() > 0); |
- ASSERT(stack_trace_ == NULL); |
- stack_trace_ = stack_trace; |
- |
- // Hit a synthetic async breakpoint. |
- if (FLAG_verbose_debug) { |
- OS::Print(">>> hit synthetic breakpoint at %s:%" Pd |
- " " |
- "(token %s) (address %#" Px ")\n", |
- String::Handle(cbpt->SourceUrl()).ToCString(), |
- cbpt->LineNumber(), cbpt->token_pos().ToCString(), |
- top_frame->pc()); |
- } |
- |
- ASSERT(synthetic_async_breakpoint_ == NULL); |
- synthetic_async_breakpoint_ = bpt_hit; |
- bpt_hit = NULL; |
- |
- // We are at the entry of an async function. |
- // We issue a step over to resume at the point after the await statement. |
- SetResumeAction(kStepOver); |
- // 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; |
- return Error::null(); |
+ TD_Print("ASYNC: hit synthetic breakpoint: %s (address: %#" Px ")\n", |
+ cbpt->ToCString(), top_frame->pc()); |
+ return StepForSyntheticAsyncBreakpoint(bpt_hit); |
} |
if (FLAG_verbose_debug) { |
@@ -3117,13 +3733,12 @@ RawError* Debugger::PauseBreakpoint() { |
cbpt->token_pos().ToCString(), top_frame->pc()); |
} |
- ASSERT(stack_trace_ == NULL); |
- stack_trace_ = stack_trace; |
+ CacheStackTraces(stack_trace, async_stack_trace, async_return_call_stack); |
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 +3750,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. |
+ |
+ // 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 |
@@ -3143,10 +3802,8 @@ void Debugger::PauseDeveloper(const String& msg) { |
return; |
} |
- DebuggerStackTrace* stack_trace = CollectStackTrace(); |
- ASSERT(stack_trace->Length() > 0); |
- ASSERT(stack_trace_ == NULL); |
- stack_trace_ = stack_trace; |
+ CacheStackTraces(CollectStackTrace(), CollectAsyncStackTrace(), |
+ CollectAsyncReturnCallStack()); |
// TODO(johnmccutchan): Send |msg| to Observatory. |
@@ -3156,7 +3813,7 @@ void Debugger::PauseDeveloper(const String& msg) { |
SetResumeAction(kStepOut); |
HandleSteppingRequest(stack_trace_); |
- stack_trace_ = NULL; |
+ ClearCachedStackTraces(); |
} |
@@ -3174,6 +3831,51 @@ void Debugger::Initialize(Isolate* isolate) { |
} |
+void Debugger::WhenRunnable() { |
+ // Lookup some internal implementation functions and mark them as |
+ // not-debuggable (so the user never steps into them) and |
+ // not-inlinable (so that we can cheaply detect their presence on the stack). |
+ Thread::EnterIsolate(isolate_); |
+ Thread* thread = Thread::Current(); |
+ ASSERT(thread != NULL); |
+ { |
+ StackZone zone(thread); |
+ HandleScope handle_scope(thread); |
+ |
+ const Library& async_library = |
+ Library::Handle(isolate_->object_store()->async_library()); |
+ ASSERT(!async_library.IsNull()); |
+ |
+ // This function is called when an async function completes their completer. |
+ const Function& complete_on_async_return_function = |
+ Function::Handle(async_library.LookupFunctionAllowPrivate( |
+ Symbols::CompleterCompleteOnAsyncReturn())); |
+ ASSERT(!complete_on_async_return_function.IsNull()); |
+ complete_on_async_return_function.set_is_debuggable(false); |
+ complete_on_async_return_function.set_is_inlinable(false); |
+ |
+ // Class used to implement async* functions. |
+ const Class& async_stream_controller = |
+ Class::Handle(async_library.LookupClassAllowPrivate( |
+ Symbols::_AsyncStarStreamController())); |
+ ASSERT(!async_stream_controller.IsNull()); |
+ const Array& functions = Array::Handle(async_stream_controller.functions()); |
+ ASSERT(!functions.IsNull()); |
+ Function& function = Function::Handle(); |
+ for (intptr_t i = 0; i < functions.Length(); i++) { |
+ function ^= functions.At(i); |
+ if (function.IsNull()) { |
+ // Skip padding. |
+ continue; |
+ } |
+ function.set_is_debuggable(false); |
+ function.set_is_inlinable(false); |
+ } |
+ } |
+ Thread::ExitIsolate(); |
+} |
+ |
+ |
void Debugger::NotifyIsolateCreated() { |
if (NeedsIsolateEvents()) { |
ServiceEvent event(isolate_, ServiceEvent::kIsolateStart); |