| // 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: |
| // - _StreamController._STATE_SUBSCRIBED. |
| const intptr_t k_StreamController__STATE_SUBSCRIBED = 1; |
| // - _StreamController._STATE_SUBSCRIPTION_MASK. |
| const intptr_t k_StreamController__STATE_SUBSCRIPTION_MASK = 3; |
| // - _StreamController._STATE_ADDSTREAM. |
| const intptr_t k_StreamController__STATE_ADDSTREAM = 8; |
| // - _BufferingStreamSubscription._STATE_HAS_ERROR_HANDLER. |
| const intptr_t k_BufferingStreamSubscription__STATE_HAS_ERROR_HANDLER = 1 << 5; |
| // - _Future._stateIgnoreError |
| const intptr_t k_Future__stateIgnoreError = 1; |
| // - _FutureListener.stateThen. |
| const intptr_t k_FutureListener_stateThen = 1; |
| // - _FutureListener.stateCatchError. |
| const intptr_t k_FutureListener_stateCatchError = 2; |
| // - _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(); |
| } |
| } |
| |
| bool TryGetAwaiterLink(const Closure& closure, Object* link) { |
| *link = Object::null(); |
| const auto& function = Function::Handle(closure.function()); |
| const auto awaiter_link = function.awaiter_link(); |
| if (awaiter_link.depth != ClosureData::kNoAwaiterLinkDepth) { |
| if (function.IsImplicitClosureFunction()) { |
| ASSERT(awaiter_link.depth == 0 && awaiter_link.index == 0); |
| *link = closure.GetImplicitClosureReceiver(); |
| return true; |
| } |
| |
| auto& context = Context::Handle(closure.GetContext()); |
| intptr_t depth = awaiter_link.depth; |
| while (depth-- > 0) { |
| context = context.parent(); |
| } |
| |
| *link = context.At(awaiter_link.index); |
| return true; |
| } |
| return false; |
| } |
| |
| // 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, |
| bool* encountered_async_catch_error) |
| : zone_(thread->zone()), |
| sync_frames_(thread, StackFrameIterator::kNoCrossThreadIteration), |
| sync_frame_(nullptr), |
| awaiter_frame_{Closure::Handle(zone_), Object::Handle(zone_)}, |
| encountered_async_catch_error_(encountered_async_catch_error), |
| closure_(Closure::Handle(zone_)), |
| code_(Code::Handle(zone_)), |
| bytecode_(Bytecode::Handle(zone_)), |
| context_(Context::Handle(zone_)), |
| function_(Function::Handle(zone_)), |
| parent_function_(Function::Handle(zone_)), |
| object_(Object::Handle(zone_)), |
| result_future_(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_)), |
| null_code_(Code::Handle(zone_)), |
| null_bytecode_(Bytecode::Handle(zone_)) { |
| if (encountered_async_catch_error_ != nullptr) { |
| *encountered_async_catch_error_ = false; |
| } |
| } |
| |
| void Unwind(intptr_t skip_frames, |
| std::function<void(const StackTraceUtils::Frame&)> handle_frame); |
| |
| 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 |_SyncStreamController| instance. |
| void UnwindFrameToStreamController(); |
| |
| // Follow awaiter-links from the given closure until the first |
| // non-closure awaiter-link is encountered. |
| // |
| // |link| will contain the first non-closure awaiter-link or `null`. |
| // |closure| will be updated to point to the last closure in the chain. |
| void FollowAwaiterLinks(Closure* closure, Object* link); |
| |
| // Update |awaiter_frame| by unwinding awaiter links of the current closure. |
| void ComputeNextFrameFromAwaiterLink(); |
| |
| ObjectPtr GetReceiver() const; |
| |
| // Returns |true| if propagating an error to the listeners of this `_Future` |
| // will always encounter an error handler. Future handles error iff: |
| // |
| // * It has no listeners and is marked as ignoring errors |
| // * All of its listeners either have an error handler or corresponding |
| // result future handles error. |
| // |
| // Note: this ignores simple error forwarding/rethrow which occurs in `await` |
| // or patterns like `fut.then(onError: c.completeError)`. |
| bool WillFutureHandleError(const Object& future, intptr_t depth = 0); |
| |
| void MarkAsHandlingAsyncError() const { |
| if (ShouldComputeIfAsyncErrorIsHandled()) { |
| *encountered_async_catch_error_ = true; |
| } |
| } |
| |
| bool ShouldComputeIfAsyncErrorIsHandled() const { |
| return encountered_async_catch_error_ != nullptr && |
| !*encountered_async_catch_error_; |
| } |
| |
| #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) \ |
| V(_StreamControllerAddStreamState) \ |
| V(_AddStreamState) |
| |
| enum ClassId { |
| #define DECLARE_CONSTANT(symbol) k##symbol, |
| USED_CLASS_LIST(DECLARE_CONSTANT) |
| #undef DECLARE_CONSTANT |
| }; |
| |
| #define USED_FIELD_LIST(V) \ |
| V(_AsyncStarStreamController, asyncStarBody) \ |
| V(_AsyncStarStreamController, controller) \ |
| V(_BufferingStreamSubscription, _onData) \ |
| V(_BufferingStreamSubscription, _onDone) \ |
| V(_BufferingStreamSubscription, _onError) \ |
| V(_BufferingStreamSubscription, _state) \ |
| V(_Completer, future) \ |
| V(_Future, _resultOrListeners) \ |
| V(_Future, _state) \ |
| V(_FutureListener, callback) \ |
| V(_FutureListener, result) \ |
| V(_FutureListener, state) \ |
| V(_FutureListener, _nextListener) \ |
| V(_StreamController, _state) \ |
| V(_StreamController, _varData) \ |
| V(_StreamControllerAddStreamState, _varData) \ |
| V(_StreamIterator, _hasValue) \ |
| V(_StreamIterator, _stateData) \ |
| V(_AddStreamState, addStreamFuture) |
| |
| 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_; |
| |
| Closure& closure_; |
| Code& code_; |
| Bytecode& bytecode_; |
| Context& context_; |
| Function& function_; |
| Function& parent_function_; |
| Object& object_; |
| Object& result_future_; |
| SuspendState& suspend_state_; |
| |
| Object& controller_; |
| Object& subscription_; |
| Object& stream_iterator_; |
| |
| const Library& async_lib_; |
| Class* classes_[kUsedClassCount] = {}; |
| Field* fields_[kUsedFieldCount] = {}; |
| |
| const Closure& null_closure_; |
| const Code& null_code_; |
| const Bytecode& null_bytecode_; |
| |
| 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) { |
| if (sync_frame_->is_interpreted()) { |
| bytecode_ = sync_frame_->LookupDartBytecode(); |
| if (bytecode_.function() != Function::null()) { |
| const uword pc_offset = sync_frame_->pc() - bytecode_.PayloadStart(); |
| handle_frame( |
| {sync_frame_, null_code_, bytecode_, pc_offset, null_closure_}); |
| } |
| } else { |
| code_ = sync_frame_->LookupDartCode(); |
| const uword pc_offset = sync_frame_->pc() - code_.PayloadStart(); |
| handle_frame( |
| {sync_frame_, code_, null_bytecode_, pc_offset, null_closure_}); |
| } |
| } |
| sync_frame_ = sync_frames_.NextFrame(); |
| } |
| |
| const StackTraceUtils::Frame gap_frame = { |
| nullptr, StubCode::AsynchronousGapMarker(), null_bytecode_, |
| /*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; |
| bytecode_ = Bytecode::null(); |
| 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(); |
| |
| #if defined(DART_DYNAMIC_MODULES) |
| if (pc == StubCode::ResumeInterpreter().EntryPoint()) { |
| bytecode_ = Interpreter::Current()->GetSuspendedLocation( |
| SuspendState::Cast(awaiter_frame_.next), &pc_offset); |
| ASSERT(!bytecode_.IsNull()); |
| code_ = Code::null(); |
| } |
| #endif // defined(DART_DYNAMIC_MODULES) |
| } 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). |
| #if defined(DART_DYNAMIC_MODULES) |
| if (function_.HasBytecode()) { |
| bytecode_ = function_.GetBytecode(); |
| code_ = Code::null(); |
| pc_offset = StackTraceUtils::kFutureListenerPcOffset; |
| } else { |
| #endif // defined(DART_DYNAMIC_MODULES) |
| code_ = function_.EnsureHasCode(); |
| RELEASE_ASSERT(!code_.IsNull()); |
| pc_offset = (function_.entry_point() + |
| StackTraceUtils::kFutureListenerPcOffset) - |
| code_.PayloadStart(); |
| #if defined(DART_DYNAMIC_MODULES) |
| } |
| #endif // defined(DART_DYNAMIC_MODULES) |
| } |
| |
| handle_frame(gap_frame); |
| handle_frame( |
| {nullptr, code_, bytecode_, 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() && |
| TryGetAwaiterLink(Closure::Cast(object_), &awaiter_frame_.next)) { |
| if (awaiter_frame_.next.IsSuspendState()) { |
| awaiter_frame_.closure ^= object_.ptr(); |
| return true; // Hide this frame from the stack trace. |
| } else if (awaiter_frame_.next.GetClassId() == _Future().id()) { |
| UnwindFrameToFutureListener(); |
| return false; // Do not hide this 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::FollowAwaiterLinks(Closure* closure, |
| Object* link) { |
| *link = Object::null(); |
| while (!closure->IsNull() && TryGetAwaiterLink(*closure, link) && |
| link->IsClosure()) { |
| *closure ^= link->ptr(); |
| } |
| } |
| |
| void AsyncAwareStackUnwinder::ComputeNextFrameFromAwaiterLink() { |
| FollowAwaiterLinks(&awaiter_frame_.closure, &object_); |
| if (!object_.IsNull()) { |
| awaiter_frame_.next = object_.ptr(); |
| } |
| } |
| |
| 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() == _AsyncStarStreamController().id()) { |
| awaiter_frame_.next = |
| Get_AsyncStarStreamController_controller(awaiter_frame_.next); |
| } |
| |
| if (awaiter_frame_.next.GetClassId() == _Future().id()) { |
| UnwindFrameToFutureListener(); |
| } else if (awaiter_frame_.next.GetClassId() == _SyncStreamController().id()) { |
| UnwindFrameToStreamController(); |
| ComputeNextFrameFromAwaiterLink(); |
| } else { |
| awaiter_frame_.closure = Closure::null(); |
| awaiter_frame_.next = Object::null(); |
| } |
| } |
| |
| void AsyncAwareStackUnwinder::UnwindFrameToFutureListener() { |
| object_ = Get_Future__resultOrListeners(awaiter_frame_.next); |
| if (object_.GetClassId() == _FutureListener().id()) { |
| InitializeAwaiterFrameFromFutureListener(object_); |
| } else { |
| if (ShouldComputeIfAsyncErrorIsHandled()) { |
| // Check if error on this future was ignored through |Future.ignore|. |
| const auto state = |
| Smi::Value(Smi::RawCast(Get_Future__state(awaiter_frame_.next))); |
| if ((state & k_Future__stateIgnoreError) != 0) { |
| MarkAsHandlingAsyncError(); |
| } |
| } |
| |
| awaiter_frame_.closure = Closure::null(); |
| awaiter_frame_.next = Object::null(); |
| } |
| } |
| |
| bool AsyncAwareStackUnwinder::WillFutureHandleError(const Object& future, |
| intptr_t depth /* = 0 */) { |
| if (depth > 100 || future.GetClassId() != _Future().id()) { |
| return true; // Conservative. |
| } |
| |
| if (Get_Future__resultOrListeners(future) == Object::null()) { |
| // No listeners: check if future is simply ignoring errors. |
| const auto state = Smi::Value(Smi::RawCast(Get_Future__state(future))); |
| return (state & k_Future__stateIgnoreError) != 0; |
| } |
| |
| for (auto& listener = Object::Handle(Get_Future__resultOrListeners(future)); |
| listener.GetClassId() == _FutureListener().id(); |
| listener = Get_FutureListener__nextListener(listener)) { |
| const auto state = |
| Smi::Value(Smi::RawCast(Get_FutureListener_state(listener))); |
| if ((state & k_FutureListener_stateCatchError) == 0 && |
| !WillFutureHandleError( |
| Object::Handle(Get_FutureListener_result(listener)), depth + 1)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| 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)) { |
| result_future_ = Get_FutureListener_result(listener); |
| } else { |
| result_future_ = Object::null(); |
| } |
| awaiter_frame_.closure = |
| Closure::RawCast(Get_FutureListener_callback(listener)); |
| awaiter_frame_.next = result_future_.ptr(); |
| |
| ComputeNextFrameFromAwaiterLink(); |
| |
| if (ShouldComputeIfAsyncErrorIsHandled() && !result_future_.IsNull() && |
| result_future_.ptr() != awaiter_frame_.next.ptr()) { |
| // We have unwound through closure rather than followed result future, this |
| // can be caused by unwinding through `await` or code like |
| // `fut.whenComplete(c.complete)`. If the current future does not |
| // catch the error then the error will be forwarded into result_future_. |
| // Check if result_future_ handles it and set |
| // encountered_async_catch_error_ respectively. |
| if ((state & k_FutureListener_stateCatchError) == 0 && |
| WillFutureHandleError(result_future_)) { |
| MarkAsHandlingAsyncError(); |
| } |
| } |
| |
| // 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. |
| // |
| // However if we fail to unwind this frame (e.g. because there is some |
| // Zone wrapping callbacks and obstructing unwinding) then we conservatively |
| // treat this error handler as handling all errors to prevent user |
| // unfriendly situations where debugger stops on handled exceptions. |
| if ((state & k_FutureListener_stateCatchError) != 0 && |
| ((state & k_FutureListener_maskAwait) == 0 || |
| !awaiter_frame_.next.IsSuspendState())) { |
| MarkAsHandlingAsyncError(); |
| } |
| } |
| |
| void AsyncAwareStackUnwinder::UnwindFrameToStreamController() { |
| controller_ = awaiter_frame_.next.ptr(); |
| |
| // 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_SUBSCRIPTION_MASK) != |
| k_StreamController__STATE_SUBSCRIBED) { |
| return; |
| } |
| |
| subscription_ = Get_StreamController__varData(controller_); |
| if ((state & k_StreamController__STATE_ADDSTREAM) != 0) { |
| subscription_ = Get_StreamControllerAddStreamState__varData(subscription_); |
| } |
| |
| closure_ ^= Get_BufferingStreamSubscription__onData(subscription_); |
| |
| const auto subscription_state = Smi::Value( |
| Smi::RawCast(Get_BufferingStreamSubscription__state(subscription_))); |
| |
| const auto has_error_handler = |
| ((subscription_state & |
| k_BufferingStreamSubscription__STATE_HAS_ERROR_HANDLER) != 0); |
| |
| // If this is not the "_StreamIterator._onData" tear-off, we return the |
| // callback we found. |
| function_ = closure_.function(); |
| if (function_.IsImplicitClosureFunction()) { |
| // Handle `await for` case. In this case onData is calling |
| // _StreamIterator._onData which then notifies awaiter by completing |
| // _StreamIterator.moveNextFuture. |
| if (function_.Owner() == _StreamIterator().ptr()) { |
| // All implicit closure functions (tear-offs) have the "this" receiver |
| // captured in the context. |
| stream_iterator_ = closure_.GetImplicitClosureReceiver(); |
| |
| 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()) { |
| if (has_error_handler) { |
| MarkAsHandlingAsyncError(); |
| } |
| 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(); |
| } else { |
| if (has_error_handler) { |
| MarkAsHandlingAsyncError(); |
| } |
| } |
| return; |
| } |
| |
| // Handle `yield*` case. Here one stream controller will be forwarding |
| // to another one. If the destination controller is |
| // _AsyncStarStreamController, then we should make sure to reflect |
| // its asyncStarBody in the stack trace. |
| if (function_.Owner() == _StreamController().ptr()) { |
| // Get the controller to which we are forwarding and check if it is |
| // in the ADD_STREAM state. |
| object_ = closure_.GetImplicitClosureReceiver(); |
| const auto state = |
| Smi::Value(Smi::RawCast(Get_StreamController__state(object_))); |
| if ((state & k_StreamController__STATE_ADDSTREAM) != 0) { |
| // Get to the addStreamFuture - if this is yield* internals we |
| // will be able to get original _AsyncStarStreamController from |
| // handler attached to that future. |
| object_ = Get_StreamController__varData(object_); |
| object_ = Get_AddStreamState_addStreamFuture(object_); |
| object_ = Get_Future__resultOrListeners(object_); |
| if (object_.GetClassId() == _FutureListener().id()) { |
| const auto state = |
| Smi::Value(Smi::RawCast(Get_FutureListener_state(object_))); |
| if (state == k_FutureListener_stateThen) { |
| auto& handler = Closure::Handle( |
| Closure::RawCast(Get_FutureListener_callback(object_))); |
| FollowAwaiterLinks(&handler, &object_); |
| if (object_.GetClassId() == _AsyncStarStreamController().id()) { |
| awaiter_frame_.closure = Closure::RawCast( |
| Get_AsyncStarStreamController_asyncStarBody(object_)); |
| return; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| awaiter_frame_.closure = closure_.ptr(); |
| |
| bool found_awaiter_link_in_sibling_handler = false; |
| |
| if (!function_.HasAwaiterLink()) { |
| // If we don't have awaiter-link in the `onData` handler, try checking |
| // if either onError or onDone have an awaiter link. |
| closure_ ^= Get_BufferingStreamSubscription__onError(subscription_); |
| function_ = closure_.function(); |
| found_awaiter_link_in_sibling_handler = function_.HasAwaiterLink(); |
| } |
| |
| if (!function_.HasAwaiterLink()) { |
| closure_ ^= Get_BufferingStreamSubscription__onDone(subscription_); |
| function_ = closure_.function(); |
| found_awaiter_link_in_sibling_handler = function_.HasAwaiterLink(); |
| } |
| |
| if (has_error_handler || found_awaiter_link_in_sibling_handler) { |
| FollowAwaiterLinks(&closure_, &object_); |
| } |
| |
| if (has_error_handler && object_.GetClassId() != _Future().id() && |
| object_.GetClassId() != _SyncStreamController().id()) { |
| MarkAsHandlingAsyncError(); |
| } |
| |
| if (found_awaiter_link_in_sibling_handler) { |
| if (object_.GetClassId() == _AsyncStarStreamController().id() || |
| object_.GetClassId() == _SyncStreamController().id()) { |
| // We can continue unwinding from here. |
| awaiter_frame_.closure = closure_.ptr(); |
| } else { |
| awaiter_frame_.next = object_.ptr(); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| bool StackTraceUtils::IsPossibleAwaiterLink(const Class& cls) { |
| if (cls.library() != Library::AsyncLibrary()) { |
| return false; |
| } |
| |
| String& class_name = String::Handle(); |
| const auto is_class = [&](const String& name) { |
| class_name = cls.Name(); |
| return String::EqualsIgnoringPrivateKey(class_name, name); |
| }; |
| |
| return is_class(Symbols::_AsyncStarStreamController()) || |
| is_class(Symbols::_SyncStreamController()) || |
| is_class(Symbols::_StreamController()) || |
| is_class(Symbols::_Completer()) || |
| is_class(Symbols::_AsyncCompleter()) || |
| is_class(Symbols::_SyncCompleter()) || is_class(Symbols::_Future()); |
| } |
| |
| 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, has_async_catch_error); |
| it.Unwind(skip_frames, handle_frame); |
| } |
| |
| bool StackTraceUtils::GetSuspendState(const Closure& closure, |
| Object* suspend_state) { |
| if (TryGetAwaiterLink(closure, suspend_state)) { |
| return suspend_state->IsSuspendState(); |
| } |
| return false; |
| } |
| |
| } // namespace dart |