| // 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. |
| |
| #include "vm/stack_trace.h" |
| |
| #include "vm/dart_api_impl.h" |
| #include "vm/object_store.h" |
| #include "vm/stack_frame.h" |
| #include "vm/symbols.h" |
| |
| namespace dart { |
| |
| namespace { |
| |
| // 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.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; |
| // - sdk/lib/async/future_impl.dart:_FutureListener.maskAwait. |
| const intptr_t k_FutureListener_maskAwait = 16; |
| |
| bool WasPreviouslySuspended(const Function& function, |
| const Object& suspend_state_var) { |
| if (!suspend_state_var.IsSuspendState()) { |
| return false; |
| } |
| if (function.IsAsyncFunction()) { |
| // Error callback is set after both 'then' and 'error' callbacks are |
| // registered with the Zone. Callback registration may query |
| // stack trace and should still collect the synchronous stack trace. |
| return SuspendState::Cast(suspend_state_var).error_callback() != |
| Object::null(); |
| } else if (function.IsAsyncGenerator()) { |
| return true; |
| } else { |
| UNREACHABLE(); |
| } |
| } |
| |
| // Unwinder which starts by unwinding the synchronous portion of the stack |
| // until it reaches a frame which has an asynchronous awaiter (e.g. an |
| // activation of the async function which has a listener attached to the |
| // corresponding Future object) and then unwinds through the chain |
| // of awaiters. |
| class AsyncAwareStackUnwinder : public ValueObject { |
| public: |
| explicit AsyncAwareStackUnwinder(Thread* thread) |
| : zone_(thread->zone()), |
| sync_frames_(thread, StackFrameIterator::kNoCrossThreadIteration), |
| sync_frame_(nullptr), |
| awaiter_frame_{Closure::Handle(zone_), Object::Handle(zone_)}, |
| closure_(Closure::Handle(zone_)), |
| code_(Code::Handle(zone_)), |
| context_(Context::Handle(zone_)), |
| function_(Function::Handle(zone_)), |
| parent_function_(Function::Handle(zone_)), |
| object_(Object::Handle(zone_)), |
| suspend_state_(SuspendState::Handle(zone_)), |
| controller_(Object::Handle(zone_)), |
| subscription_(Object::Handle(zone_)), |
| stream_iterator_(Object::Handle(zone_)), |
| async_lib_(Library::Handle(zone_, Library::AsyncLibrary())), |
| null_closure_(Closure::Handle(zone_)) {} |
| |
| void Unwind(intptr_t skip_frames, |
| std::function<void(const StackTraceUtils::Frame&)> handle_frame); |
| |
| bool encountered_async_catch_error() const { |
| return encountered_async_catch_error_; |
| } |
| |
| private: |
| bool HandleSynchronousFrame(); |
| |
| void UnwindAwaiterFrame(); |
| |
| // Returns either the `onData` or the Future awaiter. |
| ObjectPtr FindCallerInAsyncStarStreamController( |
| const Object& async_star_stream_controller); |
| |
| void InitializeAwaiterFrameFromSuspendState(); |
| |
| void InitializeAwaiterFrameFromFutureListener(const Object& listener); |
| |
| void UnwindToAwaiter(); |
| |
| // |frame.next| is a |_Future| instance. Unwind to the next frame. |
| void UnwindFrameToFutureListener(); |
| |
| // |frame.next| is an |_AsyncStarStreamController| instance corresponding to |
| // an async* function. Unwind to the next frame. |
| void UnwindFrameToStreamListener(); |
| |
| ObjectPtr GetReceiver() const; |
| |
| #define USED_CLASS_LIST(V) \ |
| V(_AsyncStarStreamController) \ |
| V(_BufferingStreamSubscription) \ |
| V(_Completer) \ |
| V(_AsyncCompleter) \ |
| V(_SyncCompleter) \ |
| V(_ControllerSubscription) \ |
| V(_Future) \ |
| V(_FutureListener) \ |
| V(_StreamController) \ |
| V(_StreamIterator) \ |
| V(_SyncStreamController) |
| |
| enum ClassId { |
| #define DECLARE_CONSTANT(symbol) k##symbol, |
| USED_CLASS_LIST(DECLARE_CONSTANT) |
| #undef DECLARE_CONSTANT |
| }; |
| |
| #define USED_FIELD_LIST(V) \ |
| V(_AsyncStarStreamController, controller) \ |
| V(_BufferingStreamSubscription, _onData) \ |
| V(_Completer, future) \ |
| V(_Future, _resultOrListeners) \ |
| V(_FutureListener, callback) \ |
| V(_FutureListener, result) \ |
| V(_FutureListener, state) \ |
| V(_StreamController, _state) \ |
| V(_StreamController, _varData) \ |
| V(_StreamIterator, _hasValue) \ |
| V(_StreamIterator, _stateData) |
| |
| enum FieldId { |
| #define DECLARE_CONSTANT(class_symbol, field_symbol) \ |
| k##class_symbol##_##field_symbol, |
| USED_FIELD_LIST(DECLARE_CONSTANT) |
| #undef DECLARE_CONSTANT |
| }; |
| |
| #define PLUS_ONE(...) +1 |
| static constexpr intptr_t kUsedClassCount = 0 USED_CLASS_LIST(PLUS_ONE); |
| static constexpr intptr_t kUsedFieldCount = 0 USED_FIELD_LIST(PLUS_ONE); |
| #undef PLUS_ONE |
| |
| #define DECLARE_GETTER(symbol) \ |
| const Class& symbol() { \ |
| auto& cls = classes_[k##symbol]; \ |
| if (cls == nullptr) { \ |
| cls = &Class::Handle( \ |
| zone_, async_lib_.LookupClassAllowPrivate(Symbols::symbol())); \ |
| ASSERT(!cls->IsNull()); \ |
| } \ |
| return *cls; \ |
| } |
| USED_CLASS_LIST(DECLARE_GETTER) |
| #undef DECLARE_GETTER |
| |
| #define DECLARE_GETTER(class_symbol, field_symbol) \ |
| ObjectPtr Get##class_symbol##_##field_symbol(const Object& obj) { \ |
| auto& field = fields_[k##class_symbol##_##field_symbol]; \ |
| if (field == nullptr) { \ |
| field = &Field::Handle(zone_, class_symbol().LookupFieldAllowPrivate( \ |
| Symbols::field_symbol())); \ |
| ASSERT(!field->IsNull()); \ |
| } \ |
| return Instance::Cast(obj).GetField(*field); \ |
| } |
| USED_FIELD_LIST(DECLARE_GETTER) |
| #undef DECLARE_GETTER |
| |
| struct AwaiterFrame { |
| Closure& closure; |
| Object& next; |
| }; |
| |
| Zone* zone_; |
| DartFrameIterator sync_frames_; |
| |
| StackFrame* sync_frame_; |
| AwaiterFrame awaiter_frame_; |
| |
| bool encountered_async_catch_error_ = false; |
| |
| Closure& closure_; |
| Code& code_; |
| Context& context_; |
| Function& function_; |
| Function& parent_function_; |
| Object& object_; |
| SuspendState& suspend_state_; |
| |
| Object& controller_; |
| Object& subscription_; |
| Object& stream_iterator_; |
| |
| const Library& async_lib_; |
| Class* classes_[kUsedClassCount] = {}; |
| Field* fields_[kUsedFieldCount] = {}; |
| |
| const Closure& null_closure_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AsyncAwareStackUnwinder); |
| }; |
| |
| void AsyncAwareStackUnwinder::Unwind( |
| intptr_t skip_frames, |
| std::function<void(const StackTraceUtils::Frame&)> handle_frame) { |
| // First skip the given number of synchronous frames. |
| sync_frame_ = sync_frames_.NextFrame(); |
| while (skip_frames > 0 && sync_frame_ != nullptr) { |
| sync_frame_ = sync_frames_.NextFrame(); |
| skip_frames--; |
| } |
| |
| // Continue unwinding synchronous portion of the stack looking for |
| // a synchronous frame which has an awaiter. |
| while (sync_frame_ != nullptr && awaiter_frame_.closure.IsNull()) { |
| const bool was_handled = HandleSynchronousFrame(); |
| if (!was_handled) { |
| code_ = sync_frame_->LookupDartCode(); |
| const uword pc_offset = sync_frame_->pc() - code_.PayloadStart(); |
| handle_frame({sync_frame_, code_, pc_offset, null_closure_}); |
| } |
| sync_frame_ = sync_frames_.NextFrame(); |
| } |
| |
| const StackTraceUtils::Frame gap_frame = {nullptr, |
| StubCode::AsynchronousGapMarker(), |
| /*pc_offset=*/0, null_closure_}; |
| |
| // Traverse awaiter frames. |
| bool any_async = false; |
| for (; !awaiter_frame_.closure.IsNull(); UnwindToAwaiter()) { |
| function_ = awaiter_frame_.closure.function(); |
| if (function_.IsNull()) { |
| continue; |
| } |
| |
| any_async = true; |
| uword pc_offset; |
| if (awaiter_frame_.next.IsSuspendState()) { |
| const uword pc = SuspendState::Cast(awaiter_frame_.next).pc(); |
| if (pc == 0) { |
| // Async function is already resumed. |
| continue; |
| } |
| |
| code_ = SuspendState::Cast(awaiter_frame_.next).GetCodeObject(); |
| pc_offset = pc - code_.PayloadStart(); |
| } else { |
| // This is an asynchronous continuation represented by a closure which |
| // will handle successful completion. This function is not yet executing |
| // so we have to use artificial marker offset (1). |
| code_ = function_.EnsureHasCode(); |
| RELEASE_ASSERT(!code_.IsNull()); |
| pc_offset = |
| (function_.entry_point() + StackTraceUtils::kFutureListenerPcOffset) - |
| code_.PayloadStart(); |
| } |
| |
| handle_frame(gap_frame); |
| handle_frame({nullptr, code_, pc_offset, awaiter_frame_.closure}); |
| } |
| |
| if (any_async) { |
| handle_frame(gap_frame); |
| } |
| } |
| |
| ObjectPtr AsyncAwareStackUnwinder::GetReceiver() const { |
| return *(reinterpret_cast<ObjectPtr*>(sync_frame_->GetCallerSp()) + |
| function_.num_fixed_parameters() - 1); |
| } |
| |
| bool AsyncAwareStackUnwinder::HandleSynchronousFrame() { |
| function_ = sync_frame_->LookupDartFunction(); |
| if (function_.IsNull()) { |
| return false; |
| } |
| |
| // This is an invocation of an `async` or `async*` function. |
| if (function_.IsAsyncFunction() || function_.IsAsyncGenerator()) { |
| InitializeAwaiterFrameFromSuspendState(); |
| return false; |
| } |
| |
| // This is an invocation of a closure which has a variable marked |
| // with `@pragma('vm:awaiter-link')` which points to the awaiter. |
| if (function_.HasAwaiterLink()) { |
| object_ = GetReceiver(); |
| if (object_.IsClosure() && |
| StackTraceUtils::GetSuspendState(Closure::Cast(object_), |
| &awaiter_frame_.next)) { |
| awaiter_frame_.closure ^= object_.ptr(); |
| return true; // Hide this frame from the stack trace. |
| } |
| } |
| |
| // This is `_FutureListener.handleValue(...)` invocation. |
| if (function_.recognized_kind() == |
| MethodRecognizer::kFutureListenerHandleValue) { |
| object_ = GetReceiver(); |
| InitializeAwaiterFrameFromFutureListener(object_); |
| UnwindToAwaiter(); |
| return true; // Hide this frame from the stack trace. |
| } |
| |
| return false; |
| } |
| |
| void AsyncAwareStackUnwinder::InitializeAwaiterFrameFromSuspendState() { |
| if (function_.IsAsyncFunction() || function_.IsAsyncGenerator()) { |
| // Check if we reached a resumed asynchronous function. In this case we |
| // are going to start following async frames after we emit this frame. |
| object_ = *reinterpret_cast<ObjectPtr*>(LocalVarAddress( |
| sync_frame_->fp(), runtime_frame_layout.FrameSlotForVariableIndex( |
| SuspendState::kSuspendStateVarIndex))); |
| |
| awaiter_frame_.closure = Closure::null(); |
| if (WasPreviouslySuspended(function_, object_)) { |
| awaiter_frame_.next = object_.ptr(); |
| UnwindToAwaiter(); |
| } |
| } |
| } |
| |
| void AsyncAwareStackUnwinder::UnwindToAwaiter() { |
| do { |
| UnwindAwaiterFrame(); |
| } while (awaiter_frame_.closure.IsNull() && !awaiter_frame_.next.IsNull()); |
| } |
| |
| void AsyncAwareStackUnwinder::UnwindAwaiterFrame() { |
| if (awaiter_frame_.next.IsSuspendState()) { |
| awaiter_frame_.next = |
| SuspendState::Cast(awaiter_frame_.next).function_data(); |
| } else if (awaiter_frame_.next.GetClassId() == _SyncCompleter().id() || |
| awaiter_frame_.next.GetClassId() == _AsyncCompleter().id()) { |
| awaiter_frame_.next = Get_Completer_future(awaiter_frame_.next); |
| } |
| |
| if (awaiter_frame_.next.GetClassId() == _Future().id()) { |
| UnwindFrameToFutureListener(); |
| } else if (awaiter_frame_.next.GetClassId() == |
| _AsyncStarStreamController().id()) { |
| UnwindFrameToStreamListener(); |
| } else { |
| awaiter_frame_.closure = Closure::null(); |
| awaiter_frame_.next = Object::null(); |
| return; |
| } |
| |
| while (!awaiter_frame_.closure.IsNull()) { |
| function_ = awaiter_frame_.closure.function(); |
| context_ = awaiter_frame_.closure.context(); |
| |
| const auto awaiter_link = function_.awaiter_link(); |
| if (awaiter_link.depth != ClosureData::kNoAwaiterLinkDepth) { |
| intptr_t depth = awaiter_link.depth; |
| while (depth-- > 0) { |
| context_ = context_.parent(); |
| } |
| |
| const Object& object = Object::Handle(context_.At(awaiter_link.index)); |
| if (object.IsClosure()) { |
| awaiter_frame_.closure ^= object.ptr(); |
| continue; |
| } else { |
| awaiter_frame_.next = object.ptr(); |
| return; |
| } |
| } |
| break; |
| } |
| } |
| |
| void AsyncAwareStackUnwinder::UnwindFrameToFutureListener() { |
| object_ = Get_Future__resultOrListeners(awaiter_frame_.next); |
| if (object_.GetClassId() == _FutureListener().id()) { |
| InitializeAwaiterFrameFromFutureListener(object_); |
| } else { |
| awaiter_frame_.closure = Closure::null(); |
| awaiter_frame_.next = Object::null(); |
| } |
| } |
| |
| void AsyncAwareStackUnwinder::InitializeAwaiterFrameFromFutureListener( |
| const Object& listener) { |
| if (listener.GetClassId() != _FutureListener().id()) { |
| awaiter_frame_.closure = Closure::null(); |
| awaiter_frame_.next = Object::null(); |
| return; |
| } |
| |
| const auto state = |
| Smi::Value(Smi::RawCast(Get_FutureListener_state(listener))); |
| if (state == k_FutureListener_stateThen || |
| state == k_FutureListener_stateCatchError || |
| state == k_FutureListener_stateWhenComplete || |
| state == |
| (k_FutureListener_stateThen | k_FutureListener_stateCatchError) || |
| state == (k_FutureListener_stateThen | k_FutureListener_stateCatchError | |
| k_FutureListener_maskAwait)) { |
| awaiter_frame_.next = Get_FutureListener_result(listener); |
| } else { |
| awaiter_frame_.next = Object::null(); |
| } |
| awaiter_frame_.closure = |
| Closure::RawCast(Get_FutureListener_callback(listener)); |
| |
| // If the Future has catchError callback attached through either |
| // `Future.catchError` or `Future.then(..., onError: ...)` then we should |
| // treat this listener as a catch all exception handler. However we should |
| // ignore the case when these callbacks are simply forwarding errors into a |
| // suspended async function, because it will be represented by its own async |
| // frame. |
| if ((state & |
| (k_FutureListener_stateCatchError | k_FutureListener_maskAwait)) == |
| k_FutureListener_stateCatchError) { |
| encountered_async_catch_error_ = true; |
| } |
| } |
| |
| void AsyncAwareStackUnwinder::UnwindFrameToStreamListener() { |
| controller_ = Get_AsyncStarStreamController_controller(awaiter_frame_.next); |
| |
| // Clear the frame. |
| awaiter_frame_.closure = Closure::null(); |
| awaiter_frame_.next = Object::null(); |
| |
| const auto state = |
| Smi::Value(Smi::RawCast(Get_StreamController__state(controller_))); |
| if (state != k_StreamController__STATE_SUBSCRIBED) { |
| return; |
| } |
| |
| subscription_ = Get_StreamController__varData(controller_); |
| closure_ ^= Get_BufferingStreamSubscription__onData(subscription_); |
| |
| // If this is not the "_StreamIterator._onData" tear-off, we return the |
| // callback we found. |
| function_ = closure_.function(); |
| if (!function_.IsImplicitInstanceClosureFunction() || |
| function_.Owner() != _StreamIterator().ptr()) { |
| awaiter_frame_.closure = closure_.ptr(); |
| return; |
| } |
| |
| // All implicit closure functions (tear-offs) have the "this" receiver |
| // captured. |
| context_ = closure_.context(); |
| ASSERT(context_.num_variables() == 1); |
| stream_iterator_ = context_.At(0); |
| ASSERT(stream_iterator_.IsInstance()); |
| |
| if (stream_iterator_.GetClassId() != _StreamIterator().id()) { |
| UNREACHABLE(); |
| } |
| |
| // If `_hasValue` is true then the `StreamIterator._stateData` field |
| // contains the iterator's value. In that case we cannot unwind anymore. |
| // |
| // Notice: With correct async* semantics this may never be true: The async* |
| // generator should only be invoked to produce a value if there's an |
| // in-progress `await streamIterator.moveNext()` call. Once such call has |
| // finished the async* generator should be paused/yielded until the next |
| // such call - and being paused/yielded means it should not appear in stack |
| // traces. |
| // |
| // See dartbug.com/48695. |
| if (Get_StreamIterator__hasValue(stream_iterator_) == |
| Object::bool_true().ptr()) { |
| return; |
| } |
| |
| // If we have an await'er for `await streamIterator.moveNext()` we continue |
| // unwinding there. |
| // |
| // Notice: With correct async* semantics this may always contain a Future |
| // See also comment above as well as dartbug.com/48695. |
| object_ = Get_StreamIterator__stateData(stream_iterator_); |
| if (object_.GetClassId() == _Future().id()) { |
| awaiter_frame_.next = object_.ptr(); |
| } |
| } |
| |
| } // namespace |
| |
| bool StackTraceUtils::IsNeededForAsyncAwareUnwinding(const Function& function) { |
| // If this is a closure function which specifies an awaiter-link then |
| // we need to retain both function and the corresponding code. |
| if (function.HasAwaiterLink()) { |
| return true; |
| } |
| |
| if (function.recognized_kind() == |
| MethodRecognizer::kFutureListenerHandleValue) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void StackTraceUtils::CollectFrames( |
| Thread* thread, |
| int skip_frames, |
| const std::function<void(const StackTraceUtils::Frame&)>& handle_frame, |
| bool* has_async_catch_error /* = null */) { |
| AsyncAwareStackUnwinder it(thread); |
| it.Unwind(skip_frames, handle_frame); |
| if (has_async_catch_error != nullptr) { |
| *has_async_catch_error = it.encountered_async_catch_error(); |
| } |
| } |
| |
| bool StackTraceUtils::GetSuspendState(const Closure& closure, |
| Object* suspend_state) { |
| const Function& function = Function::Handle(closure.function()); |
| const auto awaiter_link = function.awaiter_link(); |
| if (awaiter_link.depth != ClosureData::kNoAwaiterLinkDepth) { |
| Context& context = Context::Handle(closure.context()); |
| intptr_t depth = awaiter_link.depth; |
| while (depth-- > 0) { |
| context = context.parent(); |
| } |
| |
| const Object& link = Object::Handle(context.At(awaiter_link.index)); |
| if (link.IsSuspendState()) { |
| *suspend_state = link.ptr(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace dart |