| 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);
|
|
|