// 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/debug/debug.h" #include <memory> #include "src/api.h" #include "src/arguments.h" #include "src/assembler-inl.h" #include "src/bootstrapper.h" #include "src/code-stubs.h" #include "src/compilation-cache.h" #include "src/compiler-dispatcher/optimizing-compile-dispatcher.h" #include "src/compiler.h" #include "src/debug/debug-evaluate.h" #include "src/debug/liveedit.h" #include "src/deoptimizer.h" #include "src/execution.h" #include "src/frames-inl.h" #include "src/global-handles.h" #include "src/globals.h" #include "src/interpreter/interpreter.h" #include "src/isolate-inl.h" #include "src/log.h" #include "src/messages.h" #include "src/objects/debug-objects-inl.h" #include "src/snapshot/natives.h" #include "src/wasm/wasm-objects-inl.h" #include "include/v8-debug.h" namespace v8 { namespace internal { Debug::Debug(Isolate* isolate) : debug_context_(Handle<Context>()), is_active_(false), hook_on_function_call_(false), is_suppressed_(false), live_edit_enabled_(false), break_disabled_(false), break_points_active_(true), break_on_exception_(false), break_on_uncaught_exception_(false), side_effect_check_failed_(false), debug_info_list_(nullptr), feature_tracker_(isolate), isolate_(isolate) { ThreadInit(); } BreakLocation BreakLocation::FromFrame(Handle<DebugInfo> debug_info, JavaScriptFrame* frame) { auto summary = FrameSummary::GetTop(frame).AsJavaScript(); int offset = summary.code_offset(); Handle<AbstractCode> abstract_code = summary.abstract_code(); BreakIterator it(debug_info); it.SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset)); return it.GetBreakLocation(); } void BreakLocation::AllAtCurrentStatement( Handle<DebugInfo> debug_info, JavaScriptFrame* frame, std::vector<BreakLocation>* result_out) { auto summary = FrameSummary::GetTop(frame).AsJavaScript(); int offset = summary.code_offset(); Handle<AbstractCode> abstract_code = summary.abstract_code(); if (abstract_code->IsCode()) offset = offset - 1; int statement_position; { BreakIterator it(debug_info); it.SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset)); statement_position = it.statement_position(); } for (BreakIterator it(debug_info); !it.Done(); it.Next()) { if (it.statement_position() == statement_position) { result_out->push_back(it.GetBreakLocation()); } } } int BreakLocation::BreakIndexFromCodeOffset(Handle<DebugInfo> debug_info, Handle<AbstractCode> abstract_code, int offset) { // Run through all break points to locate the one closest to the address. int closest_break = 0; int distance = kMaxInt; DCHECK(0 <= offset && offset < abstract_code->Size()); for (BreakIterator it(debug_info); !it.Done(); it.Next()) { // Check if this break point is closer that what was previously found. if (it.code_offset() <= offset && offset - it.code_offset() < distance) { closest_break = it.break_index(); distance = offset - it.code_offset(); // Check whether we can't get any closer. if (distance == 0) break; } } return closest_break; } bool BreakLocation::HasBreakPoint(Handle<DebugInfo> debug_info) const { // First check whether there is a break point with the same source position. if (!debug_info->HasBreakPoint(position_)) return false; // Then check whether a break point at that source position would have // the same code offset. Otherwise it's just a break location that we can // step to, but not actually a location where we can put a break point. DCHECK(abstract_code_->IsBytecodeArray()); BreakIterator it(debug_info); it.SkipToPosition(position_); return it.code_offset() == code_offset_; } debug::BreakLocationType BreakLocation::type() const { switch (type_) { case DEBUGGER_STATEMENT: return debug::kDebuggerStatementBreakLocation; case DEBUG_BREAK_SLOT_AT_CALL: return debug::kCallBreakLocation; case DEBUG_BREAK_SLOT_AT_RETURN: return debug::kReturnBreakLocation; default: return debug::kCommonBreakLocation; } return debug::kCommonBreakLocation; } BreakIterator::BreakIterator(Handle<DebugInfo> debug_info) : debug_info_(debug_info), break_index_(-1), source_position_iterator_( debug_info->DebugBytecodeArray()->SourcePositionTable()) { position_ = debug_info->shared()->start_position(); statement_position_ = position_; // There is at least one break location. DCHECK(!Done()); Next(); } int BreakIterator::BreakIndexFromPosition(int source_position) { int distance = kMaxInt; int closest_break = break_index(); while (!Done()) { int next_position = position(); if (source_position <= next_position && next_position - source_position < distance) { closest_break = break_index(); distance = next_position - source_position; // Check whether we can't get any closer. if (distance == 0) break; } Next(); } return closest_break; } void BreakIterator::Next() { DisallowHeapAllocation no_gc; DCHECK(!Done()); bool first = break_index_ == -1; while (!Done()) { if (!first) source_position_iterator_.Advance(); first = false; if (Done()) return; position_ = source_position_iterator_.source_position().ScriptOffset(); if (source_position_iterator_.is_statement()) { statement_position_ = position_; } DCHECK_LE(0, position_); DCHECK_LE(0, statement_position_); DebugBreakType type = GetDebugBreakType(); if (type != NOT_DEBUG_BREAK) break; } break_index_++; } DebugBreakType BreakIterator::GetDebugBreakType() { BytecodeArray* bytecode_array = debug_info_->OriginalBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); if (bytecode == interpreter::Bytecode::kDebugger) { return DEBUGGER_STATEMENT; } else if (bytecode == interpreter::Bytecode::kReturn) { return DEBUG_BREAK_SLOT_AT_RETURN; } else if (interpreter::Bytecodes::IsCallOrConstruct(bytecode)) { return DEBUG_BREAK_SLOT_AT_CALL; } else if (source_position_iterator_.is_statement()) { return DEBUG_BREAK_SLOT; } else { return NOT_DEBUG_BREAK; } } void BreakIterator::SkipToPosition(int position) { BreakIterator it(debug_info_); SkipTo(it.BreakIndexFromPosition(position)); } void BreakIterator::SetDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); BytecodeArray* bytecode_array = debug_info_->DebugBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); if (interpreter::Bytecodes::IsDebugBreak(bytecode)) return; interpreter::Bytecode debugbreak = interpreter::Bytecodes::GetDebugBreak(bytecode); bytecode_array->set(code_offset(), interpreter::Bytecodes::ToByte(debugbreak)); } void BreakIterator::ClearDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); BytecodeArray* bytecode_array = debug_info_->DebugBytecodeArray(); BytecodeArray* original = debug_info_->OriginalBytecodeArray(); bytecode_array->set(code_offset(), original->get(code_offset())); } BreakLocation BreakIterator::GetBreakLocation() { Handle<AbstractCode> code( AbstractCode::cast(debug_info_->DebugBytecodeArray())); return BreakLocation(code, GetDebugBreakType(), code_offset(), position_); } void DebugFeatureTracker::Track(DebugFeatureTracker::Feature feature) { uint32_t mask = 1 << feature; // Only count one sample per feature and isolate. if (bitfield_ & mask) return; isolate_->counters()->debug_feature_usage()->AddSample(feature); bitfield_ |= mask; } // Threading support. void Debug::ThreadInit() { thread_local_.break_count_ = 0; thread_local_.break_id_ = 0; thread_local_.break_frame_id_ = StackFrame::NO_ID; thread_local_.last_step_action_ = StepNone; thread_local_.last_statement_position_ = kNoSourcePosition; thread_local_.last_frame_count_ = -1; thread_local_.fast_forward_to_return_ = false; thread_local_.ignore_step_into_function_ = Smi::kZero; thread_local_.target_frame_count_ = -1; thread_local_.return_value_ = Smi::kZero; thread_local_.async_task_count_ = 0; thread_local_.last_breakpoint_id_ = 0; clear_suspended_generator(); thread_local_.restart_fp_ = nullptr; base::Relaxed_Store(&thread_local_.current_debug_scope_, static_cast<base::AtomicWord>(0)); UpdateHookOnFunctionCall(); } char* Debug::ArchiveDebug(char* storage) { // Simply reset state. Don't archive anything. ThreadInit(); return storage + ArchiveSpacePerThread(); } char* Debug::RestoreDebug(char* storage) { // Simply reset state. Don't restore anything. ThreadInit(); return storage + ArchiveSpacePerThread(); } int Debug::ArchiveSpacePerThread() { return 0; } void Debug::Iterate(RootVisitor* v) { v->VisitRootPointer(Root::kDebug, &thread_local_.return_value_); v->VisitRootPointer(Root::kDebug, &thread_local_.suspended_generator_); v->VisitRootPointer(Root::kDebug, &thread_local_.ignore_step_into_function_); } DebugInfoListNode::DebugInfoListNode(DebugInfo* debug_info) : next_(nullptr) { // Globalize the request debug info object and make it weak. GlobalHandles* global_handles = debug_info->GetIsolate()->global_handles(); debug_info_ = global_handles->Create(debug_info).location(); } DebugInfoListNode::~DebugInfoListNode() { if (debug_info_ == nullptr) return; GlobalHandles::Destroy(reinterpret_cast<Object**>(debug_info_)); debug_info_ = nullptr; } bool Debug::Load() { // Return if debugger is already loaded. if (is_loaded()) return true; // Bail out if we're already in the process of compiling the native // JavaScript source code for the debugger. if (is_suppressed_) return false; SuppressDebug while_loading(this); // Disable breakpoints and interrupts while compiling and running the // debugger scripts including the context creation code. DisableBreak disable(this); PostponeInterruptsScope postpone(isolate_); // Create the debugger context. HandleScope scope(isolate_); ExtensionConfiguration no_extensions; // TODO(yangguo): we rely on the fact that first context snapshot is usable // as debug context. This dependency is gone once we remove // debug context completely. static const int kFirstContextSnapshotIndex = 0; Handle<Context> context = isolate_->bootstrapper()->CreateEnvironment( MaybeHandle<JSGlobalProxy>(), v8::Local<ObjectTemplate>(), &no_extensions, kFirstContextSnapshotIndex, v8::DeserializeEmbedderFieldsCallback(), DEBUG_CONTEXT); // Fail if no context could be created. if (context.is_null()) return false; debug_context_ = isolate_->global_handles()->Create(*context); feature_tracker()->Track(DebugFeatureTracker::kActive); return true; } void Debug::Unload() { ClearAllBreakPoints(); ClearStepping(); RemoveAllCoverageInfos(); RemoveDebugDelegate(); // Return debugger is not loaded. if (!is_loaded()) return; // Clear debugger context global handle. GlobalHandles::Destroy(Handle<Object>::cast(debug_context_).location()); debug_context_ = Handle<Context>(); } void Debug::Break(JavaScriptFrame* frame) { // Initialize LiveEdit. LiveEdit::InitializeThreadLocal(this); // Just continue if breaks are disabled or debugger cannot be loaded. if (break_disabled()) return; // Enter the debugger. DebugScope debug_scope(this); if (debug_scope.failed()) return; // Postpone interrupt during breakpoint processing. PostponeInterruptsScope postpone(isolate_); DisableBreak no_recursive_break(this); // Return if we fail to retrieve debug info. Handle<JSFunction> function(frame->function()); Handle<SharedFunctionInfo> shared(function->shared()); if (!EnsureBreakInfo(shared)) return; Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_); // Find the break location where execution has stopped. BreakLocation location = BreakLocation::FromFrame(debug_info, frame); // Find actual break points, if any, and trigger debug break event. MaybeHandle<FixedArray> break_points_hit = CheckBreakPoints(debug_info, &location); if (!break_points_hit.is_null()) { // Clear all current stepping setup. ClearStepping(); // Notify the debug event listeners. OnDebugBreak(break_points_hit.ToHandleChecked()); return; } // No break point. Check for stepping. StepAction step_action = last_step_action(); int current_frame_count = CurrentFrameCount(); int target_frame_count = thread_local_.target_frame_count_; int last_frame_count = thread_local_.last_frame_count_; // StepOut at not return position was requested and return break locations // were flooded with one shots. if (thread_local_.fast_forward_to_return_) { DCHECK(location.IsReturn()); // We have to ignore recursive calls to function. if (current_frame_count > target_frame_count) return; ClearStepping(); PrepareStep(StepOut); return; } bool step_break = false; switch (step_action) { case StepNone: return; case StepOut: // Step out should not break in a deeper frame than target frame. if (current_frame_count > target_frame_count) return; step_break = true; break; case StepNext: // Step next should not break in a deeper frame than target frame. if (current_frame_count > target_frame_count) return; // Fall through. case StepIn: { FrameSummary summary = FrameSummary::GetTop(frame); step_break = step_break || location.IsReturn() || current_frame_count != last_frame_count || thread_local_.last_statement_position_ != summary.SourceStatementPosition(); break; } } // Clear all current stepping setup. ClearStepping(); if (step_break) { // Notify the debug event listeners. OnDebugBreak(isolate_->factory()->empty_fixed_array()); } else { // Re-prepare to continue. PrepareStep(step_action); } } // Find break point objects for this location, if any, and evaluate them. // Return an array of break point objects that evaluated true, or an empty // handle if none evaluated true. MaybeHandle<FixedArray> Debug::CheckBreakPoints(Handle<DebugInfo> debug_info, BreakLocation* location, bool* has_break_points) { bool has_break_points_to_check = break_points_active_ && location->HasBreakPoint(debug_info); if (has_break_points) *has_break_points = has_break_points_to_check; if (!has_break_points_to_check) return {}; Handle<Object> break_point_objects = debug_info->GetBreakPointObjects(location->position()); return Debug::GetHitBreakPointObjects(break_point_objects); } bool Debug::IsMutedAtCurrentLocation(JavaScriptFrame* frame) { HandleScope scope(isolate_); // A break location is considered muted if break locations on the current // statement have at least one break point, and all of these break points // evaluate to false. Aside from not triggering a debug break event at the // break location, we also do not trigger one for debugger statements, nor // an exception event on exception at this location. FrameSummary summary = FrameSummary::GetTop(frame); DCHECK(!summary.IsWasm()); Handle<JSFunction> function = summary.AsJavaScript().function(); if (!function->shared()->HasBreakInfo()) return false; Handle<DebugInfo> debug_info(function->shared()->GetDebugInfo()); // Enter the debugger. DebugScope debug_scope(this); if (debug_scope.failed()) return false; std::vector<BreakLocation> break_locations; BreakLocation::AllAtCurrentStatement(debug_info, frame, &break_locations); bool has_break_points_at_all = false; for (size_t i = 0; i < break_locations.size(); i++) { bool has_break_points; MaybeHandle<FixedArray> check_result = CheckBreakPoints(debug_info, &break_locations[i], &has_break_points); has_break_points_at_all |= has_break_points; if (has_break_points && !check_result.is_null()) return false; } return has_break_points_at_all; } MaybeHandle<Object> Debug::CallFunction(const char* name, int argc, Handle<Object> args[], bool catch_exceptions) { AllowJavascriptExecutionDebugOnly allow_script(isolate_); PostponeInterruptsScope no_interrupts(isolate_); AssertDebugContext(); Handle<JSReceiver> holder = Handle<JSReceiver>::cast(isolate_->natives_utils_object()); Handle<JSFunction> fun = Handle<JSFunction>::cast( JSReceiver::GetProperty(isolate_, holder, name).ToHandleChecked()); Handle<Object> undefined = isolate_->factory()->undefined_value(); if (catch_exceptions) { MaybeHandle<Object> maybe_exception; return Execution::TryCall(isolate_, fun, undefined, argc, args, Execution::MessageHandling::kReport, &maybe_exception); } else { return Execution::Call(isolate_, fun, undefined, argc, args); } } // Check whether a single break point object is triggered. bool Debug::CheckBreakPoint(Handle<Object> break_point_object) { Factory* factory = isolate_->factory(); HandleScope scope(isolate_); // TODO(kozyatinskiy): replace this if by DCHEK once the JS debug API has been // removed. if (break_point_object->IsBreakPoint()) { Handle<BreakPoint> break_point = Handle<BreakPoint>::cast(break_point_object); if (!break_point->condition()->length()) return true; Handle<String> condition(break_point->condition()); Handle<Object> result; // Since we call CheckBreakpoint only for deoptimized frame on top of stack, // we can use 0 as index of inlined frame. if (!DebugEvaluate::Local(isolate_, break_frame_id(), /* inlined_jsframe_index */ 0, condition, false) .ToHandle(&result)) { if (isolate_->has_pending_exception()) { isolate_->clear_pending_exception(); } return false; } return result->BooleanValue(); } // Ignore check if break point object is not a JSObject. if (!break_point_object->IsJSObject()) return true; // Get the break id as an object. Handle<Object> break_id = factory->NewNumberFromInt(Debug::break_id()); // Call IsBreakPointTriggered. Handle<Object> argv[] = { break_id, break_point_object }; Handle<Object> result; if (!CallFunction("IsBreakPointTriggered", arraysize(argv), argv) .ToHandle(&result)) { return false; } // Return whether the break point is triggered. return result->IsTrue(isolate_); } bool Debug::SetBreakPoint(Handle<JSFunction> function, Handle<Object> break_point_object, int* source_position) { HandleScope scope(isolate_); // Make sure the function is compiled and has set up the debug info. Handle<SharedFunctionInfo> shared(function->shared()); if (!EnsureBreakInfo(shared)) return true; PrepareFunctionForBreakPoints(shared); Handle<DebugInfo> debug_info(shared->GetDebugInfo()); // Source positions starts with zero. DCHECK_LE(0, *source_position); // Find the break point and change it. *source_position = FindBreakablePosition(debug_info, *source_position); DebugInfo::SetBreakPoint(debug_info, *source_position, break_point_object); // At least one active break point now. DCHECK_LT(0, debug_info->GetBreakPointCount()); ClearBreakPoints(debug_info); ApplyBreakPoints(debug_info); feature_tracker()->Track(DebugFeatureTracker::kBreakPoint); return true; } bool Debug::SetBreakPointForScript(Handle<Script> script, Handle<Object> break_point_object, int* source_position) { if (script->type() == Script::TYPE_WASM) { Handle<WasmCompiledModule> compiled_module( WasmCompiledModule::cast(script->wasm_compiled_module()), isolate_); return WasmCompiledModule::SetBreakPoint(compiled_module, source_position, break_point_object); } HandleScope scope(isolate_); // Obtain shared function info for the function. Handle<Object> result = FindSharedFunctionInfoInScript(script, *source_position); if (result->IsUndefined(isolate_)) return false; // Make sure the function has set up the debug info. Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>::cast(result); if (!EnsureBreakInfo(shared)) return false; PrepareFunctionForBreakPoints(shared); // Find position within function. The script position might be before the // source position of the first function. if (shared->start_position() > *source_position) { *source_position = shared->start_position(); } Handle<DebugInfo> debug_info(shared->GetDebugInfo()); // Find breakable position returns first breakable position after // *source_position, it can return 0 if no break location is found after // *source_position. int breakable_position = FindBreakablePosition(debug_info, *source_position); if (breakable_position < *source_position) return false; *source_position = breakable_position; DebugInfo::SetBreakPoint(debug_info, *source_position, break_point_object); // At least one active break point now. DCHECK_LT(0, debug_info->GetBreakPointCount()); ClearBreakPoints(debug_info); ApplyBreakPoints(debug_info); feature_tracker()->Track(DebugFeatureTracker::kBreakPoint); return true; } int Debug::FindBreakablePosition(Handle<DebugInfo> debug_info, int source_position) { DCHECK(debug_info->HasDebugBytecodeArray()); BreakIterator it(debug_info); it.SkipToPosition(source_position); return it.position(); } void Debug::ApplyBreakPoints(Handle<DebugInfo> debug_info) { DisallowHeapAllocation no_gc; if (debug_info->break_points()->IsUndefined(isolate_)) return; FixedArray* break_points = debug_info->break_points(); for (int i = 0; i < break_points->length(); i++) { if (break_points->get(i)->IsUndefined(isolate_)) continue; BreakPointInfo* info = BreakPointInfo::cast(break_points->get(i)); if (info->GetBreakPointCount() == 0) continue; DCHECK(debug_info->HasDebugBytecodeArray()); BreakIterator it(debug_info); it.SkipToPosition(info->source_position()); it.SetDebugBreak(); } } void Debug::ClearBreakPoints(Handle<DebugInfo> debug_info) { // If we attempt to clear breakpoints but none exist, simply return. This can // happen e.g. CoverageInfos exit but no breakpoints are set. if (!debug_info->HasDebugBytecodeArray()) return; DisallowHeapAllocation no_gc; for (BreakIterator it(debug_info); !it.Done(); it.Next()) { it.ClearDebugBreak(); } } void Debug::ClearBreakPoint(Handle<Object> break_point_object) { HandleScope scope(isolate_); for (DebugInfoListNode* node = debug_info_list_; node != nullptr; node = node->next()) { Handle<Object> result = DebugInfo::FindBreakPointInfo(node->debug_info(), break_point_object); if (result->IsUndefined(isolate_)) continue; Handle<DebugInfo> debug_info = node->debug_info(); if (DebugInfo::ClearBreakPoint(debug_info, break_point_object)) { ClearBreakPoints(debug_info); if (debug_info->GetBreakPointCount() == 0) { RemoveBreakInfoAndMaybeFree(debug_info); } else { ApplyBreakPoints(debug_info); } return; } } } bool Debug::SetBreakpoint(Handle<Script> script, Handle<String> condition, int* offset, int* id) { *id = ++thread_local_.last_breakpoint_id_; Handle<BreakPoint> breakpoint = isolate_->factory()->NewBreakPoint(*id, condition); return SetBreakPointForScript(script, breakpoint, offset); } void Debug::RemoveBreakpoint(int id) { Handle<BreakPoint> breakpoint = isolate_->factory()->NewBreakPoint( id, isolate_->factory()->empty_string()); ClearBreakPoint(breakpoint); } // Clear out all the debug break code. void Debug::ClearAllBreakPoints() { ClearAllDebugInfos([=](Handle<DebugInfo> info) { ClearBreakPoints(info); return info->ClearBreakInfo(); }); } void Debug::FloodWithOneShot(Handle<SharedFunctionInfo> shared, bool returns_only) { if (IsBlackboxed(shared)) return; // Make sure the function is compiled and has set up the debug info. if (!EnsureBreakInfo(shared)) return; PrepareFunctionForBreakPoints(shared); Handle<DebugInfo> debug_info(shared->GetDebugInfo()); // Flood the function with break points. DCHECK(debug_info->HasDebugBytecodeArray()); for (BreakIterator it(debug_info); !it.Done(); it.Next()) { if (returns_only && !it.GetBreakLocation().IsReturn()) continue; it.SetDebugBreak(); } } void Debug::ChangeBreakOnException(ExceptionBreakType type, bool enable) { if (type == BreakUncaughtException) { break_on_uncaught_exception_ = enable; } else { break_on_exception_ = enable; } } bool Debug::IsBreakOnException(ExceptionBreakType type) { if (type == BreakUncaughtException) { return break_on_uncaught_exception_; } else { return break_on_exception_; } } MaybeHandle<FixedArray> Debug::GetHitBreakPointObjects( Handle<Object> break_point_objects) { DCHECK(!break_point_objects->IsUndefined(isolate_)); if (!break_point_objects->IsFixedArray()) { if (!CheckBreakPoint(break_point_objects)) return {}; Handle<FixedArray> break_points_hit = isolate_->factory()->NewFixedArray(1); break_points_hit->set(0, *break_point_objects); return break_points_hit; } Handle<FixedArray> array(FixedArray::cast(*break_point_objects)); int num_objects = array->length(); Handle<FixedArray> break_points_hit = isolate_->factory()->NewFixedArray(num_objects); int break_points_hit_count = 0; for (int i = 0; i < num_objects; ++i) { Handle<Object> break_point_object(array->get(i), isolate_); if (CheckBreakPoint(break_point_object)) { break_points_hit->set(break_points_hit_count++, *break_point_object); } } if (break_points_hit_count == 0) return {}; break_points_hit->Shrink(break_points_hit_count); return break_points_hit; } void Debug::PrepareStepIn(Handle<JSFunction> function) { CHECK(last_step_action() >= StepIn); if (ignore_events()) return; if (in_debug_scope()) return; if (break_disabled()) return; Handle<SharedFunctionInfo> shared(function->shared()); if (IsBlackboxed(shared)) return; if (*function == thread_local_.ignore_step_into_function_) return; thread_local_.ignore_step_into_function_ = Smi::kZero; FloodWithOneShot(Handle<SharedFunctionInfo>(function->shared(), isolate_)); } void Debug::PrepareStepInSuspendedGenerator() { CHECK(has_suspended_generator()); if (ignore_events()) return; if (in_debug_scope()) return; if (break_disabled()) return; thread_local_.last_step_action_ = StepIn; UpdateHookOnFunctionCall(); Handle<JSFunction> function( JSGeneratorObject::cast(thread_local_.suspended_generator_)->function()); FloodWithOneShot(Handle<SharedFunctionInfo>(function->shared(), isolate_)); clear_suspended_generator(); } void Debug::PrepareStepOnThrow() { if (last_step_action() == StepNone) return; if (ignore_events()) return; if (in_debug_scope()) return; if (break_disabled()) return; ClearOneShot(); int current_frame_count = CurrentFrameCount(); // Iterate through the JavaScript stack looking for handlers. JavaScriptFrameIterator it(isolate_); while (!it.done()) { JavaScriptFrame* frame = it.frame(); if (frame->LookupExceptionHandlerInTable(nullptr, nullptr) > 0) break; std::vector<SharedFunctionInfo*> infos; frame->GetFunctions(&infos); current_frame_count -= infos.size(); it.Advance(); } // No handler found. Nothing to instrument. if (it.done()) return; bool found_handler = false; // Iterate frames, including inlined frames. First, find the handler frame. // Then skip to the frame we want to break in, then instrument for stepping. for (; !it.done(); it.Advance()) { JavaScriptFrame* frame = JavaScriptFrame::cast(it.frame()); if (last_step_action() == StepIn) { // Deoptimize frame to ensure calls are checked for step-in. Deoptimizer::DeoptimizeFunction(frame->function()); } std::vector<FrameSummary> summaries; frame->Summarize(&summaries); for (size_t i = summaries.size(); i != 0; i--, current_frame_count--) { const FrameSummary& summary = summaries[i - 1]; if (!found_handler) { // We have yet to find the handler. If the frame inlines multiple // functions, we have to check each one for the handler. // If it only contains one function, we already found the handler. if (summaries.size() > 1) { Handle<AbstractCode> code = summary.AsJavaScript().abstract_code(); CHECK_EQ(AbstractCode::INTERPRETED_FUNCTION, code->kind()); BytecodeArray* bytecode = code->GetBytecodeArray(); HandlerTable* table = HandlerTable::cast(bytecode->handler_table()); int code_offset = summary.code_offset(); HandlerTable::CatchPrediction prediction; int index = table->LookupRange(code_offset, nullptr, &prediction); if (index > 0) found_handler = true; } else { found_handler = true; } } if (found_handler) { // We found the handler. If we are stepping next or out, we need to // iterate until we found the suitable target frame to break in. if ((last_step_action() == StepNext || last_step_action() == StepOut) && current_frame_count > thread_local_.target_frame_count_) { continue; } Handle<SharedFunctionInfo> info( summary.AsJavaScript().function()->shared()); if (IsBlackboxed(info)) continue; FloodWithOneShot(info); return; } } } } void Debug::PrepareStep(StepAction step_action) { HandleScope scope(isolate_); DCHECK(in_debug_scope()); // Get the frame where the execution has stopped and skip the debug frame if // any. The debug frame will only be present if execution was stopped due to // hitting a break point. In other situations (e.g. unhandled exception) the // debug frame is not present. StackFrame::Id frame_id = break_frame_id(); // If there is no JavaScript stack don't do anything. if (frame_id == StackFrame::NO_ID) return; feature_tracker()->Track(DebugFeatureTracker::kStepping); thread_local_.last_step_action_ = step_action; StackTraceFrameIterator frames_it(isolate_, frame_id); StandardFrame* frame = frames_it.frame(); // Handle stepping in wasm functions via the wasm interpreter. if (frame->is_wasm()) { // If the top frame is compiled, we cannot step. if (frame->is_wasm_compiled()) return; WasmInterpreterEntryFrame* wasm_frame = WasmInterpreterEntryFrame::cast(frame); wasm_frame->wasm_instance()->debug_info()->PrepareStep(step_action); return; } JavaScriptFrame* js_frame = JavaScriptFrame::cast(frame); DCHECK(js_frame->function()->IsJSFunction()); // Get the debug info (create it if it does not exist). auto summary = FrameSummary::GetTop(frame).AsJavaScript(); Handle<JSFunction> function(summary.function()); Handle<SharedFunctionInfo> shared(function->shared()); if (!EnsureBreakInfo(shared)) return; Handle<DebugInfo> debug_info(shared->GetDebugInfo()); BreakLocation location = BreakLocation::FromFrame(debug_info, js_frame); // Any step at a return is a step-out and we need to schedule DebugOnFunction // call callback. if (location.IsReturn()) { // On StepOut we'll ignore our further calls to current function in // PrepareStepIn callback. if (last_step_action() == StepOut) { thread_local_.ignore_step_into_function_ = *function; } step_action = StepOut; thread_local_.last_step_action_ = StepIn; } UpdateHookOnFunctionCall(); // A step-next in blackboxed function is a step-out. if (step_action == StepNext && IsBlackboxed(shared)) step_action = StepOut; thread_local_.last_statement_position_ = summary.abstract_code()->SourceStatementPosition(summary.code_offset()); int current_frame_count = CurrentFrameCount(); thread_local_.last_frame_count_ = current_frame_count; // No longer perform the current async step. clear_suspended_generator(); switch (step_action) { case StepNone: UNREACHABLE(); break; case StepOut: { // Clear last position info. For stepping out it does not matter. thread_local_.last_statement_position_ = kNoSourcePosition; thread_local_.last_frame_count_ = -1; if (!location.IsReturn() && !IsBlackboxed(shared)) { // At not return position we flood return positions with one shots and // will repeat StepOut automatically at next break. thread_local_.target_frame_count_ = current_frame_count; thread_local_.fast_forward_to_return_ = true; FloodWithOneShot(shared, true); return; } // Skip the current frame, find the first frame we want to step out to // and deoptimize every frame along the way. bool in_current_frame = true; for (; !frames_it.done(); frames_it.Advance()) { // TODO(clemensh): Implement stepping out from JS to wasm. if (frames_it.frame()->is_wasm()) continue; JavaScriptFrame* frame = JavaScriptFrame::cast(frames_it.frame()); if (last_step_action() == StepIn) { // Deoptimize frame to ensure calls are checked for step-in. Deoptimizer::DeoptimizeFunction(frame->function()); } HandleScope scope(isolate_); std::vector<Handle<SharedFunctionInfo>> infos; frame->GetFunctions(&infos); for (; !infos.empty(); current_frame_count--) { Handle<SharedFunctionInfo> info = infos.back(); infos.pop_back(); if (in_current_frame) { // We want to skip out, so skip the current frame. in_current_frame = false; continue; } if (IsBlackboxed(info)) continue; FloodWithOneShot(info); thread_local_.target_frame_count_ = current_frame_count; return; } } break; } case StepNext: thread_local_.target_frame_count_ = current_frame_count; // Fall through. case StepIn: // TODO(clemensh): Implement stepping from JS into wasm. FloodWithOneShot(shared); break; } } // Simple function for returning the source positions for active break points. Handle<Object> Debug::GetSourceBreakLocations( Handle<SharedFunctionInfo> shared) { Isolate* isolate = shared->GetIsolate(); if (!shared->HasBreakInfo()) { return isolate->factory()->undefined_value(); } Handle<DebugInfo> debug_info(shared->GetDebugInfo()); if (debug_info->GetBreakPointCount() == 0) { return isolate->factory()->undefined_value(); } Handle<FixedArray> locations = isolate->factory()->NewFixedArray(debug_info->GetBreakPointCount()); int count = 0; for (int i = 0; i < debug_info->break_points()->length(); ++i) { if (!debug_info->break_points()->get(i)->IsUndefined(isolate)) { BreakPointInfo* break_point_info = BreakPointInfo::cast(debug_info->break_points()->get(i)); int break_points = break_point_info->GetBreakPointCount(); if (break_points == 0) continue; for (int j = 0; j < break_points; ++j) { locations->set(count++, Smi::FromInt(break_point_info->source_position())); } } } return locations; } void Debug::ClearStepping() { // Clear the various stepping setup. ClearOneShot(); thread_local_.last_step_action_ = StepNone; thread_local_.last_statement_position_ = kNoSourcePosition; thread_local_.ignore_step_into_function_ = Smi::kZero; thread_local_.fast_forward_to_return_ = false; thread_local_.last_frame_count_ = -1; thread_local_.target_frame_count_ = -1; UpdateHookOnFunctionCall(); } // Clears all the one-shot break points that are currently set. Normally this // function is called each time a break point is hit as one shot break points // are used to support stepping. void Debug::ClearOneShot() { // The current implementation just runs through all the breakpoints. When the // last break point for a function is removed that function is automatically // removed from the list. for (DebugInfoListNode* node = debug_info_list_; node != nullptr; node = node->next()) { Handle<DebugInfo> debug_info = node->debug_info(); ClearBreakPoints(debug_info); ApplyBreakPoints(debug_info); } } class RedirectActiveFunctions : public ThreadVisitor { public: explicit RedirectActiveFunctions(SharedFunctionInfo* shared) : shared_(shared) { DCHECK(shared->HasBytecodeArray()); } void VisitThread(Isolate* isolate, ThreadLocalTop* top) { for (JavaScriptFrameIterator it(isolate, top); !it.done(); it.Advance()) { JavaScriptFrame* frame = it.frame(); JSFunction* function = frame->function(); if (!frame->is_interpreted()) continue; if (function->shared() != shared_) continue; InterpretedFrame* interpreted_frame = reinterpret_cast<InterpretedFrame*>(frame); BytecodeArray* debug_copy = shared_->GetDebugInfo()->DebugBytecodeArray(); interpreted_frame->PatchBytecodeArray(debug_copy); } } private: SharedFunctionInfo* shared_; DisallowHeapAllocation no_gc_; }; void Debug::DeoptimizeFunction(Handle<SharedFunctionInfo> shared) { // Deoptimize all code compiled from this shared function info including // inlining. if (isolate_->concurrent_recompilation_enabled()) { isolate_->optimizing_compile_dispatcher()->Flush( OptimizingCompileDispatcher::BlockingBehavior::kBlock); } // Make sure we abort incremental marking. isolate_->heap()->CollectAllGarbage(Heap::kMakeHeapIterableMask, GarbageCollectionReason::kDebugger); bool found_something = false; Code::OptimizedCodeIterator iterator(isolate_); while (Code* code = iterator.Next()) { if (code->Inlines(*shared)) { code->set_marked_for_deoptimization(true); found_something = true; } } if (found_something) { // Only go through with the deoptimization if something was found. Deoptimizer::DeoptimizeMarkedCode(isolate_); } } void Debug::PrepareFunctionForBreakPoints(Handle<SharedFunctionInfo> shared) { // To prepare bytecode for debugging, we already need to have the debug // info (containing the debug copy) upfront, but since we do not recompile, // preparing for break points cannot fail. DCHECK(shared->is_compiled()); DCHECK(shared->HasDebugInfo()); DCHECK(shared->HasBreakInfo()); Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared); if (debug_info->IsPreparedForBreakpoints()) return; DeoptimizeFunction(shared); // Update PCs on the stack to point to recompiled code. RedirectActiveFunctions redirect_visitor(*shared); redirect_visitor.VisitThread(isolate_, isolate_->thread_local_top()); isolate_->thread_manager()->IterateArchivedThreads(&redirect_visitor); debug_info->set_flags(debug_info->flags() | DebugInfo::kPreparedForBreakpoints); } namespace { template <typename Iterator> void GetBreakablePositions(Iterator* it, int start_position, int end_position, std::vector<BreakLocation>* locations) { while (!it->Done()) { if (it->position() >= start_position && it->position() < end_position) { locations->push_back(it->GetBreakLocation()); } it->Next(); } } void FindBreakablePositions(Handle<DebugInfo> debug_info, int start_position, int end_position, std::vector<BreakLocation>* locations) { DCHECK(debug_info->HasDebugBytecodeArray()); BreakIterator it(debug_info); GetBreakablePositions(&it, start_position, end_position, locations); } } // namespace bool Debug::GetPossibleBreakpoints(Handle<Script> script, int start_position, int end_position, bool restrict_to_function, std::vector<BreakLocation>* locations) { if (restrict_to_function) { Handle<Object> result = FindSharedFunctionInfoInScript(script, start_position); if (result->IsUndefined(isolate_)) return false; // Make sure the function has set up the debug info. Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>::cast(result); if (!EnsureBreakInfo(shared)) return false; Handle<DebugInfo> debug_info(shared->GetDebugInfo()); FindBreakablePositions(debug_info, start_position, end_position, locations); return true; } while (true) { HandleScope scope(isolate_); std::vector<Handle<SharedFunctionInfo>> candidates; SharedFunctionInfo::ScriptIterator iterator(script); for (SharedFunctionInfo* info = iterator.Next(); info != nullptr; info = iterator.Next()) { if (info->end_position() < start_position || info->start_position() >= end_position) { continue; } if (!info->IsSubjectToDebugging()) continue; if (!info->is_compiled() && !info->allows_lazy_compilation()) continue; candidates.push_back(i::handle(info)); } bool was_compiled = false; for (const auto& candidate : candidates) { // Code that cannot be compiled lazily are internal and not debuggable. DCHECK(candidate->allows_lazy_compilation()); if (!candidate->is_compiled()) { if (!Compiler::Compile(candidate, Compiler::CLEAR_EXCEPTION)) { return false; } else { was_compiled = true; } } if (!EnsureBreakInfo(candidate)) return false; } if (was_compiled) continue; for (const auto& candidate : candidates) { CHECK(candidate->HasBreakInfo()); Handle<DebugInfo> debug_info(candidate->GetDebugInfo()); FindBreakablePositions(debug_info, start_position, end_position, locations); } return true; } UNREACHABLE(); } void Debug::RecordGenerator(Handle<JSGeneratorObject> generator_object) { if (last_step_action() <= StepOut) return; if (last_step_action() == StepNext) { // Only consider this generator a step-next target if not stepping in. if (thread_local_.target_frame_count_ < CurrentFrameCount()) return; } DCHECK(!has_suspended_generator()); thread_local_.suspended_generator_ = *generator_object; ClearStepping(); } class SharedFunctionInfoFinder { public: explicit SharedFunctionInfoFinder(int target_position) : current_candidate_(nullptr), current_candidate_closure_(nullptr), current_start_position_(kNoSourcePosition), target_position_(target_position) {} void NewCandidate(SharedFunctionInfo* shared, JSFunction* closure = nullptr) { if (!shared->IsSubjectToDebugging()) return; int start_position = shared->function_token_position(); if (start_position == kNoSourcePosition) { start_position = shared->start_position(); } if (start_position > target_position_) return; if (target_position_ > shared->end_position()) return; if (current_candidate_ != nullptr) { if (current_start_position_ == start_position && shared->end_position() == current_candidate_->end_position()) { // If we already have a matching closure, do not throw it away. if (current_candidate_closure_ != nullptr && closure == nullptr) return; // If a top-level function contains only one function // declaration the source for the top-level and the function // is the same. In that case prefer the non top-level function. if (!current_candidate_->is_toplevel() && shared->is_toplevel()) return; } else if (start_position < current_start_position_ || current_candidate_->end_position() < shared->end_position()) { return; } } current_start_position_ = start_position; current_candidate_ = shared; current_candidate_closure_ = closure; } SharedFunctionInfo* Result() { return current_candidate_; } JSFunction* ResultClosure() { return current_candidate_closure_; } private: SharedFunctionInfo* current_candidate_; JSFunction* current_candidate_closure_; int current_start_position_; int target_position_; DisallowHeapAllocation no_gc_; }; // We need to find a SFI for a literal that may not yet have been compiled yet, // and there may not be a JSFunction referencing it. Find the SFI closest to // the given position, compile it to reveal possible inner SFIs and repeat. // While we are at this, also ensure code with debug break slots so that we do // not have to compile a SFI without JSFunction, which is paifu for those that // cannot be compiled without context (need to find outer compilable SFI etc.) Handle<Object> Debug::FindSharedFunctionInfoInScript(Handle<Script> script, int position) { for (int iteration = 0;; iteration++) { // Go through all shared function infos associated with this script to // find the inner most function containing this position. // If there is no shared function info for this script at all, there is // no point in looking for it by walking the heap. SharedFunctionInfo* shared; { SharedFunctionInfoFinder finder(position); SharedFunctionInfo::ScriptIterator iterator(script); for (SharedFunctionInfo* info = iterator.Next(); info != nullptr; info = iterator.Next()) { finder.NewCandidate(info); } shared = finder.Result(); if (shared == nullptr) break; // We found it if it's already compiled. if (shared->is_compiled()) { Handle<SharedFunctionInfo> shared_handle(shared); // If the iteration count is larger than 1, we had to compile the outer // function in order to create this shared function info. So there can // be no JSFunction referencing it. We can anticipate creating a debug // info while bypassing PrepareFunctionForBreakpoints. if (iteration > 1) { AllowHeapAllocation allow_before_return; CreateBreakInfo(shared_handle); } return shared_handle; } } // If not, compile to reveal inner functions. HandleScope scope(isolate_); // Code that cannot be compiled lazily are internal and not debuggable. DCHECK(shared->allows_lazy_compilation()); if (!Compiler::Compile(handle(shared), Compiler::CLEAR_EXCEPTION)) break; } return isolate_->factory()->undefined_value(); } // Ensures the debug information is present for shared. bool Debug::EnsureBreakInfo(Handle<SharedFunctionInfo> shared) { // Return if we already have the break info for shared. if (shared->HasBreakInfo()) return true; if (!shared->IsSubjectToDebugging()) return false; if (!shared->is_compiled() && !Compiler::Compile(shared, Compiler::CLEAR_EXCEPTION)) { return false; } CreateBreakInfo(shared); return true; } void Debug::CreateBreakInfo(Handle<SharedFunctionInfo> shared) { HandleScope scope(isolate_); Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared); // Initialize with break information. DCHECK(!debug_info->HasBreakInfo()); Factory* factory = isolate_->factory(); Handle<FixedArray> break_points( factory->NewFixedArray(DebugInfo::kEstimatedNofBreakPointsInFunction)); // Make a copy of the bytecode array if available. Handle<Object> maybe_debug_bytecode_array = factory->undefined_value(); if (shared->HasBytecodeArray()) { Handle<BytecodeArray> original(shared->bytecode_array()); maybe_debug_bytecode_array = factory->CopyBytecodeArray(original); } debug_info->set_flags(debug_info->flags() | DebugInfo::kHasBreakInfo); debug_info->set_debug_bytecode_array(*maybe_debug_bytecode_array); debug_info->set_break_points(*break_points); } Handle<DebugInfo> Debug::GetOrCreateDebugInfo( Handle<SharedFunctionInfo> shared) { if (shared->HasDebugInfo()) return handle(shared->GetDebugInfo()); // Create debug info and add it to the list. Handle<DebugInfo> debug_info = isolate_->factory()->NewDebugInfo(shared); DebugInfoListNode* node = new DebugInfoListNode(*debug_info); node->set_next(debug_info_list_); debug_info_list_ = node; return debug_info; } void Debug::InstallCoverageInfo(Handle<SharedFunctionInfo> shared, Handle<CoverageInfo> coverage_info) { DCHECK(!coverage_info.is_null()); Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared); DCHECK(!debug_info->HasCoverageInfo()); debug_info->set_flags(debug_info->flags() | DebugInfo::kHasCoverageInfo); debug_info->set_coverage_info(*coverage_info); } void Debug::RemoveAllCoverageInfos() { ClearAllDebugInfos( [=](Handle<DebugInfo> info) { return info->ClearCoverageInfo(); }); } void Debug::FindDebugInfo(Handle<DebugInfo> debug_info, DebugInfoListNode** prev, DebugInfoListNode** curr) { HandleScope scope(isolate_); *prev = nullptr; *curr = debug_info_list_; while (*curr != nullptr) { if ((*curr)->debug_info().is_identical_to(debug_info)) return; *prev = *curr; *curr = (*curr)->next(); } UNREACHABLE(); } void Debug::ClearAllDebugInfos(DebugInfoClearFunction clear_function) { DebugInfoListNode* prev = nullptr; DebugInfoListNode* current = debug_info_list_; while (current != nullptr) { DebugInfoListNode* next = current->next(); Handle<DebugInfo> debug_info = current->debug_info(); if (clear_function(debug_info)) { FreeDebugInfoListNode(prev, current); current = next; } else { prev = current; current = next; } } } void Debug::RemoveBreakInfoAndMaybeFree(Handle<DebugInfo> debug_info) { bool should_unlink = debug_info->ClearBreakInfo(); if (should_unlink) { DebugInfoListNode* prev; DebugInfoListNode* node; FindDebugInfo(debug_info, &prev, &node); FreeDebugInfoListNode(prev, node); } } void Debug::FreeDebugInfoListNode(DebugInfoListNode* prev, DebugInfoListNode* node) { DCHECK(node->debug_info()->IsEmpty()); // Unlink from list. If prev is nullptr we are looking at the first element. if (prev == nullptr) { debug_info_list_ = node->next(); } else { prev->set_next(node->next()); } // Pack debugger hints back into the SFI::debug_info field. Handle<DebugInfo> debug_info(node->debug_info()); debug_info->shared()->set_debug_info( Smi::FromInt(debug_info->debugger_hints())); delete node; } bool Debug::IsBreakAtReturn(JavaScriptFrame* frame) { HandleScope scope(isolate_); // Get the executing function in which the debug break occurred. Handle<SharedFunctionInfo> shared(frame->function()->shared()); // With no debug info there are no break points, so we can't be at a return. if (!shared->HasBreakInfo()) return false; DCHECK(!frame->is_optimized()); Handle<DebugInfo> debug_info(shared->GetDebugInfo()); BreakLocation location = BreakLocation::FromFrame(debug_info, frame); return location.IsReturn(); } void Debug::ScheduleFrameRestart(StackFrame* frame) { // Set a target FP for the FrameDropperTrampoline builtin to drop to once // we return from the debugger. DCHECK(frame->is_java_script()); // Only reschedule to a frame further below a frame we already scheduled for. if (frame->fp() <= thread_local_.restart_fp_) return; // If the frame is optimized, trigger a deopt and jump into the // FrameDropperTrampoline in the deoptimizer. thread_local_.restart_fp_ = frame->fp(); // Reset break frame ID to the frame below the restarted frame. StackTraceFrameIterator it(isolate_); thread_local_.break_frame_id_ = StackFrame::NO_ID; for (StackTraceFrameIterator it(isolate_); !it.done(); it.Advance()) { if (it.frame()->fp() > thread_local_.restart_fp_) { thread_local_.break_frame_id_ = it.frame()->id(); return; } } } bool Debug::IsDebugGlobal(JSGlobalObject* global) { return is_loaded() && global == debug_context()->global_object(); } Handle<FixedArray> Debug::GetLoadedScripts() { isolate_->heap()->CollectAllGarbage(Heap::kFinalizeIncrementalMarkingMask, GarbageCollectionReason::kDebugger); Factory* factory = isolate_->factory(); if (!factory->script_list()->IsWeakFixedArray()) { return factory->empty_fixed_array(); } Handle<WeakFixedArray> array = Handle<WeakFixedArray>::cast(factory->script_list()); Handle<FixedArray> results = factory->NewFixedArray(array->Length()); int length = 0; { Script::Iterator iterator(isolate_); Script* script; while ((script = iterator.Next()) != nullptr) { if (script->HasValidSource()) results->set(length++, script); } } results->Shrink(length); return results; } MaybeHandle<Object> Debug::MakeExecutionState() { // Create the execution state object. Handle<Object> argv[] = { isolate_->factory()->NewNumberFromInt(break_id()) }; return CallFunction("MakeExecutionState", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeBreakEvent(Handle<Object> break_points_hit) { // Create the new break event object. Handle<Object> argv[] = { isolate_->factory()->NewNumberFromInt(break_id()), break_points_hit }; return CallFunction("MakeBreakEvent", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeExceptionEvent(Handle<Object> exception, bool uncaught, Handle<Object> promise) { // Create the new exception event object. Handle<Object> argv[] = { isolate_->factory()->NewNumberFromInt(break_id()), exception, isolate_->factory()->ToBoolean(uncaught), promise }; return CallFunction("MakeExceptionEvent", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeCompileEvent(Handle<Script> script, v8::DebugEvent type) { // Create the compile event object. Handle<Object> script_wrapper = Script::GetWrapper(script); Handle<Object> argv[] = { script_wrapper, isolate_->factory()->NewNumberFromInt(type) }; return CallFunction("MakeCompileEvent", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeAsyncTaskEvent( v8::debug::PromiseDebugActionType type, int id) { // Create the async task event object. Handle<Object> argv[] = {Handle<Smi>(Smi::FromInt(type), isolate_), Handle<Smi>(Smi::FromInt(id), isolate_)}; return CallFunction("MakeAsyncTaskEvent", arraysize(argv), argv); } void Debug::OnThrow(Handle<Object> exception) { if (in_debug_scope() || ignore_events()) return; // Temporarily clear any scheduled_exception to allow evaluating // JavaScript from the debug event handler. HandleScope scope(isolate_); Handle<Object> scheduled_exception; if (isolate_->has_scheduled_exception()) { scheduled_exception = handle(isolate_->scheduled_exception(), isolate_); isolate_->clear_scheduled_exception(); } OnException(exception, isolate_->GetPromiseOnStackOnThrow()); if (!scheduled_exception.is_null()) { isolate_->thread_local_top()->scheduled_exception_ = *scheduled_exception; } PrepareStepOnThrow(); } void Debug::OnPromiseReject(Handle<Object> promise, Handle<Object> value) { if (in_debug_scope() || ignore_events()) return; HandleScope scope(isolate_); // Check whether the promise has been marked as having triggered a message. Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol(); if (!promise->IsJSObject() || JSReceiver::GetDataProperty(Handle<JSObject>::cast(promise), key) ->IsUndefined(isolate_)) { OnException(value, promise); } } namespace { v8::Local<v8::Context> GetDebugEventContext(Isolate* isolate) { Handle<Context> context = isolate->debug()->debugger_entry()->GetContext(); // Isolate::context() may have been nullptr when "script collected" event // occurred. if (context.is_null()) return v8::Local<v8::Context>(); Handle<Context> native_context(context->native_context()); return v8::Utils::ToLocal(native_context); } } // anonymous namespace bool Debug::IsExceptionBlackboxed(bool uncaught) { // Uncaught exception is blackboxed if all current frames are blackboxed, // caught exception if top frame is blackboxed. StackTraceFrameIterator it(isolate_); while (!it.done() && it.is_wasm()) it.Advance(); bool is_top_frame_blackboxed = !it.done() ? IsFrameBlackboxed(it.javascript_frame()) : true; if (!uncaught || !is_top_frame_blackboxed) return is_top_frame_blackboxed; return AllFramesOnStackAreBlackboxed(); } bool Debug::IsFrameBlackboxed(JavaScriptFrame* frame) { HandleScope scope(isolate_); std::vector<Handle<SharedFunctionInfo>> infos; frame->GetFunctions(&infos); for (const auto& info : infos) { if (!IsBlackboxed(info)) return false; } return true; } void Debug::OnException(Handle<Object> exception, Handle<Object> promise) { // We cannot generate debug events when JS execution is disallowed. // TODO(5530): Reenable debug events within DisallowJSScopes once relevant // code (MakeExceptionEvent and ProcessDebugEvent) have been moved to C++. if (!AllowJavascriptExecution::IsAllowed(isolate_)) return; Isolate::CatchType catch_type = isolate_->PredictExceptionCatcher(); // Don't notify listener of exceptions that are internal to a desugaring. if (catch_type == Isolate::CAUGHT_BY_DESUGARING) return; bool uncaught = catch_type == Isolate::NOT_CAUGHT; if (promise->IsJSObject()) { Handle<JSObject> jspromise = Handle<JSObject>::cast(promise); // Mark the promise as already having triggered a message. Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol(); JSObject::SetProperty(jspromise, key, key, LanguageMode::kStrict).Assert(); // Check whether the promise reject is considered an uncaught exception. uncaught = !isolate_->PromiseHasUserDefinedRejectHandler(jspromise); } if (!debug_delegate_) return; // Bail out if exception breaks are not active if (uncaught) { // Uncaught exceptions are reported by either flags. if (!(break_on_uncaught_exception_ || break_on_exception_)) return; } else { // Caught exceptions are reported is activated. if (!break_on_exception_) return; } { JavaScriptFrameIterator it(isolate_); // Check whether the top frame is blackboxed or the break location is muted. if (!it.done() && (IsMutedAtCurrentLocation(it.frame()) || IsExceptionBlackboxed(uncaught))) { return; } if (it.done()) return; // Do not trigger an event with an empty stack. } DebugScope debug_scope(this); if (debug_scope.failed()) return; HandleScope scope(isolate_); PostponeInterruptsScope postpone(isolate_); DisableBreak no_recursive_break(this); // Create the execution state. Handle<Object> exec_state; // Bail out and don't call debugger if exception. if (!MakeExecutionState().ToHandle(&exec_state)) return; debug_delegate_->ExceptionThrown( GetDebugEventContext(isolate_), v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)), v8::Utils::ToLocal(exception), v8::Utils::ToLocal(promise), uncaught); } void Debug::OnDebugBreak(Handle<FixedArray> break_points_hit) { DCHECK(!break_points_hit.is_null()); // The caller provided for DebugScope. AssertDebugContext(); // Bail out if there is no listener for this event if (ignore_events()) return; #ifdef DEBUG PrintBreakLocation(); #endif // DEBUG if (!debug_delegate_) return; HandleScope scope(isolate_); PostponeInterruptsScope no_interrupts(isolate_); DisableBreak no_recursive_break(this); // Create the execution state. Handle<Object> exec_state; // Bail out and don't call debugger if exception. if (!MakeExecutionState().ToHandle(&exec_state)) return; std::vector<int> inspector_break_points_hit; int inspector_break_points_count = 0; // This array contains breakpoints installed using JS debug API. for (int i = 0; i < break_points_hit->length(); ++i) { Object* break_point = break_points_hit->get(i); if (break_point->IsBreakPoint()) { inspector_break_points_hit.push_back(BreakPoint::cast(break_point)->id()); ++inspector_break_points_count; } else { break_points_hit->set(i - inspector_break_points_count, break_point); } } int break_points_length = break_points_hit->length() - inspector_break_points_count; Handle<Object> break_points; if (break_points_length) { break_points_hit->Shrink(break_points_length); break_points = isolate_->factory()->NewJSArrayWithElements( break_points_hit, PACKED_ELEMENTS, break_points_length); } else { break_points = isolate_->factory()->undefined_value(); } debug_delegate_->BreakProgramRequested( GetDebugEventContext(isolate_), v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)), v8::Utils::ToLocal(break_points), inspector_break_points_hit); } void Debug::OnCompileError(Handle<Script> script) { ProcessCompileEvent(v8::CompileError, script); } // Handle debugger actions when a new script is compiled. void Debug::OnAfterCompile(Handle<Script> script) { ProcessCompileEvent(v8::AfterCompile, script); } namespace { // In an async function, reuse the existing stack related to the outer // Promise. Otherwise, e.g. in a direct call to then, save a new stack. // Promises with multiple reactions with one or more of them being async // functions will not get a good stack trace, as async functions require // different stacks from direct Promise use, but we save and restore a // stack once for all reactions. // // If this isn't a case of async function, we return false, otherwise // we set the correct id and return true. // // TODO(littledan): Improve this case. int GetReferenceAsyncTaskId(Isolate* isolate, Handle<JSPromise> promise) { Handle<Symbol> handled_by_symbol = isolate->factory()->promise_handled_by_symbol(); Handle<Object> handled_by_promise = JSObject::GetDataProperty(promise, handled_by_symbol); if (!handled_by_promise->IsJSPromise()) { return isolate->debug()->NextAsyncTaskId(promise); } Handle<JSPromise> handled_by_promise_js = Handle<JSPromise>::cast(handled_by_promise); Handle<Symbol> async_stack_id_symbol = isolate->factory()->promise_async_stack_id_symbol(); Handle<Object> async_task_id = JSObject::GetDataProperty(handled_by_promise_js, async_stack_id_symbol); if (!async_task_id->IsSmi()) { return isolate->debug()->NextAsyncTaskId(promise); } return Handle<Smi>::cast(async_task_id)->value(); } } // namespace void Debug::RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise, Handle<Object> parent) { if (!debug_delegate_) return; int id = GetReferenceAsyncTaskId(isolate_, promise); switch (type) { case PromiseHookType::kInit: OnAsyncTaskEvent(debug::kDebugPromiseCreated, id, parent->IsJSPromise() ? GetReferenceAsyncTaskId( isolate_, Handle<JSPromise>::cast(parent)) : 0); return; case PromiseHookType::kResolve: // We can't use this hook because it's called before promise object will // get resolved status. return; case PromiseHookType::kBefore: OnAsyncTaskEvent(debug::kDebugWillHandle, id, 0); return; case PromiseHookType::kAfter: OnAsyncTaskEvent(debug::kDebugDidHandle, id, 0); return; } } int Debug::NextAsyncTaskId(Handle<JSObject> promise) { LookupIterator it(promise, isolate_->factory()->promise_async_id_symbol()); Maybe<bool> maybe = JSReceiver::HasProperty(&it); if (maybe.ToChecked()) { MaybeHandle<Object> result = Object::GetProperty(&it); return Handle<Smi>::cast(result.ToHandleChecked())->value(); } Handle<Smi> async_id = handle(Smi::FromInt(++thread_local_.async_task_count_), isolate_); Object::SetProperty(&it, async_id, LanguageMode::kSloppy, Object::MAY_BE_STORE_FROM_KEYED) .ToChecked(); return async_id->value(); } namespace { debug::Location GetDebugLocation(Handle<Script> script, int source_position) { Script::PositionInfo info; Script::GetPositionInfo(script, source_position, &info, Script::WITH_OFFSET); // V8 provides ScriptCompiler::CompileFunctionInContext method which takes // expression and compile it as anonymous function like (function() .. // expression ..). To produce correct locations for stmts inside of this // expression V8 compile this function with negative offset. Instead of stmt // position blackboxing use function start position which is negative in // described case. return debug::Location(std::max(info.line, 0), std::max(info.column, 0)); } } // namespace bool Debug::IsBlackboxed(Handle<SharedFunctionInfo> shared) { if (!debug_delegate_) return !shared->IsSubjectToDebugging(); if (!shared->computed_debug_is_blackboxed()) { bool is_blackboxed = !shared->IsSubjectToDebugging() || !shared->script()->IsScript(); if (!is_blackboxed) { SuppressDebug while_processing(this); HandleScope handle_scope(isolate_); PostponeInterruptsScope no_interrupts(isolate_); DisableBreak no_recursive_break(this); DCHECK(shared->script()->IsScript()); Handle<Script> script(Script::cast(shared->script())); DCHECK(script->IsUserJavaScript()); debug::Location start = GetDebugLocation(script, shared->start_position()); debug::Location end = GetDebugLocation(script, shared->end_position()); is_blackboxed = debug_delegate_->IsFunctionBlackboxed( ToApiHandle<debug::Script>(script), start, end); } shared->set_debug_is_blackboxed(is_blackboxed); shared->set_computed_debug_is_blackboxed(true); } return shared->debug_is_blackboxed(); } bool Debug::AllFramesOnStackAreBlackboxed() { HandleScope scope(isolate_); for (StackTraceFrameIterator it(isolate_); !it.done(); it.Advance()) { if (!IsFrameBlackboxed(it.javascript_frame())) return false; } return true; } bool Debug::SetScriptSource(Handle<Script> script, Handle<String> source, bool preview, bool* stack_changed) { DebugScope debug_scope(this); set_live_edit_enabled(true); Handle<Object> script_wrapper = Script::GetWrapper(script); Handle<Object> argv[] = {script_wrapper, source, isolate_->factory()->ToBoolean(preview), isolate_->factory()->NewJSArray(0)}; Handle<Object> result; if (!CallFunction("SetScriptSource", arraysize(argv), argv, false) .ToHandle(&result)) { isolate_->OptionalRescheduleException(false); set_live_edit_enabled(false); return false; } set_live_edit_enabled(false); Handle<Object> stack_changed_value = JSReceiver::GetProperty(isolate_, Handle<JSObject>::cast(result), "stack_modified") .ToHandleChecked(); *stack_changed = stack_changed_value->IsTrue(isolate_); return true; } void Debug::OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id, int parent_id) { if (in_debug_scope() || ignore_events()) return; if (!debug_delegate_) return; SuppressDebug while_processing(this); PostponeInterruptsScope no_interrupts(isolate_); DisableBreak no_recursive_break(this); bool created_by_user = false; if (type == debug::kDebugPromiseCreated) { JavaScriptFrameIterator it(isolate_); // We need to skip top frame which contains instrumentation. it.Advance(); created_by_user = !it.done() && !IsFrameBlackboxed(it.frame()); } debug_delegate_->PromiseEventOccurred(type, id, parent_id, created_by_user); } void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) { // Attach the correct debug id to the script. The debug id is used by the // inspector to filter scripts by native context. script->set_context_data(isolate_->native_context()->debug_context_id()); if (ignore_events()) return; if (!script->IsUserJavaScript() && script->type() != i::Script::TYPE_WASM) { return; } if (!debug_delegate_) return; SuppressDebug while_processing(this); DebugScope debug_scope(this); if (debug_scope.failed()) return; HandleScope scope(isolate_); PostponeInterruptsScope postpone(isolate_); DisableBreak no_recursive_break(this); AllowJavascriptExecution allow_script(isolate_); debug_delegate_->ScriptCompiled(ToApiHandle<debug::Script>(script), live_edit_enabled(), event != v8::AfterCompile); } Handle<Context> Debug::GetDebugContext() { if (!is_loaded()) return Handle<Context>(); DebugScope debug_scope(this); if (debug_scope.failed()) return Handle<Context>(); // The global handle may be destroyed soon after. Return it reboxed. return handle(*debug_context(), isolate_); } int Debug::CurrentFrameCount() { StackTraceFrameIterator it(isolate_); if (break_frame_id() != StackFrame::NO_ID) { // Skip to break frame. DCHECK(in_debug_scope()); while (!it.done() && it.frame()->id() != break_frame_id()) it.Advance(); } int counter = 0; while (!it.done()) { if (it.frame()->is_optimized()) { std::vector<SharedFunctionInfo*> infos; OptimizedFrame::cast(it.frame())->GetFunctions(&infos); counter += infos.size(); } else { counter++; } it.Advance(); } return counter; } void Debug::SetDebugDelegate(debug::DebugDelegate* delegate, bool pass_ownership) { RemoveDebugDelegate(); debug_delegate_ = delegate; owns_debug_delegate_ = pass_ownership; UpdateState(); } void Debug::RemoveDebugDelegate() { if (debug_delegate_ == nullptr) return; if (owns_debug_delegate_) { owns_debug_delegate_ = false; delete debug_delegate_; } debug_delegate_ = nullptr; } void Debug::UpdateState() { bool is_active = debug_delegate_ != nullptr; if (is_active || in_debug_scope()) { // Note that the debug context could have already been loaded to // bootstrap test cases. isolate_->compilation_cache()->Disable(); is_active = Load(); } else if (is_loaded()) { isolate_->compilation_cache()->Enable(); Unload(); } is_active_ = is_active; isolate_->DebugStateUpdated(); } void Debug::UpdateHookOnFunctionCall() { STATIC_ASSERT(LastStepAction == StepIn); hook_on_function_call_ = thread_local_.last_step_action_ == StepIn || isolate_->needs_side_effect_check(); } MaybeHandle<Object> Debug::Call(Handle<Object> fun, Handle<Object> data) { AllowJavascriptExecutionDebugOnly allow_script(isolate_); DebugScope debug_scope(this); if (debug_scope.failed()) return isolate_->factory()->undefined_value(); // Create the execution state. Handle<Object> exec_state; if (!MakeExecutionState().ToHandle(&exec_state)) { return isolate_->factory()->undefined_value(); } Handle<Object> argv[] = { exec_state, data }; return Execution::Call( isolate_, fun, Handle<Object>(debug_context()->global_proxy(), isolate_), arraysize(argv), argv); } void Debug::HandleDebugBreak(IgnoreBreakMode ignore_break_mode) { // Initialize LiveEdit. LiveEdit::InitializeThreadLocal(this); // Ignore debug break during bootstrapping. if (isolate_->bootstrapper()->IsActive()) return; // Just continue if breaks are disabled. if (break_disabled()) return; // Ignore debug break if debugger is not active. if (!is_active()) return; StackLimitCheck check(isolate_); if (check.HasOverflowed()) return; { JavaScriptFrameIterator it(isolate_); DCHECK(!it.done()); Object* fun = it.frame()->function(); if (fun && fun->IsJSFunction()) { HandleScope scope(isolate_); Handle<JSFunction> function(JSFunction::cast(fun), isolate_); // Don't stop in builtin and blackboxed functions. Handle<SharedFunctionInfo> shared(function->shared(), isolate_); bool ignore_break = ignore_break_mode == kIgnoreIfTopFrameBlackboxed ? IsBlackboxed(shared) : AllFramesOnStackAreBlackboxed(); if (ignore_break) { // Inspector uses pause on next statement for asynchronous breakpoints. // When breakpoint is fired we try to break on first not blackboxed // statement. To achieve this goal we need to deoptimize current // function and don't clear requested DebugBreak even if it's blackboxed // to be able to break on not blackboxed function call. // TODO(yangguo): introduce break_on_function_entry since current // implementation is slow. if (isolate_->stack_guard()->CheckDebugBreak()) { Deoptimizer::DeoptimizeFunction(*function); } return; } JSGlobalObject* global = function->context()->global_object(); // Don't stop in debugger functions. if (IsDebugGlobal(global)) return; // Don't stop if the break location is muted. if (IsMutedAtCurrentLocation(it.frame())) return; } } isolate_->stack_guard()->ClearDebugBreak(); // Clear stepping to avoid duplicate breaks. ClearStepping(); HandleScope scope(isolate_); DebugScope debug_scope(this); if (debug_scope.failed()) return; OnDebugBreak(isolate_->factory()->empty_fixed_array()); } #ifdef DEBUG void Debug::PrintBreakLocation() { if (!FLAG_print_break_location) return; HandleScope scope(isolate_); StackTraceFrameIterator iterator(isolate_); if (iterator.done()) return; StandardFrame* frame = iterator.frame(); FrameSummary summary = FrameSummary::GetTop(frame); int source_position = summary.SourcePosition(); Handle<Object> script_obj = summary.script(); PrintF("[debug] break in function '"); summary.FunctionName()->PrintOn(stdout); PrintF("'.\n"); if (script_obj->IsScript()) { Handle<Script> script = Handle<Script>::cast(script_obj); Handle<String> source(String::cast(script->source())); Script::InitLineEnds(script); int line = Script::GetLineNumber(script, source_position) - script->line_offset(); int column = Script::GetColumnNumber(script, source_position) - (line == 0 ? script->column_offset() : 0); Handle<FixedArray> line_ends(FixedArray::cast(script->line_ends())); int line_start = line == 0 ? 0 : Smi::ToInt(line_ends->get(line - 1)) + 1; int line_end = Smi::ToInt(line_ends->get(line)); DisallowHeapAllocation no_gc; String::FlatContent content = source->GetFlatContent(); if (content.IsOneByte()) { PrintF("[debug] %.*s\n", line_end - line_start, content.ToOneByteVector().start() + line_start); PrintF("[debug] "); for (int i = 0; i < column; i++) PrintF(" "); PrintF("^\n"); } else { PrintF("[debug] at line %d column %d\n", line, column); } } } #endif // DEBUG DebugScope::DebugScope(Debug* debug) : debug_(debug), prev_(debug->debugger_entry()), save_(debug_->isolate_), no_termination_exceptons_(debug_->isolate_, StackGuard::TERMINATE_EXECUTION) { // Link recursive debugger entry. base::Relaxed_Store(&debug_->thread_local_.current_debug_scope_, reinterpret_cast<base::AtomicWord>(this)); // Store the previous break id, frame id and return value. break_id_ = debug_->break_id(); break_frame_id_ = debug_->break_frame_id(); // Create the new break info. If there is no proper frames there is no break // frame id. StackTraceFrameIterator it(isolate()); bool has_frames = !it.done(); debug_->thread_local_.break_frame_id_ = has_frames ? it.frame()->id() : StackFrame::NO_ID; debug_->SetNextBreakId(); debug_->UpdateState(); // Make sure that debugger is loaded and enter the debugger context. // The previous context is kept in save_. failed_ = !debug_->is_loaded(); if (!failed_) isolate()->set_context(*debug->debug_context()); } DebugScope::~DebugScope() { // Leaving this debugger entry. base::Relaxed_Store(&debug_->thread_local_.current_debug_scope_, reinterpret_cast<base::AtomicWord>(prev_)); // Restore to the previous break state. debug_->thread_local_.break_frame_id_ = break_frame_id_; debug_->thread_local_.break_id_ = break_id_; debug_->UpdateState(); } ReturnValueScope::ReturnValueScope(Debug* debug) : debug_(debug) { return_value_ = debug_->return_value_handle(); } ReturnValueScope::~ReturnValueScope() { debug_->set_return_value(*return_value_); } bool Debug::PerformSideEffectCheck(Handle<JSFunction> function) { DCHECK(isolate_->needs_side_effect_check()); DisallowJavascriptExecution no_js(isolate_); if (!function->is_compiled() && !Compiler::Compile(function, Compiler::KEEP_EXCEPTION)) { return false; } Deoptimizer::DeoptimizeFunction(*function); if (!function->shared()->HasNoSideEffect()) { if (FLAG_trace_side_effect_free_debug_evaluate) { PrintF("[debug-evaluate] Function %s failed side effect check.\n", function->shared()->DebugName()->ToCString().get()); } side_effect_check_failed_ = true; // Throw an uncatchable termination exception. isolate_->TerminateExecution(); return false; } return true; } bool Debug::PerformSideEffectCheckForCallback(Address function) { DCHECK(isolate_->needs_side_effect_check()); if (DebugEvaluate::CallbackHasNoSideEffect(function)) return true; side_effect_check_failed_ = true; // Throw an uncatchable termination exception. isolate_->TerminateExecution(); isolate_->OptionalRescheduleException(false); return false; } void LegacyDebugDelegate::PromiseEventOccurred( v8::debug::PromiseDebugActionType type, int id, int parent_id, bool created_by_user) { DebugScope debug_scope(isolate_->debug()); if (debug_scope.failed()) return; HandleScope scope(isolate_); Handle<Object> event_data; if (isolate_->debug()->MakeAsyncTaskEvent(type, id).ToHandle(&event_data)) { ProcessDebugEvent(v8::AsyncTaskEvent, Handle<JSObject>::cast(event_data)); } } void LegacyDebugDelegate::ScriptCompiled(v8::Local<v8::debug::Script> script, bool is_live_edited, bool is_compile_error) { Handle<Object> event_data; v8::DebugEvent event = is_compile_error ? v8::CompileError : v8::AfterCompile; if (isolate_->debug() ->MakeCompileEvent(v8::Utils::OpenHandle(*script), event) .ToHandle(&event_data)) { ProcessDebugEvent(event, Handle<JSObject>::cast(event_data)); } } void LegacyDebugDelegate::BreakProgramRequested( v8::Local<v8::Context> paused_context, v8::Local<v8::Object> exec_state, v8::Local<v8::Value> break_points_hit, const std::vector<debug::BreakpointId>&) { Handle<Object> event_data; if (isolate_->debug() ->MakeBreakEvent(v8::Utils::OpenHandle(*break_points_hit)) .ToHandle(&event_data)) { ProcessDebugEvent( v8::Break, Handle<JSObject>::cast(event_data), Handle<JSObject>::cast(v8::Utils::OpenHandle(*exec_state))); } } void LegacyDebugDelegate::ExceptionThrown(v8::Local<v8::Context> paused_context, v8::Local<v8::Object> exec_state, v8::Local<v8::Value> exception, v8::Local<v8::Value> promise, bool is_uncaught) { Handle<Object> event_data; if (isolate_->debug() ->MakeExceptionEvent(v8::Utils::OpenHandle(*exception), is_uncaught, v8::Utils::OpenHandle(*promise)) .ToHandle(&event_data)) { ProcessDebugEvent( v8::Exception, Handle<JSObject>::cast(event_data), Handle<JSObject>::cast(v8::Utils::OpenHandle(*exec_state))); } } void LegacyDebugDelegate::ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data) { Handle<Object> exec_state; if (isolate_->debug()->MakeExecutionState().ToHandle(&exec_state)) { ProcessDebugEvent(event, event_data, Handle<JSObject>::cast(exec_state)); } } JavaScriptDebugDelegate::JavaScriptDebugDelegate(Isolate* isolate, Handle<JSFunction> listener, Handle<Object> data) : LegacyDebugDelegate(isolate) { GlobalHandles* global_handles = isolate->global_handles(); listener_ = global_handles->Create(*listener); data_ = global_handles->Create(*data); } JavaScriptDebugDelegate::~JavaScriptDebugDelegate() { GlobalHandles::Destroy(Handle<Object>::cast(listener_).location()); GlobalHandles::Destroy(data_.location()); } void JavaScriptDebugDelegate::ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data, Handle<JSObject> exec_state) { AllowJavascriptExecutionDebugOnly allow_script(isolate_); Handle<Object> argv[] = {Handle<Object>(Smi::FromInt(event), isolate_), exec_state, event_data, data_}; Handle<JSReceiver> global = isolate_->global_proxy(); // Listener must not throw. Execution::Call(isolate_, listener_, global, arraysize(argv), argv) .ToHandleChecked(); } NativeDebugDelegate::NativeDebugDelegate(Isolate* isolate, v8::Debug::EventCallback callback, Handle<Object> data) : LegacyDebugDelegate(isolate), callback_(callback) { data_ = isolate->global_handles()->Create(*data); } NativeDebugDelegate::~NativeDebugDelegate() { GlobalHandles::Destroy(data_.location()); } NativeDebugDelegate::EventDetails::EventDetails(DebugEvent event, Handle<JSObject> exec_state, Handle<JSObject> event_data, Handle<Object> callback_data) : event_(event), exec_state_(exec_state), event_data_(event_data), callback_data_(callback_data) {} DebugEvent NativeDebugDelegate::EventDetails::GetEvent() const { return event_; } v8::Local<v8::Object> NativeDebugDelegate::EventDetails::GetExecutionState() const { return v8::Utils::ToLocal(exec_state_); } v8::Local<v8::Object> NativeDebugDelegate::EventDetails::GetEventData() const { return v8::Utils::ToLocal(event_data_); } v8::Local<v8::Context> NativeDebugDelegate::EventDetails::GetEventContext() const { return GetDebugEventContext(exec_state_->GetIsolate()); } v8::Local<v8::Value> NativeDebugDelegate::EventDetails::GetCallbackData() const { return v8::Utils::ToLocal(callback_data_); } v8::Isolate* NativeDebugDelegate::EventDetails::GetIsolate() const { return reinterpret_cast<v8::Isolate*>(exec_state_->GetIsolate()); } void NativeDebugDelegate::ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data, Handle<JSObject> exec_state) { EventDetails event_details(event, exec_state, event_data, data_); Isolate* isolate = isolate_; callback_(event_details); CHECK(!isolate->has_scheduled_exception()); } NoSideEffectScope::~NoSideEffectScope() { if (isolate_->needs_side_effect_check() && isolate_->debug()->side_effect_check_failed_) { DCHECK(isolate_->has_pending_exception()); DCHECK_EQ(isolate_->heap()->termination_exception(), isolate_->pending_exception()); // Convert the termination exception into a regular exception. isolate_->CancelTerminateExecution(); isolate_->Throw(*isolate_->factory()->NewEvalError( MessageTemplate::kNoSideEffectDebugEvaluate)); } isolate_->set_needs_side_effect_check(old_needs_side_effect_check_); isolate_->debug()->UpdateHookOnFunctionCall(); isolate_->debug()->side_effect_check_failed_ = false; } } // namespace internal } // namespace v8