// Copyright 2012 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/isolate.h" #include #include // NOLINT(readability/streams) #include #include "src/assembler-inl.h" #include "src/ast/ast-value-factory.h" #include "src/ast/context-slot-cache.h" #include "src/base/hashmap.h" #include "src/base/platform/platform.h" #include "src/base/sys-info.h" #include "src/base/utils/random-number-generator.h" #include "src/basic-block-profiler.h" #include "src/bootstrapper.h" #include "src/cancelable-task.h" #include "src/codegen.h" #include "src/compilation-cache.h" #include "src/compilation-statistics.h" #include "src/compiler-dispatcher/compiler-dispatcher.h" #include "src/compiler-dispatcher/optimizing-compile-dispatcher.h" #include "src/crankshaft/hydrogen.h" #include "src/debug/debug.h" #include "src/deoptimizer.h" #include "src/elements.h" #include "src/external-reference-table.h" #include "src/frames-inl.h" #include "src/ic/access-compiler-data.h" #include "src/ic/stub-cache.h" #include "src/interface-descriptors.h" #include "src/interpreter/interpreter.h" #include "src/isolate-inl.h" #include "src/libsampler/sampler.h" #include "src/log.h" #include "src/messages.h" #include "src/profiler/cpu-profiler.h" #include "src/prototype.h" #include "src/regexp/regexp-stack.h" #include "src/runtime-profiler.h" #include "src/simulator.h" #include "src/snapshot/deserializer.h" #include "src/tracing/tracing-category-observer.h" #include "src/v8.h" #include "src/version.h" #include "src/vm-state-inl.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects.h" #include "src/zone/accounting-allocator.h" namespace v8 { namespace internal { base::Atomic32 ThreadId::highest_thread_id_ = 0; int ThreadId::AllocateThreadId() { int new_id = base::NoBarrier_AtomicIncrement(&highest_thread_id_, 1); return new_id; } int ThreadId::GetCurrentThreadId() { int thread_id = base::Thread::GetThreadLocalInt(Isolate::thread_id_key_); if (thread_id == 0) { thread_id = AllocateThreadId(); base::Thread::SetThreadLocalInt(Isolate::thread_id_key_, thread_id); } return thread_id; } ThreadLocalTop::ThreadLocalTop() { InitializeInternal(); } void ThreadLocalTop::InitializeInternal() { c_entry_fp_ = 0; c_function_ = 0; handler_ = 0; #ifdef USE_SIMULATOR simulator_ = NULL; #endif js_entry_sp_ = NULL; external_callback_scope_ = NULL; current_vm_state_ = EXTERNAL; try_catch_handler_ = NULL; context_ = NULL; thread_id_ = ThreadId::Invalid(); external_caught_exception_ = false; failed_access_check_callback_ = NULL; save_context_ = NULL; promise_on_stack_ = NULL; // These members are re-initialized later after deserialization // is complete. pending_exception_ = NULL; rethrowing_message_ = false; pending_message_obj_ = NULL; scheduled_exception_ = NULL; } void ThreadLocalTop::Initialize() { InitializeInternal(); #ifdef USE_SIMULATOR simulator_ = Simulator::current(isolate_); #endif thread_id_ = ThreadId::Current(); } void ThreadLocalTop::Free() { // Match unmatched PopPromise calls. while (promise_on_stack_) isolate_->PopPromise(); } base::Thread::LocalStorageKey Isolate::isolate_key_; base::Thread::LocalStorageKey Isolate::thread_id_key_; base::Thread::LocalStorageKey Isolate::per_isolate_thread_data_key_; base::LazyMutex Isolate::thread_data_table_mutex_ = LAZY_MUTEX_INITIALIZER; Isolate::ThreadDataTable* Isolate::thread_data_table_ = NULL; base::Atomic32 Isolate::isolate_counter_ = 0; #if DEBUG base::Atomic32 Isolate::isolate_key_created_ = 0; #endif Isolate::PerIsolateThreadData* Isolate::FindOrAllocatePerThreadDataForThisThread() { ThreadId thread_id = ThreadId::Current(); PerIsolateThreadData* per_thread = NULL; { base::LockGuard lock_guard(thread_data_table_mutex_.Pointer()); per_thread = thread_data_table_->Lookup(this, thread_id); if (per_thread == NULL) { per_thread = new PerIsolateThreadData(this, thread_id); thread_data_table_->Insert(per_thread); } DCHECK(thread_data_table_->Lookup(this, thread_id) == per_thread); } return per_thread; } void Isolate::DiscardPerThreadDataForThisThread() { int thread_id_int = base::Thread::GetThreadLocalInt(Isolate::thread_id_key_); if (thread_id_int) { ThreadId thread_id = ThreadId(thread_id_int); DCHECK(!thread_manager_->mutex_owner_.Equals(thread_id)); base::LockGuard lock_guard(thread_data_table_mutex_.Pointer()); PerIsolateThreadData* per_thread = thread_data_table_->Lookup(this, thread_id); if (per_thread) { DCHECK(!per_thread->thread_state_); thread_data_table_->Remove(per_thread); } } } Isolate::PerIsolateThreadData* Isolate::FindPerThreadDataForThisThread() { ThreadId thread_id = ThreadId::Current(); return FindPerThreadDataForThread(thread_id); } Isolate::PerIsolateThreadData* Isolate::FindPerThreadDataForThread( ThreadId thread_id) { PerIsolateThreadData* per_thread = NULL; { base::LockGuard lock_guard(thread_data_table_mutex_.Pointer()); per_thread = thread_data_table_->Lookup(this, thread_id); } return per_thread; } void Isolate::InitializeOncePerProcess() { base::LockGuard lock_guard(thread_data_table_mutex_.Pointer()); CHECK(thread_data_table_ == NULL); isolate_key_ = base::Thread::CreateThreadLocalKey(); #if DEBUG base::NoBarrier_Store(&isolate_key_created_, 1); #endif thread_id_key_ = base::Thread::CreateThreadLocalKey(); per_isolate_thread_data_key_ = base::Thread::CreateThreadLocalKey(); thread_data_table_ = new Isolate::ThreadDataTable(); } Address Isolate::get_address_from_id(Isolate::AddressId id) { return isolate_addresses_[id]; } char* Isolate::Iterate(ObjectVisitor* v, char* thread_storage) { ThreadLocalTop* thread = reinterpret_cast(thread_storage); Iterate(v, thread); return thread_storage + sizeof(ThreadLocalTop); } void Isolate::IterateThread(ThreadVisitor* v, char* t) { ThreadLocalTop* thread = reinterpret_cast(t); v->VisitThread(this, thread); } void Isolate::Iterate(ObjectVisitor* v, ThreadLocalTop* thread) { // Visit the roots from the top for a given thread. v->VisitPointer(&thread->pending_exception_); v->VisitPointer(&(thread->pending_message_obj_)); v->VisitPointer(bit_cast(&(thread->context_))); v->VisitPointer(&thread->scheduled_exception_); for (v8::TryCatch* block = thread->try_catch_handler(); block != NULL; block = block->next_) { v->VisitPointer(bit_cast(&(block->exception_))); v->VisitPointer(bit_cast(&(block->message_obj_))); } // Iterate over pointers on native execution stack. for (StackFrameIterator it(this, thread); !it.done(); it.Advance()) { it.frame()->Iterate(v); } } void Isolate::Iterate(ObjectVisitor* v) { ThreadLocalTop* current_t = thread_local_top(); Iterate(v, current_t); } void Isolate::IterateDeferredHandles(ObjectVisitor* visitor) { for (DeferredHandles* deferred = deferred_handles_head_; deferred != NULL; deferred = deferred->next_) { deferred->Iterate(visitor); } } #ifdef DEBUG bool Isolate::IsDeferredHandle(Object** handle) { // Each DeferredHandles instance keeps the handles to one job in the // concurrent recompilation queue, containing a list of blocks. Each block // contains kHandleBlockSize handles except for the first block, which may // not be fully filled. // We iterate through all the blocks to see whether the argument handle // belongs to one of the blocks. If so, it is deferred. for (DeferredHandles* deferred = deferred_handles_head_; deferred != NULL; deferred = deferred->next_) { List* blocks = &deferred->blocks_; for (int i = 0; i < blocks->length(); i++) { Object** block_limit = (i == 0) ? deferred->first_block_limit_ : blocks->at(i) + kHandleBlockSize; if (blocks->at(i) <= handle && handle < block_limit) return true; } } return false; } #endif // DEBUG void Isolate::RegisterTryCatchHandler(v8::TryCatch* that) { thread_local_top()->set_try_catch_handler(that); } void Isolate::UnregisterTryCatchHandler(v8::TryCatch* that) { DCHECK(thread_local_top()->try_catch_handler() == that); thread_local_top()->set_try_catch_handler(that->next_); } Handle Isolate::StackTraceString() { if (stack_trace_nesting_level_ == 0) { stack_trace_nesting_level_++; HeapStringAllocator allocator; StringStream::ClearMentionedObjectCache(this); StringStream accumulator(&allocator); incomplete_message_ = &accumulator; PrintStack(&accumulator); Handle stack_trace = accumulator.ToString(this); incomplete_message_ = NULL; stack_trace_nesting_level_ = 0; return stack_trace; } else if (stack_trace_nesting_level_ == 1) { stack_trace_nesting_level_++; base::OS::PrintError( "\n\nAttempt to print stack while printing stack (double fault)\n"); base::OS::PrintError( "If you are lucky you may find a partial stack dump on stdout.\n\n"); incomplete_message_->OutputToStdOut(); return factory()->empty_string(); } else { base::OS::Abort(); // Unreachable return factory()->empty_string(); } } void Isolate::PushStackTraceAndDie(unsigned int magic, void* ptr1, void* ptr2, unsigned int magic2) { const int kMaxStackTraceSize = 32 * KB; Handle trace = StackTraceString(); uint8_t buffer[kMaxStackTraceSize]; int length = Min(kMaxStackTraceSize - 1, trace->length()); String::WriteToFlat(*trace, buffer, 0, length); buffer[length] = '\0'; // TODO(dcarney): convert buffer to utf8? base::OS::PrintError("Stacktrace (%x-%x) %p %p: %s\n", magic, magic2, ptr1, ptr2, reinterpret_cast(buffer)); base::OS::Abort(); } namespace { class StackTraceHelper { public: StackTraceHelper(Isolate* isolate, FrameSkipMode mode, Handle caller) : isolate_(isolate), mode_(mode), caller_(caller), skip_next_frame_(true) { switch (mode_) { case SKIP_FIRST: skip_next_frame_ = true; break; case SKIP_UNTIL_SEEN: DCHECK(caller_->IsJSFunction()); skip_next_frame_ = true; break; case SKIP_NONE: skip_next_frame_ = false; break; } encountered_strict_function_ = false; } // Poison stack frames below the first strict mode frame. // The stack trace API should not expose receivers and function // objects on frames deeper than the top-most one with a strict mode // function. bool IsStrictFrame(JSFunction* fun) { if (!encountered_strict_function_) { encountered_strict_function_ = is_strict(fun->shared()->language_mode()); } return encountered_strict_function_; } // Determines whether the given stack frame should be displayed in a stack // trace. bool IsVisibleInStackTrace(JSFunction* fun) { return ShouldIncludeFrame(fun) && IsNotHidden(fun) && IsInSameSecurityContext(fun); } private: // This mechanism excludes a number of uninteresting frames from the stack // trace. This can be be the first frame (which will be a builtin-exit frame // for the error constructor builtin) or every frame until encountering a // user-specified function. bool ShouldIncludeFrame(JSFunction* fun) { switch (mode_) { case SKIP_NONE: return true; case SKIP_FIRST: if (!skip_next_frame_) return true; skip_next_frame_ = false; return false; case SKIP_UNTIL_SEEN: if (skip_next_frame_ && (fun == *caller_)) { skip_next_frame_ = false; return false; } return !skip_next_frame_; } UNREACHABLE(); return false; } bool IsNotHidden(JSFunction* fun) { // Functions defined not in user scripts are not visible unless directly // exposed, in which case the native flag is set. // The --builtins-in-stack-traces command line flag allows including // internal call sites in the stack trace for debugging purposes. if (!FLAG_builtins_in_stack_traces && !fun->shared()->IsUserJavaScript()) { return fun->shared()->native(); } return true; } bool IsInSameSecurityContext(JSFunction* fun) { return isolate_->context()->HasSameSecurityTokenAs(fun->context()); } Isolate* isolate_; const FrameSkipMode mode_; const Handle caller_; bool skip_next_frame_; bool encountered_strict_function_; }; // TODO(jgruber): Fix all cases in which frames give us a hole value (e.g. the // receiver in RegExp constructor frames. Handle TheHoleToUndefined(Isolate* isolate, Handle in) { return (in->IsTheHole(isolate)) ? Handle::cast(isolate->factory()->undefined_value()) : in; } bool GetStackTraceLimit(Isolate* isolate, int* result) { Handle error = isolate->error_function(); Handle key = isolate->factory()->stackTraceLimit_string(); Handle stack_trace_limit = JSReceiver::GetDataProperty(error, key); if (!stack_trace_limit->IsNumber()) return false; // Ensure that limit is not negative. *result = Max(FastD2IChecked(stack_trace_limit->Number()), 0); return true; } } // namespace Handle Isolate::CaptureSimpleStackTrace(Handle error_object, FrameSkipMode mode, Handle caller) { DisallowJavascriptExecution no_js(this); int limit; if (!GetStackTraceLimit(this, &limit)) return factory()->undefined_value(); const int initial_size = Min(limit, 10); Handle elements = factory()->NewFrameArray(initial_size); StackTraceHelper helper(this, mode, caller); for (StackFrameIterator iter(this); !iter.done() && elements->FrameCount() < limit; iter.Advance()) { StackFrame* frame = iter.frame(); switch (frame->type()) { case StackFrame::JAVA_SCRIPT: case StackFrame::OPTIMIZED: case StackFrame::INTERPRETED: case StackFrame::BUILTIN: { JavaScriptFrame* js_frame = JavaScriptFrame::cast(frame); // Set initial size to the maximum inlining level + 1 for the outermost // function. List frames(FLAG_max_inlining_levels + 1); js_frame->Summarize(&frames); for (int i = frames.length() - 1; i >= 0; i--) { const auto& summ = frames[i].AsJavaScript(); Handle fun = summ.function(); // Filter out internal frames that we do not want to show. if (!helper.IsVisibleInStackTrace(*fun)) continue; Handle recv = frames[i].receiver(); Handle abstract_code = summ.abstract_code(); const int offset = frames[i].code_offset(); bool force_constructor = false; if (frame->type() == StackFrame::BUILTIN) { // Help CallSite::IsConstructor correctly detect hand-written // construct stubs. if (Code::cast(*abstract_code)->is_construct_stub()) { force_constructor = true; } } int flags = 0; if (helper.IsStrictFrame(*fun)) flags |= FrameArray::kIsStrict; if (force_constructor) flags |= FrameArray::kForceConstructor; elements = FrameArray::AppendJSFrame( elements, TheHoleToUndefined(this, recv), fun, abstract_code, offset, flags); } } break; case StackFrame::BUILTIN_EXIT: { BuiltinExitFrame* exit_frame = BuiltinExitFrame::cast(frame); Handle fun = handle(exit_frame->function(), this); // Filter out internal frames that we do not want to show. if (!helper.IsVisibleInStackTrace(*fun)) continue; Handle recv(exit_frame->receiver(), this); Handle code(exit_frame->LookupCode(), this); const int offset = static_cast(exit_frame->pc() - code->instruction_start()); int flags = 0; if (helper.IsStrictFrame(*fun)) flags |= FrameArray::kIsStrict; if (exit_frame->IsConstructor()) flags |= FrameArray::kForceConstructor; elements = FrameArray::AppendJSFrame(elements, recv, fun, Handle::cast(code), offset, flags); } break; case StackFrame::WASM_COMPILED: { WasmCompiledFrame* wasm_frame = WasmCompiledFrame::cast(frame); Handle instance(wasm_frame->wasm_instance(), this); const int wasm_function_index = wasm_frame->function_index(); Code* code = wasm_frame->unchecked_code(); Handle abstract_code(AbstractCode::cast(code), this); const int offset = static_cast(wasm_frame->pc() - code->instruction_start()); int flags = 0; if (instance->compiled_module()->is_asm_js()) { flags |= FrameArray::kIsAsmJsWasmFrame; if (wasm_frame->at_to_number_conversion()) { flags |= FrameArray::kAsmJsAtNumberConversion; } } else { flags |= FrameArray::kIsWasmFrame; } elements = FrameArray::AppendWasmFrame(elements, instance, wasm_function_index, abstract_code, offset, flags); } break; case StackFrame::WASM_INTERPRETER_ENTRY: // TODO(clemensh): Add frames. break; default: break; } } elements->ShrinkToFit(); // TODO(yangguo): Queue this structured stack trace for preprocessing on GC. return factory()->NewJSArrayWithElements(elements); } MaybeHandle Isolate::CaptureAndSetDetailedStackTrace( Handle error_object) { if (capture_stack_trace_for_uncaught_exceptions_) { // Capture stack trace for a detailed exception message. Handle key = factory()->detailed_stack_trace_symbol(); Handle stack_trace = CaptureCurrentStackTrace( stack_trace_for_uncaught_exceptions_frame_limit_, stack_trace_for_uncaught_exceptions_options_); RETURN_ON_EXCEPTION( this, JSReceiver::SetProperty(error_object, key, stack_trace, STRICT), JSReceiver); } return error_object; } MaybeHandle Isolate::CaptureAndSetSimpleStackTrace( Handle error_object, FrameSkipMode mode, Handle caller) { // Capture stack trace for simple stack trace string formatting. Handle key = factory()->stack_trace_symbol(); Handle stack_trace = CaptureSimpleStackTrace(error_object, mode, caller); RETURN_ON_EXCEPTION( this, JSReceiver::SetProperty(error_object, key, stack_trace, STRICT), JSReceiver); return error_object; } Handle Isolate::GetDetailedStackTrace(Handle error_object) { Handle key_detailed = factory()->detailed_stack_trace_symbol(); Handle stack_trace = JSReceiver::GetDataProperty(error_object, key_detailed); if (stack_trace->IsJSArray()) return Handle::cast(stack_trace); return Handle(); } class CaptureStackTraceHelper { public: CaptureStackTraceHelper(Isolate* isolate, StackTrace::StackTraceOptions options) : isolate_(isolate) { if (options & StackTrace::kColumnOffset) { column_key_ = factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("column")); } if (options & StackTrace::kLineNumber) { line_key_ = factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("lineNumber")); } if (options & StackTrace::kScriptId) { script_id_key_ = factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("scriptId")); } if (options & StackTrace::kScriptName) { script_name_key_ = factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("scriptName")); } if (options & StackTrace::kScriptNameOrSourceURL) { script_name_or_source_url_key_ = factory()->InternalizeOneByteString( STATIC_CHAR_VECTOR("scriptNameOrSourceURL")); } if (options & StackTrace::kFunctionName) { function_key_ = factory()->InternalizeOneByteString( STATIC_CHAR_VECTOR("functionName")); } if (options & StackTrace::kIsEval) { eval_key_ = factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("isEval")); } if (options & StackTrace::kIsConstructor) { constructor_key_ = factory()->InternalizeOneByteString( STATIC_CHAR_VECTOR("isConstructor")); } } Handle NewStackFrameObject(FrameSummary& summ) { if (summ.IsJavaScript()) return NewStackFrameObject(summ.AsJavaScript()); if (summ.IsWasm()) return NewStackFrameObject(summ.AsWasm()); UNREACHABLE(); return Handle::null(); } Handle NewStackFrameObject( const FrameSummary::JavaScriptFrameSummary& summ) { Handle stack_frame = factory()->NewJSObject(isolate_->object_function()); Handle