// 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/compiler/frontend/bytecode_reader.h" #include "vm/dart_api_impl.h" #include "vm/stack_frame.h" #include "vm/symbols.h" namespace dart { // Keep in sync with // sdk/lib/async/stream_controller.dart:_StreamController._STATE_SUBSCRIBED. const intptr_t kStreamController_StateSubscribed = 1; RawClosure* FindClosureInFrame(RawObject** last_object_in_caller, const Function& function, bool is_interpreted) { NoSafepointScope nsp; // The callee has function signature // :async_op([result, exception, stack]) // So we are guaranteed to // a) have only tagged arguments on the stack until we find the :async_op // closure, and // b) find the async closure. auto& closure = Closure::Handle(); for (intptr_t i = 0; i < 4; i++) { // KBC builds the stack upwards instead of the usual downwards stack. RawObject* arg = last_object_in_caller[(is_interpreted ? -i : i)]; if (arg->IsHeapObject() && arg->GetClassId() == kClosureCid) { closure = Closure::RawCast(arg); if (closure.function() == function.raw()) { return closure.raw(); } } } UNREACHABLE(); } // Find current yield index from async closure. // Async closures contains a variable, :await_jump_var that holds the index into // async wrapper. intptr_t GetYieldIndex(const Closure& receiver_closure) { const auto& function = Function::Handle(receiver_closure.function()); if (!function.IsAsyncClosure() && !function.IsAsyncGenClosure()) { return RawPcDescriptors::kInvalidYieldIndex; } const auto& await_jump_var = Object::Handle(Context::Handle(receiver_closure.context()) .At(Context::kAwaitJumpVarIndex)); ASSERT(await_jump_var.IsSmi()); return Smi::Cast(await_jump_var).Value(); } intptr_t FindPcOffset(const PcDescriptors& pc_descs, intptr_t yield_index) { if (yield_index == RawPcDescriptors::kInvalidYieldIndex) { return 0; } PcDescriptors::Iterator iter(pc_descs, RawPcDescriptors::kAnyKind); while (iter.MoveNext()) { if (iter.YieldIndex() == yield_index) { return iter.PcOffset(); } } UNREACHABLE(); // If we cannot find it we have a bug. } #if !defined(DART_PRECOMPILED_RUNTIME) intptr_t FindPcOffset(const Bytecode& bytecode, intptr_t yield_index) { if (yield_index == RawPcDescriptors::kInvalidYieldIndex) { return 0; } if (!bytecode.HasSourcePositions()) { return 0; } intptr_t last_yield_point = 0; kernel::BytecodeSourcePositionsIterator iter(Thread::Current()->zone(), bytecode); while (iter.MoveNext()) { if (iter.IsYieldPoint()) { last_yield_point++; } if (last_yield_point == yield_index) { return iter.PcOffset(); } } UNREACHABLE(); // If we cannot find it we have a bug. } #endif // Helper class for finding the closure of the caller. // This is done via the _AsyncAwaitCompleter which holds a // FutureResultOrListeners which in turn holds a callback. class CallerClosureFinder { public: // Instance caches library and field references. // This way we don't have to do the look-ups for every frame in the stack. explicit CallerClosureFinder(Zone* zone) : receiver_context_(Context::Handle(zone)), receiver_function_(Function::Handle(zone)), context_entry_(Object::Handle(zone)), is_sync(Object::Handle(zone)), future_(Object::Handle(zone)), listener_(Object::Handle(zone)), callback_(Object::Handle(zone)), controller_(Object::Handle(zone)), state_(Object::Handle(zone)), var_data_(Object::Handle(zone)), callback_instance_(Object::Handle(zone)), future_impl_class(Class::Handle(zone)), async_await_completer_class(Class::Handle(zone)), future_listener_class(Class::Handle(zone)), async_start_stream_controller_class(Class::Handle(zone)), stream_controller_class(Class::Handle(zone)), async_stream_controller_class(Class::Handle(zone)), controller_subscription_class(Class::Handle(zone)), buffering_stream_subscription_class(Class::Handle(zone)), stream_iterator_class(Class::Handle(zone)), completer_is_sync_field(Field::Handle(zone)), completer_future_field(Field::Handle(zone)), future_result_or_listeners_field(Field::Handle(zone)), callback_field(Field::Handle(zone)), controller_controller_field(Field::Handle(zone)), var_data_field(Field::Handle(zone)), state_field(Field::Handle(zone)), on_data_field(Field::Handle(zone)), state_data_field(Field::Handle(zone)) { const auto& async_lib = Library::Handle(zone, Library::AsyncLibrary()); // Look up classes: // - async: future_impl_class = async_lib.LookupClassAllowPrivate(Symbols::FutureImpl()); ASSERT(!future_impl_class.IsNull()); async_await_completer_class = async_lib.LookupClassAllowPrivate(Symbols::_AsyncAwaitCompleter()); ASSERT(!async_await_completer_class.IsNull()); future_listener_class = async_lib.LookupClassAllowPrivate(Symbols::_FutureListener()); ASSERT(!future_listener_class.IsNull()); // - async*: async_start_stream_controller_class = async_lib.LookupClassAllowPrivate( Symbols::_AsyncStarStreamController()); ASSERT(!async_start_stream_controller_class.IsNull()); stream_controller_class = async_lib.LookupClassAllowPrivate(Symbols::_StreamController()); ASSERT(!stream_controller_class.IsNull()); async_stream_controller_class = async_lib.LookupClassAllowPrivate(Symbols::_AsyncStreamController()); ASSERT(!async_stream_controller_class.IsNull()); controller_subscription_class = async_lib.LookupClassAllowPrivate(Symbols::_ControllerSubscription()); ASSERT(!controller_subscription_class.IsNull()); buffering_stream_subscription_class = async_lib.LookupClassAllowPrivate( Symbols::_BufferingStreamSubscription()); ASSERT(!buffering_stream_subscription_class.IsNull()); stream_iterator_class = async_lib.LookupClassAllowPrivate(Symbols::_StreamIterator()); ASSERT(!stream_iterator_class.IsNull()); // Look up fields: // - async: completer_is_sync_field = async_await_completer_class.LookupFieldAllowPrivate(Symbols::isSync()); ASSERT(!completer_is_sync_field.IsNull()); completer_future_field = async_await_completer_class.LookupFieldAllowPrivate(Symbols::_future()); ASSERT(!completer_future_field.IsNull()); future_result_or_listeners_field = future_impl_class.LookupFieldAllowPrivate( Symbols::_resultOrListeners()); ASSERT(!future_result_or_listeners_field.IsNull()); callback_field = future_listener_class.LookupFieldAllowPrivate(Symbols::callback()); ASSERT(!callback_field.IsNull()); // - async*: controller_controller_field = async_start_stream_controller_class.LookupFieldAllowPrivate( Symbols::controller()); ASSERT(!controller_controller_field.IsNull()); state_field = stream_controller_class.LookupFieldAllowPrivate(Symbols::_state()); ASSERT(!state_field.IsNull()); var_data_field = stream_controller_class.LookupFieldAllowPrivate(Symbols::_varData()); ASSERT(!var_data_field.IsNull()); on_data_field = buffering_stream_subscription_class.LookupFieldAllowPrivate( Symbols::_onData()); ASSERT(!on_data_field.IsNull()); state_data_field = stream_iterator_class.LookupFieldAllowPrivate(Symbols::_stateData()); ASSERT(!state_data_field.IsNull()); } RawClosure* GetCallerInFutureImpl(const Object& future_) { ASSERT(!future_.IsNull()); ASSERT(future_.GetClassId() == future_impl_class.id()); listener_ = Instance::Cast(future_).GetField(future_result_or_listeners_field); if (listener_.GetClassId() != future_listener_class.id()) { return Closure::null(); } callback_ = Instance::Cast(listener_).GetField(callback_field); // This happens for e.g.: await f().catchError(..); if (callback_.IsNull()) { return Closure::null(); } ASSERT(callback_.IsClosure()); return Closure::Cast(callback_).raw(); } RawClosure* FindCallerInAsyncClosure(const Context& receiver_context) { context_entry_ = receiver_context.At(Context::kAsyncCompleterIndex); ASSERT(context_entry_.IsInstance()); ASSERT(context_entry_.GetClassId() == async_await_completer_class.id()); const Instance& completer = Instance::Cast(context_entry_); future_ = completer.GetField(completer_future_field); return GetCallerInFutureImpl(future_); } RawClosure* FindCallerInAsyncGenClosure(const Context& receiver_context) { context_entry_ = receiver_context.At(Context::kControllerIndex); ASSERT(context_entry_.IsInstance()); ASSERT(context_entry_.GetClassId() == async_start_stream_controller_class.id()); const Instance& controller = Instance::Cast(context_entry_); controller_ = controller.GetField(controller_controller_field); ASSERT(!controller_.IsNull()); ASSERT(controller_.GetClassId() == async_stream_controller_class.id()); state_ = Instance::Cast(controller_).GetField(state_field); ASSERT(state_.IsSmi()); if (Smi::Cast(state_).Value() != kStreamController_StateSubscribed) { return Closure::null(); } // _StreamController._varData var_data_ = Instance::Cast(controller_).GetField(var_data_field); ASSERT(var_data_.GetClassId() == controller_subscription_class.id()); // _ControllerSubscription/_BufferingStreamSubscription._onData callback_ = Instance::Cast(var_data_).GetField(on_data_field); ASSERT(callback_.IsClosure()); // If this is not the "_StreamIterator._onData" tear-off, we return the // callback we found. receiver_function_ = Closure::Cast(callback_).function(); if (!receiver_function_.IsImplicitInstanceClosureFunction() || receiver_function_.Owner() != stream_iterator_class.raw()) { return Closure::Cast(callback_).raw(); } // All implicit closure functions (tear-offs) have the "this" receiver // captured. receiver_context_ = Closure::Cast(callback_).context(); ASSERT(receiver_context_.num_variables() == 1); callback_instance_ = receiver_context_.At(0); ASSERT(callback_instance_.IsInstance()); // If the async* stream is await-for'd: if (callback_instance_.GetClassId() == stream_iterator_class.id()) { // _StreamIterator._stateData future_ = Instance::Cast(callback_instance_).GetField(state_data_field); return GetCallerInFutureImpl(future_); } UNREACHABLE(); // If no onData is found we have a bug. } RawClosure* FindCaller(const Closure& receiver_closure) { receiver_function_ = receiver_closure.function(); receiver_context_ = receiver_closure.context(); if (receiver_function_.IsAsyncClosure()) { return FindCallerInAsyncClosure(receiver_context_); } else if (receiver_function_.IsAsyncGenClosure()) { return FindCallerInAsyncGenClosure(receiver_context_); } return Closure::null(); } bool IsRunningAsync(const Closure& receiver_closure) { receiver_function_ = receiver_closure.function(); receiver_context_ = receiver_closure.context(); // The async* functions are never started synchronously, they start running // after the first `listen()` call to its returned `Stream`. if (receiver_function_.IsAsyncGenClosure()) { return true; } ASSERT(receiver_function_.IsAsyncClosure()); context_entry_ = receiver_context_.At(Context::kAsyncCompleterIndex); ASSERT(context_entry_.IsInstance()); ASSERT(context_entry_.GetClassId() == async_await_completer_class.id()); const Instance& completer = Instance::Cast(context_entry_); is_sync = completer.GetField(completer_is_sync_field); ASSERT(!is_sync.IsNull()); ASSERT(is_sync.IsBool()); // _AsyncAwaitCompleter.isSync indicates whether the future should be // completed async. or sync., based on whether it has yielded yet. // isSync is true when the :async_op is running async. return Bool::Cast(is_sync).value(); } private: Context& receiver_context_; Function& receiver_function_; Object& context_entry_; Object& is_sync; Object& future_; Object& listener_; Object& callback_; Object& controller_; Object& state_; Object& var_data_; Object& callback_instance_; Class& future_impl_class; Class& async_await_completer_class; Class& future_listener_class; Class& async_start_stream_controller_class; Class& stream_controller_class; Class& async_stream_controller_class; Class& controller_subscription_class; Class& buffering_stream_subscription_class; Class& stream_iterator_class; Field& completer_is_sync_field; Field& completer_future_field; Field& future_result_or_listeners_field; Field& callback_field; Field& controller_controller_field; Field& var_data_field; Field& state_field; Field& on_data_field; Field& state_data_field; }; void StackTraceUtils::CollectFramesLazy( Thread* thread, const GrowableObjectArray& code_array, const GrowableObjectArray& pc_offset_array, int skip_frames, std::function* on_sync_frames, bool* has_async) { if (has_async != nullptr) { *has_async = false; } Zone* zone = thread->zone(); DartFrameIterator frames(thread, StackFrameIterator::kNoCrossThreadIteration); StackFrame* frame = frames.NextFrame(); // If e.g. the isolate is paused before executing anything, we might not get // any frames at all. Bail: if (frame == nullptr) { return; } auto& function = Function::Handle(zone); auto& code = Code::Handle(zone); auto& bytecode = Bytecode::Handle(zone); auto& offset = Smi::Handle(zone); auto& closure = Closure::Handle(zone); CallerClosureFinder caller_closure_finder(zone); auto& pc_descs = PcDescriptors::Handle(); for (; frame != nullptr; frame = frames.NextFrame()) { if (skip_frames > 0) { skip_frames--; continue; } if (frame->is_interpreted()) { bytecode = frame->LookupDartBytecode(); ASSERT(!bytecode.IsNull()); function = bytecode.function(); if (function.IsNull()) { continue; } RELEASE_ASSERT(function.raw() == frame->LookupDartFunction()); } else { function = frame->LookupDartFunction(); } // Add the current synchronous frame. if (frame->is_interpreted()) { code_array.Add(bytecode); const intptr_t pc_offset = frame->pc() - bytecode.PayloadStart(); ASSERT(pc_offset >= 0 && pc_offset <= bytecode.Size()); offset = Smi::New(pc_offset); } else { code = frame->LookupDartCode(); ASSERT(function.raw() == code.function()); code_array.Add(code); offset = Smi::New(frame->pc() - code.PayloadStart()); } pc_offset_array.Add(offset); if (on_sync_frames != nullptr) { (*on_sync_frames)(frame); } // Either continue the loop (sync-async case) or find all await'ers and // return. if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) { if (has_async != nullptr) { *has_async = true; } // Next, look up caller's closure on the stack and walk backwards through // the yields. RawObject** last_caller_obj = reinterpret_cast( frame->GetCallerSp()); closure = FindClosureInFrame(last_caller_obj, function, frame->is_interpreted()); // If this async function hasn't yielded yet, we're still dealing with a // normal stack. Continue to next frame as usual. if (!caller_closure_finder.IsRunningAsync(closure)) { continue; } // Inject async suspension marker. code_array.Add(StubCode::AsynchronousGapMarker()); offset = Smi::New(0); pc_offset_array.Add(offset); // Skip: Already handled this frame's function above. closure = caller_closure_finder.FindCaller(closure); for (; !closure.IsNull(); closure = caller_closure_finder.FindCaller(closure)) { function = closure.function(); // In hot-reload-test-mode we sometimes have to do this: if (!function.HasCode() && !function.HasBytecode()) { function.EnsureHasCode(); } if (function.HasBytecode()) { #if !defined(DART_PRECOMPILED_RUNTIME) bytecode = function.bytecode(); code_array.Add(bytecode); offset = Smi::New(FindPcOffset(bytecode, GetYieldIndex(closure))); #else UNREACHABLE(); #endif // !defined(DART_PRECOMPILED_RUNTIME) } else if (function.HasCode()) { code = function.CurrentCode(); code_array.Add(code); pc_descs = code.pc_descriptors(); offset = Smi::New(FindPcOffset(pc_descs, GetYieldIndex(closure))); } else { UNREACHABLE(); } ASSERT(offset.Value() >= 0); pc_offset_array.Add(offset); // Inject async suspension marker. code_array.Add(StubCode::AsynchronousGapMarker()); offset = Smi::New(0); pc_offset_array.Add(offset); } // Ignore the rest of the stack; already unwound all async calls. return; } } return; } // Count the number of frames that are on the stack. intptr_t StackTraceUtils::CountFrames(Thread* thread, int skip_frames, const Function& async_function, bool* sync_async_end) { Zone* zone = thread->zone(); intptr_t frame_count = 0; StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread, StackFrameIterator::kNoCrossThreadIteration); StackFrame* frame = frames.NextFrame(); ASSERT(frame != NULL); // We expect to find a dart invocation frame. Function& function = Function::Handle(zone); Code& code = Code::Handle(zone); Bytecode& bytecode = Bytecode::Handle(zone); String& function_name = String::Handle(zone); const bool async_function_is_null = async_function.IsNull(); int sync_async_gap_frames = -1; ASSERT(async_function_is_null || sync_async_end != NULL); for (; frame != NULL && sync_async_gap_frames != 0; frame = frames.NextFrame()) { if (!frame->IsDartFrame()) { continue; } if (skip_frames > 0) { skip_frames--; continue; } if (frame->is_interpreted()) { bytecode = frame->LookupDartBytecode(); function = bytecode.function(); if (function.IsNull()) { continue; } } else { code = frame->LookupDartCode(); function = code.function(); } if (sync_async_gap_frames > 0) { function_name = function.QualifiedScrubbedName(); if (!CheckAndSkipAsync(&sync_async_gap_frames, function_name)) { *sync_async_end = false; return frame_count; } } else { frame_count++; } if (!async_function_is_null && (async_function.raw() == function.parent_function())) { sync_async_gap_frames = kSyncAsyncFrameGap; } } if (!async_function_is_null) { *sync_async_end = sync_async_gap_frames == 0; } return frame_count; } intptr_t StackTraceUtils::CollectFrames(Thread* thread, const Array& code_array, const Array& pc_offset_array, intptr_t array_offset, intptr_t count, int skip_frames) { Zone* zone = thread->zone(); StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread, StackFrameIterator::kNoCrossThreadIteration); StackFrame* frame = frames.NextFrame(); ASSERT(frame != NULL); // We expect to find a dart invocation frame. Function& function = Function::Handle(zone); Code& code = Code::Handle(zone); Bytecode& bytecode = Bytecode::Handle(zone); Smi& offset = Smi::Handle(zone); intptr_t collected_frames_count = 0; for (; (frame != NULL) && (collected_frames_count < count); frame = frames.NextFrame()) { if (!frame->IsDartFrame()) { continue; } if (skip_frames > 0) { skip_frames--; continue; } if (frame->is_interpreted()) { bytecode = frame->LookupDartBytecode(); function = bytecode.function(); if (function.IsNull()) { continue; } offset = Smi::New(frame->pc() - bytecode.PayloadStart()); code_array.SetAt(array_offset, bytecode); } else { code = frame->LookupDartCode(); offset = Smi::New(frame->pc() - code.PayloadStart()); code_array.SetAt(array_offset, code); } pc_offset_array.SetAt(array_offset, offset); array_offset++; collected_frames_count++; } return collected_frames_count; } intptr_t StackTraceUtils::ExtractAsyncStackTraceInfo( Thread* thread, Function* async_function, StackTrace* async_stack_trace_out, Array* async_code_array, Array* async_pc_offset_array) { if (thread->async_stack_trace() == StackTrace::null()) { return 0; } *async_stack_trace_out = thread->async_stack_trace(); ASSERT(!async_stack_trace_out->IsNull()); const StackTrace& async_stack_trace = StackTrace::Handle(thread->async_stack_trace()); const intptr_t async_stack_trace_length = async_stack_trace.Length(); // At least two entries (0: gap marker, 1: async function). ASSERT(async_stack_trace_length >= 2); // Validate the structure of this stack trace. *async_code_array = async_stack_trace.code_array(); ASSERT(!async_code_array->IsNull()); *async_pc_offset_array = async_stack_trace.pc_offset_array(); ASSERT(!async_pc_offset_array->IsNull()); // We start with the asynchronous gap marker. ASSERT(async_code_array->At(0) != Code::null()); ASSERT(async_code_array->At(0) == StubCode::AsynchronousGapMarker().raw()); const Object& code_object = Object::Handle(async_code_array->At(1)); if (code_object.IsCode()) { *async_function = Code::Cast(code_object).function(); } else { ASSERT(code_object.IsBytecode()); *async_function = Bytecode::Cast(code_object).function(); } ASSERT(!async_function->IsNull()); ASSERT(async_function->IsAsyncFunction() || async_function->IsAsyncGenerator()); return async_stack_trace_length; } } // namespace dart