blob: 01818576f1d35e72076e300d339cea5265c6e7bb [file] [log] [blame]
// 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_)),
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_)) {
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_;
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_;
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::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