| // 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 false; // Do not hide this 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 |