| // Copyright (c) 2012, 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_frame.h" | 
 |  | 
 | #include "platform/memory_sanitizer.h" | 
 | #include "vm/code_descriptors.h" | 
 | #include "vm/compiler/runtime_api.h" | 
 | #include "vm/heap/become.h" | 
 | #include "vm/isolate.h" | 
 | #include "vm/object.h" | 
 | #include "vm/object_store.h" | 
 | #include "vm/os.h" | 
 | #include "vm/parser.h" | 
 | #include "vm/raw_object.h" | 
 | #include "vm/reusable_handles.h" | 
 | #include "vm/reverse_pc_lookup_cache.h" | 
 | #include "vm/scopes.h" | 
 | #include "vm/stub_code.h" | 
 | #include "vm/visitor.h" | 
 |  | 
 | #if !defined(DART_PRECOMPILED_RUNTIME) | 
 | #include "vm/deopt_instructions.h" | 
 | #endif  // !defined(DART_PRECOMPILED_RUNTIME) | 
 |  | 
 | namespace dart { | 
 |  | 
 | const FrameLayout invalid_frame_layout = { | 
 |     /*.first_object_from_fp = */ -1, | 
 |     /*.last_fixed_object_from_fp = */ -1, | 
 |     /*.param_end_from_fp = */ -1, | 
 |     /*.last_param_from_entry_sp = */ -1, | 
 |     /*.first_local_from_fp = */ -1, | 
 |     /*.dart_fixed_frame_size = */ -1, | 
 |     /*.saved_caller_pp_from_fp = */ -1, | 
 |     /*.saved_caller_fp_from_fp = */ -1, | 
 |     /*.saved_caller_pc_from_fp = */ -1, | 
 |     /*.code_from_fp = */ -1, | 
 |     /*.exit_link_slot_from_entry_fp = */ -1, | 
 | }; | 
 |  | 
 | const FrameLayout default_frame_layout = { | 
 |     /*.first_object_from_fp = */ kFirstObjectSlotFromFp, | 
 |     /*.last_fixed_object_from_fp = */ kLastFixedObjectSlotFromFp, | 
 |     /*.param_end_from_fp = */ kParamEndSlotFromFp, | 
 |     /*.last_param_from_entry_sp = */ kLastParamSlotFromEntrySp, | 
 |     /*.first_local_from_fp = */ kFirstLocalSlotFromFp, | 
 |     /*.dart_fixed_frame_size = */ kDartFrameFixedSize, | 
 |     /*.saved_caller_pp_from_fp = */ kSavedCallerPpSlotFromFp, | 
 |     /*.saved_caller_fp_from_fp = */ kSavedCallerFpSlotFromFp, | 
 |     /*.saved_caller_pc_from_fp = */ kSavedCallerPcSlotFromFp, | 
 |     /*.code_from_fp = */ kPcMarkerSlotFromFp, | 
 |     /*.exit_link_slot_from_entry_fp = */ kExitLinkSlotFromEntryFp, | 
 | }; | 
 | const FrameLayout bare_instructions_frame_layout = { | 
 |     /*.first_object_from_pc =*/kFirstObjectSlotFromFp,  // No saved PP slot. | 
 |     /*.last_fixed_object_from_fp = */ kLastFixedObjectSlotFromFp + | 
 |         2,  // No saved CODE, PP slots | 
 |     /*.param_end_from_fp = */ kParamEndSlotFromFp, | 
 |     /*.last_param_from_entry_sp = */ kLastParamSlotFromEntrySp, | 
 |     /*.first_local_from_fp =*/kFirstLocalSlotFromFp + | 
 |         2,  // No saved CODE, PP slots. | 
 |     /*.dart_fixed_frame_size =*/kDartFrameFixedSize - | 
 |         2,                              // No saved CODE, PP slots. | 
 |     /*.saved_caller_pp_from_fp = */ 0,  // No saved PP slot. | 
 |     /*.saved_caller_fp_from_fp = */ kSavedCallerFpSlotFromFp, | 
 |     /*.saved_caller_pc_from_fp = */ kSavedCallerPcSlotFromFp, | 
 |     /*.code_from_fp = */ 0,  // No saved CODE | 
 |     /*.exit_link_slot_from_entry_fp = */ kExitLinkSlotFromEntryFp, | 
 | }; | 
 |  | 
 | namespace compiler { | 
 |  | 
 | namespace target { | 
 | FrameLayout frame_layout = invalid_frame_layout; | 
 | } | 
 |  | 
 | }  // namespace compiler | 
 |  | 
 | FrameLayout runtime_frame_layout = invalid_frame_layout; | 
 |  | 
 | int FrameLayout::FrameSlotForVariable(const LocalVariable* variable) const { | 
 |   ASSERT(!variable->is_captured()); | 
 |   return this->FrameSlotForVariableIndex(variable->index().value()); | 
 | } | 
 |  | 
 | int FrameLayout::FrameSlotForVariableIndex(int variable_index) const { | 
 |   // Variable indices are: | 
 |   //    [1, 2, ..., M] for the M parameters. | 
 |   //    [0, -1, -2, ... -(N-1)] for the N [LocalVariable]s | 
 |   // See (runtime/vm/scopes.h) | 
 |   return variable_index <= 0 ? (variable_index + first_local_from_fp) | 
 |                              : (variable_index + param_end_from_fp); | 
 | } | 
 |  | 
 | void FrameLayout::Init() { | 
 |   // By default we use frames with CODE_REG/PP in the frame. | 
 |   compiler::target::frame_layout = default_frame_layout; | 
 |   runtime_frame_layout = default_frame_layout; | 
 |  | 
 |   if (FLAG_precompiled_mode) { | 
 |     compiler::target::frame_layout = bare_instructions_frame_layout; | 
 |   } | 
 | #if defined(DART_PRECOMPILED_RUNTIME) | 
 |   if (FLAG_precompiled_mode) { | 
 |     compiler::target::frame_layout = invalid_frame_layout; | 
 |     runtime_frame_layout = bare_instructions_frame_layout; | 
 |   } | 
 | #endif | 
 | } | 
 |  | 
 | bool StackFrame::IsBareInstructionsDartFrame() const { | 
 |   if (!FLAG_precompiled_mode) { | 
 |     return false; | 
 |   } | 
 |   NoSafepointScope no_safepoint; | 
 |  | 
 |   Code code; | 
 |   code = ReversePc::Lookup(this->isolate_group(), pc(), | 
 |                            /*is_return_address=*/true); | 
 |   if (!code.IsNull()) { | 
 |     auto const cid = code.OwnerClassId(); | 
 |     ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid); | 
 |     return cid == kFunctionCid; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | bool StackFrame::IsBareInstructionsStubFrame() const { | 
 |   if (!FLAG_precompiled_mode) { | 
 |     return false; | 
 |   } | 
 |   NoSafepointScope no_safepoint; | 
 |  | 
 |   Code code; | 
 |   code = ReversePc::Lookup(this->isolate_group(), pc(), | 
 |                            /*is_return_address=*/true); | 
 |   if (!code.IsNull()) { | 
 |     auto const cid = code.OwnerClassId(); | 
 |     ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid); | 
 |     return cid == kNullCid || cid == kClassCid; | 
 |   } | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | bool StackFrame::IsStubFrame() const { | 
 |   if (FLAG_precompiled_mode) { | 
 |     return IsBareInstructionsStubFrame(); | 
 |   } | 
 |  | 
 |   ASSERT(!(IsEntryFrame() || IsExitFrame())); | 
 | #if !defined(DART_HOST_OS_WINDOWS) && !defined(DART_HOST_OS_FUCHSIA) | 
 |   // On Windows and Fuchsia, the profiler calls this from a separate thread | 
 |   // where Thread::Current() is nullptr, so we cannot create a NoSafepointScope. | 
 |   NoSafepointScope no_safepoint; | 
 | #endif | 
 |  | 
 |   CodePtr code = GetCodeObject(); | 
 |   ASSERT(code != Object::null()); | 
 |   auto const cid = Code::OwnerClassIdOf(code); | 
 |   ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid); | 
 |   return cid == kNullCid || cid == kClassCid; | 
 | } | 
 |  | 
 | const char* StackFrame::ToCString() const { | 
 |   ASSERT(thread_ == Thread::Current()); | 
 |   Zone* zone = Thread::Current()->zone(); | 
 |   const Code& code = Code::Handle(zone, GetCodeObject()); | 
 |   const char* name = | 
 |       code.IsNull() | 
 |           ? "Cannot find code object" | 
 |           : code.QualifiedName(NameFormattingParams(Object::kInternalName)); | 
 |   return zone->PrintToString("  pc 0x%" Pp " fp 0x%" Pp " sp 0x%" Pp " %s", | 
 |                              pc(), fp(), sp(), name); | 
 | } | 
 |  | 
 | void ExitFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) { | 
 |   ASSERT(visitor != nullptr); | 
 |   // Visit pc marker and saved pool pointer. | 
 |   ObjectPtr* last_fixed = reinterpret_cast<ObjectPtr*>(fp()) + | 
 |                           runtime_frame_layout.first_object_from_fp; | 
 |   ObjectPtr* first_fixed = reinterpret_cast<ObjectPtr*>(fp()) + | 
 |                            runtime_frame_layout.last_fixed_object_from_fp; | 
 |   if (first_fixed <= last_fixed) { | 
 |     visitor->VisitPointers(first_fixed, last_fixed); | 
 |   } else { | 
 |     ASSERT(runtime_frame_layout.first_object_from_fp == | 
 |            runtime_frame_layout.first_local_from_fp); | 
 |   } | 
 | } | 
 |  | 
 | void EntryFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) { | 
 |   ASSERT(visitor != nullptr); | 
 |   // Visit objects between SP and (FP - callee_save_area). | 
 |   ObjectPtr* first = reinterpret_cast<ObjectPtr*>(sp()); | 
 |   ObjectPtr* last = | 
 |       reinterpret_cast<ObjectPtr*>(fp()) + kExitLinkSlotFromEntryFp - 1; | 
 |   // There may not be any pointer to visit; in this case, first > last. | 
 |   visitor->VisitPointers(first, last); | 
 | } | 
 |  | 
 | void StackFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) { | 
 |   ASSERT(visitor != nullptr); | 
 |   // NOTE: This code runs while GC is in progress and runs within | 
 |   // a NoHandleScope block. Hence it is not ok to use regular Zone or | 
 |   // Scope handles. We use direct stack handles, the raw pointers in | 
 |   // these handles are not traversed. The use of handles is mainly to | 
 |   // be able to reuse the handle based code and avoid having to add | 
 |   // helper functions to the raw object interface. | 
 |   NoSafepointScope no_safepoint; | 
 |   Code code; | 
 |  | 
 |   CompressedStackMaps::RawPayloadHandle maps; | 
 |   CompressedStackMaps::RawPayloadHandle global_table; | 
 |  | 
 |   uword code_start; | 
 |  | 
 |   if (FLAG_precompiled_mode) { | 
 |     const UntaggedCompressedStackMaps::Payload* global_table_payload; | 
 |     maps = ReversePc::FindStackMap(isolate_group(), pc(), | 
 |                                    /*is_return_address=*/true, &code_start, | 
 |                                    &global_table_payload); | 
 |     global_table = global_table_payload; | 
 |   } else { | 
 |     ObjectPtr pc_marker = *(reinterpret_cast<ObjectPtr*>( | 
 |         fp() + (runtime_frame_layout.code_from_fp * kWordSize))); | 
 |     // May forward raw code. Note we don't just visit the pc marker slot first | 
 |     // because the visitor's forwarding might not be idempotent. | 
 |     visitor->VisitPointer(&pc_marker); | 
 |     if (pc_marker->IsHeapObject() && (pc_marker->GetClassId() == kCodeCid)) { | 
 |       code ^= pc_marker; | 
 |       code_start = code.PayloadStart(); | 
 |       ASSERT(code.compressed_stackmaps() != CompressedStackMaps::null()); | 
 |       maps = code.compressed_stackmaps(); | 
 |       if (maps.UsesGlobalTable()) { | 
 |         global_table = | 
 |             isolate_group()->object_store()->canonicalized_stack_map_entries(); | 
 |       } | 
 |     } else { | 
 |       ASSERT(pc_marker == Object::null()); | 
 |     } | 
 |   } | 
 |  | 
 |   if (!maps.IsNull()) { | 
 |     // Optimized frames have a stack map. We need to visit the frame based | 
 |     // on the stack map. | 
 |     CompressedStackMaps::Iterator<CompressedStackMaps::RawPayloadHandle> it( | 
 |         maps, global_table); | 
 |     const uint32_t pc_offset = pc() - code_start; | 
 |     if (it.Find(pc_offset)) { | 
 |       ObjectPtr* first = reinterpret_cast<ObjectPtr*>(sp()); | 
 |       ObjectPtr* last = reinterpret_cast<ObjectPtr*>( | 
 |           fp() + (runtime_frame_layout.first_local_from_fp * kWordSize)); | 
 |  | 
 |       // A stack map is present in the code object, use the stack map to | 
 |       // visit frame slots which are marked as having objects. | 
 |       // | 
 |       // The layout of the frame is (lower addresses to the right): | 
 |       // | spill slots | outgoing arguments | saved registers | slow-path args | | 
 |       // |XXXXXXXXXXXXX|--------------------|XXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX| | 
 |       // | 
 |       // The spill slots and any saved registers are described in the stack | 
 |       // map.  The outgoing arguments are assumed to be tagged; the number | 
 |       // of outgoing arguments is not explicitly tracked. | 
 |  | 
 |       // Spill slots are at the 'bottom' of the frame. | 
 |       intptr_t spill_slot_count = it.SpillSlotBitCount(); | 
 |       for (intptr_t bit = 0; bit < spill_slot_count; ++bit) { | 
 |         if (it.IsObject(bit)) { | 
 |           visitor->VisitPointer(last); | 
 |         } | 
 |         --last; | 
 |       } | 
 |  | 
 |       // The live registers at the 'top' of the frame comprise the rest of the | 
 |       // stack map. | 
 |       for (intptr_t bit = it.Length() - 1; bit >= spill_slot_count; --bit) { | 
 |         if (it.IsObject(bit)) { | 
 |           visitor->VisitPointer(first); | 
 |         } | 
 |         ++first; | 
 |       } | 
 |  | 
 |       // The last slot can be one slot (but not more) past the last slot | 
 |       // in the case that all slots were covered by the stack map. | 
 |       ASSERT((last + 1) >= first); | 
 |       visitor->VisitPointers(first, last); | 
 |  | 
 |       // Now visit other slots which might be part of the calling convention. | 
 |       first = reinterpret_cast<ObjectPtr*>( | 
 |           fp() + ((runtime_frame_layout.first_local_from_fp + 1) * kWordSize)); | 
 |       last = reinterpret_cast<ObjectPtr*>( | 
 |           fp() + (runtime_frame_layout.first_object_from_fp * kWordSize)); | 
 |       visitor->VisitPointers(first, last); | 
 |       return; | 
 |     } | 
 |  | 
 |     // If we are missing a stack map for a given PC offset, this must either be | 
 |     // unoptimized code, code with no stack map information at all, or the entry | 
 |     // to an osr function. In each of these cases, all stack slots contain | 
 |     // tagged pointers, so fall through. | 
 | #if defined(DEBUG) | 
 |     if (FLAG_precompiled_mode) { | 
 |       ASSERT(IsStubFrame()); | 
 |     } else { | 
 |       ASSERT(!code.is_optimized() || | 
 |              (pc_offset == code.EntryPoint() - code.PayloadStart())); | 
 |     } | 
 | #endif  // defined(DEBUG) | 
 |   } | 
 |  | 
 |   // For normal unoptimized Dart frames and Stub frames each slot | 
 |   // between the first and last included are tagged objects. | 
 |   ObjectPtr* first = reinterpret_cast<ObjectPtr*>(sp()); | 
 |   ObjectPtr* last = reinterpret_cast<ObjectPtr*>( | 
 |       fp() + (runtime_frame_layout.first_object_from_fp * kWordSize)); | 
 |  | 
 |   visitor->VisitPointers(first, last); | 
 | } | 
 |  | 
 | FunctionPtr StackFrame::LookupDartFunction() const { | 
 |   const Code& code = Code::Handle(LookupDartCode()); | 
 |   if (!code.IsNull()) { | 
 |     const Object& owner = Object::Handle(code.owner()); | 
 |     if (owner.IsFunction()) { | 
 |       return Function::Cast(owner).ptr(); | 
 |     } | 
 |   } | 
 |   return Function::null(); | 
 | } | 
 |  | 
 | CodePtr StackFrame::LookupDartCode() const { | 
 | // We add a no gc scope to ensure that the code below does not trigger | 
 | // a GC as we are handling raw object references here. It is possible | 
 | // that the code is called while a GC is in progress, that is ok. | 
 | #if !defined(DART_HOST_OS_WINDOWS) && !defined(DART_HOST_OS_FUCHSIA) | 
 |   // On Windows and Fuchsia, the profiler calls this from a separate thread | 
 |   // where Thread::Current() is nullptr, so we cannot create a NoSafepointScope. | 
 |   NoSafepointScope no_safepoint; | 
 | #endif | 
 |   CodePtr code = GetCodeObject(); | 
 |   if ((code != Code::null()) && Code::OwnerClassIdOf(code) == kFunctionCid) { | 
 |     return code; | 
 |   } | 
 |   return Code::null(); | 
 | } | 
 |  | 
 | CodePtr StackFrame::GetCodeObject() const { | 
 | #if defined(DART_PRECOMPILED_RUNTIME) | 
 |   if (FLAG_precompiled_mode) { | 
 |     NoSafepointScope no_safepoint; | 
 |     CodePtr code = ReversePc::Lookup(isolate_group(), pc(), | 
 |                                      /*is_return_address=*/true); | 
 |     ASSERT(code != Code::null()); | 
 |     return code; | 
 |   } | 
 | #endif  // defined(DART_PRECOMPILED_RUNTIME) | 
 |  | 
 |   ObjectPtr pc_marker = *(reinterpret_cast<ObjectPtr*>( | 
 |       fp() + runtime_frame_layout.code_from_fp * kWordSize)); | 
 |   ASSERT((pc_marker == Object::null()) || | 
 |          (pc_marker->GetClassId() == kCodeCid)); | 
 |   return static_cast<CodePtr>(pc_marker); | 
 | } | 
 |  | 
 | bool StackFrame::FindExceptionHandler(Thread* thread, | 
 |                                       uword* handler_pc, | 
 |                                       bool* needs_stacktrace, | 
 |                                       bool* has_catch_all, | 
 |                                       bool* is_optimized) const { | 
 |   REUSABLE_CODE_HANDLESCOPE(thread); | 
 |   Code& code = reused_code_handle.Handle(); | 
 |   REUSABLE_EXCEPTION_HANDLERS_HANDLESCOPE(thread); | 
 |   ExceptionHandlers& handlers = reused_exception_handlers_handle.Handle(); | 
 |   REUSABLE_PC_DESCRIPTORS_HANDLESCOPE(thread); | 
 |   PcDescriptors& descriptors = reused_pc_descriptors_handle.Handle(); | 
 |   uword start; | 
 |   code = LookupDartCode(); | 
 |   if (code.IsNull()) { | 
 |     return false;  // Stub frames do not have exception handlers. | 
 |   } | 
 |   start = code.PayloadStart(); | 
 |   handlers = code.exception_handlers(); | 
 |   descriptors = code.pc_descriptors(); | 
 |   *is_optimized = code.is_optimized(); | 
 |   HandlerInfoCache* cache = thread->isolate()->handler_info_cache(); | 
 |   ExceptionHandlerInfo* info = cache->Lookup(pc()); | 
 |   if (info != nullptr) { | 
 |     *handler_pc = start + info->handler_pc_offset; | 
 |     *needs_stacktrace = (info->needs_stacktrace != 0); | 
 |     *has_catch_all = (info->has_catch_all != 0); | 
 |     return true; | 
 |   } | 
 |  | 
 |   intptr_t try_index = -1; | 
 |   if (handlers.num_entries() != 0) { | 
 |     uword pc_offset = pc() - code.PayloadStart(); | 
 |     PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind); | 
 |     while (iter.MoveNext()) { | 
 |       const intptr_t current_try_index = iter.TryIndex(); | 
 |       if ((iter.PcOffset() == pc_offset) && (current_try_index != -1)) { | 
 |         try_index = current_try_index; | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |   if (try_index == -1) { | 
 |     if (handlers.has_async_handler()) { | 
 |       *handler_pc = StubCode::AsyncExceptionHandler().EntryPoint(); | 
 |       *needs_stacktrace = true; | 
 |       *has_catch_all = true; | 
 |       return true; | 
 |     } | 
 |     return false; | 
 |   } | 
 |   ExceptionHandlerInfo handler_info; | 
 |   handlers.GetHandlerInfo(try_index, &handler_info); | 
 |   *handler_pc = start + handler_info.handler_pc_offset; | 
 |   *needs_stacktrace = (handler_info.needs_stacktrace != 0); | 
 |   *has_catch_all = (handler_info.has_catch_all != 0); | 
 |   cache->Insert(pc(), handler_info); | 
 |   return true; | 
 | } | 
 |  | 
 | TokenPosition StackFrame::GetTokenPos() const { | 
 |   const Code& code = Code::Handle(LookupDartCode()); | 
 |   if (code.IsNull()) { | 
 |     return TokenPosition::kNoSource;  // Stub frames do not have token_pos. | 
 |   } | 
 |   uword pc_offset = pc() - code.PayloadStart(); | 
 |   const PcDescriptors& descriptors = | 
 |       PcDescriptors::Handle(code.pc_descriptors()); | 
 |   ASSERT(!descriptors.IsNull()); | 
 |   PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind); | 
 |   while (iter.MoveNext()) { | 
 |     if (iter.PcOffset() == pc_offset) { | 
 |       return TokenPosition(iter.TokenPos()); | 
 |     } | 
 |   } | 
 |   return TokenPosition::kNoSource; | 
 | } | 
 |  | 
 | bool StackFrame::IsValid() const { | 
 |   if (IsEntryFrame() || IsExitFrame() || IsStubFrame()) { | 
 |     return true; | 
 |   } | 
 |   return (LookupDartCode() != Code::null()); | 
 | } | 
 |  | 
 | void StackFrame::DumpCurrentTrace() { | 
 |   StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, | 
 |                             Thread::Current(), | 
 |                             StackFrameIterator::kNoCrossThreadIteration); | 
 |   StackFrame* frame = frames.NextFrame(); | 
 |   while (frame != nullptr) { | 
 |     OS::PrintErr("%s\n", frame->ToCString()); | 
 |     frame = frames.NextFrame(); | 
 |   } | 
 | } | 
 |  | 
 | void StackFrameIterator::SetupLastExitFrameData() { | 
 |   ASSERT(thread_ != nullptr); | 
 |   uword exit_marker = thread_->top_exit_frame_info(); | 
 |   frames_.fp_ = exit_marker; | 
 |   frames_.sp_ = 0; | 
 |   frames_.pc_ = 0; | 
 |   frames_.Unpoison(); | 
 | } | 
 |  | 
 | void StackFrameIterator::SetupNextExitFrameData() { | 
 |   ASSERT(entry_.fp() != 0); | 
 |   uword exit_address = entry_.fp() + (kExitLinkSlotFromEntryFp * kWordSize); | 
 |   uword exit_marker = *reinterpret_cast<uword*>(exit_address); | 
 |   frames_.fp_ = exit_marker; | 
 |   frames_.sp_ = 0; | 
 |   frames_.pc_ = 0; | 
 |   frames_.Unpoison(); | 
 | } | 
 |  | 
 | StackFrameIterator::StackFrameIterator(ValidationPolicy validation_policy, | 
 |                                        Thread* thread, | 
 |                                        CrossThreadPolicy cross_thread_policy) | 
 |     : validate_(validation_policy == ValidationPolicy::kValidateFrames), | 
 |       entry_(thread), | 
 |       exit_(thread), | 
 |       frames_(thread), | 
 |       current_frame_(nullptr), | 
 |       thread_(thread) { | 
 |   ASSERT(cross_thread_policy == kAllowCrossThreadIteration || | 
 |          thread_ == Thread::Current()); | 
 |   SetupLastExitFrameData();  // Setup data for last exit frame. | 
 | } | 
 |  | 
 | StackFrameIterator::StackFrameIterator(uword last_fp, | 
 |                                        ValidationPolicy validation_policy, | 
 |                                        Thread* thread, | 
 |                                        CrossThreadPolicy cross_thread_policy) | 
 |     : validate_(validation_policy == ValidationPolicy::kValidateFrames), | 
 |       entry_(thread), | 
 |       exit_(thread), | 
 |       frames_(thread), | 
 |       current_frame_(nullptr), | 
 |       thread_(thread) { | 
 |   ASSERT(cross_thread_policy == kAllowCrossThreadIteration || | 
 |          thread_ == Thread::Current()); | 
 |   frames_.fp_ = last_fp; | 
 |   frames_.sp_ = 0; | 
 |   frames_.pc_ = 0; | 
 |   frames_.Unpoison(); | 
 | } | 
 |  | 
 | StackFrameIterator::StackFrameIterator(uword fp, | 
 |                                        uword sp, | 
 |                                        uword pc, | 
 |                                        ValidationPolicy validation_policy, | 
 |                                        Thread* thread, | 
 |                                        CrossThreadPolicy cross_thread_policy) | 
 |     : validate_(validation_policy == ValidationPolicy::kValidateFrames), | 
 |       entry_(thread), | 
 |       exit_(thread), | 
 |       frames_(thread), | 
 |       current_frame_(nullptr), | 
 |       thread_(thread) { | 
 |   ASSERT(cross_thread_policy == kAllowCrossThreadIteration || | 
 |          thread_ == Thread::Current()); | 
 |   frames_.fp_ = fp; | 
 |   frames_.sp_ = sp; | 
 |   frames_.pc_ = pc; | 
 |   frames_.Unpoison(); | 
 | } | 
 |  | 
 | StackFrameIterator::StackFrameIterator(const StackFrameIterator& orig) | 
 |     : validate_(orig.validate_), | 
 |       entry_(orig.thread_), | 
 |       exit_(orig.thread_), | 
 |       frames_(orig.thread_), | 
 |       current_frame_(nullptr), | 
 |       thread_(orig.thread_) { | 
 |   frames_.fp_ = orig.frames_.fp_; | 
 |   frames_.sp_ = orig.frames_.sp_; | 
 |   frames_.pc_ = orig.frames_.pc_; | 
 |   frames_.Unpoison(); | 
 | } | 
 |  | 
 | StackFrame* StackFrameIterator::NextFrame() { | 
 |   // When we are at the start of iteration after having created an | 
 |   // iterator object, current_frame_ will be nullptr as we haven't seen | 
 |   // any frames yet (unless we start iterating in the simulator from a given | 
 |   // triplet of fp, sp, and pc). At this point, if NextFrame is called, it tries | 
 |   // to set up the next exit frame by reading the top_exit_frame_info | 
 |   // from the isolate. If we do not have any dart invocations yet, | 
 |   // top_exit_frame_info will be 0 and so we would return nullptr. | 
 |  | 
 |   // current_frame_ will also be nullptr, when we are at the end of having | 
 |   // iterated through all the frames. If NextFrame is called at this | 
 |   // point, we will try and set up the next exit frame, but since we are | 
 |   // at the end of the iteration, fp_ will be 0 and we would return nullptr. | 
 |   if (current_frame_ == nullptr) { | 
 |     if (!HasNextFrame()) { | 
 |       return nullptr; | 
 |     } | 
 |     if (frames_.pc_ == 0) { | 
 |       // Iteration starts from an exit frame given by its fp. | 
 |       current_frame_ = NextExitFrame(); | 
 |     } else if (*(reinterpret_cast<uword*>( | 
 |                    frames_.fp_ + (kSavedCallerFpSlotFromFp * kWordSize))) == | 
 |                0) { | 
 |       // Iteration starts from an entry frame given by its fp, sp, and pc. | 
 |       current_frame_ = NextEntryFrame(); | 
 |     } else { | 
 |       // Iteration starts from a Dart or stub frame given by its fp, sp, and pc. | 
 |       current_frame_ = frames_.NextFrame(validate_); | 
 |     } | 
 |     return current_frame_; | 
 |   } | 
 |   ASSERT(!validate_ || current_frame_->IsValid()); | 
 |   if (current_frame_->IsEntryFrame()) { | 
 |     if (HasNextFrame()) {  // We have another chained block. | 
 |       current_frame_ = NextExitFrame(); | 
 |       return current_frame_; | 
 |     } | 
 |     current_frame_ = nullptr;  // No more frames. | 
 |     return current_frame_; | 
 |   } | 
 |   ASSERT(!validate_ || current_frame_->IsExitFrame() || | 
 |          current_frame_->IsDartFrame(validate_) || | 
 |          current_frame_->IsStubFrame()); | 
 |  | 
 |   // Consume dart/stub frames using StackFrameIterator::FrameSetIterator | 
 |   // until we are out of dart/stub frames at which point we return the | 
 |   // corresponding entry frame for that set of dart/stub frames. | 
 |   current_frame_ = | 
 |       (frames_.HasNext()) ? frames_.NextFrame(validate_) : NextEntryFrame(); | 
 |   return current_frame_; | 
 | } | 
 |  | 
 | // Tell MemorySanitizer that generated code initializes part of the stack. | 
 | void StackFrameIterator::FrameSetIterator::Unpoison() { | 
 |   // When using a simulator, all writes to the stack happened from MSAN | 
 |   // instrumented C++, so there is nothing to unpoison. Additionally, | 
 |   // fp_ will be somewhere in the simulator's stack instead of the OSThread's | 
 |   // stack. | 
 | #if !defined(USING_SIMULATOR) | 
 |   if (fp_ == 0) return; | 
 |   // Note that Thread::os_thread_ is cleared when the thread is descheduled. | 
 |   ASSERT((thread_->os_thread() == nullptr) || | 
 |          ((thread_->os_thread()->stack_limit() < fp_) && | 
 |           (thread_->os_thread()->stack_base() > fp_))); | 
 |   uword lower; | 
 |   if (sp_ == 0) { | 
 |     // Exit frame: guess sp. | 
 |     lower = fp_ - kDartFrameFixedSize * kWordSize; | 
 |   } else { | 
 |     lower = sp_; | 
 |   } | 
 |   uword upper = fp_ + kSavedCallerPcSlotFromFp * kWordSize; | 
 |   // Both lower and upper are inclusive, so we add one word when computing size. | 
 |   MSAN_UNPOISON(reinterpret_cast<void*>(lower), upper - lower + kWordSize); | 
 | #endif  // !defined(USING_SIMULATOR) | 
 | } | 
 |  | 
 | StackFrame* StackFrameIterator::FrameSetIterator::NextFrame(bool validate) { | 
 |   StackFrame* frame; | 
 |   ASSERT(HasNext()); | 
 |   frame = &stack_frame_; | 
 |   frame->sp_ = sp_; | 
 |   frame->fp_ = fp_; | 
 |   frame->pc_ = pc_; | 
 |   sp_ = frame->GetCallerSp(); | 
 |   fp_ = frame->GetCallerFp(); | 
 |   pc_ = frame->GetCallerPc(); | 
 |   Unpoison(); | 
 |   ASSERT(!validate || frame->IsValid()); | 
 |   return frame; | 
 | } | 
 |  | 
 | ExitFrame* StackFrameIterator::NextExitFrame() { | 
 |   exit_.sp_ = frames_.sp_; | 
 |   exit_.fp_ = frames_.fp_; | 
 |   exit_.pc_ = frames_.pc_; | 
 |   frames_.sp_ = exit_.GetCallerSp(); | 
 |   frames_.fp_ = exit_.GetCallerFp(); | 
 |   frames_.pc_ = exit_.GetCallerPc(); | 
 |   frames_.Unpoison(); | 
 |   ASSERT(!validate_ || exit_.IsValid()); | 
 |   return &exit_; | 
 | } | 
 |  | 
 | EntryFrame* StackFrameIterator::NextEntryFrame() { | 
 |   ASSERT(!frames_.HasNext()); | 
 |   entry_.sp_ = frames_.sp_; | 
 |   entry_.fp_ = frames_.fp_; | 
 |   entry_.pc_ = frames_.pc_; | 
 |   SetupNextExitFrameData();  // Setup data for next exit frame in chain. | 
 |   ASSERT(!validate_ || entry_.IsValid()); | 
 |   return &entry_; | 
 | } | 
 |  | 
 | InlinedFunctionsIterator::InlinedFunctionsIterator(const Code& code, uword pc) | 
 |     : index_(0), | 
 |       num_materializations_(0), | 
 |       dest_frame_size_(0), | 
 |       code_(Code::Handle(code.ptr())), | 
 |       deopt_info_(TypedData::Handle()), | 
 |       function_(Function::Handle()), | 
 |       pc_(pc), | 
 |       deopt_instructions_(), | 
 |       object_table_(ObjectPool::Handle()) { | 
 |   ASSERT(code_.is_optimized()); | 
 |   ASSERT(pc_ != 0); | 
 |   ASSERT(code.ContainsInstructionAt(pc)); | 
 | #if defined(DART_PRECOMPILED_RUNTIME) | 
 |   ASSERT(deopt_info_.IsNull()); | 
 |   function_ = code_.function(); | 
 | #else | 
 |   ICData::DeoptReasonId deopt_reason = ICData::kDeoptUnknown; | 
 |   uint32_t deopt_flags = 0; | 
 |   deopt_info_ = code_.GetDeoptInfoAtPc(pc, &deopt_reason, &deopt_flags); | 
 |   if (deopt_info_.IsNull()) { | 
 |     // This is the case when a call without deopt info in optimized code | 
 |     // throws an exception. (e.g. in the parameter copying prologue). | 
 |     // In that case there won't be any inlined frames. | 
 |     function_ = code_.function(); | 
 |   } else { | 
 |     // Unpack deopt info into instructions (translate away suffixes). | 
 |     const Array& deopt_table = Array::Handle(code_.deopt_info_array()); | 
 |     ASSERT(!deopt_table.IsNull()); | 
 |     DeoptInfo::Unpack(deopt_table, deopt_info_, &deopt_instructions_); | 
 |     num_materializations_ = DeoptInfo::NumMaterializations(deopt_instructions_); | 
 |     dest_frame_size_ = DeoptInfo::FrameSize(deopt_info_); | 
 |     object_table_ = code_.GetObjectPool(); | 
 |     Advance(); | 
 |   } | 
 | #endif  // defined(DART_PRECOMPILED_RUNTIME) | 
 | } | 
 |  | 
 | void InlinedFunctionsIterator::Advance() { | 
 |   // Iterate over the deopt instructions and determine the inlined | 
 |   // functions if any and iterate over them. | 
 |   ASSERT(!Done()); | 
 |  | 
 | #if defined(DART_PRECOMPILED_RUNTIME) | 
 |   ASSERT(deopt_info_.IsNull()); | 
 |   SetDone(); | 
 |   return; | 
 | #else | 
 |   if (deopt_info_.IsNull()) { | 
 |     SetDone(); | 
 |     return; | 
 |   } | 
 |  | 
 |   ASSERT(deopt_instructions_.length() != 0); | 
 |   while (index_ < deopt_instructions_.length()) { | 
 |     DeoptInstr* deopt_instr = deopt_instructions_[index_++]; | 
 |     if (deopt_instr->kind() == DeoptInstr::kRetAddress) { | 
 |       pc_ = DeoptInstr::GetRetAddress(deopt_instr, object_table_, &code_); | 
 |       function_ = code_.function(); | 
 |       return; | 
 |     } | 
 |   } | 
 |   SetDone(); | 
 | #endif  // defined(DART_PRECOMPILED_RUNTIME) | 
 | } | 
 |  | 
 | #if !defined(DART_PRECOMPILED_RUNTIME) | 
 | // Finds the potential offset for the current function's FP if the | 
 | // current frame were to be deoptimized. | 
 | intptr_t InlinedFunctionsIterator::GetDeoptFpOffset() const { | 
 |   ASSERT(deopt_instructions_.length() != 0); | 
 |   for (intptr_t index = index_; index < deopt_instructions_.length(); index++) { | 
 |     DeoptInstr* deopt_instr = deopt_instructions_[index]; | 
 |     if (deopt_instr->kind() == DeoptInstr::kCallerFp) { | 
 |       return index - num_materializations_ - kSavedCallerFpSlotFromFp; | 
 |     } | 
 |   } | 
 |   UNREACHABLE(); | 
 |   return 0; | 
 | } | 
 | #endif  // !defined(DART_PRECOMPILED_RUNTIME) | 
 |  | 
 | #if defined(DEBUG) | 
 | void ValidateFrames() { | 
 |   StackFrameIterator frames(ValidationPolicy::kValidateFrames, | 
 |                             Thread::Current(), | 
 |                             StackFrameIterator::kNoCrossThreadIteration); | 
 |   StackFrame* frame = frames.NextFrame(); | 
 |   while (frame != nullptr) { | 
 |     frame = frames.NextFrame(); | 
 |   } | 
 | } | 
 | #endif | 
 |  | 
 | }  // namespace dart |