Chromium Code Reviews| Index: runtime/vm/debugger.cc |
| diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc |
| index a74a0f53fb38beae9814b94fdaa9e8c8f4466b7e..5cf5419f449621b75e7f9f63387cb4fe403fa3e9 100644 |
| --- a/runtime/vm/debugger.cc |
| +++ b/runtime/vm/debugger.cc |
| @@ -259,7 +259,7 @@ ActivationFrame::ActivationFrame(uword pc, |
| ctx_(Context::ZoneHandle()), |
| code_(Code::ZoneHandle(code.raw())), |
| function_(Function::ZoneHandle(code.function())), |
| - live_frame_(kind == kRegular), |
| + live_frame_((kind == kRegular) || (kind == kAsyncActivation)), |
| token_pos_initialized_(false), |
| token_pos_(TokenPosition::kNoSource), |
| try_index_(-1), |
| @@ -297,6 +297,37 @@ ActivationFrame::ActivationFrame(Kind kind) |
| 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()), |
| + live_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_offset_(0), |
| + kind_(kAsyncActivation), |
| + 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(); |
| + ASSERT(fp_ == 0); |
| + ASSERT(!ctx_.IsNull()); |
| +} |
| + |
| + |
| bool Debugger::NeedsIsolateEvents() { |
| return ((isolate_ != Dart::vm_isolate()) && |
| !ServiceIsolate::IsServiceIsolateDescendant(isolate_) && |
| @@ -679,6 +710,231 @@ intptr_t ActivationFrame::ContextLevel() { |
| return context_level_; |
| } |
| +RawObject* ActivationFrame::GetAsyncCompleter() { |
| + if (!function_.IsAsyncClosure()) { |
| + return Object::null(); |
| + } |
| + GetVarDescriptors(); |
| + intptr_t var_desc_len = var_descriptors_.Length(); |
| + if (!live_frame_) { |
| + // Not actually on the stack. Pull it out of the closure's context. |
| + intptr_t var_desc_len = var_descriptors_.Length(); |
|
hausner
2017/02/28 19:04:51
This is a duplicate variable definition that shado
Cutch
2017/02/28 21:46:52
Good catch!
|
| + 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 { |
|
hausner
2017/02/28 19:04:51
Can't these two cases be unified? When the entry w
Cutch
2017/02/28 21:46:52
Done.
|
| + ASSERT(fp() != 0); |
| + // 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 (!live_frame_) { |
| + // Not actually on the stack. Pull it out of the closure's context. |
| + intptr_t var_desc_len = var_descriptors_.Length(); |
|
hausner
2017/02/28 19:04:51
Same comments as in GetAsyncCompleter() above
Cutch
2017/02/28 21:46:52
I've actually made a helper function which these t
|
| + 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 { |
| + ASSERT(fp() != 0); |
| + // 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(); |
| +} |
| + |
| + |
| +bool ActivationFrame::HandlesException(const Instance& exc_obj) { |
| + intptr_t try_index = TryIndex(); |
| + if (try_index < 0) { |
| + return false; |
| + } |
| + ExceptionHandlers& handlers = ExceptionHandlers::Handle(); |
| + Array& handled_types = Array::Handle(); |
| + AbstractType& type = Type::Handle(); |
| + const TypeArguments& no_instantiator = TypeArguments::Handle(); |
| + const intptr_t try_index_threshold = CatchClauseNode::kImplicitAsyncTryIndex; |
| + const bool is_async = |
| + function().IsAsyncClosure() || function().IsAsyncGenClosure(); |
| + handlers = code().exception_handlers(); |
| + ASSERT(!handlers.IsNull()); |
| + intptr_t num_handlers_checked = 0; |
| + while (try_index >= try_index_threshold) { |
|
hausner
2017/02/28 19:04:51
I realize this is a direct transformation from the
Cutch
2017/02/28 21:46:52
This code used to care but then I made further cha
|
| + // Detect circles in the exception handler data. |
| + num_handlers_checked++; |
| + ASSERT(num_handlers_checked <= handlers.num_entries()); |
| + // Only consider user written handlers for async methods. |
| + if (!is_async || !handlers.IsGenerated(try_index)) { |
| + handled_types = handlers.GetHandledTypes(try_index); |
| + const intptr_t num_types = handled_types.Length(); |
| + for (intptr_t k = 0; k < num_types; k++) { |
| + type ^= handled_types.At(k); |
| + ASSERT(!type.IsNull()); |
| + // Uninstantiated types are not added to ExceptionHandlers data. |
| + ASSERT(type.IsInstantiated()); |
| + if (type.IsMalformed()) { |
| + continue; |
| + } |
| + if (type.IsDynamicType()) { |
| + return true; |
| + } |
| + if (exc_obj.IsInstanceOf(type, no_instantiator, NULL)) { |
| + return true; |
| + } |
| + } |
| + } |
| + try_index = handlers.OuterTryIndex(try_index); |
| + } |
| + return false; |
| +} |
| + |
| + |
| +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()) { |
| + // 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(); |
| + } |
| + } |
| + if (await_jump_var < 0) { |
| + 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()) { |
| + return; |
| + } |
| + ASSERT(token_pos.IsSmi()); |
| + token_pos_ = TokenPosition(Smi::Cast(token_pos).Value()); |
| + token_pos_initialized_ = true; |
| + PcDescriptors::Iterator iter(pc_desc_, RawPcDescriptors::kAnyKind); |
| + while (iter.MoveNext()) { |
| + if (iter.TokenPos() == token_pos_) { |
| + // Match the lowest try index at this token position. |
| + // TODO(johnmccutchan): Is this heuristic precise enough? |
| + if (iter.TryIndex() != CatchClauseNode::kInvalidTryIndex) { |
| + if ((try_index_ == -1) || (iter.TryIndex() < try_index_)) { |
| + try_index_ = iter.TryIndex(); |
| + } |
| + } |
| + } |
| + } |
| +} |
| + |
| // Get the saved current context of this activation. |
| const Context& ActivationFrame::GetSavedCurrentContext() { |
| @@ -724,35 +980,10 @@ RawObject* ActivationFrame::GetAsyncOperation() { |
| ActivationFrame* DebuggerStackTrace::GetHandlerFrame( |
| const Instance& exc_obj) const { |
| - ExceptionHandlers& handlers = ExceptionHandlers::Handle(); |
| - Array& handled_types = Array::Handle(); |
| - AbstractType& type = Type::Handle(); |
| - const TypeArguments& no_instantiator = TypeArguments::Handle(); |
| for (intptr_t frame_index = 0; frame_index < Length(); frame_index++) { |
| ActivationFrame* frame = FrameAt(frame_index); |
| - intptr_t try_index = frame->TryIndex(); |
| - if (try_index < 0) continue; |
| - handlers = frame->code().exception_handlers(); |
| - ASSERT(!handlers.IsNull()); |
| - intptr_t num_handlers_checked = 0; |
| - while (try_index >= 0) { |
| - // Detect circles in the exception handler data. |
| - num_handlers_checked++; |
| - ASSERT(num_handlers_checked <= handlers.num_entries()); |
| - handled_types = handlers.GetHandledTypes(try_index); |
| - const intptr_t num_types = handled_types.Length(); |
| - for (intptr_t k = 0; k < num_types; k++) { |
| - type ^= handled_types.At(k); |
| - ASSERT(!type.IsNull()); |
| - // Uninstantiated types are not added to ExceptionHandlers data. |
| - ASSERT(type.IsInstantiated()); |
| - if (type.IsMalformed()) continue; |
| - if (type.IsDynamicType()) return frame; |
| - if (exc_obj.IsInstanceOf(type, no_instantiator, NULL)) { |
| - return frame; |
| - } |
| - } |
| - try_index = handlers.OuterTryIndex(try_index); |
| + if (frame->HandlesException(exc_obj)) { |
| + return frame; |
| } |
| } |
| return NULL; |
| @@ -1152,7 +1383,10 @@ void ActivationFrame::PrintToJSONObjectRegular(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()) && |
| + (var_name.raw() != Symbols::ControllerStream().raw()) && |
| + (var_name.raw() != Symbols::AwaitJumpVar().raw())) { |
| JSONObject jsvar(&jsvars); |
| jsvar.AddProperty("type", "BoundVariable"); |
| var_name = String::ScrubName(var_name); |
| @@ -1548,10 +1782,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()); |
| @@ -1728,6 +1964,64 @@ DebuggerStackTrace* Debugger::CollectAsyncCausalStackTrace() { |
| return stack_trace; |
| } |
| + |
| +DebuggerStackTrace* Debugger::CollectAwaiterReturnStackTrace() { |
| + if (!FLAG_causal_async_stacks) { |
| + return NULL; |
| + } |
| + Thread* thread = Thread::Current(); |
| + Zone* zone = thread->zone(); |
| + Isolate* isolate = thread->isolate(); |
| + DebuggerStackTrace* stack_trace = new DebuggerStackTrace(8); |
| + |
| + StackFrameIterator iterator(StackFrameIterator::kDontValidateFrames); |
| + |
| + Code& code = Code::Handle(zone); |
| + Function& function = Function::Handle(zone); |
| + Code& inlined_code = Code::Handle(zone); |
| + Closure& async_activation = Closure::Handle(zone); |
| + Array& deopt_frame = Array::Handle(zone); |
| + |
| + for (StackFrame* frame = iterator.NextFrame(); frame != NULL; |
| + frame = iterator.NextFrame()) { |
| + ASSERT(frame->IsValid()); |
| + 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::kAsyncActivation); |
| + ASSERT(activation != NULL); |
| + stack_trace->AddActivation(activation); |
| + // Grab the awaiter. |
| + async_activation ^= activation->GetAsyncAwaiter(); |
| + break; |
| + } else { |
| + AppendCodeFrames(thread, isolate, zone, stack_trace, frame, &code, |
| + &inlined_code, &deopt_frame); |
| + } |
| + } |
| + } |
| + |
| + // Return NULL to indicate that there is no useful information in this stack |
| + // trace because we never found an awaiter. |
| + if (async_activation.IsNull()) { |
| + return NULL; |
| + } |
| + |
| + // Append the awaiter 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(); |
| @@ -1828,6 +2122,29 @@ Dart_ExceptionPauseInfo Debugger::GetExceptionPauseInfo() const { |
| } |
| +bool Debugger::ShouldPauseOnAsyncException(DebuggerStackTrace* stack_trace, |
| + const Instance& exc) { |
| + if (exc_pause_info_ == kNoPauseOnExceptions) { |
| + return false; |
| + } |
| + if (exc_pause_info_ == kPauseOnAllExceptions) { |
| + return true; |
| + } |
| + ASSERT(exc_pause_info_ == kPauseOnUnhandledExceptions); |
| + for (intptr_t i = 0; i < stack_trace->Length(); i++) { |
| + ActivationFrame* frame = stack_trace->FrameAt(i); |
| + if (frame->HandlesException(exc)) { |
| + if (FLAG_verbose_debug) { |
| + OS::PrintErr("%s is caught by frame %s\n", exc.ToCString(), |
| + frame->ToCString()); |
| + } |
| + return false; |
| + } |
| + } |
| + return true; |
| +} |
| + |
| + |
| bool Debugger::ShouldPauseOnException(DebuggerStackTrace* stack_trace, |
| const Instance& exception) { |
| if (exc_pause_info_ == kNoPauseOnExceptions) { |
| @@ -1859,9 +2176,16 @@ void Debugger::PauseException(const Instance& exc) { |
| (exc_pause_info_ == kNoPauseOnExceptions)) { |
| return; |
| } |
| + DebuggerStackTrace* awaiter_stack_trace = CollectAwaiterReturnStackTrace(); |
| DebuggerStackTrace* stack_trace = CollectStackTrace(); |
| - if (!ShouldPauseOnException(stack_trace, exc)) { |
| - return; |
| + if (awaiter_stack_trace != NULL) { |
| + if (!ShouldPauseOnAsyncException(awaiter_stack_trace, exc)) { |
| + return; |
| + } |
| + } else { |
| + if (!ShouldPauseOnException(stack_trace, exc)) { |
| + return; |
| + } |
| } |
| ServiceEvent event(isolate_, ServiceEvent::kPauseException); |
| event.set_exception(&exc); |