[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:
Clement Skau
2021-01-26 14:02:39 +00:00
committed by commit-bot@chromium.org
parent aed72ae8c8
commit c3ec3e53a1
15 changed files with 1853 additions and 1423 deletions
+3 -16
View File
@@ -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).
+1 -1
View File
@@ -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;
}
+13
View File
@@ -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
+4 -3
View File
@@ -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
View File
@@ -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;
}
+18
View File
@@ -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).
///
+12 -9
View File
@@ -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);
}
+1
View File
@@ -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);