mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 03:36:59 +00:00
Include the awaiter stack trace in the service protocol
- [x] Include the (non-empty) awaiter stack trace in every `getStack` RPC. - [x] Append the causal stack trace to the final frame of the awaiter stack trace. - [x] Unit test for awaiter stack trace. BUG= R=rmacnak@google.com Review-Url: https://codereview.chromium.org/2782703002 .
This commit is contained in:
parent
4de94b2d7b
commit
d8555fb5a8
|
@ -4,7 +4,7 @@
|
|||
|
||||
part of models;
|
||||
|
||||
enum FrameKind { regular, asyncCausal, asyncSuspensionMarker }
|
||||
enum FrameKind { regular, asyncCausal, asyncSuspensionMarker, asyncActivation }
|
||||
|
||||
abstract class Frame {
|
||||
FrameKind get kind;
|
||||
|
|
|
@ -4494,6 +4494,8 @@ class Frame extends ServiceObject implements M.Frame {
|
|||
return M.FrameKind.asyncCausal;
|
||||
case 'AsyncSuspensionMarker':
|
||||
return M.FrameKind.asyncSuspensionMarker;
|
||||
case 'AsyncActivation':
|
||||
return M.FrameKind.asyncActivation;
|
||||
default:
|
||||
throw new UnsupportedError('Unknown FrameKind: $frameKind');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
// VMOptions=--error_on_bad_type --error_on_bad_override --verbose_debug
|
||||
|
||||
import 'dart:developer';
|
||||
import 'package:observatory/models.dart' as M;
|
||||
import 'package:observatory/service_io.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
const LINE_C = 19;
|
||||
const LINE_A = 24;
|
||||
const LINE_B = 30;
|
||||
|
||||
foobar() async {
|
||||
debugger();
|
||||
print('foobar'); // LINE_C.
|
||||
}
|
||||
|
||||
helper() async {
|
||||
debugger();
|
||||
print('helper'); // LINE_A.
|
||||
await foobar();
|
||||
}
|
||||
|
||||
testMain() {
|
||||
debugger();
|
||||
helper(); // LINE_B.
|
||||
}
|
||||
|
||||
var tests = [
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_B),
|
||||
(Isolate isolate) async {
|
||||
ServiceMap stack = await isolate.getStack();
|
||||
// No awaiter frames because we are in a completely synchronous stack.
|
||||
expect(stack['awaiterFrames'], isNull);
|
||||
},
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_A),
|
||||
(Isolate isolate) async {
|
||||
ServiceMap stack = await isolate.getStack();
|
||||
// No awaiter frames because there is no awaiter.
|
||||
expect(stack['awaiterFrames'], isNull);
|
||||
},
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_C),
|
||||
(Isolate isolate) async {
|
||||
// Verify awaiter stack trace is the current frame + the awaiter.
|
||||
ServiceMap stack = await isolate.getStack();
|
||||
expect(stack['awaiterFrames'], isNotNull);
|
||||
List awaiterFrames = stack['awaiterFrames'];
|
||||
expect(awaiterFrames.length, greaterThanOrEqualTo(4));
|
||||
// Awaiter frame.
|
||||
expect(await awaiterFrames[0].toUserString(),
|
||||
stringContainsInOrder(['foobar', '.dart:19']));
|
||||
// Awaiter frame.
|
||||
expect(await awaiterFrames[1].toUserString(),
|
||||
stringContainsInOrder(['helper', '.dart:25']));
|
||||
// Suspension point.
|
||||
expect(awaiterFrames[2].kind, equals(M.FrameKind.asyncSuspensionMarker));
|
||||
// Causal frame.
|
||||
expect(await awaiterFrames[3].toUserString(),
|
||||
stringContainsInOrder(['testMain', '.dart:30']));
|
||||
},
|
||||
];
|
||||
|
||||
main(args) =>
|
||||
runIsolateTestsSynchronous(args, tests, testeeConcurrent: testMain);
|
|
@ -377,7 +377,8 @@ RawError* Debugger::PauseRequest(ServiceEvent::EventKind kind) {
|
|||
if (trace->Length() > 0) {
|
||||
event.set_top_frame(trace->FrameAt(0));
|
||||
}
|
||||
CacheStackTraces(trace, CollectAsyncCausalStackTrace());
|
||||
CacheStackTraces(trace, CollectAsyncCausalStackTrace(),
|
||||
CollectAwaiterReturnStackTrace());
|
||||
resume_action_ = kContinue;
|
||||
Pause(&event);
|
||||
HandleSteppingRequest(trace);
|
||||
|
@ -713,7 +714,7 @@ intptr_t ActivationFrame::ContextLevel() {
|
|||
|
||||
|
||||
RawObject* ActivationFrame::GetAsyncContextVariable(const String& name) {
|
||||
if (!function_.IsAsyncClosure()) {
|
||||
if (!function_.IsAsyncClosure() && !function_.IsAsyncGenClosure()) {
|
||||
return Object::null();
|
||||
}
|
||||
GetVarDescriptors();
|
||||
|
@ -799,6 +800,11 @@ RawObject* ActivationFrame::GetAsyncAwaiter() {
|
|||
}
|
||||
|
||||
|
||||
RawObject* ActivationFrame::GetCausalStack() {
|
||||
return GetAsyncContextVariable(Symbols::AsyncStackTraceVar());
|
||||
}
|
||||
|
||||
|
||||
bool ActivationFrame::HandlesException(const Instance& exc_obj) {
|
||||
intptr_t try_index = TryIndex();
|
||||
if (try_index < 0) {
|
||||
|
@ -1309,6 +1315,8 @@ void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) {
|
|||
PrintToJSONObjectAsyncCausal(jsobj, full);
|
||||
} else if (kind_ == kAsyncSuspensionMarker) {
|
||||
PrintToJSONObjectAsyncSuspensionMarker(jsobj, full);
|
||||
} else if (kind_ == kAsyncActivation) {
|
||||
PrintToJSONObjectAsyncActivation(jsobj, full);
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
@ -1343,7 +1351,8 @@ void ActivationFrame::PrintToJSONObjectRegular(JSONObject* jsobj, bool full) {
|
|||
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())) {
|
||||
(var_name.raw() != Symbols::AwaitJumpVar().raw()) &&
|
||||
(var_name.raw() != Symbols::AsyncStackTraceVar().raw())) {
|
||||
JSONObject jsvar(&jsvars);
|
||||
jsvar.AddProperty("type", "BoundVariable");
|
||||
var_name = String::ScrubName(var_name);
|
||||
|
@ -1387,6 +1396,24 @@ void ActivationFrame::PrintToJSONObjectAsyncSuspensionMarker(JSONObject* jsobj,
|
|||
}
|
||||
|
||||
|
||||
void ActivationFrame::PrintToJSONObjectAsyncActivation(JSONObject* jsobj,
|
||||
bool full) {
|
||||
jsobj->AddProperty("type", "Frame");
|
||||
jsobj->AddProperty("kind", KindToCString(kind_));
|
||||
const Script& script = Script::Handle(SourceScript());
|
||||
const TokenPosition pos = TokenPos().SourcePosition();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool IsFunctionVisible(const Function& function) {
|
||||
return FLAG_show_invisible_frames || function.is_visible();
|
||||
}
|
||||
|
@ -1540,6 +1567,7 @@ Debugger::Debugger()
|
|||
obj_cache_(NULL),
|
||||
stack_trace_(NULL),
|
||||
async_causal_stack_trace_(NULL),
|
||||
awaiter_stack_trace_(NULL),
|
||||
stepping_fp_(0),
|
||||
skip_next_step_(false),
|
||||
needs_breakpoint_cleanup_(false),
|
||||
|
@ -1937,10 +1965,13 @@ DebuggerStackTrace* Debugger::CollectAwaiterReturnStackTrace() {
|
|||
StackFrameIterator iterator(StackFrameIterator::kDontValidateFrames);
|
||||
|
||||
Code& code = Code::Handle(zone);
|
||||
Smi& offset = Smi::Handle(zone);
|
||||
Function& function = Function::Handle(zone);
|
||||
Code& inlined_code = Code::Handle(zone);
|
||||
Closure& async_activation = Closure::Handle(zone);
|
||||
Object& next_async_activation = Object::Handle(zone);
|
||||
Array& deopt_frame = Array::Handle(zone);
|
||||
class StackTrace& async_stack_trace = StackTrace::Handle(zone);
|
||||
|
||||
for (StackFrame* frame = iterator.NextFrame(); frame != NULL;
|
||||
frame = iterator.NextFrame()) {
|
||||
|
@ -2011,10 +2042,51 @@ DebuggerStackTrace* Debugger::CollectAwaiterReturnStackTrace() {
|
|||
|
||||
// Append the awaiter return call stack.
|
||||
while (!async_activation.IsNull()) {
|
||||
ActivationFrame* activation = new ActivationFrame(async_activation);
|
||||
async_activation ^= activation->GetAsyncAwaiter();
|
||||
ActivationFrame* activation = new (zone) ActivationFrame(async_activation);
|
||||
activation->ExtractTokenPositionFromAsyncClosure();
|
||||
stack_trace->AddActivation(activation);
|
||||
next_async_activation = activation->GetAsyncAwaiter();
|
||||
if (next_async_activation.IsNull()) {
|
||||
// No more awaiters. Extract the causal stack trace (if it exists).
|
||||
async_stack_trace ^= activation->GetCausalStack();
|
||||
break;
|
||||
}
|
||||
async_activation = Closure::RawCast(next_async_activation.raw());
|
||||
}
|
||||
|
||||
// Now we append the asynchronous causal stack trace. These are not active
|
||||
// frames but a historical record of how this asynchronous function was
|
||||
// activated.
|
||||
while (!async_stack_trace.IsNull()) {
|
||||
for (intptr_t i = 0; i < async_stack_trace.Length(); i++) {
|
||||
if (async_stack_trace.CodeAtFrame(i) == Code::null()) {
|
||||
// Incomplete OutOfMemory/StackOverflow trace OR array padding.
|
||||
break;
|
||||
}
|
||||
if (async_stack_trace.CodeAtFrame(i) ==
|
||||
StubCode::AsynchronousGapMarker_entry()->code()) {
|
||||
stack_trace->AddMarker(ActivationFrame::kAsyncSuspensionMarker);
|
||||
// The frame immediately below the asynchronous gap marker is the
|
||||
// identical to the frame above the marker. Skip the frame to enhance
|
||||
// the readability of the trace.
|
||||
i++;
|
||||
} else {
|
||||
code = Code::RawCast(async_stack_trace.CodeAtFrame(i));
|
||||
offset = Smi::RawCast(async_stack_trace.PcOffsetAtFrame(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->AddAsyncCausalFrame(it.pc(), inlined_code);
|
||||
}
|
||||
} else {
|
||||
stack_trace->AddAsyncCausalFrame(pc, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Follow the link.
|
||||
async_stack_trace = async_stack_trace.async_link();
|
||||
}
|
||||
|
||||
return stack_trace;
|
||||
|
@ -2055,6 +2127,17 @@ DebuggerStackTrace* Debugger::CurrentAsyncCausalStackTrace() {
|
|||
}
|
||||
|
||||
|
||||
DebuggerStackTrace* Debugger::AwaiterStackTrace() {
|
||||
return (awaiter_stack_trace_ != NULL) ? awaiter_stack_trace_
|
||||
: CollectAwaiterReturnStackTrace();
|
||||
}
|
||||
|
||||
|
||||
DebuggerStackTrace* Debugger::CurrentAwaiterStackTrace() {
|
||||
return CollectAwaiterReturnStackTrace();
|
||||
}
|
||||
|
||||
|
||||
DebuggerStackTrace* Debugger::StackTraceFrom(const class StackTrace& ex_trace) {
|
||||
DebuggerStackTrace* stack_trace = new DebuggerStackTrace(8);
|
||||
Function& function = Function::Handle();
|
||||
|
@ -2171,7 +2254,8 @@ void Debugger::PauseException(const Instance& exc) {
|
|||
if (stack_trace->Length() > 0) {
|
||||
event.set_top_frame(stack_trace->FrameAt(0));
|
||||
}
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace());
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace(),
|
||||
CollectAwaiterReturnStackTrace());
|
||||
Pause(&event);
|
||||
HandleSteppingRequest(stack_trace_); // we may get a rewind request
|
||||
ClearCachedStackTraces();
|
||||
|
@ -3175,17 +3259,21 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace,
|
|||
|
||||
|
||||
void Debugger::CacheStackTraces(DebuggerStackTrace* stack_trace,
|
||||
DebuggerStackTrace* async_causal_stack_trace) {
|
||||
DebuggerStackTrace* async_causal_stack_trace,
|
||||
DebuggerStackTrace* awaiter_stack_trace) {
|
||||
ASSERT(stack_trace_ == NULL);
|
||||
stack_trace_ = stack_trace;
|
||||
ASSERT(async_causal_stack_trace_ == NULL);
|
||||
async_causal_stack_trace_ = async_causal_stack_trace;
|
||||
ASSERT(awaiter_stack_trace_ == NULL);
|
||||
awaiter_stack_trace_ = awaiter_stack_trace;
|
||||
}
|
||||
|
||||
|
||||
void Debugger::ClearCachedStackTraces() {
|
||||
stack_trace_ = NULL;
|
||||
async_causal_stack_trace_ = NULL;
|
||||
awaiter_stack_trace_ = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
@ -3526,7 +3614,8 @@ RawError* Debugger::PauseStepping() {
|
|||
}
|
||||
|
||||
|
||||
CacheStackTraces(CollectStackTrace(), CollectAsyncCausalStackTrace());
|
||||
CacheStackTraces(CollectStackTrace(), CollectAsyncCausalStackTrace(),
|
||||
CollectAwaiterReturnStackTrace());
|
||||
// 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.
|
||||
|
@ -3567,7 +3656,8 @@ RawError* Debugger::PauseBreakpoint() {
|
|||
if (bpt_hit->is_synthetic_async()) {
|
||||
DebuggerStackTrace* stack_trace = CollectStackTrace();
|
||||
ASSERT(stack_trace->Length() > 0);
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace());
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace(),
|
||||
CollectAwaiterReturnStackTrace());
|
||||
|
||||
// Hit a synthetic async breakpoint.
|
||||
if (FLAG_verbose_debug) {
|
||||
|
@ -3602,7 +3692,8 @@ RawError* Debugger::PauseBreakpoint() {
|
|||
top_frame->pc());
|
||||
}
|
||||
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace());
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace(),
|
||||
CollectAwaiterReturnStackTrace());
|
||||
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.
|
||||
|
@ -3671,7 +3762,8 @@ void Debugger::PauseDeveloper(const String& msg) {
|
|||
|
||||
DebuggerStackTrace* stack_trace = CollectStackTrace();
|
||||
ASSERT(stack_trace->Length() > 0);
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace());
|
||||
CacheStackTraces(stack_trace, CollectAsyncCausalStackTrace(),
|
||||
CollectAwaiterReturnStackTrace());
|
||||
// TODO(johnmccutchan): Send |msg| to Observatory.
|
||||
|
||||
// We are in the native call to Developer_debugger. the developer
|
||||
|
|
|
@ -336,6 +336,7 @@ class ActivationFrame : public ZoneAllocated {
|
|||
void PrintToJSONObject(JSONObject* jsobj, bool full = false);
|
||||
|
||||
RawObject* GetAsyncAwaiter();
|
||||
RawObject* GetCausalStack();
|
||||
|
||||
bool HandlesException(const Instance& exc_obj);
|
||||
|
||||
|
@ -343,7 +344,7 @@ class ActivationFrame : public ZoneAllocated {
|
|||
void PrintToJSONObjectRegular(JSONObject* jsobj, bool full);
|
||||
void PrintToJSONObjectAsyncCausal(JSONObject* jsobj, bool full);
|
||||
void PrintToJSONObjectAsyncSuspensionMarker(JSONObject* jsobj, bool full);
|
||||
|
||||
void PrintToJSONObjectAsyncActivation(JSONObject* jsobj, bool full);
|
||||
void PrintContextMismatchError(intptr_t ctx_slot,
|
||||
intptr_t frame_ctx_level,
|
||||
intptr_t var_ctx_level);
|
||||
|
@ -368,6 +369,8 @@ class ActivationFrame : public ZoneAllocated {
|
|||
return "AsyncCausal";
|
||||
case kAsyncSuspensionMarker:
|
||||
return "AsyncSuspensionMarker";
|
||||
case kAsyncActivation:
|
||||
return "AsyncActivation";
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
|
@ -541,6 +544,9 @@ class Debugger {
|
|||
DebuggerStackTrace* AsyncCausalStackTrace();
|
||||
DebuggerStackTrace* CurrentAsyncCausalStackTrace();
|
||||
|
||||
DebuggerStackTrace* AwaiterStackTrace();
|
||||
DebuggerStackTrace* CurrentAwaiterStackTrace();
|
||||
|
||||
// Returns a debugger stack trace corresponding to a dart.core.StackTrace.
|
||||
// Frames corresponding to invisible functions are omitted. It is not valid
|
||||
// to query local variables in the returned stack.
|
||||
|
@ -693,7 +699,8 @@ class Debugger {
|
|||
bool skip_next_step = false);
|
||||
|
||||
void CacheStackTraces(DebuggerStackTrace* stack_trace,
|
||||
DebuggerStackTrace* async_causal_stack_trace);
|
||||
DebuggerStackTrace* async_causal_stack_trace,
|
||||
DebuggerStackTrace* awaiter_stack_trace);
|
||||
void ClearCachedStackTraces();
|
||||
|
||||
|
||||
|
@ -739,6 +746,7 @@ class Debugger {
|
|||
// Current stack trace. Valid only while IsPaused().
|
||||
DebuggerStackTrace* stack_trace_;
|
||||
DebuggerStackTrace* async_causal_stack_trace_;
|
||||
DebuggerStackTrace* awaiter_stack_trace_;
|
||||
|
||||
// When stepping through code, only pause the program if the top
|
||||
// frame corresponds to this fp value, or if the top frame is
|
||||
|
|
|
@ -276,6 +276,10 @@ static bool IsFilteredIdentifier(const String& str) {
|
|||
// Keep :await_jump_var for asynchronous debugging.
|
||||
return false;
|
||||
}
|
||||
if (str.raw() == Symbols::AsyncStackTraceVar().raw()) {
|
||||
// Keep :async_stack_trace for asynchronous debugging.
|
||||
return false;
|
||||
}
|
||||
return str.CharAt(0) == ':';
|
||||
}
|
||||
|
||||
|
|
|
@ -1345,6 +1345,7 @@ static bool GetStack(Thread* thread, JSONStream* js) {
|
|||
DebuggerStackTrace* stack = isolate->debugger()->StackTrace();
|
||||
DebuggerStackTrace* async_causal_stack =
|
||||
isolate->debugger()->AsyncCausalStackTrace();
|
||||
DebuggerStackTrace* awaiter_stack = isolate->debugger()->AwaiterStackTrace();
|
||||
// Do we want the complete script object and complete local variable objects?
|
||||
// This is true for dump requests.
|
||||
const bool full = BoolParameter::Parse(js->LookupParam("_full"), false);
|
||||
|
@ -1373,6 +1374,17 @@ static bool GetStack(Thread* thread, JSONStream* js) {
|
|||
}
|
||||
}
|
||||
|
||||
if (awaiter_stack != NULL) {
|
||||
JSONArray jsarr(&jsobj, "awaiterFrames");
|
||||
intptr_t num_frames = awaiter_stack->Length();
|
||||
for (intptr_t i = 0; i < num_frames; i++) {
|
||||
ActivationFrame* frame = awaiter_stack->FrameAt(i);
|
||||
JSONObject jsobj(&jsarr);
|
||||
frame->PrintToJSONObject(&jsobj, full);
|
||||
jsobj.AddProperty("index", i);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MessageHandler::AcquiredQueues aq(isolate->message_handler());
|
||||
jsobj.AddProperty("messages", aq.queue());
|
||||
|
|
|
@ -2259,6 +2259,7 @@ enum FrameKind {
|
|||
Regular,
|
||||
AsyncCausal,
|
||||
AsyncSuspensionMarker,
|
||||
AsyncActivation
|
||||
}
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in a new issue