[VM] Adds Future.then stack unwind. (3)
TEST=ASAN; Various 'causal' tests updated below. Issues addressed in this revision: - https://github.com/dart-lang/sdk/issues/44708 ASAN - https://github.com/dart-lang/sdk/issues/44700 SegFault Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-asan-linux-release-x64-try,vm-kernel-asan-linux-release-x64-try,analyzer-linux-release-try,analyzer-analysis-server-linux-try,analyzer-nnbd-linux-release-try Bug: https://github.com/dart-lang/sdk/issues/40815, https://github.com/dart-lang/sdk/issues/37953 Change-Id: I8b8f6ee2e5d4ca2e6bea988ec1cd9f912ddf8240 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/180186 Commit-Queue: Clement Skau <cskau@google.com> Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
aed72ae8c8
commit
c3ec3e53a1
@@ -52,11 +52,12 @@ final tests = <IsolateTest>[
|
||||
// Before the first await.
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_A),
|
||||
// At LINE_A we're still running sync. so no asyncCausalFrames.
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final result = await service.getStack(isolateRef.id!);
|
||||
|
||||
expect(result.frames, hasLength(16));
|
||||
expect(result.asyncCausalFrames, hasLength(16));
|
||||
expect(result.asyncCausalFrames, isNull);
|
||||
expect(result.awaiterFrames, hasLength(16));
|
||||
|
||||
expectFrames(result.frames, [
|
||||
@@ -73,20 +74,6 @@ final tests = <IsolateTest>[
|
||||
[equals('Regular'), endsWith(' testMain')],
|
||||
]);
|
||||
|
||||
expectFrames(result.asyncCausalFrames, [
|
||||
[equals('Regular'), endsWith(' func10')],
|
||||
[equals('Regular'), endsWith(' func9')],
|
||||
[equals('Regular'), endsWith(' func8')],
|
||||
[equals('Regular'), endsWith(' func7')],
|
||||
[equals('Regular'), endsWith(' func6')],
|
||||
[equals('Regular'), endsWith(' func5')],
|
||||
[equals('Regular'), endsWith(' func4')],
|
||||
[equals('Regular'), endsWith(' func3')],
|
||||
[equals('Regular'), endsWith(' func2')],
|
||||
[equals('Regular'), endsWith(' func1')],
|
||||
[equals('Regular'), endsWith(' testMain')],
|
||||
]);
|
||||
|
||||
expectFrames(result.awaiterFrames, [
|
||||
[equals('AsyncActivation'), endsWith(' func10')],
|
||||
[equals('AsyncActivation'), endsWith(' func9')],
|
||||
@@ -101,10 +88,10 @@ final tests = <IsolateTest>[
|
||||
[equals('AsyncActivation'), endsWith(' testMain')],
|
||||
]);
|
||||
},
|
||||
// After resuming the continuation - i.e. running async.
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_B),
|
||||
// After resuming the continuation - i.e. running async.
|
||||
(VmService service, IsolateRef isolateRef) async {
|
||||
final result = await service.getStack(isolateRef.id!);
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
const LINE_C = 19;
|
||||
const LINE_A = 24;
|
||||
const LINE_B = 30;
|
||||
const LINE_A = 25;
|
||||
const LINE_B = 31;
|
||||
|
||||
foobar() {
|
||||
debugger();
|
||||
@@ -20,6 +20,7 @@ foobar() {
|
||||
}
|
||||
|
||||
helper() async {
|
||||
await 0; // Yield. The rest will run async.
|
||||
debugger();
|
||||
print('helper'); // LINE_A.
|
||||
foobar();
|
||||
@@ -36,6 +37,7 @@ var tests = <IsolateTest>[
|
||||
(Isolate isolate) async {
|
||||
ServiceMap stack = await isolate.getStack();
|
||||
// No causal frames because we are in a completely synchronous stack.
|
||||
// Async function hasn't yielded yet.
|
||||
expect(stack['asyncCausalFrames'], isNull);
|
||||
},
|
||||
resumeIsolate,
|
||||
@@ -43,7 +45,7 @@ var tests = <IsolateTest>[
|
||||
stoppedAtLine(LINE_A),
|
||||
(Isolate isolate) async {
|
||||
ServiceMap stack = await isolate.getStack();
|
||||
// Has causal frames (we are inside an async function)
|
||||
// Async function has yielded once, so it's now running async.
|
||||
expect(stack['asyncCausalFrames'], isNotNull);
|
||||
},
|
||||
resumeIsolate,
|
||||
|
||||
@@ -39,7 +39,7 @@ var tests = <IsolateTest>[
|
||||
(Isolate isolate) async {
|
||||
ServiceMap stack = await isolate.getStack();
|
||||
// No causal frames because we are in a completely synchronous stack.
|
||||
expect(stack['asyncCausalFrames'], isNotNull);
|
||||
expect(stack['asyncCausalFrames'], isNull);
|
||||
},
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
|
||||
@@ -11,8 +11,8 @@ import 'service_test_common.dart';
|
||||
import 'test_helper.dart';
|
||||
|
||||
const LINE_C = 19;
|
||||
const LINE_A = 24;
|
||||
const LINE_B = 30;
|
||||
const LINE_A = 25;
|
||||
const LINE_B = 31;
|
||||
|
||||
foobar() {
|
||||
debugger();
|
||||
@@ -20,6 +20,7 @@ foobar() {
|
||||
}
|
||||
|
||||
helper() async {
|
||||
await 0; // Yield. The rest will run async.
|
||||
debugger();
|
||||
print('helper'); // LINE_A.
|
||||
foobar();
|
||||
|
||||
@@ -39,7 +39,7 @@ var tests = <IsolateTest>[
|
||||
(Isolate isolate) async {
|
||||
ServiceMap stack = await isolate.getStack();
|
||||
// No causal frames because we are in a completely synchronous stack.
|
||||
expect(stack['asyncCausalFrames'], isNotNull);
|
||||
expect(stack['asyncCausalFrames'], isNull);
|
||||
},
|
||||
resumeIsolate,
|
||||
hasStoppedAtBreakpoint,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -196,6 +196,8 @@ namespace dart {
|
||||
V(_Utf8Decoder, _scan, Utf8DecoderScan, 0xb35ced99) \
|
||||
V(_Future, timeout, FutureTimeout, 0x6ad7d1ef) \
|
||||
V(Future, wait, FutureWait, 0x264aacc2) \
|
||||
V(_RootZone, runUnary, RootZoneRunUnary, 0x76e41d34) \
|
||||
V(_FutureListener, handleValue, FutureListenerHandleValue, 0x73894d16) \
|
||||
|
||||
// List of intrinsics:
|
||||
// (class-name, function-name, intrinsification method, fingerprint).
|
||||
|
||||
@@ -1848,7 +1848,7 @@ DebuggerStackTrace* Debugger::CollectAsyncLazyStackTrace() {
|
||||
/*skip_frames=*/0, &on_sync_frame,
|
||||
&has_async);
|
||||
|
||||
// If the entire stack is sync, return no trace.
|
||||
// If the entire stack is sync, return no (async) trace.
|
||||
if (!has_async) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -528,6 +528,19 @@ StackFrameIterator::StackFrameIterator(uword fp,
|
||||
frames_.Unpoison();
|
||||
}
|
||||
|
||||
StackFrameIterator::StackFrameIterator(const StackFrameIterator& orig)
|
||||
: validate_(orig.validate_),
|
||||
entry_(orig.thread_),
|
||||
exit_(orig.thread_),
|
||||
frames_(orig.thread_),
|
||||
current_frame_(nullptr),
|
||||
thread_(orig.thread_) {
|
||||
frames_.fp_ = orig.frames_.fp_;
|
||||
frames_.sp_ = orig.frames_.sp_;
|
||||
frames_.pc_ = orig.frames_.pc_;
|
||||
frames_.Unpoison();
|
||||
}
|
||||
|
||||
StackFrame* StackFrameIterator::NextFrame() {
|
||||
// When we are at the start of iteration after having created an
|
||||
// iterator object, current_frame_ will be NULL as we haven't seen
|
||||
|
||||
@@ -236,6 +236,8 @@ class StackFrameIterator : public ValueObject {
|
||||
Thread* thread,
|
||||
CrossThreadPolicy cross_thread_policy);
|
||||
|
||||
StackFrameIterator(const StackFrameIterator& orig);
|
||||
|
||||
// Checks if a next frame exists.
|
||||
bool HasNextFrame() const { return frames_.fp_ != 0; }
|
||||
|
||||
@@ -300,7 +302,6 @@ class StackFrameIterator : public ValueObject {
|
||||
Thread* thread_;
|
||||
|
||||
friend class ProfilerDartStackWalker;
|
||||
DISALLOW_COPY_AND_ASSIGN(StackFrameIterator);
|
||||
};
|
||||
|
||||
// Iterator for iterating over all dart frames (skips over exit frames,
|
||||
@@ -339,6 +340,8 @@ class DartFrameIterator : public ValueObject {
|
||||
thread,
|
||||
cross_thread_policy) {}
|
||||
|
||||
DartFrameIterator(const DartFrameIterator& orig) : frames_(orig.frames_) {}
|
||||
|
||||
// Get next dart frame.
|
||||
StackFrame* NextFrame() {
|
||||
StackFrame* frame = frames_.NextFrame();
|
||||
@@ -350,8 +353,6 @@ class DartFrameIterator : public ValueObject {
|
||||
|
||||
private:
|
||||
StackFrameIterator frames_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DartFrameIterator);
|
||||
};
|
||||
|
||||
// Iterator for iterating over all inlined dart functions in an optimized
|
||||
|
||||
+176
-88
@@ -10,11 +10,18 @@
|
||||
|
||||
namespace dart {
|
||||
|
||||
// Keep in sync with
|
||||
// sdk/lib/async/stream_controller.dart:_StreamController._STATE_SUBSCRIBED.
|
||||
// Keep in sync with:
|
||||
// - sdk/lib/async/stream_controller.dart:_StreamController._STATE_SUBSCRIBED.
|
||||
const intptr_t k_StreamController__STATE_SUBSCRIBED = 1;
|
||||
// sdk/lib/async/future_impl.dart:_FutureListener.stateWhencomplete.
|
||||
const intptr_t k_FutureListener_stateWhencomplete = 8;
|
||||
// - sdk/lib/async/future_impl.dart:_FutureListener.stateThen.
|
||||
const intptr_t k_FutureListener_stateThen = 1;
|
||||
// - sdk/lib/async/future_impl.dart:_FutureListener.stateCatchError.
|
||||
const intptr_t k_FutureListener_stateCatchError = 2;
|
||||
// - sdk/lib/async/future_impl.dart:_FutureListener.stateWhenComplete.
|
||||
const intptr_t k_FutureListener_stateWhenComplete = 8;
|
||||
|
||||
// Keep in sync with sdk/lib/async/future_impl.dart:_FutureListener.handleValue.
|
||||
const intptr_t kNumArgsFutureListenerHandleValue = 1;
|
||||
|
||||
// Find current yield index from async closure.
|
||||
// Async closures contains a variable, :await_jump_var that holds the index into
|
||||
@@ -140,21 +147,19 @@ ClosurePtr CallerClosureFinder::GetCallerInFutureImpl(const Object& future) {
|
||||
ASSERT(!future.IsNull());
|
||||
ASSERT(future.GetClassId() == future_impl_class.id());
|
||||
|
||||
listener_ = Instance::Cast(future).GetField(future_result_or_listeners_field);
|
||||
if (listener_.GetClassId() != future_listener_class.id()) {
|
||||
// Since this function is recursive, we have to keep a local ref.
|
||||
auto& listener = Object::Handle(
|
||||
Instance::Cast(future).GetField(future_result_or_listeners_field));
|
||||
if (listener.GetClassId() != future_listener_class.id()) {
|
||||
return Closure::null();
|
||||
}
|
||||
|
||||
// If the _FutureListener is a whenComplete listener, follow the Future being
|
||||
// completed, `result`, instead of the dangling whenComplete `callback`.
|
||||
state_ = Instance::Cast(listener_).GetField(future_listener_state_field);
|
||||
ASSERT(state_.IsSmi());
|
||||
if (Smi::Cast(state_).Value() == k_FutureListener_stateWhencomplete) {
|
||||
future_ = Instance::Cast(listener_).GetField(future_listener_result_field);
|
||||
return GetCallerInFutureImpl(future_);
|
||||
callback_ = GetCallerInFutureListener(listener);
|
||||
if (callback_.IsInstance() && !callback_.IsNull()) {
|
||||
return Closure::Cast(callback_).ptr();
|
||||
}
|
||||
|
||||
callback_ = Instance::Cast(listener_).GetField(callback_field);
|
||||
callback_ = Instance::Cast(listener).GetField(callback_field);
|
||||
// This happens for e.g.: await f().catchError(..);
|
||||
if (callback_.IsNull()) {
|
||||
return Closure::null();
|
||||
@@ -221,22 +226,46 @@ ClosurePtr CallerClosureFinder::FindCallerInAsyncGenClosure(
|
||||
UNREACHABLE(); // If no onData is found we have a bug.
|
||||
}
|
||||
|
||||
ClosurePtr CallerClosureFinder::GetCallerInFutureListener(
|
||||
const Object& future_listener) {
|
||||
ASSERT(future_listener.GetClassId() == future_listener_class.id());
|
||||
|
||||
state_ =
|
||||
Instance::Cast(future_listener).GetField(future_listener_state_field);
|
||||
|
||||
auto value = Smi::Cast(state_).Value();
|
||||
// If the _FutureListener is a `then`, `catchError`, or `whenComplete`
|
||||
// listener, follow the Future being completed, `result`, instead of the
|
||||
// dangling whenComplete `callback`.
|
||||
if (value == k_FutureListener_stateThen ||
|
||||
value == k_FutureListener_stateCatchError ||
|
||||
value == k_FutureListener_stateWhenComplete) {
|
||||
future_ =
|
||||
Instance::Cast(future_listener).GetField(future_listener_result_field);
|
||||
return GetCallerInFutureImpl(future_);
|
||||
}
|
||||
|
||||
return Closure::null();
|
||||
}
|
||||
|
||||
ClosurePtr CallerClosureFinder::FindCaller(const Closure& receiver_closure) {
|
||||
receiver_function_ = receiver_closure.function();
|
||||
receiver_context_ = receiver_closure.context();
|
||||
|
||||
if (receiver_function_.IsAsyncClosure()) {
|
||||
return FindCallerInAsyncClosure(receiver_context_);
|
||||
} else if (receiver_function_.IsAsyncGenClosure()) {
|
||||
}
|
||||
if (receiver_function_.IsAsyncGenClosure()) {
|
||||
return FindCallerInAsyncGenClosure(receiver_context_);
|
||||
} else if (receiver_function_.IsLocalFunction()) {
|
||||
}
|
||||
if (receiver_function_.IsLocalFunction()) {
|
||||
parent_function_ = receiver_function_.parent_function();
|
||||
if (parent_function_.recognized_kind() ==
|
||||
MethodRecognizer::kFutureTimeout) {
|
||||
context_entry_ = receiver_context_.At(Context::kFutureTimeoutFutureIndex);
|
||||
return GetCallerInFutureImpl(context_entry_);
|
||||
} else if (parent_function_.recognized_kind() ==
|
||||
MethodRecognizer::kFutureWait) {
|
||||
}
|
||||
if (parent_function_.recognized_kind() == MethodRecognizer::kFutureWait) {
|
||||
receiver_context_ = receiver_context_.parent();
|
||||
ASSERT(!receiver_context_.IsNull());
|
||||
context_entry_ = receiver_context_.At(Context::kFutureWaitFutureIndex);
|
||||
@@ -300,6 +329,110 @@ ClosurePtr StackTraceUtils::FindClosureInFrame(ObjectPtr* last_object_in_caller,
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
ClosurePtr StackTraceUtils::ClosureFromFrameFunction(
|
||||
Zone* zone,
|
||||
CallerClosureFinder* caller_closure_finder,
|
||||
const DartFrameIterator& frames,
|
||||
StackFrame* frame,
|
||||
bool* skip_frame,
|
||||
bool* is_async) {
|
||||
auto& closure = Closure::Handle(zone);
|
||||
auto& function = Function::Handle(zone);
|
||||
|
||||
function = frame->LookupDartFunction();
|
||||
if (function.IsNull()) {
|
||||
return Closure::null();
|
||||
}
|
||||
|
||||
if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
|
||||
// Next, look up caller's closure on the stack and walk backwards
|
||||
// through the yields.
|
||||
ObjectPtr* last_caller_obj =
|
||||
reinterpret_cast<ObjectPtr*>(frame->GetCallerSp());
|
||||
closure = FindClosureInFrame(last_caller_obj, function);
|
||||
|
||||
// If this async function hasn't yielded yet, we're still dealing with a
|
||||
// normal stack. Continue to next frame as usual.
|
||||
if (!caller_closure_finder->IsRunningAsync(closure)) {
|
||||
return Closure::null();
|
||||
}
|
||||
|
||||
*is_async = true;
|
||||
|
||||
// Skip: Already handled this as a sync. frame.
|
||||
return caller_closure_finder->FindCaller(closure);
|
||||
}
|
||||
|
||||
// May have been called from `_FutureListener.handleValue`, which means its
|
||||
// receiver holds the Future chain.
|
||||
DartFrameIterator future_frames(frames);
|
||||
if (function.recognized_kind() == MethodRecognizer::kRootZoneRunUnary) {
|
||||
frame = future_frames.NextFrame();
|
||||
function = frame->LookupDartFunction();
|
||||
if (function.recognized_kind() !=
|
||||
MethodRecognizer::kFutureListenerHandleValue) {
|
||||
return Closure::null();
|
||||
}
|
||||
}
|
||||
if (function.recognized_kind() ==
|
||||
MethodRecognizer::kFutureListenerHandleValue) {
|
||||
*is_async = true;
|
||||
*skip_frame = true;
|
||||
|
||||
// The _FutureListener receiver is at the top of the previous frame, right
|
||||
// before the arguments to the call.
|
||||
Object& receiver =
|
||||
Object::Handle(*(reinterpret_cast<ObjectPtr*>(frame->GetCallerSp()) +
|
||||
kNumArgsFutureListenerHandleValue));
|
||||
|
||||
return caller_closure_finder->GetCallerInFutureListener(receiver);
|
||||
}
|
||||
|
||||
return Closure::null();
|
||||
}
|
||||
|
||||
void StackTraceUtils::UnwindAwaiterChain(
|
||||
Zone* zone,
|
||||
const GrowableObjectArray& code_array,
|
||||
const GrowableObjectArray& pc_offset_array,
|
||||
CallerClosureFinder* caller_closure_finder,
|
||||
const Closure& leaf_closure) {
|
||||
auto& code = Code::Handle(zone);
|
||||
auto& function = Function::Handle(zone);
|
||||
auto& closure = Closure::Handle(zone, leaf_closure.ptr());
|
||||
auto& pc_descs = PcDescriptors::Handle(zone);
|
||||
auto& offset = Smi::Handle(zone);
|
||||
|
||||
// Inject async suspension marker.
|
||||
code_array.Add(StubCode::AsynchronousGapMarker());
|
||||
offset = Smi::New(0);
|
||||
pc_offset_array.Add(offset);
|
||||
|
||||
// Traverse the trail of async futures all the way up.
|
||||
for (; !closure.IsNull();
|
||||
closure = caller_closure_finder->FindCaller(closure)) {
|
||||
function = closure.function();
|
||||
if (function.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
// In hot-reload-test-mode we sometimes have to do this:
|
||||
code = function.EnsureHasCode();
|
||||
RELEASE_ASSERT(!code.IsNull());
|
||||
code_array.Add(code);
|
||||
pc_descs = code.pc_descriptors();
|
||||
offset = Smi::New(FindPcOffset(pc_descs, GetYieldIndex(closure)));
|
||||
// Unlike other sources of PC offsets, the offset may be 0 here if we
|
||||
// reach a non-async closure receiving the yielded value.
|
||||
ASSERT(offset.Value() >= 0);
|
||||
pc_offset_array.Add(offset);
|
||||
|
||||
// Inject async suspension marker.
|
||||
code_array.Add(StubCode::AsynchronousGapMarker());
|
||||
offset = Smi::New(0);
|
||||
pc_offset_array.Add(offset);
|
||||
}
|
||||
}
|
||||
|
||||
void StackTraceUtils::CollectFramesLazy(
|
||||
Thread* thread,
|
||||
const GrowableObjectArray& code_array,
|
||||
@@ -320,13 +453,11 @@ void StackTraceUtils::CollectFramesLazy(
|
||||
return;
|
||||
}
|
||||
|
||||
auto& function = Function::Handle(zone);
|
||||
auto& code = Code::Handle(zone);
|
||||
auto& offset = Smi::Handle(zone);
|
||||
|
||||
auto& closure = Closure::Handle(zone);
|
||||
|
||||
CallerClosureFinder caller_closure_finder(zone);
|
||||
auto& pc_descs = PcDescriptors::Handle();
|
||||
|
||||
// Start by traversing the sync. part of the stack.
|
||||
for (; frame != nullptr; frame = frames.NextFrame()) {
|
||||
@@ -335,79 +466,36 @@ void StackTraceUtils::CollectFramesLazy(
|
||||
continue;
|
||||
}
|
||||
|
||||
function = frame->LookupDartFunction();
|
||||
// If we encounter a known part of the async/Future mechanism, unwind the
|
||||
// awaiter chain from the closures.
|
||||
bool skip_frame = false;
|
||||
bool is_async = false;
|
||||
closure = ClosureFromFrameFunction(zone, &caller_closure_finder, frames,
|
||||
frame, &skip_frame, &is_async);
|
||||
|
||||
// Add the current synchronous frame.
|
||||
code = frame->LookupDartCode();
|
||||
ASSERT(function.ptr() == code.function());
|
||||
code_array.Add(code);
|
||||
const intptr_t pc_offset = frame->pc() - code.PayloadStart();
|
||||
ASSERT(pc_offset > 0 && pc_offset <= code.Size());
|
||||
offset = Smi::New(pc_offset);
|
||||
pc_offset_array.Add(offset);
|
||||
if (on_sync_frames != nullptr) {
|
||||
(*on_sync_frames)(frame);
|
||||
// This isn't a special (async) frame we should skip.
|
||||
if (!skip_frame) {
|
||||
// Add the current synchronous frame.
|
||||
code = frame->LookupDartCode();
|
||||
code_array.Add(code);
|
||||
const intptr_t pc_offset = frame->pc() - code.PayloadStart();
|
||||
ASSERT(pc_offset > 0 && pc_offset <= code.Size());
|
||||
offset = Smi::New(pc_offset);
|
||||
pc_offset_array.Add(offset);
|
||||
// Callback for sync frame.
|
||||
if (on_sync_frames != nullptr) {
|
||||
(*on_sync_frames)(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// Either continue the loop (sync-async case) or find all await'ers and
|
||||
// return.
|
||||
if (!function.IsNull() &&
|
||||
(function.IsAsyncClosure() || function.IsAsyncGenClosure())) {
|
||||
// This frame is running async.
|
||||
// Note: The closure might still be null in case it's an unawaited future.
|
||||
if (is_async) {
|
||||
UnwindAwaiterChain(zone, code_array, pc_offset_array,
|
||||
&caller_closure_finder, closure);
|
||||
if (has_async != nullptr) {
|
||||
*has_async = true;
|
||||
}
|
||||
|
||||
{
|
||||
NoSafepointScope nsp;
|
||||
|
||||
// Next, look up caller's closure on the stack and walk backwards
|
||||
// through the yields.
|
||||
ObjectPtr* last_caller_obj =
|
||||
reinterpret_cast<ObjectPtr*>(frame->GetCallerSp());
|
||||
closure = FindClosureInFrame(last_caller_obj, function);
|
||||
|
||||
// If this async function hasn't yielded yet, we're still dealing with a
|
||||
// normal stack. Continue to next frame as usual.
|
||||
if (!caller_closure_finder.IsRunningAsync(closure)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject async suspension marker.
|
||||
code_array.Add(StubCode::AsynchronousGapMarker());
|
||||
offset = Smi::New(0);
|
||||
pc_offset_array.Add(offset);
|
||||
|
||||
// Skip: Already handled this frame's function above.
|
||||
closure = caller_closure_finder.FindCaller(closure);
|
||||
|
||||
// Traverse the trail of async futures all the way up.
|
||||
for (; !closure.IsNull();
|
||||
closure = caller_closure_finder.FindCaller(closure)) {
|
||||
function = closure.function();
|
||||
// In hot-reload-test-mode we sometimes have to do this:
|
||||
if (!function.HasCode()) {
|
||||
function.EnsureHasCode();
|
||||
}
|
||||
if (function.HasCode()) {
|
||||
code = function.CurrentCode();
|
||||
code_array.Add(code);
|
||||
pc_descs = code.pc_descriptors();
|
||||
offset = Smi::New(FindPcOffset(pc_descs, GetYieldIndex(closure)));
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
// Unlike other sources of PC offsets, the offset may be 0 here if we
|
||||
// reach a non-async closure receiving the yielded value.
|
||||
ASSERT(offset.Value() >= 0);
|
||||
pc_offset_array.Add(offset);
|
||||
|
||||
// Inject async suspension marker.
|
||||
code_array.Add(StubCode::AsynchronousGapMarker());
|
||||
offset = Smi::New(0);
|
||||
pc_offset_array.Add(offset);
|
||||
}
|
||||
|
||||
// Ignore the rest of the stack; already unwound all async calls.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ class CallerClosureFinder {
|
||||
|
||||
ClosurePtr GetCallerInFutureImpl(const Object& future_);
|
||||
|
||||
ClosurePtr GetCallerInFutureListener(const Object& future_listener);
|
||||
|
||||
ClosurePtr FindCallerInAsyncClosure(const Context& receiver_context);
|
||||
|
||||
ClosurePtr FindCallerInAsyncGenClosure(const Context& receiver_context);
|
||||
@@ -61,6 +63,8 @@ class CallerClosureFinder {
|
||||
Field& state_field;
|
||||
Field& on_data_field;
|
||||
Field& state_data_field;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CallerClosureFinder);
|
||||
};
|
||||
|
||||
class StackTraceUtils : public AllStatic {
|
||||
@@ -69,6 +73,20 @@ class StackTraceUtils : public AllStatic {
|
||||
static ClosurePtr FindClosureInFrame(ObjectPtr* last_object_in_caller,
|
||||
const Function& function);
|
||||
|
||||
static ClosurePtr ClosureFromFrameFunction(
|
||||
Zone* zone,
|
||||
CallerClosureFinder* caller_closure_finder,
|
||||
const DartFrameIterator& frames,
|
||||
StackFrame* frame,
|
||||
bool* skip_frame,
|
||||
bool* is_async);
|
||||
|
||||
static void UnwindAwaiterChain(Zone* zone,
|
||||
const GrowableObjectArray& code_array,
|
||||
const GrowableObjectArray& pc_offset_array,
|
||||
CallerClosureFinder* caller_closure_finder,
|
||||
const Closure& leaf_closure);
|
||||
|
||||
/// Collects all frames on the current stack until an async/async* frame is
|
||||
/// hit which has yielded before (i.e. is not in sync-async case).
|
||||
///
|
||||
|
||||
@@ -63,18 +63,19 @@ class _SyncCompleter<T> extends _Completer<T> {
|
||||
}
|
||||
|
||||
class _FutureListener<S, T> {
|
||||
// Keep in sync with sdk/runtime/vm/stack_trace.cc.
|
||||
static const int maskValue = 1;
|
||||
static const int maskError = 2;
|
||||
static const int maskTestError = 4;
|
||||
static const int maskWhencomplete = 8;
|
||||
static const int maskWhenComplete = 8;
|
||||
static const int stateChain = 0;
|
||||
static const int stateThen = maskValue;
|
||||
static const int stateThenOnerror = maskValue | maskError;
|
||||
static const int stateCatcherror = maskError;
|
||||
static const int stateCatcherrorTest = maskError | maskTestError;
|
||||
static const int stateWhencomplete = maskWhencomplete;
|
||||
static const int stateCatchError = maskError;
|
||||
static const int stateCatchErrorTest = maskError | maskTestError;
|
||||
static const int stateWhenComplete = maskWhenComplete;
|
||||
static const int maskType =
|
||||
maskValue | maskError | maskTestError | maskWhencomplete;
|
||||
maskValue | maskError | maskTestError | maskWhenComplete;
|
||||
static const int stateIsAwait = 16;
|
||||
|
||||
// Listeners on the same future are linked through this link.
|
||||
@@ -109,18 +110,18 @@ class _FutureListener<S, T> {
|
||||
stateIsAwait;
|
||||
|
||||
_FutureListener.catchError(this.result, this.errorCallback, this.callback)
|
||||
: state = (callback == null) ? stateCatcherror : stateCatcherrorTest;
|
||||
: state = (callback == null) ? stateCatchError : stateCatchErrorTest;
|
||||
|
||||
_FutureListener.whenComplete(this.result, this.callback)
|
||||
: errorCallback = null,
|
||||
state = stateWhencomplete;
|
||||
state = stateWhenComplete;
|
||||
|
||||
_Zone get _zone => result._zone;
|
||||
|
||||
bool get handlesValue => (state & maskValue != 0);
|
||||
bool get handlesError => (state & maskError != 0);
|
||||
bool get hasErrorTest => (state & maskType == stateCatcherrorTest);
|
||||
bool get handlesComplete => (state & maskType == stateWhencomplete);
|
||||
bool get hasErrorTest => (state & maskType == stateCatchErrorTest);
|
||||
bool get handlesComplete => (state & maskType == stateWhenComplete);
|
||||
bool get isAwait => (state & stateIsAwait != 0);
|
||||
|
||||
FutureOr<T> Function(S) get _onValue {
|
||||
@@ -148,6 +149,8 @@ class _FutureListener<S, T> {
|
||||
return _onError != null;
|
||||
}
|
||||
|
||||
@pragma("vm:recognized", "other")
|
||||
@pragma("vm:never-inline")
|
||||
FutureOr<T> handleValue(S sourceResult) {
|
||||
return _zone.runUnary<FutureOr<T>, S>(_onValue, sourceResult);
|
||||
}
|
||||
|
||||
@@ -1608,6 +1608,7 @@ class _RootZone extends _Zone {
|
||||
return _rootRun(null, null, this, f);
|
||||
}
|
||||
|
||||
@pragma("vm:recognized", "other")
|
||||
R runUnary<R, T>(R f(T arg), T arg) {
|
||||
if (identical(Zone._current, _rootZone)) return f(arg);
|
||||
return _rootRunUnary(null, null, this, f, arg);
|
||||
|
||||
Reference in New Issue
Block a user