blob: dfdab89b84a4db2e20d99283c98f101deda6bf12 [file] [log] [blame]
// 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 <unordered_set>
#include "src/api/api-inl.h"
#include "src/api/api-natives.h"
#include "src/base/platform/mutex.h"
#include "src/builtins/builtins.h"
#include "src/codegen/assembler-inl.h"
#include "src/codegen/compilation-cache.h"
#include "src/codegen/compiler.h"
#include "src/common/globals.h"
#include "src/common/message-template.h"
#include "src/debug/debug-evaluate.h"
#include "src/debug/liveedit.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/arguments.h"
#include "src/execution/execution.h"
#include "src/execution/frames-inl.h"
#include "src/execution/isolate-inl.h"
#include "src/execution/v8threads.h"
#include "src/handles/global-handles.h"
#include "src/heap/heap-inl.h" // For NextDebuggingId.
#include "src/init/bootstrapper.h"
#include "src/interpreter/bytecode-array-accessor.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/interpreter/interpreter.h"
#include "src/logging/counters.h"
#include "src/objects/api-callbacks-inl.h"
#include "src/objects/debug-objects-inl.h"
#include "src/objects/js-generator-inl.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/slots.h"
#include "src/snapshot/snapshot.h"
#include "src/wasm/wasm-objects-inl.h"
namespace v8 {
namespace internal {
class Debug::TemporaryObjectsTracker : public HeapObjectAllocationTracker {
public:
TemporaryObjectsTracker() = default;
~TemporaryObjectsTracker() override = default;
void AllocationEvent(Address addr, int) override { objects_.insert(addr); }
void MoveEvent(Address from, Address to, int) override {
if (from == to) return;
base::MutexGuard guard(&mutex_);
auto it = objects_.find(from);
if (it == objects_.end()) {
// If temporary object was collected we can get MoveEvent which moves
// existing non temporary object to the address where we had temporary
// object. So we should mark new address as non temporary.
objects_.erase(to);
return;
}
objects_.erase(it);
objects_.insert(to);
}
bool HasObject(Handle<HeapObject> obj) const {
if (obj->IsJSObject() &&
Handle<JSObject>::cast(obj)->GetEmbedderFieldCount()) {
// Embedder may store any pointers using embedder fields and implements
// non trivial logic, e.g. create wrappers lazily and store pointer to
// native object inside embedder field. We should consider all objects
// with embedder fields as non temporary.
return false;
}
return objects_.find(obj->address()) != objects_.end();
}
private:
std::unordered_set<Address> objects_;
base::Mutex mutex_;
DISALLOW_COPY_AND_ASSIGN(TemporaryObjectsTracker);
};
Debug::Debug(Isolate* isolate)
: is_active_(false),
hook_on_function_call_(false),
is_suppressed_(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();
}
Debug::~Debug() { DCHECK_NULL(debug_delegate_); }
BreakLocation BreakLocation::FromFrame(Handle<DebugInfo> debug_info,
JavaScriptFrame* frame) {
if (debug_info->CanBreakAtEntry()) {
return BreakLocation(Debug::kBreakAtEntryPosition, DEBUG_BREAK_AT_ENTRY);
}
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) {
DCHECK(!debug_info->CanBreakAtEntry());
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());
}
}
}
JSGeneratorObject BreakLocation::GetGeneratorObjectForSuspendedFrame(
JavaScriptFrame* frame) const {
DCHECK(IsSuspend());
DCHECK_GE(generator_obj_reg_index_, 0);
Object generator_obj = InterpretedFrame::cast(frame)->ReadInterpreterRegister(
generator_obj_reg_index_);
return JSGeneratorObject::cast(generator_obj);
}
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(Isolate* isolate,
Handle<DebugInfo> debug_info) const {
// First check whether there is a break point with the same source position.
if (!debug_info->HasBreakPoint(isolate, position_)) return false;
if (debug_info->CanBreakAtEntry()) {
DCHECK_EQ(Debug::kBreakAtEntryPosition, position_);
return debug_info->BreakAtEntry();
} else {
// 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;
// Externally, suspend breaks should look like normal breaks.
case DEBUG_BREAK_SLOT_AT_SUSPEND:
default:
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().StartPosition();
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()));
// Make sure we read the actual bytecode, not a prefix scaling bytecode.
if (interpreter::Bytecodes::IsPrefixScalingBytecode(bytecode)) {
bytecode =
interpreter::Bytecodes::FromByte(bytecode_array.get(code_offset() + 1));
}
if (bytecode == interpreter::Bytecode::kDebugger) {
return DEBUGGER_STATEMENT;
} else if (bytecode == interpreter::Bytecode::kReturn) {
return DEBUG_BREAK_SLOT_AT_RETURN;
} else if (bytecode == interpreter::Bytecode::kSuspendGenerator) {
return DEBUG_BREAK_SLOT_AT_SUSPEND;
} 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;
HandleScope scope(isolate());
DCHECK(debug_break_type >= DEBUG_BREAK_SLOT);
Handle<BytecodeArray> bytecode_array(debug_info_->DebugBytecodeArray(),
isolate());
interpreter::BytecodeArrayAccessor(bytecode_array, code_offset())
.ApplyDebugBreak();
}
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()), isolate());
DebugBreakType type = GetDebugBreakType();
int generator_object_reg_index = -1;
if (type == DEBUG_BREAK_SLOT_AT_SUSPEND) {
// For suspend break, we'll need the generator object to be able to step
// over the suspend as if it didn't return. We get the interpreter register
// index that holds the generator object by reading it directly off the
// bytecode array, and we'll read the actual generator object off the
// interpreter stack frame in GetGeneratorObjectForSuspendedFrame.
BytecodeArray bytecode_array = debug_info_->OriginalBytecodeArray();
interpreter::BytecodeArrayAccessor accessor(
handle(bytecode_array, isolate()), code_offset());
DCHECK_EQ(accessor.current_bytecode(),
interpreter::Bytecode::kSuspendGenerator);
interpreter::Register generator_obj_reg = accessor.GetRegisterOperand(0);
generator_object_reg_index = generator_obj_reg.index();
}
return BreakLocation(code, type, code_offset(), position_,
generator_object_reg_index);
}
Isolate* BreakIterator::isolate() { return debug_info_->GetIsolate(); }
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_frame_id_ = StackFrameId::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::zero();
thread_local_.target_frame_count_ = -1;
thread_local_.return_value_ = Smi::zero();
thread_local_.last_breakpoint_id_ = 0;
clear_suspended_generator();
thread_local_.restart_fp_ = kNullAddress;
base::Relaxed_Store(&thread_local_.current_debug_scope_,
static_cast<base::AtomicWord>(0));
thread_local_.break_on_next_function_call_ = false;
UpdateHookOnFunctionCall();
}
char* Debug::ArchiveDebug(char* storage) {
MemCopy(storage, reinterpret_cast<char*>(&thread_local_),
ArchiveSpacePerThread());
return storage + ArchiveSpacePerThread();
}
char* Debug::RestoreDebug(char* storage) {
MemCopy(reinterpret_cast<char*>(&thread_local_), storage,
ArchiveSpacePerThread());
// Enter the debugger.
DebugScope debug_scope(this);
// Clear any one-shot breakpoints that may have been set by the other
// thread, and reapply breakpoints for this thread.
ClearOneShot();
if (thread_local_.last_step_action_ != StepNone) {
// Reset the previous step action for this thread.
PrepareStep(thread_local_.last_step_action_);
}
return storage + ArchiveSpacePerThread();
}
int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal); }
void Debug::Iterate(RootVisitor* v) {
v->VisitRootPointer(Root::kDebug, nullptr,
FullObjectSlot(&thread_local_.return_value_));
v->VisitRootPointer(Root::kDebug, nullptr,
FullObjectSlot(&thread_local_.suspended_generator_));
v->VisitRootPointer(
Root::kDebug, nullptr,
FullObjectSlot(&thread_local_.ignore_step_into_function_));
}
DebugInfoListNode::DebugInfoListNode(Isolate* isolate, DebugInfo debug_info)
: next_(nullptr) {
// Globalize the request debug info object and make it weak.
GlobalHandles* global_handles = isolate->global_handles();
debug_info_ = global_handles->Create(debug_info).location();
}
DebugInfoListNode::~DebugInfoListNode() {
if (debug_info_ == nullptr) return;
GlobalHandles::Destroy(debug_info_);
debug_info_ = nullptr;
}
void Debug::Unload() {
ClearAllBreakPoints();
ClearStepping();
RemoveAllCoverageInfos();
ClearAllDebuggerHints();
debug_delegate_ = nullptr;
}
void Debug::Break(JavaScriptFrame* frame, Handle<JSFunction> break_target) {
// 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);
DisableBreak no_recursive_break(this);
// Return if we fail to retrieve debug info.
Handle<SharedFunctionInfo> shared(break_target->shared(), isolate_);
if (!EnsureBreakInfo(shared)) return;
PrepareFunctionForDebugExecution(shared);
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() || break_on_next_function_call()) {
// Clear all current stepping setup.
ClearStepping();
// Notify the debug event listeners.
OnDebugBreak(!break_points_hit.is_null()
? break_points_hit.ToHandleChecked()
: isolate_->factory()->empty_fixed_array());
return;
}
// Debug break at function entry, do not worry about stepping.
if (location.IsDebugBreakAtEntry()) {
DCHECK(debug_info->BreakAtEntry());
return;
}
DCHECK_NOT_NULL(frame);
// 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.IsReturnOrSuspend());
// 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;
V8_FALLTHROUGH;
case StepIn: {
// Special case "next" and "in" for generators that are about to suspend.
if (location.IsSuspend()) {
DCHECK(!has_suspended_generator());
thread_local_.suspended_generator_ =
location.GetGeneratorObjectForSuspendedFrame(frame);
ClearStepping();
return;
}
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(isolate_, debug_info);
if (has_break_points) *has_break_points = has_break_points_to_check;
if (!has_break_points_to_check) return {};
return Debug::GetHitBreakPoints(debug_info, location->position());
}
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(), isolate_);
// Enter the debugger.
DebugScope debug_scope(this);
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;
}
// Check whether a single break point object is triggered.
bool Debug::CheckBreakPoint(Handle<BreakPoint> break_point,
bool is_break_at_entry) {
HandleScope scope(isolate_);
if (!break_point->condition().length()) return true;
Handle<String> condition(break_point->condition(), isolate_);
MaybeHandle<Object> maybe_result;
Handle<Object> result;
if (is_break_at_entry) {
maybe_result = DebugEvaluate::WithTopmostArguments(isolate_, condition);
} else {
// Since we call CheckBreakpoint only for deoptimized frame on top of stack,
// we can use 0 as index of inlined frame.
const int inlined_jsframe_index = 0;
const bool throw_on_side_effect = false;
maybe_result =
DebugEvaluate::Local(isolate_, break_frame_id(), inlined_jsframe_index,
condition, throw_on_side_effect);
}
if (!maybe_result.ToHandle(&result)) {
if (isolate_->has_pending_exception()) {
isolate_->clear_pending_exception();
}
return false;
}
return result->BooleanValue(isolate_);
}
bool Debug::SetBreakpoint(Handle<SharedFunctionInfo> shared,
Handle<BreakPoint> break_point,
int* source_position) {
HandleScope scope(isolate_);
// Make sure the function is compiled and has set up the debug info.
if (!EnsureBreakInfo(shared)) return false;
PrepareFunctionForDebugExecution(shared);
Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
// 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(isolate_, debug_info, *source_position, break_point);
// At least one active break point now.
DCHECK_LT(0, debug_info->GetBreakPointCount(isolate_));
ClearBreakPoints(debug_info);
ApplyBreakPoints(debug_info);
feature_tracker()->Track(DebugFeatureTracker::kBreakPoint);
return true;
}
bool Debug::SetBreakPointForScript(Handle<Script> script,
Handle<String> condition,
int* source_position, int* id) {
*id = ++thread_local_.last_breakpoint_id_;
Handle<BreakPoint> break_point =
isolate_->factory()->NewBreakPoint(*id, condition);
if (script->type() == Script::TYPE_WASM) {
return WasmScript::SetBreakPoint(script, source_position, break_point);
}
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;
PrepareFunctionForDebugExecution(shared);
// Find position within function. The script position might be before the
// source position of the first function.
if (shared->StartPosition() > *source_position) {
*source_position = shared->StartPosition();
}
Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
// 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(isolate_, debug_info, *source_position, break_point);
// At least one active break point now.
DCHECK_LT(0, debug_info->GetBreakPointCount(isolate_));
ClearBreakPoints(debug_info);
ApplyBreakPoints(debug_info);
feature_tracker()->Track(DebugFeatureTracker::kBreakPoint);
return true;
}
int Debug::FindBreakablePosition(Handle<DebugInfo> debug_info,
int source_position) {
if (debug_info->CanBreakAtEntry()) {
return kBreakAtEntryPosition;
} else {
DCHECK(debug_info->HasInstrumentedBytecodeArray());
BreakIterator it(debug_info);
it.SkipToPosition(source_position);
return it.position();
}
}
void Debug::ApplyBreakPoints(Handle<DebugInfo> debug_info) {
DisallowHeapAllocation no_gc;
if (debug_info->CanBreakAtEntry()) {
debug_info->SetBreakAtEntry();
} else {
if (!debug_info->HasInstrumentedBytecodeArray()) 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(isolate_) == 0) continue;
DCHECK(debug_info->HasInstrumentedBytecodeArray());
BreakIterator it(debug_info);
it.SkipToPosition(info.source_position());
it.SetDebugBreak();
}
}
debug_info->SetDebugExecutionMode(DebugInfo::kBreakpoints);
}
void Debug::ClearBreakPoints(Handle<DebugInfo> debug_info) {
if (debug_info->CanBreakAtEntry()) {
debug_info->ClearBreakAtEntry();
} else {
// If we attempt to clear breakpoints but none exist, simply return. This
// can happen e.g. CoverageInfos exist but no breakpoints are set.
if (!debug_info->HasInstrumentedBytecodeArray() ||
!debug_info->HasBreakInfo()) {
return;
}
DisallowHeapAllocation no_gc;
for (BreakIterator it(debug_info); !it.Done(); it.Next()) {
it.ClearDebugBreak();
}
}
}
void Debug::ClearBreakPoint(Handle<BreakPoint> break_point) {
HandleScope scope(isolate_);
for (DebugInfoListNode* node = debug_info_list_; node != nullptr;
node = node->next()) {
if (!node->debug_info()->HasBreakInfo()) continue;
Handle<Object> result = DebugInfo::FindBreakPointInfo(
isolate_, node->debug_info(), break_point);
if (result->IsUndefined(isolate_)) continue;
Handle<DebugInfo> debug_info = node->debug_info();
if (DebugInfo::ClearBreakPoint(isolate_, debug_info, break_point)) {
ClearBreakPoints(debug_info);
if (debug_info->GetBreakPointCount(isolate_) == 0) {
RemoveBreakInfoAndMaybeFree(debug_info);
} else {
ApplyBreakPoints(debug_info);
}
return;
}
}
}
int Debug::GetFunctionDebuggingId(Handle<JSFunction> function) {
Handle<SharedFunctionInfo> shared = handle(function->shared(), isolate_);
Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared);
int id = debug_info->debugging_id();
if (id == DebugInfo::kNoDebuggingId) {
id = isolate_->heap()->NextDebuggingId();
debug_info->set_debugging_id(id);
}
return id;
}
bool Debug::SetBreakpointForFunction(Handle<SharedFunctionInfo> shared,
Handle<String> condition, int* id) {
*id = ++thread_local_.last_breakpoint_id_;
Handle<BreakPoint> breakpoint =
isolate_->factory()->NewBreakPoint(*id, condition);
int source_position = 0;
// Handle wasm function.
if (shared->HasWasmExportedFunctionData()) {
int func_index = shared->wasm_exported_function_data().function_index();
Handle<WasmInstanceObject> wasm_instance(
shared->wasm_exported_function_data().instance(), isolate_);
Handle<Script> script(Script::cast(wasm_instance->module_object().script()),
isolate_);
return WasmScript::SetBreakPointOnFirstBreakableForFunction(
script, func_index, breakpoint);
}
return SetBreakpoint(shared, breakpoint, &source_position);
}
void Debug::RemoveBreakpoint(int id) {
Handle<BreakPoint> breakpoint = isolate_->factory()->NewBreakPoint(
id, isolate_->factory()->empty_string());
ClearBreakPoint(breakpoint);
}
void Debug::RemoveBreakpointForWasmScript(Handle<Script> script, int id) {
if (script->type() == Script::TYPE_WASM) {
WasmScript::ClearBreakPointById(script, id);
}
}
// Clear out all the debug break code.
void Debug::ClearAllBreakPoints() {
ClearAllDebugInfos([=](Handle<DebugInfo> info) {
ClearBreakPoints(info);
info->ClearBreakInfo(isolate_);
});
}
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;
PrepareFunctionForDebugExecution(shared);
Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
// Flood the function with break points.
DCHECK(debug_info->HasInstrumentedBytecodeArray());
for (BreakIterator it(debug_info); !it.Done(); it.Next()) {
if (returns_only && !it.GetBreakLocation().IsReturnOrSuspend()) 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::GetHitBreakPoints(Handle<DebugInfo> debug_info,
int position) {
Handle<Object> break_points = debug_info->GetBreakPoints(isolate_, position);
bool is_break_at_entry = debug_info->BreakAtEntry();
DCHECK(!break_points->IsUndefined(isolate_));
if (!break_points->IsFixedArray()) {
if (!CheckBreakPoint(Handle<BreakPoint>::cast(break_points),
is_break_at_entry)) {
return {};
}
Handle<FixedArray> break_points_hit = isolate_->factory()->NewFixedArray(1);
break_points_hit->set(0, *break_points);
return break_points_hit;
}
Handle<FixedArray> array(FixedArray::cast(*break_points), isolate_);
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(array->get(i), isolate_);
if (CheckBreakPoint(Handle<BreakPoint>::cast(break_point),
is_break_at_entry)) {
break_points_hit->set(break_points_hit_count++, *break_point);
}
}
if (break_points_hit_count == 0) return {};
break_points_hit->Shrink(isolate_, break_points_hit_count);
return break_points_hit;
}
void Debug::SetBreakOnNextFunctionCall() {
// This method forces V8 to break on next function call regardless current
// last_step_action_. If any break happens between SetBreakOnNextFunctionCall
// and ClearBreakOnNextFunctionCall, we will clear this flag and stepping. If
// break does not happen, e.g. all called functions are blackboxed or no
// function is called, then we will clear this flag and let stepping continue
// its normal business.
thread_local_.break_on_next_function_call_ = true;
UpdateHookOnFunctionCall();
}
void Debug::ClearBreakOnNextFunctionCall() {
thread_local_.break_on_next_function_call_ = false;
UpdateHookOnFunctionCall();
}
void Debug::PrepareStepIn(Handle<JSFunction> function) {
CHECK(last_step_action() >= StepIn || break_on_next_function_call());
if (ignore_events()) return;
if (in_debug_scope()) return;
if (break_disabled()) return;
Handle<SharedFunctionInfo> shared(function->shared(), isolate_);
// If stepping from JS into Wasm, prepare for it.
if (shared->HasWasmExportedFunctionData()) {
auto imported_function = Handle<WasmExportedFunction>::cast(function);
Handle<WasmInstanceObject> wasm_instance(imported_function->instance(),
isolate_);
Handle<WasmDebugInfo> wasm_debug_info =
WasmInstanceObject::GetOrCreateDebugInfo(wasm_instance);
int func_index = shared->wasm_exported_function_data().function_index();
WasmDebugInfo::PrepareStepIn(wasm_debug_info, func_index);
// We need to reset all of this since break would be
// handled in Wasm Interpreter now. Otherwise it would be a loop here.
ClearStepping();
}
if (IsBlackboxed(shared)) return;
if (*function == thread_local_.ignore_step_into_function_) return;
thread_local_.ignore_step_into_function_ = Smi::zero();
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(),
isolate_);
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());
HandlerTable table(code->GetBytecodeArray());
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(), isolate_);
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.
StackFrameId frame_id = break_frame_id();
// If there is no JavaScript stack don't do anything.
if (frame_id == StackFrameId::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_interpreter_entry()) {
WasmInterpreterEntryFrame* wasm_frame =
WasmInterpreterEntryFrame::cast(frame);
if (wasm_frame->NumberOfActiveFrames() > 0) {
wasm_frame->debug_info().PrepareStep(step_action);
return;
}
}
// If this is wasm, but there are no interpreted frames on top, all we can do
// is step out.
if (frame->is_wasm()) step_action = StepOut;
BreakLocation location = BreakLocation::Invalid();
Handle<SharedFunctionInfo> shared;
int current_frame_count = CurrentFrameCount();
if (frame->is_java_script()) {
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());
shared = Handle<SharedFunctionInfo>(function->shared(), isolate_);
if (!EnsureBreakInfo(shared)) return;
PrepareFunctionForDebugExecution(shared);
Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
location = BreakLocation::FromFrame(debug_info, js_frame);
// Any step at a return is a step-out, and a step-out at a suspend behaves
// like a return.
if (location.IsReturn() ||
(location.IsSuspend() && step_action == StepOut)) {
// 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;
}
// We need to schedule DebugOnFunction call callback
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());
thread_local_.last_frame_count_ = current_frame_count;
// No longer perform the current async step.
clear_suspended_generator();
}
switch (step_action) {
case StepNone:
UNREACHABLE();
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 (!shared.is_null() && !location.IsReturnOrSuspend() &&
!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()) {
if (frames_it.frame()->is_wasm()) {
in_current_frame = false;
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;
V8_FALLTHROUGH;
case StepIn:
// TODO(clemensb): Implement stepping from JS into wasm.
FloodWithOneShot(shared);
break;
}
}
// Simple function for returning the source positions for active break points.
Handle<Object> Debug::GetSourceBreakLocations(
Isolate* isolate, Handle<SharedFunctionInfo> shared) {
if (!shared->HasBreakInfo()) {
return isolate->factory()->undefined_value();
}
Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate);
if (debug_info->GetBreakPointCount(isolate) == 0) {
return isolate->factory()->undefined_value();
}
Handle<FixedArray> locations = isolate->factory()->NewFixedArray(
debug_info->GetBreakPointCount(isolate));
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(isolate);
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::zero();
thread_local_.fast_forward_to_return_ = false;
thread_local_.last_frame_count_ = -1;
thread_local_.target_frame_count_ = -1;
thread_local_.break_on_next_function_call_ = false;
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);
}
}
void Debug::DeoptimizeFunction(Handle<SharedFunctionInfo> shared) {
// Deoptimize all code compiled from this shared function info including
// inlining.
isolate_->AbortConcurrentOptimization(BlockingBehavior::kBlock);
bool found_something = false;
Code::OptimizedCodeIterator iterator(isolate_);
do {
Code code = iterator.Next();
if (code.is_null()) break;
if (code.Inlines(*shared)) {
code.set_marked_for_deoptimization(true);
found_something = true;
}
} while (true);
if (found_something) {
// Only go through with the deoptimization if something was found.
Deoptimizer::DeoptimizeMarkedCode(isolate_);
}
}
void Debug::PrepareFunctionForDebugExecution(
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());
Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared);
if (debug_info->flags() & DebugInfo::kPreparedForDebugExecution) return;
// Make a copy of the bytecode array if available.
Handle<HeapObject> maybe_original_bytecode_array =
isolate_->factory()->undefined_value();
if (shared->HasBytecodeArray()) {
Handle<BytecodeArray> original_bytecode_array =
handle(shared->GetBytecodeArray(), isolate_);
Handle<BytecodeArray> debug_bytecode_array =
isolate_->factory()->CopyBytecodeArray(original_bytecode_array);
debug_info->set_debug_bytecode_array(*debug_bytecode_array);
shared->SetDebugBytecodeArray(*debug_bytecode_array);
maybe_original_bytecode_array = original_bytecode_array;
}
debug_info->set_original_bytecode_array(*maybe_original_bytecode_array);
if (debug_info->CanBreakAtEntry()) {
// Deopt everything in case the function is inlined anywhere.
Deoptimizer::DeoptimizeAll(isolate_);
InstallDebugBreakTrampoline();
} else {
DeoptimizeFunction(shared);
// Update PCs on the stack to point to recompiled code.
RedirectActiveFunctions redirect_visitor(
*shared, RedirectActiveFunctions::Mode::kUseDebugBytecode);
redirect_visitor.VisitThread(isolate_, isolate_->thread_local_top());
isolate_->thread_manager()->IterateArchivedThreads(&redirect_visitor);
}
debug_info->set_flags(debug_info->flags() |
DebugInfo::kPreparedForDebugExecution);
}
void Debug::InstallDebugBreakTrampoline() {
// Check the list of debug infos whether the debug break trampoline needs to
// be installed. If that's the case, iterate the heap for functions to rewire
// to the trampoline.
HandleScope scope(isolate_);
// If there is a breakpoint at function entry, we need to install trampoline.
bool needs_to_use_trampoline = false;
// If there we break at entry to an api callback, we need to clear ICs.
bool needs_to_clear_ic = false;
for (DebugInfoListNode* current = debug_info_list_; current != nullptr;
current = current->next()) {
if (current->debug_info()->CanBreakAtEntry()) {
needs_to_use_trampoline = true;
if (current->debug_info()->shared().IsApiFunction()) {
needs_to_clear_ic = true;
break;
}
}
}
if (!needs_to_use_trampoline) return;
Handle<Code> trampoline = BUILTIN_CODE(isolate_, DebugBreakTrampoline);
std::vector<Handle<JSFunction>> needs_compile;
using AccessorPairWithContext =
std::pair<Handle<AccessorPair>, Handle<NativeContext>>;
std::vector<AccessorPairWithContext> needs_instantiate;
{
// Deduplicate {needs_instantiate} by recording all collected AccessorPairs.
std::set<AccessorPair> recorded;
HeapObjectIterator iterator(isolate_->heap());
for (HeapObject obj = iterator.Next(); !obj.is_null();
obj = iterator.Next()) {
if (needs_to_clear_ic && obj.IsFeedbackVector()) {
FeedbackVector::cast(obj).ClearSlots(isolate_);
continue;
} else if (obj.IsJSFunction()) {
JSFunction fun = JSFunction::cast(obj);
SharedFunctionInfo shared = fun.shared();
if (!shared.HasDebugInfo()) continue;
if (!shared.GetDebugInfo().CanBreakAtEntry()) continue;
if (!fun.is_compiled()) {
needs_compile.push_back(handle(fun, isolate_));
} else {
fun.set_code(*trampoline);
}
} else if (obj.IsJSObject()) {
JSObject object = JSObject::cast(obj);
DescriptorArray descriptors = object.map().instance_descriptors();
for (InternalIndex i : object.map().IterateOwnDescriptors()) {
if (descriptors.GetDetails(i).kind() == PropertyKind::kAccessor) {
Object value = descriptors.GetStrongValue(i);
if (!value.IsAccessorPair()) continue;
AccessorPair accessor_pair = AccessorPair::cast(value);
if (!accessor_pair.getter().IsFunctionTemplateInfo() &&
!accessor_pair.setter().IsFunctionTemplateInfo()) {
continue;
}
if (recorded.find(accessor_pair) != recorded.end()) continue;
needs_instantiate.emplace_back(handle(accessor_pair, isolate_),
object.GetCreationContext());
recorded.insert(accessor_pair);
}
}
}
}
}
// Forcibly instantiate all lazy accessor pairs to make sure that they
// properly hit the debug break trampoline.
for (AccessorPairWithContext tuple : needs_instantiate) {
Handle<AccessorPair> accessor_pair = tuple.first;
Handle<NativeContext> native_context = tuple.second;
if (accessor_pair->getter().IsFunctionTemplateInfo()) {
Handle<JSFunction> fun =
ApiNatives::InstantiateFunction(
isolate_, native_context,
handle(FunctionTemplateInfo::cast(accessor_pair->getter()),
isolate_))
.ToHandleChecked();
accessor_pair->set_getter(*fun);
}
if (accessor_pair->setter().IsFunctionTemplateInfo()) {
Handle<JSFunction> fun =
ApiNatives::InstantiateFunction(
isolate_, native_context,
handle(FunctionTemplateInfo::cast(accessor_pair->setter()),
isolate_))
.ToHandleChecked();
accessor_pair->set_setter(*fun);
}
}
// By overwriting the function code with DebugBreakTrampoline, which tailcalls
// to shared code, we bypass CompileLazy. Perform CompileLazy here instead.
for (Handle<JSFunction> fun : needs_compile) {
IsCompiledScope is_compiled_scope;
Compiler::Compile(fun, Compiler::CLEAR_EXCEPTION, &is_compiled_scope);
DCHECK(is_compiled_scope.is_compiled());
fun->set_code(*trampoline);
}
}
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->HasInstrumentedBytecodeArray());
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;
PrepareFunctionForDebugExecution(shared);
Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
FindBreakablePositions(debug_info, start_position, end_position, locations);
return true;
}
while (true) {
HandleScope scope(isolate_);
std::vector<Handle<SharedFunctionInfo>> candidates;
std::vector<IsCompiledScope> compiled_scopes;
SharedFunctionInfo::ScriptIterator iterator(isolate_, *script);
for (SharedFunctionInfo info = iterator.Next(); !info.is_null();
info = iterator.Next()) {
if (info.EndPosition() < start_position ||
info.StartPosition() >= end_position) {
continue;
}
if (!info.IsSubjectToDebugging()) continue;
if (!info.is_compiled() && !info.allows_lazy_compilation()) continue;
candidates.push_back(i::handle(info, isolate_));
}
bool was_compiled = false;
for (const auto& candidate : candidates) {
IsCompiledScope is_compiled_scope(candidate->is_compiled_scope());
if (!is_compiled_scope.is_compiled()) {
// Code that cannot be compiled lazily are internal and not debuggable.
DCHECK(candidate->allows_lazy_compilation());
if (!Compiler::Compile(candidate, Compiler::CLEAR_EXCEPTION,
&is_compiled_scope)) {
return false;
} else {
was_compiled = true;
}
}
DCHECK(is_compiled_scope.is_compiled());
compiled_scopes.push_back(is_compiled_scope);
if (!EnsureBreakInfo(candidate)) return false;
PrepareFunctionForDebugExecution(candidate);
}
if (was_compiled) continue;
for (const auto& candidate : candidates) {
CHECK(candidate->HasBreakInfo());
Handle<DebugInfo> debug_info(candidate->GetDebugInfo(), isolate_);
FindBreakablePositions(debug_info, start_position, end_position,
locations);
}
return true;
}
UNREACHABLE();
}
class SharedFunctionInfoFinder {
public:
explicit SharedFunctionInfoFinder(int target_position)
: current_start_position_(kNoSourcePosition),
target_position_(target_position) {}
void NewCandidate(SharedFunctionInfo shared,
JSFunction closure = JSFunction()) {
if (!shared.IsSubjectToDebugging()) return;
int start_position = shared.function_token_position();
if (start_position == kNoSourcePosition) {
start_position = shared.StartPosition();
}
if (start_position > target_position_) return;
if (target_position_ > shared.EndPosition()) return;
if (!current_candidate_.is_null()) {
if (current_start_position_ == start_position &&
shared.EndPosition() == current_candidate_.EndPosition()) {
// If we already have a matching closure, do not throw it away.
if (!current_candidate_closure_.is_null() && closure.is_null()) 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_.EndPosition() < shared.EndPosition()) {
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;
IsCompiledScope is_compiled_scope;
{
SharedFunctionInfoFinder finder(position);
SharedFunctionInfo::ScriptIterator iterator(isolate_, *script);
for (SharedFunctionInfo info = iterator.Next(); !info.is_null();
info = iterator.Next()) {
finder.NewCandidate(info);
}
shared = finder.Result();
if (shared.is_null()) break;
// We found it if it's already compiled.
is_compiled_scope = shared.is_compiled_scope();
if (is_compiled_scope.is_compiled()) {
Handle<SharedFunctionInfo> shared_handle(shared, isolate_);
// 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 PrepareFunctionForDebugExecution.
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, isolate_), Compiler::CLEAR_EXCEPTION,
&is_compiled_scope)) {
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() && !CanBreakAtEntry(shared)) {
return false;
}
IsCompiledScope is_compiled_scope = shared->is_compiled_scope();
if (!is_compiled_scope.is_compiled() &&
!Compiler::Compile(shared, Compiler::CLEAR_EXCEPTION,
&is_compiled_scope)) {
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));
int flags = debug_info->flags();
flags |= DebugInfo::kHasBreakInfo;
if (CanBreakAtEntry(shared)) flags |= DebugInfo::kCanBreakAtEntry;
debug_info->set_flags(flags);
debug_info->set_break_points(*break_points);
SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate_, shared);
}
Handle<DebugInfo> Debug::GetOrCreateDebugInfo(
Handle<SharedFunctionInfo> shared) {
if (shared->HasDebugInfo()) return handle(shared->GetDebugInfo(), isolate_);
// Create debug info and add it to the list.
Handle<DebugInfo> debug_info = isolate_->factory()->NewDebugInfo(shared);
DebugInfoListNode* node = new DebugInfoListNode(isolate_, *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) { info->ClearCoverageInfo(isolate_); });
}
void Debug::ClearAllDebuggerHints() {
ClearAllDebugInfos(
[=](Handle<DebugInfo> info) { info->set_debugger_hints(0); });
}
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(const 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();
clear_function(debug_info);
if (debug_info->IsEmpty()) {
FreeDebugInfoListNode(prev, current);
current = next;
} else {
prev = current;
current = next;
}
}
}
void Debug::RemoveBreakInfoAndMaybeFree(Handle<DebugInfo> debug_info) {
debug_info->ClearBreakInfo(isolate_);
if (debug_info->IsEmpty()) {
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 script back into the
// SFI::script_or_debug_info field.
Handle<DebugInfo> debug_info(node->debug_info());
debug_info->shared().set_script_or_debug_info(debug_info->script());
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(), isolate_);
// 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(), isolate_);
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_ = StackFrameId::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;
}
}
}
Handle<FixedArray> Debug::GetLoadedScripts() {
isolate_->heap()->CollectAllGarbage(Heap::kNoGCFlags,
GarbageCollectionReason::kDebugger);
Factory* factory = isolate_->factory();
if (!factory->script_list()->IsWeakArrayList()) {
return factory->empty_fixed_array();
}
Handle<WeakArrayList> array =
Handle<WeakArrayList>::cast(factory->script_list());
Handle<FixedArray> results = factory->NewFixedArray(array->length());
int length = 0;
{
Script::Iterator iterator(isolate_);
for (Script script = iterator.Next(); !script.is_null();
script = iterator.Next()) {
if (script.HasValidSource()) results->set(length++, script);
}
}
return FixedArray::ShrinkOrEmpty(isolate_, results, length);
}
base::Optional<Object> 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();
}
Handle<Object> maybe_promise = isolate_->GetPromiseOnStackOnThrow();
OnException(exception, maybe_promise,
maybe_promise->IsJSPromise() ? v8::debug::kPromiseRejection
: v8::debug::kException);
if (!scheduled_exception.is_null()) {
isolate_->thread_local_top()->scheduled_exception_ = *scheduled_exception;
}
PrepareStepOnThrow();
// If the OnException handler requested termination, then indicated this to
// our caller Isolate::Throw so it can deal with it immediatelly instead of
// throwing the original exception.
if (isolate_->stack_guard()->CheckTerminateExecution()) {
isolate_->stack_guard()->ClearTerminateExecution();
return isolate_->TerminateExecution();
}
return {};
}
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, v8::debug::kPromiseRejection);
}
}
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,
v8::debug::ExceptionType exception_type) {
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();
Object::SetProperty(isolate_, jspromise, key, key, StoreOrigin::kMaybeKeyed,
Just(ShouldThrow::kThrowOnError))
.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.
}
// Do not trigger exception event on stack overflow. We cannot perform
// anything useful for debugging in that situation.
StackLimitCheck stack_limit_check(isolate_);
if (stack_limit_check.JsHasOverflowed()) return;
DebugScope debug_scope(this);
HandleScope scope(isolate_);
DisableBreak no_recursive_break(this);
Handle<Context> native_context(isolate_->native_context());
debug_delegate_->ExceptionThrown(
v8::Utils::ToLocal(native_context), v8::Utils::ToLocal(exception),
v8::Utils::ToLocal(promise), uncaught, exception_type);
}
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;
DCHECK(in_debug_scope());
HandleScope scope(isolate_);
DisableBreak no_recursive_break(this);
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) {
BreakPoint break_point = BreakPoint::cast(break_points_hit->get(i));
inspector_break_points_hit.push_back(break_point.id());
++inspector_break_points_count;
}
Handle<Context> native_context(isolate_->native_context());
debug_delegate_->BreakProgramRequested(v8::Utils::ToLocal(native_context),
inspector_break_points_hit);
}
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();
Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared);
if (!debug_info->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()), isolate_);
DCHECK(script->IsUserJavaScript());
debug::Location start = GetDebugLocation(script, shared->StartPosition());
debug::Location end = GetDebugLocation(script, shared->EndPosition());
is_blackboxed = debug_delegate_->IsFunctionBlackboxed(
ToApiHandle<debug::Script>(script), start, end);
}
debug_info->set_debug_is_blackboxed(is_blackboxed);
debug_info->set_computed_debug_is_blackboxed(true);
}
return debug_info->debug_is_blackboxed();
}
bool Debug::AllFramesOnStackAreBlackboxed() {
HandleScope scope(isolate_);
for (StackTraceFrameIterator it(isolate_); !it.done(); it.Advance()) {
if (!it.is_javascript()) continue;
if (!IsFrameBlackboxed(it.javascript_frame())) return false;
}
return true;
}
bool Debug::CanBreakAtEntry(Handle<SharedFunctionInfo> shared) {
// Allow break at entry for builtin functions.
if (shared->native() || shared->IsApiFunction()) {
// Functions that are subject to debugging can have regular breakpoints.
DCHECK(!shared->IsSubjectToDebugging());
return true;
}
return false;
}
bool Debug::SetScriptSource(Handle<Script> script, Handle<String> source,
bool preview, debug::LiveEditResult* result) {
DebugScope debug_scope(this);
feature_tracker()->Track(DebugFeatureTracker::kLiveEdit);
running_live_edit_ = true;
LiveEdit::PatchScript(isolate_, script, source, preview, result);
running_live_edit_ = false;
return result->status == debug::LiveEditResult::OK;
}
void Debug::OnCompileError(Handle<Script> script) {
ProcessCompileEvent(true, script);
}
void Debug::OnAfterCompile(Handle<Script> script) {
ProcessCompileEvent(false, script);
}
void Debug::ProcessCompileEvent(bool has_compile_error, Handle<Script> script) {
// TODO(kozyatinskiy): teach devtools to work with liveedit scripts better
// first and then remove this fast return.
if (running_live_edit_) return;
// 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);
HandleScope scope(isolate_);
DisableBreak no_recursive_break(this);
AllowJavascriptExecution allow_script(isolate_);
debug_delegate_->ScriptCompiled(ToApiHandle<debug::Script>(script),
running_live_edit_, has_compile_error);
}
int Debug::CurrentFrameCount() {
StackTraceFrameIterator it(isolate_);
if (break_frame_id() != StackFrameId::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) {
debug_delegate_ = delegate;
UpdateState();
}
void Debug::UpdateState() {
bool is_active = debug_delegate_ != nullptr;
if (is_active == is_active_) return;
if (is_active) {
// Note that the debug context could have already been loaded to
// bootstrap test cases.
isolate_->compilation_cache()->DisableScriptAndEval();
is_active = true;
feature_tracker()->Track(DebugFeatureTracker::kActive);
} else {
isolate_->compilation_cache()->EnableScriptAndEval();
Unload();
}
is_active_ = is_active;
isolate_->PromiseHookStateUpdated();
}
void Debug::UpdateHookOnFunctionCall() {
STATIC_ASSERT(LastStepAction == StepIn);
hook_on_function_call_ =
thread_local_.last_step_action_ == StepIn ||
isolate_->debug_execution_mode() == DebugInfo::kSideEffects ||
thread_local_.break_on_next_function_call_;
}
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.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) return;
// Don't stop if the break location is muted.
if (IsMutedAtCurrentLocation(it.frame())) return;
}
}
// Clear stepping to avoid duplicate breaks.
ClearStepping();
HandleScope scope(isolate_);
DebugScope debug_scope(this);
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);
summary.EnsureSourcePositionsAvailable();
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()), isolate_);
Script::InitLineEnds(isolate_, 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()),
isolate_);
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(no_gc);
if (content.IsOneByte()) {
PrintF("[debug] %.*s\n", line_end - line_start,
content.ToOneByteVector().begin() + 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_(reinterpret_cast<DebugScope*>(
base::Relaxed_Load(&debug->thread_local_.current_debug_scope_))),
no_interrupts_(debug_->isolate_) {
// Link recursive debugger entry.
base::Relaxed_Store(&debug_->thread_local_.current_debug_scope_,
reinterpret_cast<base::AtomicWord>(this));
// Store the previous frame id and return value.
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() : StackFrameId::NO_ID;
debug_->UpdateState();
}
void DebugScope::set_terminate_on_resume() { terminate_on_resume_ = true; }
DebugScope::~DebugScope() {
// Terminate on resume must have been handled by retrieving it, if this is
// the outer scope.
if (terminate_on_resume_) {
if (!prev_) {
debug_->isolate_->stack_guard()->RequestTerminateExecution();
} else {
prev_->set_terminate_on_resume();
}
}
// 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_->UpdateState();
}
ReturnValueScope::ReturnValueScope(Debug* debug) : debug_(debug) {
return_value_ = debug_->return_value_handle();
}
ReturnValueScope::~ReturnValueScope() {
debug_->set_return_value(*return_value_);
}
void Debug::UpdateDebugInfosForExecutionMode() {
// Walk all debug infos and update their execution mode if it is different
// from the isolate execution mode.
DebugInfoListNode* current = debug_info_list_;
while (current != nullptr) {
Handle<DebugInfo> debug_info = current->debug_info();
if (debug_info->HasInstrumentedBytecodeArray() &&
debug_info->DebugExecutionMode() != isolate_->debug_execution_mode()) {
DCHECK(debug_info->shared().HasBytecodeArray());
if (isolate_->debug_execution_mode() == DebugInfo::kBreakpoints) {
ClearSideEffectChecks(debug_info);
ApplyBreakPoints(debug_info);
} else {
ClearBreakPoints(debug_info);
ApplySideEffectChecks(debug_info);
}
}
current = current->next();
}
}
void Debug::SetTerminateOnResume() {
DebugScope* scope = reinterpret_cast<DebugScope*>(
base::Acquire_Load(&thread_local_.current_debug_scope_));
CHECK_NOT_NULL(scope);
scope->set_terminate_on_resume();
}
void Debug::StartSideEffectCheckMode() {
DCHECK(isolate_->debug_execution_mode() != DebugInfo::kSideEffects);
isolate_->set_debug_execution_mode(DebugInfo::kSideEffects);
UpdateHookOnFunctionCall();
side_effect_check_failed_ = false;
DCHECK(!temporary_objects_);
temporary_objects_.reset(new TemporaryObjectsTracker());
isolate_->heap()->AddHeapObjectAllocationTracker(temporary_objects_.get());
Handle<FixedArray> array(isolate_->native_context()->regexp_last_match_info(),
isolate_);
regexp_match_info_ =
Handle<RegExpMatchInfo>::cast(isolate_->factory()->CopyFixedArray(array));
// Update debug infos to have correct execution mode.
UpdateDebugInfosForExecutionMode();
}
void Debug::StopSideEffectCheckMode() {
DCHECK(isolate_->debug_execution_mode() == DebugInfo::kSideEffects);
if (side_effect_check_failed_) {
DCHECK(isolate_->has_pending_exception());
DCHECK_EQ(ReadOnlyRoots(isolate_).termination_exception(),
isolate_->pending_exception());
// Convert the termination exception into a regular exception.
isolate_->CancelTerminateExecution();
isolate_->Throw(*isolate_->factory()->NewEvalError(
MessageTemplate::kNoSideEffectDebugEvaluate));
}
isolate_->set_debug_execution_mode(DebugInfo::kBreakpoints);
UpdateHookOnFunctionCall();
side_effect_check_failed_ = false;
DCHECK(temporary_objects_);
isolate_->heap()->RemoveHeapObjectAllocationTracker(temporary_objects_.get());
temporary_objects_.reset();
isolate_->native_context()->set_regexp_last_match_info(*regexp_match_info_);
regexp_match_info_ = Handle<RegExpMatchInfo>::null();
// Update debug infos to have correct execution mode.
UpdateDebugInfosForExecutionMode();
}
void Debug::ApplySideEffectChecks(Handle<DebugInfo> debug_info) {
DCHECK(debug_info->HasInstrumentedBytecodeArray());
Handle<BytecodeArray> debug_bytecode(debug_info->DebugBytecodeArray(),
isolate_);
DebugEvaluate::ApplySideEffectChecks(debug_bytecode);
debug_info->SetDebugExecutionMode(DebugInfo::kSideEffects);
}
void Debug::ClearSideEffectChecks(Handle<DebugInfo> debug_info) {
DCHECK(debug_info->HasInstrumentedBytecodeArray());
Handle<BytecodeArray> debug_bytecode(debug_info->DebugBytecodeArray(),
isolate_);
Handle<BytecodeArray> original(debug_info->OriginalBytecodeArray(), isolate_);
for (interpreter::BytecodeArrayIterator it(debug_bytecode); !it.done();
it.Advance()) {
// Restore from original. This may copy only the scaling prefix, which is
// correct, since we patch scaling prefixes to debug breaks if exists.
debug_bytecode->set(it.current_offset(),
original->get(it.current_offset()));
}
}
bool Debug::PerformSideEffectCheck(Handle<JSFunction> function,
Handle<Object> receiver) {
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
DisallowJavascriptExecution no_js(isolate_);
IsCompiledScope is_compiled_scope(function->shared().is_compiled_scope());
if (!function->is_compiled() &&
!Compiler::Compile(function, Compiler::KEEP_EXCEPTION,
&is_compiled_scope)) {
return false;
}
DCHECK(is_compiled_scope.is_compiled());
Handle<SharedFunctionInfo> shared(function->shared(), isolate_);
Handle<DebugInfo> debug_info = GetOrCreateDebugInfo(shared);
DebugInfo::SideEffectState side_effect_state =
debug_info->GetSideEffectState(isolate_);
switch (side_effect_state) {
case DebugInfo::kHasSideEffects:
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;
case DebugInfo::kRequiresRuntimeChecks: {
if (!shared->HasBytecodeArray()) {
return PerformSideEffectCheckForObject(receiver);
}
// If function has bytecode array then prepare function for debug
// execution to perform runtime side effect checks.
DCHECK(shared->is_compiled());
PrepareFunctionForDebugExecution(shared);
ApplySideEffectChecks(debug_info);
return true;
}
case DebugInfo::kHasNoSideEffect:
return true;
case DebugInfo::kNotComputed:
UNREACHABLE();
return false;
}
UNREACHABLE();
return false;
}
Handle<Object> Debug::return_value_handle() {
return handle(thread_local_.return_value_, isolate_);
}
bool Debug::PerformSideEffectCheckForCallback(
Handle<Object> callback_info, Handle<Object> receiver,
Debug::AccessorKind accessor_kind) {
DCHECK_EQ(!receiver.is_null(), callback_info->IsAccessorInfo());
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
if (!callback_info.is_null() && callback_info->IsCallHandlerInfo() &&
i::CallHandlerInfo::cast(*callback_info).NextCallHasNoSideEffect()) {
return true;
}
// TODO(7515): always pass a valid callback info object.
if (!callback_info.is_null()) {
if (callback_info->IsAccessorInfo()) {
// List of whitelisted internal accessors can be found in accessors.h.
AccessorInfo info = AccessorInfo::cast(*callback_info);
DCHECK_NE(kNotAccessor, accessor_kind);
switch (accessor_kind == kSetter ? info.setter_side_effect_type()
: info.getter_side_effect_type()) {
case SideEffectType::kHasNoSideEffect:
// We do not support setter accessors with no side effects, since
// calling set accessors go through a store bytecode. Store bytecodes
// are considered to cause side effects (to non-temporary objects).
DCHECK_NE(kSetter, accessor_kind);
return true;
case SideEffectType::kHasSideEffectToReceiver:
DCHECK(!receiver.is_null());
if (PerformSideEffectCheckForObject(receiver)) return true;
isolate_->OptionalRescheduleException(false);
return false;
case SideEffectType::kHasSideEffect:
break;
}
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] API Callback '");
info.name().ShortPrint();
PrintF("' may cause side effect.\n");
}
} else if (callback_info->IsInterceptorInfo()) {
InterceptorInfo info = InterceptorInfo::cast(*callback_info);
if (info.has_no_side_effect()) return true;
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] API Interceptor may cause side effect.\n");
}
} else if (callback_info->IsCallHandlerInfo()) {
CallHandlerInfo info = CallHandlerInfo::cast(*callback_info);
if (info.IsSideEffectFreeCallHandlerInfo()) return true;
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] API CallHandlerInfo may cause side effect.\n");
}
}
}
side_effect_check_failed_ = true;
// Throw an uncatchable termination exception.
isolate_->TerminateExecution();
isolate_->OptionalRescheduleException(false);
return false;
}
bool Debug::PerformSideEffectCheckAtBytecode(InterpretedFrame* frame) {
using interpreter::Bytecode;
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
SharedFunctionInfo shared = frame->function().shared();
BytecodeArray bytecode_array = shared.GetBytecodeArray();
int offset = frame->GetBytecodeOffset();
interpreter::BytecodeArrayAccessor bytecode_accessor(
handle(bytecode_array, isolate_), offset);
Bytecode bytecode = bytecode_accessor.current_bytecode();
interpreter::Register reg;
switch (bytecode) {
case Bytecode::kStaCurrentContextSlot:
reg = interpreter::Register::current_context();
break;
default:
reg = bytecode_accessor.GetRegisterOperand(0);
break;
}
Handle<Object> object =
handle(frame->ReadInterpreterRegister(reg.index()), isolate_);
return PerformSideEffectCheckForObject(object);
}
bool Debug::PerformSideEffectCheckForObject(Handle<Object> object) {
DCHECK_EQ(isolate_->debug_execution_mode(), DebugInfo::kSideEffects);
// We expect no side-effects for primitives.
if (object->IsNumber()) return true;
if (object->IsName()) return true;
if (temporary_objects_->HasObject(Handle<HeapObject>::cast(object))) {
return true;
}
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] failed runtime side effect check.\n");
}
side_effect_check_failed_ = true;
// Throw an uncatchable termination exception.
isolate_->TerminateExecution();
return false;
}
} // namespace internal
} // namespace v8