Upgrade V8 to 8.8.278.14
Bug: 162604069
Bug: 167389063
Test: gts-tradefed run gts-dev --module GtsGmscoreHostTestCases
--test com.google.android.gts.devicepolicy.DeviceOwnerTest#testProxyPacProxyTest
Test: m -j proxy_resolver_v8_unittest && adb sync && adb shell \
/data/nativetest/proxy_resolver_v8_unittest/proxy_resolver_v8_unittest
Merged-In: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
Change-Id: Ifb09923b9d7f6d8990fb062d7dc0294edf2c098e
(cherry picked from commit 9580a23bc5b8874a0979001d3595d027cbb68128)
diff --git a/src/debug/DIR_METADATA b/src/debug/DIR_METADATA
new file mode 100644
index 0000000..3ba1106
--- /dev/null
+++ b/src/debug/DIR_METADATA
@@ -0,0 +1,11 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail {
+ component: "Platform>DevTools>JavaScript"
+}
\ No newline at end of file
diff --git a/src/debug/OWNERS b/src/debug/OWNERS
index 4e493cd..5b93352 100644
--- a/src/debug/OWNERS
+++ b/src/debug/OWNERS
@@ -1,8 +1,6 @@
-set noparent
-
bmeurer@chromium.org
jgruber@chromium.org
mvstanton@chromium.org
-ulan@chromium.org
+szuend@chromium.org
verwaest@chromium.org
yangguo@chromium.org
diff --git a/src/debug/arm/OWNERS b/src/debug/arm/OWNERS
deleted file mode 100644
index 906a5ce..0000000
--- a/src/debug/arm/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-rmcilroy@chromium.org
diff --git a/src/debug/arm/debug-arm.cc b/src/debug/arm/debug-arm.cc
index 4839282..23460d6 100644
--- a/src/debug/arm/debug-arm.cc
+++ b/src/debug/arm/debug-arm.cc
@@ -6,8 +6,11 @@
#include "src/debug/debug.h"
-#include "src/codegen.h"
+#include "src/codegen/assembler-inl.h"
+#include "src/codegen/macro-assembler.h"
#include "src/debug/liveedit.h"
+#include "src/execution/frames-inl.h"
+#include "src/objects/objects-inl.h"
namespace v8 {
namespace internal {
@@ -15,101 +18,6 @@
#define __ ACCESS_MASM(masm)
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_size;
- __ bind(&check_size);
- for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) {
- __ nop(MacroAssembler::DEBUG_BREAK_NOP);
- }
- DCHECK_EQ(Assembler::kDebugBreakSlotInstructions,
- masm->InstructionsGeneratedSince(&check_size));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction. Avoid emitting
- // the constant pool in the debug break slot code.
- Assembler::BlockConstPoolScope block_const_pool(masm);
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- // Patch the code changing the debug break slot code from
- // mov r2, r2
- // mov r2, r2
- // mov r2, r2
- // mov r2, r2
- // to a call to the debug break slot code.
- // ldr ip, [pc, #0]
- // b skip
- // <debug break slot code entry point address>
- // skip:
- // blx ip
- Label skip_constant;
- patcher.masm()->ldr(ip, MemOperand(v8::internal::pc, 0));
- patcher.masm()->b(&skip_constant);
- patcher.Emit(code->entry());
- patcher.masm()->bind(&skip_constant);
- patcher.masm()->blx(ip);
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- Instr current_instr = Assembler::instr_at(pc);
- return !Assembler::IsNop(current_instr, Assembler::DEBUG_BREAK_NOP);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
- {
- FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ push(r0);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ mov(r0, Operand(1));
- __ mov(r1,
- Operand(ExternalReference(
- Runtime::FunctionForId(Runtime::kDebugBreak), masm->isolate())));
-
- CEntryStub ceb(masm->isolate(), 1);
- __ CallStub(&ceb);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; i++) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber r0 if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function.
- if (!(reg.is(r0) && (mode == SAVE_RESULT_REGISTER))) {
- __ mov(reg, Operand(kDebugZapValue));
- }
- }
- }
- // Leave the internal frame.
- }
-
- __ MaybeDropFrames();
-
- // Return to caller.
- __ Ret();
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -128,18 +36,15 @@
// - Leave the frame.
// - Restart the frame by calling the function.
__ mov(fp, r1);
- __ ldr(r1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
+ __ ldr(r1, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
__ LeaveFrame(StackFrame::INTERNAL);
__ ldr(r0, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
- __ ldr(r0,
- FieldMemOperand(r0, SharedFunctionInfo::kFormalParameterCountOffset));
+ __ ldrh(r0,
+ FieldMemOperand(r0, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(r2, r0);
- ParameterCount dummy1(r2);
- ParameterCount dummy2(r0);
- __ InvokeFunction(r1, dummy1, dummy2, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ __ InvokeFunction(r1, r2, r0, JUMP_FUNCTION);
}
diff --git a/src/debug/arm64/OWNERS b/src/debug/arm64/OWNERS
deleted file mode 100644
index 906a5ce..0000000
--- a/src/debug/arm64/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-rmcilroy@chromium.org
diff --git a/src/debug/arm64/debug-arm64.cc b/src/debug/arm64/debug-arm64.cc
index 06929c6..251856e 100644
--- a/src/debug/arm64/debug-arm64.cc
+++ b/src/debug/arm64/debug-arm64.cc
@@ -6,122 +6,17 @@
#include "src/debug/debug.h"
-#include "src/arm64/frames-arm64.h"
-#include "src/codegen.h"
+#include "src/codegen/arm64/macro-assembler-arm64-inl.h"
#include "src/debug/liveedit.h"
+#include "src/execution/frame-constants.h"
+#include "src/execution/frames-inl.h"
+#include "src/objects/objects-inl.h"
namespace v8 {
namespace internal {
#define __ ACCESS_MASM(masm)
-
-void EmitDebugBreakSlot(Assembler* masm) {
- Label check_size;
- __ bind(&check_size);
- for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) {
- __ nop(Assembler::DEBUG_BREAK_NOP);
- }
- DCHECK_EQ(Assembler::kDebugBreakSlotInstructions,
- static_cast<int>(masm->InstructionsGeneratedSince(&check_size)));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction. Avoid emitting
- // the constant pool in the debug break slot code.
- InstructionAccurateScope scope(masm, Assembler::kDebugBreakSlotInstructions);
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- PatchingAssembler patcher(isolate, reinterpret_cast<Instruction*>(pc),
- Assembler::kDebugBreakSlotInstructions);
- EmitDebugBreakSlot(&patcher);
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- PatchingAssembler patcher(isolate, reinterpret_cast<Instruction*>(pc),
- Assembler::kDebugBreakSlotInstructions);
- // Patch the code emitted by DebugCodegen::GenerateSlots, changing the debug
- // break slot code from
- // mov x0, x0 @ nop DEBUG_BREAK_NOP
- // mov x0, x0 @ nop DEBUG_BREAK_NOP
- // mov x0, x0 @ nop DEBUG_BREAK_NOP
- // mov x0, x0 @ nop DEBUG_BREAK_NOP
- // mov x0, x0 @ nop DEBUG_BREAK_NOP
- // to a call to the debug slot code.
- // ldr ip0, [pc, #(2 * kInstructionSize)]
- // blr ip0
- // b skip
- // <debug break slot code entry point address (64 bits)>
- // skip:
-
- Label skip_constant;
- // The first instruction of a patched debug break slot must be a load literal
- // loading the address of the debug break slot code.
- patcher.ldr_pcrel(ip0, (2 * kInstructionSize) >> kLoadLiteralScaleLog2);
- patcher.b(&skip_constant);
- patcher.dc64(reinterpret_cast<int64_t>(code->entry()));
- patcher.bind(&skip_constant);
- // TODO(all): check the following is correct.
- // The debug break slot code will push a frame and call statically compiled
- // code. By using blr, this call site will be registered in the frame.
- // The debugger can now iterate on the frames to find this call.
- patcher.blr(ip0);
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- Instruction* current_instr = reinterpret_cast<Instruction*>(pc);
- return !current_instr->IsNop(Assembler::DEBUG_BREAK_NOP);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
- Register scratch = x10;
- {
- FrameScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ Push(x0);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ Mov(x0, 1);
- __ Mov(x1, ExternalReference(Runtime::FunctionForId(Runtime::kDebugBreak),
- masm->isolate()));
-
- CEntryStub stub(masm->isolate(), 1);
- __ CallStub(&stub);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; i++) {
- Register reg = Register::XRegFromCode(JSCallerSavedCode(i));
- // Do not clobber x0 if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function.
- if (!(reg.is(x0) && (mode == SAVE_RESULT_REGISTER))) {
- __ Mov(reg, Operand(kDebugZapValue));
- }
- }
- }
- // Leave the internal frame.
- }
-
- __ MaybeDropFrames();
-
- // Return to caller.
- __ Ret();
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -140,21 +35,18 @@
// - Leave the frame.
// - Restart the frame by calling the function.
__ Mov(fp, x1);
- __ AssertStackConsistency();
- __ Ldr(x1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
+ __ Ldr(x1, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
- __ Mov(masm->StackPointer(), Operand(fp));
- __ Pop(fp, lr); // Frame, Return address.
+ __ Mov(sp, fp);
+ __ Pop<TurboAssembler::kAuthLR>(fp, lr);
- __ Ldr(x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
- __ Ldr(x0,
- FieldMemOperand(x0, SharedFunctionInfo::kFormalParameterCountOffset));
- __ mov(x2, x0);
+ __ LoadTaggedPointerField(
+ x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
+ __ Ldrh(x0,
+ FieldMemOperand(x0, SharedFunctionInfo::kFormalParameterCountOffset));
+ __ mov(x3, x0);
- ParameterCount dummy1(x2);
- ParameterCount dummy2(x0);
- __ InvokeFunction(x1, dummy1, dummy2, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ __ InvokeFunctionWithNewTarget(x1, x3, x0, JUMP_FUNCTION);
}
@@ -163,4 +55,6 @@
} // namespace internal
} // namespace v8
+#undef __
+
#endif // V8_TARGET_ARCH_ARM64
diff --git a/src/debug/debug-coverage.cc b/src/debug/debug-coverage.cc
index 8a13b6c..bee4122 100644
--- a/src/debug/debug-coverage.cc
+++ b/src/debug/debug-coverage.cc
@@ -4,22 +4,26 @@
#include "src/debug/debug-coverage.h"
+#include "src/ast/ast-source-ranges.h"
+#include "src/ast/ast.h"
#include "src/base/hashmap.h"
-#include "src/deoptimizer.h"
-#include "src/isolate.h"
-#include "src/objects-inl.h"
-#include "src/objects.h"
+#include "src/debug/debug.h"
+#include "src/deoptimizer/deoptimizer.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/isolate.h"
+#include "src/objects/debug-objects-inl.h"
+#include "src/objects/objects.h"
namespace v8 {
namespace internal {
class SharedToCounterMap
- : public base::TemplateHashMapImpl<SharedFunctionInfo*, uint32_t,
- base::KeyEqualityMatcher<void*>,
+ : public base::TemplateHashMapImpl<SharedFunctionInfo, uint32_t,
+ base::KeyEqualityMatcher<Object>,
base::DefaultAllocationPolicy> {
public:
- typedef base::TemplateHashMapEntry<SharedFunctionInfo*, uint32_t> Entry;
- inline void Add(SharedFunctionInfo* key, uint32_t count) {
+ using Entry = base::TemplateHashMapEntry<SharedFunctionInfo, uint32_t>;
+ inline void Add(SharedFunctionInfo key, uint32_t count) {
Entry* entry = LookupOrInsert(key, Hash(key), []() { return 0; });
uint32_t old_count = entry->value;
if (UINT32_MAX - count < old_count) {
@@ -29,140 +33,779 @@
}
}
- inline uint32_t Get(SharedFunctionInfo* key) {
+ inline uint32_t Get(SharedFunctionInfo key) {
Entry* entry = Lookup(key, Hash(key));
if (entry == nullptr) return 0;
return entry->value;
}
private:
- static uint32_t Hash(SharedFunctionInfo* key) {
- return static_cast<uint32_t>(reinterpret_cast<intptr_t>(key));
+ static uint32_t Hash(SharedFunctionInfo key) {
+ return static_cast<uint32_t>(key.ptr());
}
DisallowHeapAllocation no_gc;
};
namespace {
-int StartPosition(SharedFunctionInfo* info) {
- int start = info->function_token_position();
- if (start == kNoSourcePosition) start = info->start_position();
+int StartPosition(SharedFunctionInfo info) {
+ int start = info.function_token_position();
+ if (start == kNoSourcePosition) start = info.StartPosition();
return start;
}
-bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) {
- int a_start = StartPosition(a);
- int b_start = StartPosition(b);
- if (a_start == b_start) return a->end_position() > b->end_position();
- return a_start < b_start;
+bool CompareCoverageBlock(const CoverageBlock& a, const CoverageBlock& b) {
+ DCHECK_NE(kNoSourcePosition, a.start);
+ DCHECK_NE(kNoSourcePosition, b.start);
+ if (a.start == b.start) return a.end > b.end;
+ return a.start < b.start;
}
-} // anonymous namespace
-Coverage* Coverage::Collect(Isolate* isolate, bool reset_count) {
- SharedToCounterMap counter_map;
+void SortBlockData(
+ std::vector<CoverageBlock>& v) { // NOLINT(runtime/references)
+ // Sort according to the block nesting structure.
+ std::sort(v.begin(), v.end(), CompareCoverageBlock);
+}
- // Feed invocation count into the counter map.
- if (isolate->IsCodeCoverageEnabled()) {
- // Feedback vectors are already listed to prevent losing them to GC.
- Handle<ArrayList> list =
- Handle<ArrayList>::cast(isolate->factory()->code_coverage_list());
- for (int i = 0; i < list->Length(); i++) {
- FeedbackVector* vector = FeedbackVector::cast(list->Get(i));
- SharedFunctionInfo* shared = vector->shared_function_info();
- DCHECK(shared->IsSubjectToDebugging());
- uint32_t count = static_cast<uint32_t>(vector->invocation_count());
- if (reset_count) vector->clear_invocation_count();
- counter_map.Add(shared, count);
- }
- } else {
- // Iterate the heap to find all feedback vectors and accumulate the
- // invocation counts into the map for each shared function info.
- HeapIterator heap_iterator(isolate->heap());
- while (HeapObject* current_obj = heap_iterator.next()) {
- if (!current_obj->IsFeedbackVector()) continue;
- FeedbackVector* vector = FeedbackVector::cast(current_obj);
- SharedFunctionInfo* shared = vector->shared_function_info();
- if (!shared->IsSubjectToDebugging()) continue;
- uint32_t count = static_cast<uint32_t>(vector->invocation_count());
- if (reset_count) vector->clear_invocation_count();
- counter_map.Add(shared, count);
- }
+std::vector<CoverageBlock> GetSortedBlockData(SharedFunctionInfo shared) {
+ DCHECK(shared.HasCoverageInfo());
+
+ CoverageInfo coverage_info =
+ CoverageInfo::cast(shared.GetDebugInfo().coverage_info());
+
+ std::vector<CoverageBlock> result;
+ if (coverage_info.slot_count() == 0) return result;
+
+ for (int i = 0; i < coverage_info.slot_count(); i++) {
+ const int start_pos = coverage_info.StartSourcePosition(i);
+ const int until_pos = coverage_info.EndSourcePosition(i);
+ const int count = coverage_info.BlockCount(i);
+
+ DCHECK_NE(kNoSourcePosition, start_pos);
+ result.emplace_back(start_pos, until_pos, count);
}
- // Iterate shared function infos of every script and build a mapping
- // between source ranges and invocation counts.
- Coverage* result = new Coverage();
- Script::Iterator scripts(isolate);
- while (Script* script = scripts.Next()) {
- // Dismiss non-user scripts.
- if (script->type() != Script::TYPE_NORMAL) continue;
+ SortBlockData(result);
- // Create and add new script data.
- Handle<Script> script_handle(script, isolate);
- result->emplace_back(isolate, script_handle);
- std::vector<CoverageFunction>* functions = &result->back().functions;
+ return result;
+}
- std::vector<SharedFunctionInfo*> sorted;
- bool has_toplevel = false;
+// A utility class to simplify logic for performing passes over block coverage
+// ranges. Provides access to the implicit tree structure of ranges (i.e. access
+// to parent and sibling blocks), and supports efficient in-place editing and
+// deletion. The underlying backing store is the array of CoverageBlocks stored
+// on the CoverageFunction.
+class CoverageBlockIterator final {
+ public:
+ explicit CoverageBlockIterator(CoverageFunction* function)
+ : function_(function) {
+ DCHECK(std::is_sorted(function_->blocks.begin(), function_->blocks.end(),
+ CompareCoverageBlock));
+ }
- {
- // Sort functions by start position, from outer to inner functions.
- SharedFunctionInfo::ScriptIterator infos(script_handle);
- while (SharedFunctionInfo* info = infos.Next()) {
- has_toplevel |= info->is_toplevel();
- sorted.push_back(info);
+ ~CoverageBlockIterator() {
+ Finalize();
+ DCHECK(std::is_sorted(function_->blocks.begin(), function_->blocks.end(),
+ CompareCoverageBlock));
+ }
+
+ bool HasNext() const {
+ return read_index_ + 1 < static_cast<int>(function_->blocks.size());
+ }
+
+ bool Next() {
+ if (!HasNext()) {
+ if (!ended_) MaybeWriteCurrent();
+ ended_ = true;
+ return false;
+ }
+
+ // If a block has been deleted, subsequent iteration moves trailing blocks
+ // to their updated position within the array.
+ MaybeWriteCurrent();
+
+ if (read_index_ == -1) {
+ // Initialize the nesting stack with the function range.
+ nesting_stack_.emplace_back(function_->start, function_->end,
+ function_->count);
+ } else if (!delete_current_) {
+ nesting_stack_.emplace_back(GetBlock());
+ }
+
+ delete_current_ = false;
+ read_index_++;
+
+ DCHECK(IsActive());
+
+ CoverageBlock& block = GetBlock();
+ while (nesting_stack_.size() > 1 &&
+ nesting_stack_.back().end <= block.start) {
+ nesting_stack_.pop_back();
+ }
+
+ DCHECK_IMPLIES(block.start >= function_->end,
+ block.end == kNoSourcePosition);
+ DCHECK_NE(block.start, kNoSourcePosition);
+ DCHECK_LE(block.end, GetParent().end);
+
+ return true;
+ }
+
+ CoverageBlock& GetBlock() {
+ DCHECK(IsActive());
+ return function_->blocks[read_index_];
+ }
+
+ CoverageBlock& GetNextBlock() {
+ DCHECK(IsActive());
+ DCHECK(HasNext());
+ return function_->blocks[read_index_ + 1];
+ }
+
+ CoverageBlock& GetPreviousBlock() {
+ DCHECK(IsActive());
+ DCHECK_GT(read_index_, 0);
+ return function_->blocks[read_index_ - 1];
+ }
+
+ CoverageBlock& GetParent() {
+ DCHECK(IsActive());
+ return nesting_stack_.back();
+ }
+
+ bool HasSiblingOrChild() {
+ DCHECK(IsActive());
+ return HasNext() && GetNextBlock().start < GetParent().end;
+ }
+
+ CoverageBlock& GetSiblingOrChild() {
+ DCHECK(HasSiblingOrChild());
+ DCHECK(IsActive());
+ return GetNextBlock();
+ }
+
+ // A range is considered to be at top level if its parent range is the
+ // function range.
+ bool IsTopLevel() const { return nesting_stack_.size() == 1; }
+
+ void DeleteBlock() {
+ DCHECK(!delete_current_);
+ DCHECK(IsActive());
+ delete_current_ = true;
+ }
+
+ private:
+ void MaybeWriteCurrent() {
+ if (delete_current_) return;
+ if (read_index_ >= 0 && write_index_ != read_index_) {
+ function_->blocks[write_index_] = function_->blocks[read_index_];
+ }
+ write_index_++;
+ }
+
+ void Finalize() {
+ while (Next()) {
+ // Just iterate to the end.
+ }
+ function_->blocks.resize(write_index_);
+ }
+
+ bool IsActive() const { return read_index_ >= 0 && !ended_; }
+
+ CoverageFunction* function_;
+ std::vector<CoverageBlock> nesting_stack_;
+ bool ended_ = false;
+ bool delete_current_ = false;
+ int read_index_ = -1;
+ int write_index_ = -1;
+};
+
+bool HaveSameSourceRange(const CoverageBlock& lhs, const CoverageBlock& rhs) {
+ return lhs.start == rhs.start && lhs.end == rhs.end;
+}
+
+void MergeDuplicateRanges(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
+
+ while (iter.Next() && iter.HasNext()) {
+ CoverageBlock& block = iter.GetBlock();
+ CoverageBlock& next_block = iter.GetNextBlock();
+
+ if (!HaveSameSourceRange(block, next_block)) continue;
+
+ DCHECK_NE(kNoSourcePosition, block.end); // Non-singleton range.
+ next_block.count = std::max(block.count, next_block.count);
+ iter.DeleteBlock();
+ }
+}
+
+// Rewrite position singletons (produced by unconditional control flow
+// like return statements, and by continuation counters) into source
+// ranges that end at the next sibling range or the end of the parent
+// range, whichever comes first.
+void RewritePositionSingletonsToRanges(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
+
+ while (iter.Next()) {
+ CoverageBlock& block = iter.GetBlock();
+ CoverageBlock& parent = iter.GetParent();
+
+ if (block.start >= function->end) {
+ DCHECK_EQ(block.end, kNoSourcePosition);
+ iter.DeleteBlock();
+ } else if (block.end == kNoSourcePosition) {
+ // The current block ends at the next sibling block (if it exists) or the
+ // end of the parent block otherwise.
+ if (iter.HasSiblingOrChild()) {
+ block.end = iter.GetSiblingOrChild().start;
+ } else if (iter.IsTopLevel()) {
+ // See https://crbug.com/v8/6661. Functions are special-cased because
+ // we never want the closing brace to be uncovered. This is mainly to
+ // avoid a noisy UI.
+ block.end = parent.end - 1;
+ } else {
+ block.end = parent.end;
}
- std::sort(sorted.begin(), sorted.end(), CompareSharedFunctionInfo);
}
+ }
+}
- functions->reserve(sorted.size() + (has_toplevel ? 0 : 1));
+void MergeConsecutiveRanges(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
- if (!has_toplevel) {
- // Add a replacement toplevel function if it does not exist.
- int source_end = String::cast(script->source())->length();
- functions->emplace_back(0, source_end, 1u,
- isolate->factory()->empty_string());
+ while (iter.Next()) {
+ CoverageBlock& block = iter.GetBlock();
+
+ if (iter.HasSiblingOrChild()) {
+ CoverageBlock& sibling = iter.GetSiblingOrChild();
+ if (sibling.start == block.end && sibling.count == block.count) {
+ // Best-effort: this pass may miss mergeable siblings in the presence of
+ // child blocks.
+ sibling.start = block.start;
+ iter.DeleteBlock();
+ }
}
+ }
+}
- // Use sorted list to reconstruct function nesting.
- for (SharedFunctionInfo* info : sorted) {
- int start = StartPosition(info);
- int end = info->end_position();
- uint32_t count = counter_map.Get(info);
- Handle<String> name(info->DebugName(), isolate);
- functions->emplace_back(start, end, count, name);
+void MergeNestedRanges(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
+
+ while (iter.Next()) {
+ CoverageBlock& block = iter.GetBlock();
+ CoverageBlock& parent = iter.GetParent();
+
+ if (parent.count == block.count) {
+ // Transformation may not be valid if sibling blocks exist with a
+ // differing count.
+ iter.DeleteBlock();
}
}
+}
+
+void RewriteFunctionScopeCounter(CoverageFunction* function) {
+ // Every function must have at least the top-level function counter.
+ DCHECK(!function->blocks.empty());
+
+ CoverageBlockIterator iter(function);
+ if (iter.Next()) {
+ DCHECK(iter.IsTopLevel());
+
+ CoverageBlock& block = iter.GetBlock();
+ if (block.start == SourceRange::kFunctionLiteralSourcePosition &&
+ block.end == SourceRange::kFunctionLiteralSourcePosition) {
+ // If a function-scope block exists, overwrite the function count. It has
+ // a more reliable count than what we get from the FeedbackVector (which
+ // is imprecise e.g. for generator functions and optimized code).
+ function->count = block.count;
+
+ // Then delete it; for compatibility with non-block coverage modes, the
+ // function-scope block is expected in CoverageFunction, not as a
+ // CoverageBlock.
+ iter.DeleteBlock();
+ }
+ }
+}
+
+void FilterAliasedSingletons(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
+
+ iter.Next(); // Advance once since we reference the previous block later.
+
+ while (iter.Next()) {
+ CoverageBlock& previous_block = iter.GetPreviousBlock();
+ CoverageBlock& block = iter.GetBlock();
+
+ bool is_singleton = block.end == kNoSourcePosition;
+ bool aliases_start = block.start == previous_block.start;
+
+ if (is_singleton && aliases_start) {
+ // The previous block must have a full range since duplicate singletons
+ // have already been merged.
+ DCHECK_NE(previous_block.end, kNoSourcePosition);
+ // Likewise, the next block must have another start position since
+ // singletons are sorted to the end.
+ DCHECK_IMPLIES(iter.HasNext(), iter.GetNextBlock().start != block.start);
+ iter.DeleteBlock();
+ }
+ }
+}
+
+void FilterUncoveredRanges(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
+
+ while (iter.Next()) {
+ CoverageBlock& block = iter.GetBlock();
+ CoverageBlock& parent = iter.GetParent();
+ if (block.count == 0 && parent.count == 0) iter.DeleteBlock();
+ }
+}
+
+void FilterEmptyRanges(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
+
+ while (iter.Next()) {
+ CoverageBlock& block = iter.GetBlock();
+ if (block.start == block.end) iter.DeleteBlock();
+ }
+}
+
+void ClampToBinary(CoverageFunction* function) {
+ CoverageBlockIterator iter(function);
+
+ while (iter.Next()) {
+ CoverageBlock& block = iter.GetBlock();
+ if (block.count > 0) block.count = 1;
+ }
+}
+
+void ResetAllBlockCounts(SharedFunctionInfo shared) {
+ DCHECK(shared.HasCoverageInfo());
+
+ CoverageInfo coverage_info =
+ CoverageInfo::cast(shared.GetDebugInfo().coverage_info());
+
+ for (int i = 0; i < coverage_info.slot_count(); i++) {
+ coverage_info.ResetBlockCount(i);
+ }
+}
+
+bool IsBlockMode(debug::CoverageMode mode) {
+ switch (mode) {
+ case debug::CoverageMode::kBlockBinary:
+ case debug::CoverageMode::kBlockCount:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool IsBinaryMode(debug::CoverageMode mode) {
+ switch (mode) {
+ case debug::CoverageMode::kBlockBinary:
+ case debug::CoverageMode::kPreciseBinary:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void CollectBlockCoverageInternal(CoverageFunction* function,
+ SharedFunctionInfo info,
+ debug::CoverageMode mode) {
+ DCHECK(IsBlockMode(mode));
+
+ // Functions with empty source ranges are not interesting to report. This can
+ // happen e.g. for internally-generated functions like class constructors.
+ if (!function->HasNonEmptySourceRange()) return;
+
+ function->has_block_coverage = true;
+ function->blocks = GetSortedBlockData(info);
+
+ // If in binary mode, only report counts of 0/1.
+ if (mode == debug::CoverageMode::kBlockBinary) ClampToBinary(function);
+
+ // To stay compatible with non-block coverage modes, the function-scope count
+ // is expected to be in the CoverageFunction, not as part of its blocks.
+ // This finds the function-scope counter, overwrites CoverageFunction::count,
+ // and removes it from the block list.
+ //
+ // Important: Must be called before other transformation passes.
+ RewriteFunctionScopeCounter(function);
+
+ // Functions without blocks don't need to be processed further.
+ if (!function->HasBlocks()) return;
+
+ // Remove singleton ranges with the same start position as a full range and
+ // throw away their counts.
+ // Singleton ranges are only intended to split existing full ranges and should
+ // never expand into a full range. Consider 'if (cond) { ... } else { ... }'
+ // as a problematic example; if the then-block produces a continuation
+ // singleton, it would incorrectly expand into the else range.
+ // For more context, see https://crbug.com/v8/8237.
+ FilterAliasedSingletons(function);
+
+ // Rewrite all singletons (created e.g. by continuations and unconditional
+ // control flow) to ranges.
+ RewritePositionSingletonsToRanges(function);
+
+ // Merge nested and consecutive ranges with identical counts.
+ // Note that it's necessary to merge duplicate ranges prior to merging nested
+ // changes in order to avoid invalid transformations. See crbug.com/827530.
+ MergeConsecutiveRanges(function);
+
+ SortBlockData(function->blocks);
+ MergeDuplicateRanges(function);
+ MergeNestedRanges(function);
+
+ MergeConsecutiveRanges(function);
+
+ // Filter out ranges with count == 0 unless the immediate parent range has
+ // a count != 0.
+ FilterUncoveredRanges(function);
+
+ // Filter out ranges of zero length.
+ FilterEmptyRanges(function);
+}
+
+void CollectBlockCoverage(CoverageFunction* function, SharedFunctionInfo info,
+ debug::CoverageMode mode) {
+ CollectBlockCoverageInternal(function, info, mode);
+
+ // Reset all counters on the DebugInfo to zero.
+ ResetAllBlockCounts(info);
+}
+
+void PrintBlockCoverage(const CoverageFunction* function,
+ SharedFunctionInfo info, bool has_nonempty_source_range,
+ bool function_is_relevant) {
+ DCHECK(FLAG_trace_block_coverage);
+ std::unique_ptr<char[]> function_name =
+ function->name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
+ i::PrintF(
+ "Coverage for function='%s', SFI=%p, has_nonempty_source_range=%d, "
+ "function_is_relevant=%d\n",
+ function_name.get(), reinterpret_cast<void*>(info.ptr()),
+ has_nonempty_source_range, function_is_relevant);
+ i::PrintF("{start: %d, end: %d, count: %d}\n", function->start, function->end,
+ function->count);
+ for (const auto& block : function->blocks) {
+ i::PrintF("{start: %d, end: %d, count: %d}\n", block.start, block.end,
+ block.count);
+ }
+}
+
+void CollectAndMaybeResetCounts(Isolate* isolate,
+ SharedToCounterMap* counter_map,
+ v8::debug::CoverageMode coverage_mode) {
+ const bool reset_count =
+ coverage_mode != v8::debug::CoverageMode::kBestEffort;
+
+ switch (isolate->code_coverage_mode()) {
+ case v8::debug::CoverageMode::kBlockBinary:
+ case v8::debug::CoverageMode::kBlockCount:
+ case v8::debug::CoverageMode::kPreciseBinary:
+ case v8::debug::CoverageMode::kPreciseCount: {
+ // Feedback vectors are already listed to prevent losing them to GC.
+ DCHECK(isolate->factory()
+ ->feedback_vectors_for_profiling_tools()
+ ->IsArrayList());
+ Handle<ArrayList> list = Handle<ArrayList>::cast(
+ isolate->factory()->feedback_vectors_for_profiling_tools());
+ for (int i = 0; i < list->Length(); i++) {
+ FeedbackVector vector = FeedbackVector::cast(list->Get(i));
+ SharedFunctionInfo shared = vector.shared_function_info();
+ DCHECK(shared.IsSubjectToDebugging());
+ uint32_t count = static_cast<uint32_t>(vector.invocation_count());
+ if (reset_count) vector.clear_invocation_count();
+ counter_map->Add(shared, count);
+ }
+ break;
+ }
+ case v8::debug::CoverageMode::kBestEffort: {
+ DCHECK(!isolate->factory()
+ ->feedback_vectors_for_profiling_tools()
+ ->IsArrayList());
+ DCHECK_EQ(v8::debug::CoverageMode::kBestEffort, coverage_mode);
+ HeapObjectIterator heap_iterator(isolate->heap());
+ for (HeapObject current_obj = heap_iterator.Next();
+ !current_obj.is_null(); current_obj = heap_iterator.Next()) {
+ if (!current_obj.IsJSFunction()) continue;
+ JSFunction func = JSFunction::cast(current_obj);
+ SharedFunctionInfo shared = func.shared();
+ if (!shared.IsSubjectToDebugging()) continue;
+ if (!(func.has_feedback_vector() ||
+ func.has_closure_feedback_cell_array())) {
+ continue;
+ }
+ uint32_t count = 0;
+ if (func.has_feedback_vector()) {
+ count =
+ static_cast<uint32_t>(func.feedback_vector().invocation_count());
+ } else if (func.raw_feedback_cell().interrupt_budget() <
+ FLAG_budget_for_feedback_vector_allocation) {
+ // We haven't allocated feedback vector, but executed the function
+ // atleast once. We don't have precise invocation count here.
+ count = 1;
+ }
+ counter_map->Add(shared, count);
+ }
+
+ // Also check functions on the stack to collect the count map. With lazy
+ // feedback allocation we may miss counting functions if the feedback
+ // vector wasn't allocated yet and the function's interrupt budget wasn't
+ // updated (i.e. it didn't execute return / jump).
+ for (JavaScriptFrameIterator it(isolate); !it.done(); it.Advance()) {
+ SharedFunctionInfo shared = it.frame()->function().shared();
+ if (counter_map->Get(shared) != 0) continue;
+ counter_map->Add(shared, 1);
+ }
+ break;
+ }
+ }
+}
+
+// A {SFI, count} tuple is used to sort by source range (stored on
+// the SFI) and call count (in the counter map).
+struct SharedFunctionInfoAndCount {
+ SharedFunctionInfoAndCount(SharedFunctionInfo info, uint32_t count)
+ : info(info),
+ count(count),
+ start(StartPosition(info)),
+ end(info.EndPosition()) {}
+
+ // Sort by:
+ // - start, ascending.
+ // - end, descending.
+ // - info.is_toplevel() first
+ // - count, descending.
+ bool operator<(const SharedFunctionInfoAndCount& that) const {
+ if (this->start != that.start) return this->start < that.start;
+ if (this->end != that.end) return this->end > that.end;
+ if (this->info.is_toplevel() != that.info.is_toplevel()) {
+ return this->info.is_toplevel();
+ }
+ return this->count > that.count;
+ }
+
+ SharedFunctionInfo info;
+ uint32_t count;
+ int start;
+ int end;
+};
+
+} // anonymous namespace
+
+std::unique_ptr<Coverage> Coverage::CollectPrecise(Isolate* isolate) {
+ DCHECK(!isolate->is_best_effort_code_coverage());
+ std::unique_ptr<Coverage> result =
+ Collect(isolate, isolate->code_coverage_mode());
+ if (!isolate->is_collecting_type_profile() &&
+ (isolate->is_precise_binary_code_coverage() ||
+ isolate->is_block_binary_code_coverage())) {
+ // We do not have to hold onto feedback vectors for invocations we already
+ // reported. So we can reset the list.
+ isolate->SetFeedbackVectorsForProfilingTools(*ArrayList::New(isolate, 0));
+ }
return result;
}
-void Coverage::TogglePrecise(Isolate* isolate, bool enable) {
- if (enable) {
- HandleScope scope(isolate);
- // Remove all optimized function. Optimized and inlined functions do not
- // increment invocation count.
- Deoptimizer::DeoptimizeAll(isolate);
- // Collect existing feedback vectors.
- std::vector<Handle<FeedbackVector>> vectors;
+std::unique_ptr<Coverage> Coverage::CollectBestEffort(Isolate* isolate) {
+ return Collect(isolate, v8::debug::CoverageMode::kBestEffort);
+}
+
+std::unique_ptr<Coverage> Coverage::Collect(
+ Isolate* isolate, v8::debug::CoverageMode collectionMode) {
+ // Collect call counts for all functions.
+ SharedToCounterMap counter_map;
+ CollectAndMaybeResetCounts(isolate, &counter_map, collectionMode);
+
+ // Iterate shared function infos of every script and build a mapping
+ // between source ranges and invocation counts.
+ std::unique_ptr<Coverage> result(new Coverage());
+ Script::Iterator scripts(isolate);
+ for (Script script = scripts.Next(); !script.is_null();
+ script = scripts.Next()) {
+ if (!script.IsUserJavaScript()) continue;
+
+ // Create and add new script data.
+ Handle<Script> script_handle(script, isolate);
+ result->emplace_back(script_handle);
+ std::vector<CoverageFunction>* functions = &result->back().functions;
+
+ std::vector<SharedFunctionInfoAndCount> sorted;
+
{
- HeapIterator heap_iterator(isolate->heap());
- while (HeapObject* current_obj = heap_iterator.next()) {
- if (!current_obj->IsFeedbackVector()) continue;
- FeedbackVector* vector = FeedbackVector::cast(current_obj);
- SharedFunctionInfo* shared = vector->shared_function_info();
- if (!shared->IsSubjectToDebugging()) continue;
- vector->clear_invocation_count();
- vectors.emplace_back(vector, isolate);
+ // Sort functions by start position, from outer to inner functions.
+ SharedFunctionInfo::ScriptIterator infos(isolate, *script_handle);
+ for (SharedFunctionInfo info = infos.Next(); !info.is_null();
+ info = infos.Next()) {
+ sorted.emplace_back(info, counter_map.Get(info));
+ }
+ std::sort(sorted.begin(), sorted.end());
+ }
+
+ // Stack to track nested functions, referring function by index.
+ std::vector<size_t> nesting;
+
+ // Use sorted list to reconstruct function nesting.
+ for (const SharedFunctionInfoAndCount& v : sorted) {
+ SharedFunctionInfo info = v.info;
+ int start = v.start;
+ int end = v.end;
+ uint32_t count = v.count;
+
+ // Find the correct outer function based on start position.
+ //
+ // This is, in general, not robust when considering two functions with
+ // identical source ranges; then the notion of inner and outer is unclear.
+ // Identical source ranges arise when the source range of top-most entity
+ // (e.g. function) in the script is identical to the whole script, e.g.
+ // <script>function foo() {}<script>. The script has its own shared
+ // function info, which has the same source range as the SFI for `foo`.
+ // Node.js creates an additional wrapper for scripts (again with identical
+ // source range) and those wrappers will have a call count of zero even if
+ // the wrapped script was executed (see v8:9212). We mitigate this issue
+ // by sorting top-level SFIs first among SFIs with the same source range:
+ // This ensures top-level SFIs are processed first. If a top-level SFI has
+ // a non-zero call count, it gets recorded due to `function_is_relevant`
+ // below (e.g. script wrappers), while top-level SFIs with zero call count
+ // do not get reported (this ensures node's extra wrappers do not get
+ // reported). If two SFIs with identical source ranges get reported, we
+ // report them in decreasing order of call count, as in all known cases
+ // this corresponds to the nesting order. In the case of the script tag
+ // example above, we report the zero call count of `foo` last. As it turns
+ // out, embedders started to rely on functions being reported in nesting
+ // order.
+ // TODO(jgruber): Investigate whether it is possible to remove node's
+ // extra top-level wrapper script, or change its source range, or ensure
+ // that it follows the invariant that nesting order is descending count
+ // order for SFIs with identical source ranges.
+ while (!nesting.empty() && functions->at(nesting.back()).end <= start) {
+ nesting.pop_back();
+ }
+
+ if (count != 0) {
+ switch (collectionMode) {
+ case v8::debug::CoverageMode::kBlockCount:
+ case v8::debug::CoverageMode::kPreciseCount:
+ break;
+ case v8::debug::CoverageMode::kBlockBinary:
+ case v8::debug::CoverageMode::kPreciseBinary:
+ count = info.has_reported_binary_coverage() ? 0 : 1;
+ info.set_has_reported_binary_coverage(true);
+ break;
+ case v8::debug::CoverageMode::kBestEffort:
+ count = 1;
+ break;
+ }
+ }
+
+ Handle<String> name(info.DebugName(), isolate);
+ CoverageFunction function(start, end, count, name);
+
+ if (IsBlockMode(collectionMode) && info.HasCoverageInfo()) {
+ CollectBlockCoverage(&function, info, collectionMode);
+ }
+
+ // Only include a function range if itself or its parent function is
+ // covered, or if it contains non-trivial block coverage.
+ bool is_covered = (count != 0);
+ bool parent_is_covered =
+ (!nesting.empty() && functions->at(nesting.back()).count != 0);
+ bool has_block_coverage = !function.blocks.empty();
+ bool function_is_relevant =
+ (is_covered || parent_is_covered || has_block_coverage);
+
+ // It must also have a non-empty source range (otherwise it is not
+ // interesting to report).
+ bool has_nonempty_source_range = function.HasNonEmptySourceRange();
+
+ if (has_nonempty_source_range && function_is_relevant) {
+ nesting.push_back(functions->size());
+ functions->emplace_back(function);
+ }
+
+ if (FLAG_trace_block_coverage) {
+ PrintBlockCoverage(&function, info, has_nonempty_source_range,
+ function_is_relevant);
}
}
- // Add collected feedback vectors to the root list lest we lose them to GC.
- Handle<ArrayList> list =
- ArrayList::New(isolate, static_cast<int>(vectors.size()));
- for (const auto& vector : vectors) list = ArrayList::Add(list, vector);
- isolate->SetCodeCoverageList(*list);
- } else {
- isolate->SetCodeCoverageList(isolate->heap()->undefined_value());
+
+ // Remove entries for scripts that have no coverage.
+ if (functions->empty()) result->pop_back();
}
+ return result;
+}
+
+void Coverage::SelectMode(Isolate* isolate, debug::CoverageMode mode) {
+ if (mode != isolate->code_coverage_mode()) {
+ // Changing the coverage mode can change the bytecode that would be
+ // generated for a function, which can interfere with lazy source positions,
+ // so just force source position collection whenever there's such a change.
+ isolate->CollectSourcePositionsForAllBytecodeArrays();
+ }
+
+ switch (mode) {
+ case debug::CoverageMode::kBestEffort:
+ // Note that DevTools switches back to best-effort coverage once the
+ // recording is stopped. Since we delete coverage infos at that point, any
+ // following coverage recording (without reloads) will be at function
+ // granularity.
+ isolate->debug()->RemoveAllCoverageInfos();
+ if (!isolate->is_collecting_type_profile()) {
+ isolate->SetFeedbackVectorsForProfilingTools(
+ ReadOnlyRoots(isolate).undefined_value());
+ }
+ break;
+ case debug::CoverageMode::kBlockBinary:
+ case debug::CoverageMode::kBlockCount:
+ case debug::CoverageMode::kPreciseBinary:
+ case debug::CoverageMode::kPreciseCount: {
+ HandleScope scope(isolate);
+
+ // Remove all optimized function. Optimized and inlined functions do not
+ // increment invocation count.
+ Deoptimizer::DeoptimizeAll(isolate);
+
+ std::vector<Handle<JSFunction>> funcs_needing_feedback_vector;
+ {
+ HeapObjectIterator heap_iterator(isolate->heap());
+ for (HeapObject o = heap_iterator.Next(); !o.is_null();
+ o = heap_iterator.Next()) {
+ if (o.IsJSFunction()) {
+ JSFunction func = JSFunction::cast(o);
+ if (func.has_closure_feedback_cell_array()) {
+ funcs_needing_feedback_vector.push_back(
+ Handle<JSFunction>(func, isolate));
+ }
+ } else if (IsBinaryMode(mode) && o.IsSharedFunctionInfo()) {
+ // If collecting binary coverage, reset
+ // SFI::has_reported_binary_coverage to avoid optimizing / inlining
+ // functions before they have reported coverage.
+ SharedFunctionInfo shared = SharedFunctionInfo::cast(o);
+ shared.set_has_reported_binary_coverage(false);
+ } else if (o.IsFeedbackVector()) {
+ // In any case, clear any collected invocation counts.
+ FeedbackVector::cast(o).clear_invocation_count();
+ }
+ }
+ }
+
+ for (Handle<JSFunction> func : funcs_needing_feedback_vector) {
+ IsCompiledScope is_compiled_scope(
+ func->shared().is_compiled_scope(isolate));
+ CHECK(is_compiled_scope.is_compiled());
+ JSFunction::EnsureFeedbackVector(func, &is_compiled_scope);
+ }
+
+ // Root all feedback vectors to avoid early collection.
+ isolate->MaybeInitializeVectorListFromHeap();
+
+ break;
+ }
+ }
+ isolate->set_code_coverage_mode(mode);
}
} // namespace internal
diff --git a/src/debug/debug-coverage.h b/src/debug/debug-coverage.h
index 36128bc..81b1781 100644
--- a/src/debug/debug-coverage.h
+++ b/src/debug/debug-coverage.h
@@ -5,10 +5,12 @@
#ifndef V8_DEBUG_DEBUG_COVERAGE_H_
#define V8_DEBUG_DEBUG_COVERAGE_H_
+#include <memory>
#include <vector>
#include "src/debug/debug-interface.h"
-#include "src/objects.h"
+#include "src/handles/handles.h"
+#include "src/objects/objects.h"
namespace v8 {
namespace internal {
@@ -16,18 +18,34 @@
// Forward declaration.
class Isolate;
+struct CoverageBlock {
+ CoverageBlock(int s, int e, uint32_t c) : start(s), end(e), count(c) {}
+ CoverageBlock() : CoverageBlock(kNoSourcePosition, kNoSourcePosition, 0) {}
+
+ int start;
+ int end;
+ uint32_t count;
+};
+
struct CoverageFunction {
CoverageFunction(int s, int e, uint32_t c, Handle<String> n)
- : start(s), end(e), count(c), name(n) {}
+ : start(s), end(e), count(c), name(n), has_block_coverage(false) {}
+
+ bool HasNonEmptySourceRange() const { return start < end && start >= 0; }
+ bool HasBlocks() const { return !blocks.empty(); }
+
int start;
int end;
uint32_t count;
Handle<String> name;
+ // Blocks are sorted by start position, from outer to inner blocks.
+ std::vector<CoverageBlock> blocks;
+ bool has_block_coverage;
};
struct CoverageScript {
// Initialize top-level function in case it has been garbage-collected.
- CoverageScript(Isolate* isolate, Handle<Script> s) : script(s) {}
+ explicit CoverageScript(Handle<Script> s) : script(s) {}
Handle<Script> script;
// Functions are sorted by start position, from outer to inner function.
std::vector<CoverageFunction> functions;
@@ -35,16 +53,24 @@
class Coverage : public std::vector<CoverageScript> {
public:
- // Allocate a new Coverage object and populate with result.
- // The ownership is transferred to the caller.
- static Coverage* Collect(Isolate* isolate, bool reset_count);
+ // Collecting precise coverage only works if the modes kPreciseCount or
+ // kPreciseBinary is selected. The invocation count is reset on collection.
+ // In case of kPreciseCount, an updated count since last collection is
+ // returned. In case of kPreciseBinary, a count of 1 is returned if a
+ // function has been executed for the first time since last collection.
+ static std::unique_ptr<Coverage> CollectPrecise(Isolate* isolate);
+ // Collecting best effort coverage always works, but may be imprecise
+ // depending on selected mode. The invocation count is not reset.
+ static std::unique_ptr<Coverage> CollectBestEffort(Isolate* isolate);
- // Enable precise code coverage. This disables optimization and makes sure
- // invocation count is not affected by GC.
- static void TogglePrecise(Isolate* isolate, bool enable);
+ // Select code coverage mode.
+ static void SelectMode(Isolate* isolate, debug::CoverageMode mode);
private:
- Coverage() {}
+ static std::unique_ptr<Coverage> Collect(
+ Isolate* isolate, v8::debug::CoverageMode collectionMode);
+
+ Coverage() = default;
};
} // namespace internal
diff --git a/src/debug/debug-evaluate.cc b/src/debug/debug-evaluate.cc
index c6fafa5..7fb0b37 100644
--- a/src/debug/debug-evaluate.cc
+++ b/src/debug/debug-evaluate.cc
@@ -4,48 +4,73 @@
#include "src/debug/debug-evaluate.h"
-#include "src/accessors.h"
-#include "src/compiler.h"
-#include "src/contexts.h"
+#include "src/builtins/accessors.h"
+#include "src/codegen/assembler-inl.h"
+#include "src/codegen/compiler.h"
+#include "src/common/globals.h"
#include "src/debug/debug-frames.h"
#include "src/debug/debug-scopes.h"
#include "src/debug/debug.h"
-#include "src/frames-inl.h"
-#include "src/globals.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/isolate-inl.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/interpreter/bytecodes.h"
-#include "src/isolate-inl.h"
+#include "src/objects/contexts.h"
+#include "src/snapshot/snapshot.h"
+#include "src/wasm/wasm-debug.h"
+#include "src/wasm/wasm-js.h"
namespace v8 {
namespace internal {
-static inline bool IsDebugContext(Isolate* isolate, Context* context) {
- return context->native_context() == *isolate->debug()->debug_context();
+namespace {
+static MaybeHandle<SharedFunctionInfo> GetFunctionInfo(Isolate* isolate,
+ Handle<String> source,
+ REPLMode repl_mode) {
+ Compiler::ScriptDetails script_details(isolate->factory()->empty_string());
+ script_details.repl_mode = repl_mode;
+ ScriptOriginOptions origin_options(false, true);
+ return Compiler::GetSharedFunctionInfoForScript(
+ isolate, source, script_details, origin_options, nullptr, nullptr,
+ ScriptCompiler::kNoCompileOptions, ScriptCompiler::kNoCacheNoReason,
+ NOT_NATIVES_CODE);
}
+} // namespace
MaybeHandle<Object> DebugEvaluate::Global(Isolate* isolate,
- Handle<String> source) {
- // Handle the processing of break.
- DisableBreak disable_break_scope(isolate->debug());
+ Handle<String> source,
+ debug::EvaluateGlobalMode mode,
+ REPLMode repl_mode) {
+ // Disable breaks in side-effect free mode.
+ DisableBreak disable_break_scope(
+ isolate->debug(),
+ mode == debug::EvaluateGlobalMode::kDisableBreaks ||
+ mode ==
+ debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect);
- // Enter the top context from before the debugger was invoked.
- SaveContext save(isolate);
- SaveContext* top = &save;
- while (top != NULL && IsDebugContext(isolate, *top->context())) {
- top = top->prev();
+ Handle<SharedFunctionInfo> shared_info;
+ if (!GetFunctionInfo(isolate, source, repl_mode).ToHandle(&shared_info)) {
+ return MaybeHandle<Object>();
}
- if (top != NULL) isolate->set_context(*top->context());
- // Get the native context now set to the top context from before the
- // debugger was invoked.
Handle<Context> context = isolate->native_context();
- Handle<JSObject> receiver(context->global_proxy());
- Handle<SharedFunctionInfo> outer_info(context->closure()->shared(), isolate);
- return Evaluate(isolate, outer_info, context, receiver, source, false);
+ Handle<JSFunction> fun =
+ isolate->factory()->NewFunctionFromSharedFunctionInfo(shared_info,
+ context);
+ if (mode == debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) {
+ isolate->debug()->StartSideEffectCheckMode();
+ }
+ MaybeHandle<Object> result = Execution::Call(
+ isolate, fun, Handle<JSObject>(context->global_proxy(), isolate), 0,
+ nullptr);
+ if (mode == debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect) {
+ isolate->debug()->StopSideEffectCheckMode();
+ }
+ return result;
}
MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
- StackFrame::Id frame_id,
+ StackFrameId frame_id,
int inlined_jsframe_index,
Handle<String> source,
bool throw_on_side_effect) {
@@ -57,13 +82,6 @@
if (!it.is_javascript()) return isolate->factory()->undefined_value();
JavaScriptFrame* frame = it.javascript_frame();
- // Traverse the saved contexts chain to find the active context for the
- // selected frame.
- SaveContext* save =
- DebugFrameHelper::FindSavedContextForFrame(isolate, frame);
- SaveContext savex(isolate);
- isolate->set_context(*(save->context()));
-
// This is not a lot different than DebugEvaluate::Global, except that
// variables accessible by the function we are evaluating from are
// materialized and included on top of the native context. Changes to
@@ -74,7 +92,7 @@
if (isolate->has_pending_exception()) return MaybeHandle<Object>();
Handle<Context> context = context_builder.evaluation_context();
- Handle<JSObject> receiver(context->global_proxy());
+ Handle<JSObject> receiver(context->global_proxy(), isolate);
MaybeHandle<Object> maybe_result =
Evaluate(isolate, context_builder.outer_info(), context, receiver, source,
throw_on_side_effect);
@@ -82,6 +100,84 @@
return maybe_result;
}
+V8_EXPORT MaybeHandle<Object> DebugEvaluate::WebAssembly(
+ Handle<WasmInstanceObject> instance, StackFrameId frame_id,
+ Handle<String> source, bool throw_on_side_effect) {
+ Isolate* isolate = instance->GetIsolate();
+
+ StackTraceFrameIterator it(isolate, frame_id);
+ if (!it.is_wasm()) return isolate->factory()->undefined_value();
+ WasmFrame* frame = WasmFrame::cast(it.frame());
+
+ Handle<JSProxy> context_extension = WasmJs::GetJSDebugProxy(frame);
+
+ DisableBreak disable_break_scope(isolate->debug(), /*disable=*/true);
+
+ Handle<SharedFunctionInfo> shared_info;
+ if (!GetFunctionInfo(isolate, source, REPLMode::kNo).ToHandle(&shared_info)) {
+ return {};
+ }
+
+ Handle<ScopeInfo> scope_info =
+ ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
+ Handle<Context> context = isolate->factory()->NewWithContext(
+ isolate->native_context(), scope_info, context_extension);
+
+ Handle<Object> result;
+ if (!DebugEvaluate::Evaluate(isolate, shared_info, context, context_extension,
+ source, throw_on_side_effect)
+ .ToHandle(&result)) {
+ return {};
+ }
+
+ return result;
+}
+
+MaybeHandle<Object> DebugEvaluate::WithTopmostArguments(Isolate* isolate,
+ Handle<String> source) {
+ // Handle the processing of break.
+ DisableBreak disable_break_scope(isolate->debug());
+ Factory* factory = isolate->factory();
+ JavaScriptFrameIterator it(isolate);
+
+ // Get context and receiver.
+ Handle<Context> native_context(
+ Context::cast(it.frame()->context()).native_context(), isolate);
+
+ // Materialize arguments as property on an extension object.
+ Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
+ Handle<String> arguments_str = factory->arguments_string();
+ JSObject::SetOwnPropertyIgnoreAttributes(
+ materialized, arguments_str,
+ Accessors::FunctionGetArguments(it.frame(), 0), NONE)
+ .Check();
+
+ // Materialize receiver.
+ Handle<Object> this_value(it.frame()->receiver(), isolate);
+ DCHECK_EQ(it.frame()->IsConstructor(), this_value->IsTheHole(isolate));
+ if (!this_value->IsTheHole(isolate)) {
+ Handle<String> this_str = factory->this_string();
+ JSObject::SetOwnPropertyIgnoreAttributes(materialized, this_str, this_value,
+ NONE)
+ .Check();
+ }
+
+ // Use extension object in a debug-evaluate scope.
+ Handle<ScopeInfo> scope_info =
+ ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
+ scope_info->SetIsDebugEvaluateScope();
+ Handle<Context> evaluation_context =
+ factory->NewDebugEvaluateContext(native_context, scope_info, materialized,
+ Handle<Context>(), Handle<StringSet>());
+ Handle<SharedFunctionInfo> outer_info(
+ native_context->empty_function().shared(), isolate);
+ Handle<JSObject> receiver(native_context->global_proxy(), isolate);
+ const bool throw_on_side_effect = false;
+ MaybeHandle<Object> maybe_result =
+ Evaluate(isolate, outer_info, evaluation_context, receiver, source,
+ throw_on_side_effect);
+ return maybe_result;
+}
// Compile and evaluate source for the given context.
MaybeHandle<Object> DebugEvaluate::Evaluate(
@@ -91,232 +187,233 @@
Handle<JSFunction> eval_fun;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, eval_fun,
- Compiler::GetFunctionFromEval(source, outer_info, context, SLOPPY,
- NO_PARSE_RESTRICTION, kNoSourcePosition,
- kNoSourcePosition, kNoSourcePosition),
+ Compiler::GetFunctionFromEval(source, outer_info, context,
+ LanguageMode::kSloppy, NO_PARSE_RESTRICTION,
+ kNoSourcePosition, kNoSourcePosition,
+ kNoSourcePosition),
Object);
Handle<Object> result;
- {
- NoSideEffectScope no_side_effect(isolate, throw_on_side_effect);
- ASSIGN_RETURN_ON_EXCEPTION(
- isolate, result, Execution::Call(isolate, eval_fun, receiver, 0, NULL),
- Object);
- }
-
- // Skip the global proxy as it has no properties and always delegates to the
- // real global object.
- if (result->IsJSGlobalProxy()) {
- PrototypeIterator iter(isolate, Handle<JSGlobalProxy>::cast(result));
- // TODO(verwaest): This will crash when the global proxy is detached.
- result = PrototypeIterator::GetCurrent<JSObject>(iter);
- }
-
- return result;
+ bool success = false;
+ if (throw_on_side_effect) isolate->debug()->StartSideEffectCheckMode();
+ success = Execution::Call(isolate, eval_fun, receiver, 0, nullptr)
+ .ToHandle(&result);
+ if (throw_on_side_effect) isolate->debug()->StopSideEffectCheckMode();
+ if (!success) DCHECK(isolate->has_pending_exception());
+ return success ? result : MaybeHandle<Object>();
}
+Handle<SharedFunctionInfo> DebugEvaluate::ContextBuilder::outer_info() const {
+ return handle(frame_inspector_.GetFunction()->shared(), isolate_);
+}
DebugEvaluate::ContextBuilder::ContextBuilder(Isolate* isolate,
JavaScriptFrame* frame,
int inlined_jsframe_index)
: isolate_(isolate),
- frame_(frame),
- inlined_jsframe_index_(inlined_jsframe_index) {
- FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
- Handle<JSFunction> local_function = frame_inspector.GetFunction();
- Handle<Context> outer_context(local_function->context());
+ frame_inspector_(frame, inlined_jsframe_index, isolate),
+ scope_iterator_(isolate, &frame_inspector_,
+ ScopeIterator::ReparseStrategy::kScript) {
+ Handle<Context> outer_context(frame_inspector_.GetFunction()->context(),
+ isolate);
evaluation_context_ = outer_context;
- outer_info_ = handle(local_function->shared());
Factory* factory = isolate->factory();
+ if (scope_iterator_.Done()) return;
+
// To evaluate as if we were running eval at the point of the debug break,
// we reconstruct the context chain as follows:
// - To make stack-allocated variables visible, we materialize them and
// use a debug-evaluate context to wrap both the materialized object and
// the original context.
- // - We use the original context chain from the function context to the
- // native context.
+ // - We also wrap all contexts on the chain between the original context
+ // and the function context.
// - Between the function scope and the native context, we only resolve
- // variable names that the current function already uses. Only for these
- // names we can be sure that they will be correctly resolved. For the
- // rest, we only resolve to with, script, and native contexts. We use a
- // whitelist to implement that.
+ // variable names that are guaranteed to not be shadowed by stack-allocated
+ // variables. Contexts between the function context and the original
+ // context have a blocklist attached to implement that.
// Context::Lookup has special handling for debug-evaluate contexts:
// - Look up in the materialized stack variables.
+ // - Check the blocklist to find out whether to abort further lookup.
// - Look up in the original context.
- // - Check the whitelist to find out whether to skip contexts during lookup.
- const ScopeIterator::Option option = ScopeIterator::COLLECT_NON_LOCALS;
- for (ScopeIterator it(isolate, &frame_inspector, option); !it.Done();
- it.Next()) {
- ScopeIterator::ScopeType scope_type = it.Type();
- if (scope_type == ScopeIterator::ScopeTypeLocal) {
- DCHECK_EQ(FUNCTION_SCOPE, it.CurrentScopeInfo()->scope_type());
- Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
- Handle<Context> local_context =
- it.HasContext() ? it.CurrentContext() : outer_context;
- Handle<StringSet> non_locals = it.GetNonLocals();
- MaterializeReceiver(materialized, local_context, local_function,
- non_locals);
- frame_inspector.MaterializeStackLocals(materialized, local_function);
- MaterializeArgumentsObject(materialized, local_function);
- ContextChainElement context_chain_element;
- context_chain_element.scope_info = it.CurrentScopeInfo();
- context_chain_element.materialized_object = materialized;
- // Non-locals that are already being referenced by the current function
- // are guaranteed to be correctly resolved.
- context_chain_element.whitelist = non_locals;
- if (it.HasContext()) {
- context_chain_element.wrapped_context = it.CurrentContext();
- }
- context_chain_.Add(context_chain_element);
- evaluation_context_ = outer_context;
- break;
- } else if (scope_type == ScopeIterator::ScopeTypeCatch ||
- scope_type == ScopeIterator::ScopeTypeWith) {
- ContextChainElement context_chain_element;
- Handle<Context> current_context = it.CurrentContext();
- if (!current_context->IsDebugEvaluateContext()) {
- context_chain_element.wrapped_context = current_context;
- }
- context_chain_.Add(context_chain_element);
- } else if (scope_type == ScopeIterator::ScopeTypeBlock ||
- scope_type == ScopeIterator::ScopeTypeEval) {
- Handle<JSObject> materialized = factory->NewJSObjectWithNullProto();
- frame_inspector.MaterializeStackLocals(materialized,
- it.CurrentScopeInfo());
- ContextChainElement context_chain_element;
- context_chain_element.scope_info = it.CurrentScopeInfo();
- context_chain_element.materialized_object = materialized;
- if (it.HasContext()) {
- context_chain_element.wrapped_context = it.CurrentContext();
- }
- context_chain_.Add(context_chain_element);
- } else {
- break;
+ for (; !scope_iterator_.Done(); scope_iterator_.Next()) {
+ ScopeIterator::ScopeType scope_type = scope_iterator_.Type();
+ if (scope_type == ScopeIterator::ScopeTypeScript) break;
+ ContextChainElement context_chain_element;
+ if (scope_iterator_.InInnerScope() &&
+ (scope_type == ScopeIterator::ScopeTypeLocal ||
+ scope_iterator_.DeclaresLocals(ScopeIterator::Mode::STACK))) {
+ context_chain_element.materialized_object =
+ scope_iterator_.ScopeObject(ScopeIterator::Mode::STACK);
}
+ if (scope_iterator_.HasContext()) {
+ context_chain_element.wrapped_context = scope_iterator_.CurrentContext();
+ }
+ if (!scope_iterator_.InInnerScope()) {
+ context_chain_element.blocklist = scope_iterator_.GetLocals();
+ }
+ context_chain_.push_back(context_chain_element);
}
- for (int i = context_chain_.length() - 1; i >= 0; i--) {
- Handle<ScopeInfo> scope_info(ScopeInfo::CreateForWithScope(
- isolate, evaluation_context_->IsNativeContext()
- ? Handle<ScopeInfo>::null()
- : Handle<ScopeInfo>(evaluation_context_->scope_info())));
+ Handle<ScopeInfo> scope_info =
+ evaluation_context_->IsNativeContext()
+ ? Handle<ScopeInfo>::null()
+ : handle(evaluation_context_->scope_info(), isolate);
+ for (auto rit = context_chain_.rbegin(); rit != context_chain_.rend();
+ rit++) {
+ ContextChainElement element = *rit;
+ scope_info = ScopeInfo::CreateForWithScope(isolate, scope_info);
scope_info->SetIsDebugEvaluateScope();
evaluation_context_ = factory->NewDebugEvaluateContext(
- evaluation_context_, scope_info, context_chain_[i].materialized_object,
- context_chain_[i].wrapped_context, context_chain_[i].whitelist);
+ evaluation_context_, scope_info, element.materialized_object,
+ element.wrapped_context, element.blocklist);
}
}
-
void DebugEvaluate::ContextBuilder::UpdateValues() {
- // TODO(yangguo): remove updating values.
- for (int i = 0; i < context_chain_.length(); i++) {
- ContextChainElement element = context_chain_[i];
+ scope_iterator_.Restart();
+ for (ContextChainElement& element : context_chain_) {
if (!element.materialized_object.is_null()) {
- // Write back potential changes to materialized stack locals to the stack.
- FrameInspector(frame_, inlined_jsframe_index_, isolate_)
- .UpdateStackLocalsFromMaterializedObject(element.materialized_object,
- element.scope_info);
+ Handle<FixedArray> keys =
+ KeyAccumulator::GetKeys(element.materialized_object,
+ KeyCollectionMode::kOwnOnly,
+ ENUMERABLE_STRINGS)
+ .ToHandleChecked();
+
+ for (int i = 0; i < keys->length(); i++) {
+ DCHECK(keys->get(i).IsString());
+ Handle<String> key(String::cast(keys->get(i)), isolate_);
+ Handle<Object> value =
+ JSReceiver::GetDataProperty(element.materialized_object, key);
+ scope_iterator_.SetVariableValue(key, value);
+ }
}
+ scope_iterator_.Next();
}
}
-
-void DebugEvaluate::ContextBuilder::MaterializeArgumentsObject(
- Handle<JSObject> target, Handle<JSFunction> function) {
- // Do not materialize the arguments object for eval or top-level code.
- // Skip if "arguments" is already taken.
- if (function->shared()->is_toplevel()) return;
- Maybe<bool> maybe = JSReceiver::HasOwnProperty(
- target, isolate_->factory()->arguments_string());
- DCHECK(maybe.IsJust());
- if (maybe.FromJust()) return;
-
- // FunctionGetArguments can't throw an exception.
- Handle<JSObject> arguments = Accessors::FunctionGetArguments(function);
- Handle<String> arguments_str = isolate_->factory()->arguments_string();
- JSObject::SetOwnPropertyIgnoreAttributes(target, arguments_str, arguments,
- NONE)
- .Check();
-}
-
-void DebugEvaluate::ContextBuilder::MaterializeReceiver(
- Handle<JSObject> target, Handle<Context> local_context,
- Handle<JSFunction> local_function, Handle<StringSet> non_locals) {
- Handle<Object> recv = isolate_->factory()->undefined_value();
- Handle<String> name = isolate_->factory()->this_string();
- if (non_locals->Has(name)) {
- // 'this' is allocated in an outer context and is is already being
- // referenced by the current function, so it can be correctly resolved.
- return;
- } else if (local_function->shared()->scope_info()->HasReceiver() &&
- !frame_->receiver()->IsTheHole(isolate_)) {
- recv = handle(frame_->receiver(), isolate_);
- }
- JSObject::SetOwnPropertyIgnoreAttributes(target, name, recv, NONE).Check();
-}
-
namespace {
bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
+// Use macro to include only the non-inlined version of an intrinsic.
+#define INTRINSIC_ALLOWLIST(V) \
+ /* Conversions */ \
+ V(NumberToStringSlow) \
+ V(ToBigInt) \
+ V(ToLength) \
+ V(ToNumber) \
+ V(ToObject) \
+ V(ToString) \
+ /* Type checks */ \
+ V(IsArray) \
+ V(IsFunction) \
+ V(IsJSProxy) \
+ V(IsJSReceiver) \
+ V(IsRegExp) \
+ V(IsSmi) \
+ /* Loads */ \
+ V(LoadLookupSlotForCall) \
+ V(GetProperty) \
+ /* Arrays */ \
+ V(ArraySpeciesConstructor) \
+ V(HasFastPackedElements) \
+ V(NewArray) \
+ V(NormalizeElements) \
+ V(TypedArrayGetBuffer) \
+ /* Errors */ \
+ V(NewTypeError) \
+ V(ReThrow) \
+ V(ThrowCalledNonCallable) \
+ V(ThrowInvalidStringLength) \
+ V(ThrowIteratorError) \
+ V(ThrowIteratorResultNotAnObject) \
+ V(ThrowPatternAssignmentNonCoercible) \
+ V(ThrowReferenceError) \
+ V(ThrowSymbolIteratorInvalid) \
+ /* Strings */ \
+ V(StringIncludes) \
+ V(StringIndexOf) \
+ V(StringReplaceOneCharWithString) \
+ V(StringSubstring) \
+ V(StringToNumber) \
+ V(StringTrim) \
+ /* BigInts */ \
+ V(BigIntEqualToBigInt) \
+ V(BigIntToBoolean) \
+ V(BigIntToNumber) \
+ /* Literals */ \
+ V(CreateArrayLiteral) \
+ V(CreateArrayLiteralWithoutAllocationSite) \
+ V(CreateObjectLiteral) \
+ V(CreateObjectLiteralWithoutAllocationSite) \
+ V(CreateRegExpLiteral) \
+ /* Called from builtins */ \
+ V(AllocateInYoungGeneration) \
+ V(AllocateInOldGeneration) \
+ V(AllocateSeqOneByteString) \
+ V(AllocateSeqTwoByteString) \
+ V(ArrayIncludes_Slow) \
+ V(ArrayIndexOf) \
+ V(ArrayIsArray) \
+ V(GetFunctionName) \
+ V(GetOwnPropertyDescriptor) \
+ V(GlobalPrint) \
+ V(HasProperty) \
+ V(ObjectCreate) \
+ V(ObjectEntries) \
+ V(ObjectEntriesSkipFastPath) \
+ V(ObjectHasOwnProperty) \
+ V(ObjectKeys) \
+ V(ObjectValues) \
+ V(ObjectValuesSkipFastPath) \
+ V(ObjectGetOwnPropertyNames) \
+ V(ObjectGetOwnPropertyNamesTryFast) \
+ V(ObjectIsExtensible) \
+ V(RegExpInitializeAndCompile) \
+ V(StackGuard) \
+ V(StringAdd) \
+ V(StringCharCodeAt) \
+ V(StringEqual) \
+ V(StringIndexOfUnchecked) \
+ V(StringParseFloat) \
+ V(StringParseInt) \
+ V(SymbolDescriptiveString) \
+ V(ThrowRangeError) \
+ V(ThrowTypeError) \
+ V(ToName) \
+ V(TransitionElementsKind) \
+ /* Misc. */ \
+ V(Call) \
+ V(CompleteInobjectSlackTrackingForMap) \
+ V(HasInPrototypeChain) \
+ V(IncrementUseCounter) \
+ V(MaxSmi) \
+ V(NewObject) \
+ V(StringMaxLength) \
+ V(StringToArray) \
+ V(AsyncFunctionEnter) \
+ V(AsyncFunctionReject) \
+ V(AsyncFunctionResolve) \
+ /* Test */ \
+ V(GetOptimizationStatus) \
+ V(OptimizeFunctionOnNextCall) \
+ V(OptimizeOsr) \
+ V(UnblockConcurrentRecompilation)
+
+// Intrinsics with inline versions have to be allowlisted here a second time.
+#define INLINE_INTRINSIC_ALLOWLIST(V) \
+ V(Call) \
+ V(IsJSReceiver) \
+ V(AsyncFunctionEnter) \
+ V(AsyncFunctionReject) \
+ V(AsyncFunctionResolve)
+
+#define CASE(Name) case Runtime::k##Name:
+#define INLINE_CASE(Name) case Runtime::kInline##Name:
switch (id) {
- // Whitelist for intrinsics amd runtime functions.
- // Conversions.
- case Runtime::kToInteger:
- case Runtime::kInlineToInteger:
- case Runtime::kToObject:
- case Runtime::kInlineToObject:
- case Runtime::kToString:
- case Runtime::kInlineToString:
- case Runtime::kToLength:
- case Runtime::kInlineToLength:
- case Runtime::kToNumber:
- // Type checks.
- case Runtime::kIsJSReceiver:
- case Runtime::kInlineIsJSReceiver:
- case Runtime::kIsSmi:
- case Runtime::kInlineIsSmi:
- case Runtime::kIsArray:
- case Runtime::kInlineIsArray:
- case Runtime::kIsFunction:
- case Runtime::kIsDate:
- case Runtime::kIsJSProxy:
- case Runtime::kIsRegExp:
- case Runtime::kIsTypedArray:
- // Loads.
- case Runtime::kLoadLookupSlotForCall:
- // Arrays.
- case Runtime::kArraySpeciesConstructor:
- case Runtime::kNormalizeElements:
- case Runtime::kGetArrayKeys:
- case Runtime::kHasComplexElements:
- case Runtime::kEstimateNumberOfElements:
- // Errors.
- case Runtime::kReThrow:
- case Runtime::kThrowReferenceError:
- case Runtime::kThrowSymbolIteratorInvalid:
- case Runtime::kThrowIteratorResultNotAnObject:
- case Runtime::kNewTypeError:
- // Strings.
- case Runtime::kInlineStringCharCodeAt:
- case Runtime::kStringCharCodeAt:
- case Runtime::kStringIndexOf:
- case Runtime::kStringReplaceOneCharWithString:
- case Runtime::kSubString:
- case Runtime::kInlineSubString:
- case Runtime::kRegExpInternalReplace:
- // Literals.
- case Runtime::kCreateArrayLiteral:
- case Runtime::kCreateObjectLiteral:
- case Runtime::kCreateRegExpLiteral:
- // Misc.
- case Runtime::kForInPrepare:
- case Runtime::kInlineCall:
- case Runtime::kCall:
- case Runtime::kInlineMaxSmi:
- case Runtime::kMaxSmi:
- return true;
+ INTRINSIC_ALLOWLIST(CASE)
+ INLINE_INTRINSIC_ALLOWLIST(INLINE_CASE)
+ return true;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] intrinsic %s may cause side effect.\n",
@@ -324,41 +421,58 @@
}
return false;
}
+
+#undef CASE
+#undef INLINE_CASE
+#undef INTRINSIC_ALLOWLIST
+#undef INLINE_INTRINSIC_ALLOWLIST
}
bool BytecodeHasNoSideEffect(interpreter::Bytecode bytecode) {
- typedef interpreter::Bytecode Bytecode;
- typedef interpreter::Bytecodes Bytecodes;
+ using interpreter::Bytecode;
+ using interpreter::Bytecodes;
if (Bytecodes::IsWithoutExternalSideEffects(bytecode)) return true;
if (Bytecodes::IsCallOrConstruct(bytecode)) return true;
- if (Bytecodes::WritesBooleanToAccumulator(bytecode)) return true;
if (Bytecodes::IsJumpIfToBoolean(bytecode)) return true;
if (Bytecodes::IsPrefixScalingBytecode(bytecode)) return true;
switch (bytecode) {
- // Whitelist for bytecodes.
+ // Allowlist for bytecodes.
// Loads.
case Bytecode::kLdaLookupSlot:
case Bytecode::kLdaGlobal:
case Bytecode::kLdaNamedProperty:
+ case Bytecode::kLdaNamedPropertyNoFeedback:
case Bytecode::kLdaKeyedProperty:
+ case Bytecode::kLdaGlobalInsideTypeof:
+ case Bytecode::kLdaLookupSlotInsideTypeof:
+ case Bytecode::kGetIterator:
// Arithmetics.
case Bytecode::kAdd:
case Bytecode::kAddSmi:
case Bytecode::kSub:
case Bytecode::kSubSmi:
case Bytecode::kMul:
+ case Bytecode::kMulSmi:
case Bytecode::kDiv:
+ case Bytecode::kDivSmi:
case Bytecode::kMod:
+ case Bytecode::kModSmi:
+ case Bytecode::kExp:
+ case Bytecode::kExpSmi:
+ case Bytecode::kNegate:
case Bytecode::kBitwiseAnd:
case Bytecode::kBitwiseAndSmi:
+ case Bytecode::kBitwiseNot:
case Bytecode::kBitwiseOr:
case Bytecode::kBitwiseOrSmi:
case Bytecode::kBitwiseXor:
+ case Bytecode::kBitwiseXorSmi:
case Bytecode::kShiftLeft:
case Bytecode::kShiftLeftSmi:
case Bytecode::kShiftRight:
case Bytecode::kShiftRightSmi:
case Bytecode::kShiftRightLogical:
+ case Bytecode::kShiftRightLogicalSmi:
case Bytecode::kInc:
case Bytecode::kDec:
case Bytecode::kLogicalNot:
@@ -372,47 +486,210 @@
case Bytecode::kCreateWithContext:
// Literals.
case Bytecode::kCreateArrayLiteral:
+ case Bytecode::kCreateEmptyArrayLiteral:
+ case Bytecode::kCreateArrayFromIterable:
case Bytecode::kCreateObjectLiteral:
+ case Bytecode::kCreateEmptyObjectLiteral:
case Bytecode::kCreateRegExpLiteral:
// Allocations.
case Bytecode::kCreateClosure:
case Bytecode::kCreateUnmappedArguments:
+ case Bytecode::kCreateRestParameter:
+ // Comparisons.
+ case Bytecode::kTestEqual:
+ case Bytecode::kTestEqualStrict:
+ case Bytecode::kTestLessThan:
+ case Bytecode::kTestLessThanOrEqual:
+ case Bytecode::kTestGreaterThan:
+ case Bytecode::kTestGreaterThanOrEqual:
+ case Bytecode::kTestInstanceOf:
+ case Bytecode::kTestIn:
+ case Bytecode::kTestReferenceEqual:
+ case Bytecode::kTestUndetectable:
+ case Bytecode::kTestTypeOf:
+ case Bytecode::kTestUndefined:
+ case Bytecode::kTestNull:
// Conversions.
case Bytecode::kToObject:
+ case Bytecode::kToName:
case Bytecode::kToNumber:
+ case Bytecode::kToNumeric:
+ case Bytecode::kToString:
// Misc.
+ case Bytecode::kIncBlockCounter: // Coverage counters.
+ case Bytecode::kForInEnumerate:
case Bytecode::kForInPrepare:
case Bytecode::kForInContinue:
case Bytecode::kForInNext:
case Bytecode::kForInStep:
+ case Bytecode::kJumpLoop:
case Bytecode::kThrow:
case Bytecode::kReThrow:
+ case Bytecode::kThrowReferenceErrorIfHole:
+ case Bytecode::kThrowSuperNotCalledIfHole:
+ case Bytecode::kThrowSuperAlreadyCalledIfNotHole:
case Bytecode::kIllegal:
case Bytecode::kCallJSRuntime:
- case Bytecode::kStackCheck:
case Bytecode::kReturn:
case Bytecode::kSetPendingMessage:
return true;
default:
- if (FLAG_trace_side_effect_free_debug_evaluate) {
- PrintF("[debug-evaluate] bytecode %s may cause side effect.\n",
- Bytecodes::ToString(bytecode));
- }
return false;
}
}
-bool BuiltinHasNoSideEffect(Builtins::Name id) {
+DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
switch (id) {
- // Whitelist for builtins.
+ // Allowlist for builtins.
+ // Object builtins.
+ case Builtins::kObjectConstructor:
+ case Builtins::kObjectCreate:
+ case Builtins::kObjectEntries:
+ case Builtins::kObjectGetOwnPropertyDescriptor:
+ case Builtins::kObjectGetOwnPropertyDescriptors:
+ case Builtins::kObjectGetOwnPropertyNames:
+ case Builtins::kObjectGetOwnPropertySymbols:
+ case Builtins::kObjectGetPrototypeOf:
+ case Builtins::kObjectIs:
+ case Builtins::kObjectIsExtensible:
+ case Builtins::kObjectIsFrozen:
+ case Builtins::kObjectIsSealed:
+ case Builtins::kObjectKeys:
+ case Builtins::kObjectPrototypeValueOf:
+ case Builtins::kObjectValues:
+ case Builtins::kObjectPrototypeHasOwnProperty:
+ case Builtins::kObjectPrototypeIsPrototypeOf:
+ case Builtins::kObjectPrototypePropertyIsEnumerable:
+ case Builtins::kObjectPrototypeToString:
+ case Builtins::kObjectPrototypeToLocaleString:
// Array builtins.
- case Builtins::kArrayCode:
+ case Builtins::kArrayIsArray:
+ case Builtins::kArrayConstructor:
case Builtins::kArrayIndexOf:
case Builtins::kArrayPrototypeValues:
case Builtins::kArrayIncludes:
case Builtins::kArrayPrototypeEntries:
+ case Builtins::kArrayPrototypeFill:
+ case Builtins::kArrayPrototypeFind:
+ case Builtins::kArrayPrototypeFindIndex:
+ case Builtins::kArrayPrototypeFlat:
+ case Builtins::kArrayPrototypeFlatMap:
+ case Builtins::kArrayPrototypeJoin:
case Builtins::kArrayPrototypeKeys:
+ case Builtins::kArrayPrototypeLastIndexOf:
+ case Builtins::kArrayPrototypeSlice:
+ case Builtins::kArrayPrototypeToLocaleString:
+ case Builtins::kArrayPrototypeToString:
case Builtins::kArrayForEach:
+ case Builtins::kArrayEvery:
+ case Builtins::kArraySome:
+ case Builtins::kArrayConcat:
+ case Builtins::kArrayFilter:
+ case Builtins::kArrayMap:
+ case Builtins::kArrayReduce:
+ case Builtins::kArrayReduceRight:
+ // Trace builtins.
+ case Builtins::kIsTraceCategoryEnabled:
+ case Builtins::kTrace:
+ // TypedArray builtins.
+ case Builtins::kTypedArrayConstructor:
+ case Builtins::kTypedArrayPrototypeBuffer:
+ case Builtins::kTypedArrayPrototypeByteLength:
+ case Builtins::kTypedArrayPrototypeByteOffset:
+ case Builtins::kTypedArrayPrototypeLength:
+ case Builtins::kTypedArrayPrototypeEntries:
+ case Builtins::kTypedArrayPrototypeKeys:
+ case Builtins::kTypedArrayPrototypeValues:
+ case Builtins::kTypedArrayPrototypeFind:
+ case Builtins::kTypedArrayPrototypeFindIndex:
+ case Builtins::kTypedArrayPrototypeIncludes:
+ case Builtins::kTypedArrayPrototypeJoin:
+ case Builtins::kTypedArrayPrototypeIndexOf:
+ case Builtins::kTypedArrayPrototypeLastIndexOf:
+ case Builtins::kTypedArrayPrototypeSlice:
+ case Builtins::kTypedArrayPrototypeSubArray:
+ case Builtins::kTypedArrayPrototypeEvery:
+ case Builtins::kTypedArrayPrototypeSome:
+ case Builtins::kTypedArrayPrototypeToLocaleString:
+ case Builtins::kTypedArrayPrototypeFilter:
+ case Builtins::kTypedArrayPrototypeMap:
+ case Builtins::kTypedArrayPrototypeReduce:
+ case Builtins::kTypedArrayPrototypeReduceRight:
+ case Builtins::kTypedArrayPrototypeForEach:
+ // ArrayBuffer builtins.
+ case Builtins::kArrayBufferConstructor:
+ case Builtins::kArrayBufferPrototypeGetByteLength:
+ case Builtins::kArrayBufferIsView:
+ case Builtins::kArrayBufferPrototypeSlice:
+ case Builtins::kReturnReceiver:
+ // DataView builtins.
+ case Builtins::kDataViewConstructor:
+ case Builtins::kDataViewPrototypeGetBuffer:
+ case Builtins::kDataViewPrototypeGetByteLength:
+ case Builtins::kDataViewPrototypeGetByteOffset:
+ case Builtins::kDataViewPrototypeGetInt8:
+ case Builtins::kDataViewPrototypeGetUint8:
+ case Builtins::kDataViewPrototypeGetInt16:
+ case Builtins::kDataViewPrototypeGetUint16:
+ case Builtins::kDataViewPrototypeGetInt32:
+ case Builtins::kDataViewPrototypeGetUint32:
+ case Builtins::kDataViewPrototypeGetFloat32:
+ case Builtins::kDataViewPrototypeGetFloat64:
+ case Builtins::kDataViewPrototypeGetBigInt64:
+ case Builtins::kDataViewPrototypeGetBigUint64:
+ // Boolean bulitins.
+ case Builtins::kBooleanConstructor:
+ case Builtins::kBooleanPrototypeToString:
+ case Builtins::kBooleanPrototypeValueOf:
+ // Date builtins.
+ case Builtins::kDateConstructor:
+ case Builtins::kDateNow:
+ case Builtins::kDateParse:
+ case Builtins::kDatePrototypeGetDate:
+ case Builtins::kDatePrototypeGetDay:
+ case Builtins::kDatePrototypeGetFullYear:
+ case Builtins::kDatePrototypeGetHours:
+ case Builtins::kDatePrototypeGetMilliseconds:
+ case Builtins::kDatePrototypeGetMinutes:
+ case Builtins::kDatePrototypeGetMonth:
+ case Builtins::kDatePrototypeGetSeconds:
+ case Builtins::kDatePrototypeGetTime:
+ case Builtins::kDatePrototypeGetTimezoneOffset:
+ case Builtins::kDatePrototypeGetUTCDate:
+ case Builtins::kDatePrototypeGetUTCDay:
+ case Builtins::kDatePrototypeGetUTCFullYear:
+ case Builtins::kDatePrototypeGetUTCHours:
+ case Builtins::kDatePrototypeGetUTCMilliseconds:
+ case Builtins::kDatePrototypeGetUTCMinutes:
+ case Builtins::kDatePrototypeGetUTCMonth:
+ case Builtins::kDatePrototypeGetUTCSeconds:
+ case Builtins::kDatePrototypeGetYear:
+ case Builtins::kDatePrototypeToDateString:
+ case Builtins::kDatePrototypeToISOString:
+ case Builtins::kDatePrototypeToUTCString:
+ case Builtins::kDatePrototypeToString:
+#ifdef V8_INTL_SUPPORT
+ case Builtins::kDatePrototypeToLocaleString:
+ case Builtins::kDatePrototypeToLocaleDateString:
+ case Builtins::kDatePrototypeToLocaleTimeString:
+#endif
+ case Builtins::kDatePrototypeToTimeString:
+ case Builtins::kDatePrototypeToJson:
+ case Builtins::kDatePrototypeToPrimitive:
+ case Builtins::kDatePrototypeValueOf:
+ // Map builtins.
+ case Builtins::kMapConstructor:
+ case Builtins::kMapPrototypeForEach:
+ case Builtins::kMapPrototypeGet:
+ case Builtins::kMapPrototypeHas:
+ case Builtins::kMapPrototypeEntries:
+ case Builtins::kMapPrototypeGetSize:
+ case Builtins::kMapPrototypeKeys:
+ case Builtins::kMapPrototypeValues:
+ // WeakMap builtins.
+ case Builtins::kWeakMapConstructor:
+ case Builtins::kWeakMapGet:
+ case Builtins::kWeakMapPrototypeHas:
// Math builtins.
case Builtins::kMathAbs:
case Builtins::kMathAcos:
@@ -440,7 +717,6 @@
case Builtins::kMathMax:
case Builtins::kMathMin:
case Builtins::kMathPow:
- case Builtins::kMathRandom:
case Builtins::kMathRound:
case Builtins::kMathSign:
case Builtins::kMathSin:
@@ -461,66 +737,181 @@
case Builtins::kNumberPrototypeToFixed:
case Builtins::kNumberPrototypeToPrecision:
case Builtins::kNumberPrototypeToString:
+ case Builtins::kNumberPrototypeToLocaleString:
case Builtins::kNumberPrototypeValueOf:
+ // BigInt builtins.
+ case Builtins::kBigIntConstructor:
+ case Builtins::kBigIntAsIntN:
+ case Builtins::kBigIntAsUintN:
+ case Builtins::kBigIntPrototypeToString:
+ case Builtins::kBigIntPrototypeValueOf:
+ // Set builtins.
+ case Builtins::kSetConstructor:
+ case Builtins::kSetPrototypeEntries:
+ case Builtins::kSetPrototypeForEach:
+ case Builtins::kSetPrototypeGetSize:
+ case Builtins::kSetPrototypeHas:
+ case Builtins::kSetPrototypeValues:
+ // WeakSet builtins.
+ case Builtins::kWeakSetConstructor:
+ case Builtins::kWeakSetPrototypeHas:
// String builtins. Strings are immutable.
case Builtins::kStringFromCharCode:
case Builtins::kStringFromCodePoint:
case Builtins::kStringConstructor:
+ case Builtins::kStringPrototypeAnchor:
+ case Builtins::kStringPrototypeBig:
+ case Builtins::kStringPrototypeBlink:
+ case Builtins::kStringPrototypeBold:
case Builtins::kStringPrototypeCharAt:
case Builtins::kStringPrototypeCharCodeAt:
+ case Builtins::kStringPrototypeCodePointAt:
+ case Builtins::kStringPrototypeConcat:
case Builtins::kStringPrototypeEndsWith:
+ case Builtins::kStringPrototypeFixed:
+ case Builtins::kStringPrototypeFontcolor:
+ case Builtins::kStringPrototypeFontsize:
case Builtins::kStringPrototypeIncludes:
case Builtins::kStringPrototypeIndexOf:
+ case Builtins::kStringPrototypeItalics:
case Builtins::kStringPrototypeLastIndexOf:
+ case Builtins::kStringPrototypeLink:
+ case Builtins::kStringPrototypeMatchAll:
+ case Builtins::kStringPrototypePadEnd:
+ case Builtins::kStringPrototypePadStart:
+ case Builtins::kStringPrototypeRepeat:
+ case Builtins::kStringPrototypeSlice:
+ case Builtins::kStringPrototypeSmall:
case Builtins::kStringPrototypeStartsWith:
+ case Builtins::kStringPrototypeStrike:
+ case Builtins::kStringPrototypeSub:
case Builtins::kStringPrototypeSubstr:
case Builtins::kStringPrototypeSubstring:
+ case Builtins::kStringPrototypeSup:
case Builtins::kStringPrototypeToString:
+#ifndef V8_INTL_SUPPORT
case Builtins::kStringPrototypeToLowerCase:
case Builtins::kStringPrototypeToUpperCase:
+#endif
case Builtins::kStringPrototypeTrim:
- case Builtins::kStringPrototypeTrimLeft:
- case Builtins::kStringPrototypeTrimRight:
+ case Builtins::kStringPrototypeTrimEnd:
+ case Builtins::kStringPrototypeTrimStart:
case Builtins::kStringPrototypeValueOf:
+ case Builtins::kStringToNumber:
+ case Builtins::kStringSubstring:
+ // Symbol builtins.
+ case Builtins::kSymbolConstructor:
+ case Builtins::kSymbolKeyFor:
+ case Builtins::kSymbolPrototypeToString:
+ case Builtins::kSymbolPrototypeValueOf:
+ case Builtins::kSymbolPrototypeToPrimitive:
// JSON builtins.
case Builtins::kJsonParse:
case Builtins::kJsonStringify:
+ // Global function builtins.
+ case Builtins::kGlobalDecodeURI:
+ case Builtins::kGlobalDecodeURIComponent:
+ case Builtins::kGlobalEncodeURI:
+ case Builtins::kGlobalEncodeURIComponent:
+ case Builtins::kGlobalEscape:
+ case Builtins::kGlobalUnescape:
+ case Builtins::kGlobalIsFinite:
+ case Builtins::kGlobalIsNaN:
+ // Function builtins.
+ case Builtins::kFunctionPrototypeToString:
+ case Builtins::kFunctionPrototypeBind:
+ case Builtins::kFastFunctionPrototypeBind:
+ case Builtins::kFunctionPrototypeCall:
+ case Builtins::kFunctionPrototypeApply:
// Error builtins.
- case Builtins::kMakeError:
- case Builtins::kMakeTypeError:
- case Builtins::kMakeSyntaxError:
- case Builtins::kMakeRangeError:
- case Builtins::kMakeURIError:
- return true;
+ case Builtins::kErrorConstructor:
+ // RegExp builtins.
+ case Builtins::kRegExpConstructor:
+ // Internal.
+ case Builtins::kStrictPoisonPillThrower:
+ case Builtins::kAllocateInYoungGeneration:
+ case Builtins::kAllocateInOldGeneration:
+ case Builtins::kAllocateRegularInYoungGeneration:
+ case Builtins::kAllocateRegularInOldGeneration:
+ return DebugInfo::kHasNoSideEffect;
+
+ // Set builtins.
+ case Builtins::kSetIteratorPrototypeNext:
+ case Builtins::kSetPrototypeAdd:
+ case Builtins::kSetPrototypeClear:
+ case Builtins::kSetPrototypeDelete:
+ // Array builtins.
+ case Builtins::kArrayIteratorPrototypeNext:
+ case Builtins::kArrayPrototypePop:
+ case Builtins::kArrayPrototypePush:
+ case Builtins::kArrayPrototypeReverse:
+ case Builtins::kArrayPrototypeShift:
+ case Builtins::kArrayPrototypeUnshift:
+ case Builtins::kArrayPrototypeSort:
+ case Builtins::kArrayPrototypeSplice:
+ case Builtins::kArrayUnshift:
+ // Map builtins.
+ case Builtins::kMapIteratorPrototypeNext:
+ case Builtins::kMapPrototypeClear:
+ case Builtins::kMapPrototypeDelete:
+ case Builtins::kMapPrototypeSet:
+ // RegExp builtins.
+ case Builtins::kRegExpPrototypeTest:
+ case Builtins::kRegExpPrototypeExec:
+ case Builtins::kRegExpPrototypeSplit:
+ case Builtins::kRegExpPrototypeFlagsGetter:
+ case Builtins::kRegExpPrototypeGlobalGetter:
+ case Builtins::kRegExpPrototypeIgnoreCaseGetter:
+ case Builtins::kRegExpPrototypeMatchAll:
+ case Builtins::kRegExpPrototypeMultilineGetter:
+ case Builtins::kRegExpPrototypeDotAllGetter:
+ case Builtins::kRegExpPrototypeUnicodeGetter:
+ case Builtins::kRegExpPrototypeStickyGetter:
+ return DebugInfo::kRequiresRuntimeChecks;
default:
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] built-in %s may cause side effect.\n",
Builtins::name(id));
}
+ return DebugInfo::kHasSideEffects;
+ }
+}
+
+bool BytecodeRequiresRuntimeCheck(interpreter::Bytecode bytecode) {
+ using interpreter::Bytecode;
+ switch (bytecode) {
+ case Bytecode::kStaNamedProperty:
+ case Bytecode::kStaNamedPropertyNoFeedback:
+ case Bytecode::kStaNamedOwnProperty:
+ case Bytecode::kStaKeyedProperty:
+ case Bytecode::kStaInArrayLiteral:
+ case Bytecode::kStaDataPropertyInLiteral:
+ case Bytecode::kStaCurrentContextSlot:
+ return true;
+ default:
return false;
}
}
-static const Address accessors_with_no_side_effect[] = {
- // Whitelist for accessors.
- FUNCTION_ADDR(Accessors::StringLengthGetter),
- FUNCTION_ADDR(Accessors::ArrayLengthGetter)};
-
} // anonymous namespace
// static
-bool DebugEvaluate::FunctionHasNoSideEffect(Handle<SharedFunctionInfo> info) {
+DebugInfo::SideEffectState DebugEvaluate::FunctionGetSideEffectState(
+ Isolate* isolate, Handle<SharedFunctionInfo> info) {
if (FLAG_trace_side_effect_free_debug_evaluate) {
PrintF("[debug-evaluate] Checking function %s for side effect.\n",
- info->DebugName()->ToCString().get());
+ info->DebugName().ToCString().get());
}
DCHECK(info->is_compiled());
-
+ DCHECK(!info->needs_script_context());
if (info->HasBytecodeArray()) {
- // Check bytecodes against whitelist.
- Handle<BytecodeArray> bytecode_array(info->bytecode_array());
- if (FLAG_trace_side_effect_free_debug_evaluate) bytecode_array->Print();
+ // Check bytecodes against allowlist.
+ Handle<BytecodeArray> bytecode_array(info->GetBytecodeArray(), isolate);
+ if (FLAG_trace_side_effect_free_debug_evaluate) {
+ bytecode_array->Print();
+ }
+ bool requires_runtime_checks = false;
for (interpreter::BytecodeArrayIterator it(bytecode_array); !it.done();
it.Advance()) {
interpreter::Bytecode bytecode = it.current_bytecode();
@@ -531,38 +922,210 @@
? it.GetIntrinsicIdOperand(0)
: it.GetRuntimeIdOperand(0);
if (IntrinsicHasNoSideEffect(id)) continue;
- return false;
+ return DebugInfo::kHasSideEffects;
}
if (BytecodeHasNoSideEffect(bytecode)) continue;
+ if (BytecodeRequiresRuntimeCheck(bytecode)) {
+ requires_runtime_checks = true;
+ continue;
+ }
- // Did not match whitelist.
- return false;
+ if (FLAG_trace_side_effect_free_debug_evaluate) {
+ PrintF("[debug-evaluate] bytecode %s may cause side effect.\n",
+ interpreter::Bytecodes::ToString(bytecode));
+ }
+
+ // Did not match allowlist.
+ return DebugInfo::kHasSideEffects;
}
- return true;
+ return requires_runtime_checks ? DebugInfo::kRequiresRuntimeChecks
+ : DebugInfo::kHasNoSideEffect;
+ } else if (info->IsApiFunction()) {
+ if (info->GetCode().is_builtin()) {
+ return info->GetCode().builtin_index() == Builtins::kHandleApiCall
+ ? DebugInfo::kHasNoSideEffect
+ : DebugInfo::kHasSideEffects;
+ }
} else {
- // Check built-ins against whitelist.
- int builtin_index = info->code()->builtin_index();
- if (builtin_index >= 0 && builtin_index < Builtins::builtin_count &&
- BuiltinHasNoSideEffect(static_cast<Builtins::Name>(builtin_index))) {
- return true;
- }
+ // Check built-ins against allowlist.
+ int builtin_index =
+ info->HasBuiltinId() ? info->builtin_id() : Builtins::kNoBuiltinId;
+ if (!Builtins::IsBuiltinId(builtin_index))
+ return DebugInfo::kHasSideEffects;
+ DebugInfo::SideEffectState state =
+ BuiltinGetSideEffectState(static_cast<Builtins::Name>(builtin_index));
+ return state;
}
- return false;
+ return DebugInfo::kHasSideEffects;
+}
+
+#ifdef DEBUG
+static bool TransitivelyCalledBuiltinHasNoSideEffect(Builtins::Name caller,
+ Builtins::Name callee) {
+ switch (callee) {
+ // Transitively called Builtins:
+ case Builtins::kAbort:
+ case Builtins::kAbortCSAAssert:
+ case Builtins::kAdaptorWithBuiltinExitFrame:
+ case Builtins::kArrayConstructorImpl:
+ case Builtins::kArrayEveryLoopContinuation:
+ case Builtins::kArrayFilterLoopContinuation:
+ case Builtins::kArrayFindIndexLoopContinuation:
+ case Builtins::kArrayFindLoopContinuation:
+ case Builtins::kArrayForEachLoopContinuation:
+ case Builtins::kArrayIncludesHoleyDoubles:
+ case Builtins::kArrayIncludesPackedDoubles:
+ case Builtins::kArrayIncludesSmiOrObject:
+ case Builtins::kArrayIndexOfHoleyDoubles:
+ case Builtins::kArrayIndexOfPackedDoubles:
+ case Builtins::kArrayIndexOfSmiOrObject:
+ case Builtins::kArrayMapLoopContinuation:
+ case Builtins::kArrayReduceLoopContinuation:
+ case Builtins::kArrayReduceRightLoopContinuation:
+ case Builtins::kArraySomeLoopContinuation:
+ case Builtins::kArrayTimSort:
+ case Builtins::kCall_ReceiverIsAny:
+ case Builtins::kCall_ReceiverIsNotNullOrUndefined:
+ case Builtins::kCall_ReceiverIsNullOrUndefined:
+ case Builtins::kCallWithArrayLike:
+ case Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit:
+ case Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit:
+ case Builtins::kCEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit:
+ case Builtins::kCEntry_Return1_SaveFPRegs_ArgvOnStack_NoBuiltinExit:
+ case Builtins::kCEntry_Return1_SaveFPRegs_ArgvOnStack_BuiltinExit:
+ case Builtins::kCEntry_Return2_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit:
+ case Builtins::kCEntry_Return2_DontSaveFPRegs_ArgvOnStack_BuiltinExit:
+ case Builtins::kCEntry_Return2_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit:
+ case Builtins::kCEntry_Return2_SaveFPRegs_ArgvOnStack_NoBuiltinExit:
+ case Builtins::kCEntry_Return2_SaveFPRegs_ArgvOnStack_BuiltinExit:
+ case Builtins::kCloneFastJSArray:
+ case Builtins::kConstruct:
+ case Builtins::kConvertToLocaleString:
+ case Builtins::kCreateTypedArray:
+ case Builtins::kDirectCEntry:
+ case Builtins::kDoubleToI:
+ case Builtins::kExtractFastJSArray:
+ case Builtins::kFastNewObject:
+ case Builtins::kFindOrderedHashMapEntry:
+ case Builtins::kFlatMapIntoArray:
+ case Builtins::kFlattenIntoArray:
+ case Builtins::kGetProperty:
+ case Builtins::kHasProperty:
+ case Builtins::kCreateHTML:
+ case Builtins::kNonNumberToNumber:
+ case Builtins::kNonPrimitiveToPrimitive_Number:
+ case Builtins::kNumberToString:
+ case Builtins::kObjectToString:
+ case Builtins::kOrderedHashTableHealIndex:
+ case Builtins::kOrdinaryToPrimitive_Number:
+ case Builtins::kOrdinaryToPrimitive_String:
+ case Builtins::kParseInt:
+ case Builtins::kProxyHasProperty:
+ case Builtins::kProxyIsExtensible:
+ case Builtins::kProxyGetPrototypeOf:
+ case Builtins::kRecordWrite:
+ case Builtins::kStringAdd_CheckNone:
+ case Builtins::kStringEqual:
+ case Builtins::kStringIndexOf:
+ case Builtins::kStringRepeat:
+ case Builtins::kToInteger:
+ case Builtins::kToLength:
+ case Builtins::kToName:
+ case Builtins::kToObject:
+ case Builtins::kToString:
+ case Builtins::kWeakMapLookupHashIndex:
+ return true;
+ case Builtins::kJoinStackPop:
+ case Builtins::kJoinStackPush:
+ switch (caller) {
+ case Builtins::kArrayPrototypeJoin:
+ case Builtins::kArrayPrototypeToLocaleString:
+ case Builtins::kTypedArrayPrototypeJoin:
+ case Builtins::kTypedArrayPrototypeToLocaleString:
+ return true;
+ default:
+ return false;
+ }
+ case Builtins::kFastCreateDataProperty:
+ switch (caller) {
+ case Builtins::kArrayPrototypeSlice:
+ case Builtins::kArrayFilter:
+ return true;
+ default:
+ return false;
+ }
+ case Builtins::kSetProperty:
+ switch (caller) {
+ case Builtins::kArrayPrototypeSlice:
+ case Builtins::kTypedArrayPrototypeMap:
+ case Builtins::kStringPrototypeMatchAll:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
}
// static
-bool DebugEvaluate::CallbackHasNoSideEffect(Address function_addr) {
- for (size_t i = 0; i < arraysize(accessors_with_no_side_effect); i++) {
- if (function_addr == accessors_with_no_side_effect[i]) return true;
- }
+void DebugEvaluate::VerifyTransitiveBuiltins(Isolate* isolate) {
+ // TODO(yangguo): also check runtime calls.
+ bool failed = false;
+ bool sanity_check = false;
+ for (int i = 0; i < Builtins::builtin_count; i++) {
+ Builtins::Name caller = static_cast<Builtins::Name>(i);
+ DebugInfo::SideEffectState state = BuiltinGetSideEffectState(caller);
+ if (state != DebugInfo::kHasNoSideEffect) continue;
+ Code code = isolate->builtins()->builtin(caller);
+ int mode = RelocInfo::ModeMask(RelocInfo::CODE_TARGET) |
+ RelocInfo::ModeMask(RelocInfo::RELATIVE_CODE_TARGET);
- if (FLAG_trace_side_effect_free_debug_evaluate) {
- PrintF("[debug-evaluate] API Callback at %p may cause side effect.\n",
- reinterpret_cast<void*>(function_addr));
+ for (RelocIterator it(code, mode); !it.done(); it.next()) {
+ RelocInfo* rinfo = it.rinfo();
+ DCHECK(RelocInfo::IsCodeTargetMode(rinfo->rmode()));
+ Code callee_code = isolate->heap()->GcSafeFindCodeForInnerPointer(
+ rinfo->target_address());
+ if (!callee_code.is_builtin()) continue;
+ Builtins::Name callee =
+ static_cast<Builtins::Name>(callee_code.builtin_index());
+ if (BuiltinGetSideEffectState(callee) == DebugInfo::kHasNoSideEffect) {
+ continue;
+ }
+ if (TransitivelyCalledBuiltinHasNoSideEffect(caller, callee)) {
+ sanity_check = true;
+ continue;
+ }
+ PrintF("Allowlisted builtin %s calls non-allowlisted builtin %s\n",
+ Builtins::name(caller), Builtins::name(callee));
+ failed = true;
+ }
}
- return false;
+ CHECK(!failed);
+#if defined(V8_TARGET_ARCH_PPC) || defined(V8_TARGET_ARCH_PPC64) || \
+ defined(V8_TARGET_ARCH_MIPS64)
+ // Isolate-independent builtin calls and jumps do not emit reloc infos
+ // on PPC. We try to avoid using PC relative code due to performance
+ // issue with especially older hardwares.
+ // MIPS64 doesn't have PC relative code currently.
+ // TODO(mips): Add PC relative code to MIPS64.
+ USE(sanity_check);
+#else
+ CHECK(sanity_check);
+#endif
+}
+#endif // DEBUG
+
+// static
+void DebugEvaluate::ApplySideEffectChecks(
+ Handle<BytecodeArray> bytecode_array) {
+ for (interpreter::BytecodeArrayIterator it(bytecode_array); !it.done();
+ it.Advance()) {
+ interpreter::Bytecode bytecode = it.current_bytecode();
+ if (BytecodeRequiresRuntimeCheck(bytecode)) it.ApplyDebugBreak();
+ }
}
} // namespace internal
diff --git a/src/debug/debug-evaluate.h b/src/debug/debug-evaluate.h
index 5f5b51e..2f4cc2d 100644
--- a/src/debug/debug-evaluate.h
+++ b/src/debug/debug-evaluate.h
@@ -5,28 +5,55 @@
#ifndef V8_DEBUG_DEBUG_EVALUATE_H_
#define V8_DEBUG_DEBUG_EVALUATE_H_
-#include "src/frames.h"
-#include "src/objects.h"
+#include <vector>
+
+#include "src/common/globals.h"
+#include "src/debug/debug-frames.h"
+#include "src/debug/debug-scopes.h"
+#include "src/debug/debug.h"
+#include "src/execution/frames.h"
+#include "src/objects/objects.h"
+#include "src/objects/shared-function-info.h"
+#include "src/objects/string-set.h"
namespace v8 {
namespace internal {
+class FrameInspector;
+
class DebugEvaluate : public AllStatic {
public:
- static MaybeHandle<Object> Global(Isolate* isolate, Handle<String> source);
+ static MaybeHandle<Object> Global(Isolate* isolate, Handle<String> source,
+ debug::EvaluateGlobalMode mode,
+ REPLMode repl_mode = REPLMode::kNo);
// Evaluate a piece of JavaScript in the context of a stack frame for
// debugging. Things that need special attention are:
// - Parameters and stack-allocated locals need to be materialized. Altered
// values need to be written back to the stack afterwards.
// - The arguments object needs to materialized.
- static MaybeHandle<Object> Local(Isolate* isolate, StackFrame::Id frame_id,
+ static MaybeHandle<Object> Local(Isolate* isolate, StackFrameId frame_id,
int inlined_jsframe_index,
Handle<String> source,
bool throw_on_side_effect);
- static bool FunctionHasNoSideEffect(Handle<SharedFunctionInfo> info);
- static bool CallbackHasNoSideEffect(Address function_addr);
+ static V8_EXPORT MaybeHandle<Object> WebAssembly(
+ Handle<WasmInstanceObject> instance, StackFrameId frame_id,
+ Handle<String> source, bool throw_on_side_effect);
+
+ // This is used for break-at-entry for builtins and API functions.
+ // Evaluate a piece of JavaScript in the native context, but with the
+ // materialized arguments object and receiver of the current call.
+ static MaybeHandle<Object> WithTopmostArguments(Isolate* isolate,
+ Handle<String> source);
+
+ static DebugInfo::SideEffectState FunctionGetSideEffectState(
+ Isolate* isolate, Handle<SharedFunctionInfo> info);
+ static void ApplySideEffectChecks(Handle<BytecodeArray> bytecode_array);
+
+#ifdef DEBUG
+ static void VerifyTransitiveBuiltins(Isolate* isolate);
+#endif // DEBUG
private:
// This class builds a context chain for evaluation of expressions
@@ -55,32 +82,20 @@
void UpdateValues();
Handle<Context> evaluation_context() const { return evaluation_context_; }
- Handle<SharedFunctionInfo> outer_info() const { return outer_info_; }
+ Handle<SharedFunctionInfo> outer_info() const;
private:
struct ContextChainElement {
- Handle<ScopeInfo> scope_info;
Handle<Context> wrapped_context;
Handle<JSObject> materialized_object;
- Handle<StringSet> whitelist;
+ Handle<StringSet> blocklist;
};
- // Helper function to find or create the arguments object for
- // Runtime_DebugEvaluate.
- void MaterializeArgumentsObject(Handle<JSObject> target,
- Handle<JSFunction> function);
-
- void MaterializeReceiver(Handle<JSObject> target,
- Handle<Context> local_context,
- Handle<JSFunction> local_function,
- Handle<StringSet> non_locals);
-
- Handle<SharedFunctionInfo> outer_info_;
Handle<Context> evaluation_context_;
- List<ContextChainElement> context_chain_;
+ std::vector<ContextChainElement> context_chain_;
Isolate* isolate_;
- JavaScriptFrame* frame_;
- int inlined_jsframe_index_;
+ FrameInspector frame_inspector_;
+ ScopeIterator scope_iterator_;
};
static MaybeHandle<Object> Evaluate(Isolate* isolate,
@@ -91,7 +106,6 @@
bool throw_on_side_effect);
};
-
} // namespace internal
} // namespace v8
diff --git a/src/debug/debug-frames.cc b/src/debug/debug-frames.cc
index d489911..4c8da80 100644
--- a/src/debug/debug-frames.cc
+++ b/src/debug/debug-frames.cc
@@ -4,224 +4,111 @@
#include "src/debug/debug-frames.h"
-#include "src/frames-inl.h"
-#include "src/wasm/wasm-interpreter.h"
-#include "src/wasm/wasm-objects.h"
+#include "src/builtins/accessors.h"
+#include "src/execution/frames-inl.h"
+#include "src/wasm/wasm-objects-inl.h"
namespace v8 {
namespace internal {
-FrameInspector::FrameInspector(StandardFrame* frame, int inlined_frame_index,
+FrameInspector::FrameInspector(CommonFrame* frame, int inlined_frame_index,
Isolate* isolate)
: frame_(frame),
- frame_summary_(FrameSummary::Get(frame, inlined_frame_index)),
+ inlined_frame_index_(inlined_frame_index),
isolate_(isolate) {
+ // Extract the relevant information from the frame summary and discard it.
+ FrameSummary summary = FrameSummary::Get(frame, inlined_frame_index);
+ summary.EnsureSourcePositionsAvailable();
+
+ is_constructor_ = summary.is_constructor();
+ source_position_ = summary.SourcePosition();
+ function_name_ = summary.FunctionName();
+ script_ = Handle<Script>::cast(summary.script());
+ receiver_ = summary.receiver();
+
+ if (summary.IsJavaScript()) {
+ function_ = summary.AsJavaScript().function();
+ }
+
JavaScriptFrame* js_frame =
frame->is_java_script() ? javascript_frame() : nullptr;
DCHECK(js_frame || frame->is_wasm());
has_adapted_arguments_ = js_frame && js_frame->has_adapted_arguments();
- is_bottommost_ = inlined_frame_index == 0;
is_optimized_ = frame_->is_optimized();
is_interpreted_ = frame_->is_interpreted();
// Calculate the deoptimized frame.
if (is_optimized_) {
- DCHECK(js_frame != nullptr);
- // TODO(turbofan): Revisit once we support deoptimization.
- if (js_frame->LookupCode()->is_turbofanned() &&
- js_frame->function()->shared()->asm_function()) {
- is_optimized_ = false;
- return;
- }
-
+ DCHECK_NOT_NULL(js_frame);
deoptimized_frame_.reset(Deoptimizer::DebuggerInspectableFrame(
js_frame, inlined_frame_index, isolate));
- } else if (frame_->is_wasm_interpreter_entry()) {
- wasm_interpreted_frame_ =
- frame_summary_.AsWasm()
- .wasm_instance()
- ->debug_info()
- ->GetInterpretedFrame(frame_->fp(), inlined_frame_index);
- DCHECK(wasm_interpreted_frame_);
}
}
-FrameInspector::~FrameInspector() {
- // Destructor needs to be defined in the .cc file, because it instantiates
- // std::unique_ptr destructors but the types are not known in the header.
-}
+// Destructor needs to be defined in the .cc file, because it instantiates
+// std::unique_ptr destructors but the types are not known in the header.
+FrameInspector::~FrameInspector() = default;
-int FrameInspector::GetParametersCount() {
- if (is_optimized_) return deoptimized_frame_->parameters_count();
- if (wasm_interpreted_frame_)
- return wasm_interpreted_frame_->GetParameterCount();
- return frame_->ComputeParametersCount();
-}
-
-Handle<Script> FrameInspector::GetScript() {
- return Handle<Script>::cast(frame_summary_.script());
-}
-
-Handle<JSFunction> FrameInspector::GetFunction() {
- return frame_summary_.AsJavaScript().function();
+JavaScriptFrame* FrameInspector::javascript_frame() {
+ return frame_->is_arguments_adaptor() ? ArgumentsAdaptorFrame::cast(frame_)
+ : JavaScriptFrame::cast(frame_);
}
Handle<Object> FrameInspector::GetParameter(int index) {
if (is_optimized_) return deoptimized_frame_->GetParameter(index);
- // TODO(clemensh): Handle wasm_interpreted_frame_.
- return handle(frame_->GetParameter(index), isolate_);
+ DCHECK(IsJavaScript());
+ return handle(javascript_frame()->GetParameter(index), isolate_);
}
Handle<Object> FrameInspector::GetExpression(int index) {
- // TODO(turbofan): Revisit once we support deoptimization.
- if (frame_->is_java_script() &&
- javascript_frame()->LookupCode()->is_turbofanned() &&
- javascript_frame()->function()->shared()->asm_function()) {
- return isolate_->factory()->undefined_value();
- }
return is_optimized_ ? deoptimized_frame_->GetExpression(index)
: handle(frame_->GetExpression(index), isolate_);
}
-int FrameInspector::GetSourcePosition() {
- return frame_summary_.SourcePosition();
-}
-
-bool FrameInspector::IsConstructor() { return frame_summary_.is_constructor(); }
-
Handle<Object> FrameInspector::GetContext() {
- return is_optimized_ ? deoptimized_frame_->GetContext()
- : handle(frame_->context(), isolate_);
+ return deoptimized_frame_ ? deoptimized_frame_->GetContext()
+ : handle(frame_->context(), isolate_);
}
-// To inspect all the provided arguments the frame might need to be
-// replaced with the arguments frame.
-void FrameInspector::SetArgumentsFrame(StandardFrame* frame) {
- DCHECK(has_adapted_arguments_);
- DCHECK(frame->is_arguments_adaptor());
- frame_ = frame;
- is_optimized_ = frame_->is_optimized();
- is_interpreted_ = frame_->is_interpreted();
- DCHECK(!is_optimized_);
-}
+bool FrameInspector::IsWasm() { return frame_->is_wasm(); }
-
-// Create a plain JSObject which materializes the local scope for the specified
-// frame.
-void FrameInspector::MaterializeStackLocals(Handle<JSObject> target,
- Handle<ScopeInfo> scope_info) {
- HandleScope scope(isolate_);
- // First fill all parameters.
- for (int i = 0; i < scope_info->ParameterCount(); ++i) {
- // Do not materialize the parameter if it is shadowed by a context local.
- // TODO(yangguo): check whether this is necessary, now that we materialize
- // context locals as well.
- Handle<String> name(scope_info->ParameterName(i));
- if (ScopeInfo::VariableIsSynthetic(*name)) continue;
- if (ParameterIsShadowedByContextLocal(scope_info, name)) continue;
-
- Handle<Object> value =
- i < GetParametersCount()
- ? GetParameter(i)
- : Handle<Object>::cast(isolate_->factory()->undefined_value());
- DCHECK(!value->IsTheHole(isolate_));
-
- JSObject::SetOwnPropertyIgnoreAttributes(target, name, value, NONE).Check();
- }
-
- // Second fill all stack locals.
- for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
- Handle<String> name(scope_info->StackLocalName(i));
- if (ScopeInfo::VariableIsSynthetic(*name)) continue;
- Handle<Object> value = GetExpression(scope_info->StackLocalIndex(i));
- // TODO(yangguo): We convert optimized out values to {undefined} when they
- // are passed to the debugger. Eventually we should handle them somehow.
- if (value->IsTheHole(isolate_)) {
- value = isolate_->factory()->undefined_value();
- }
- if (value->IsOptimizedOut(isolate_)) {
- value = isolate_->factory()->undefined_value();
- }
- JSObject::SetOwnPropertyIgnoreAttributes(target, name, value, NONE).Check();
- }
-}
-
-
-void FrameInspector::MaterializeStackLocals(Handle<JSObject> target,
- Handle<JSFunction> function) {
- Handle<SharedFunctionInfo> shared(function->shared());
- Handle<ScopeInfo> scope_info(shared->scope_info());
- MaterializeStackLocals(target, scope_info);
-}
-
-
-void FrameInspector::UpdateStackLocalsFromMaterializedObject(
- Handle<JSObject> target, Handle<ScopeInfo> scope_info) {
- // Optimized frames and wasm frames are not supported. Simply give up.
- if (is_optimized_ || frame_->is_wasm()) return;
-
- HandleScope scope(isolate_);
-
- // Parameters.
- for (int i = 0; i < scope_info->ParameterCount(); ++i) {
- // Shadowed parameters were not materialized.
- Handle<String> name(scope_info->ParameterName(i));
- if (ScopeInfo::VariableIsSynthetic(*name)) continue;
- if (ParameterIsShadowedByContextLocal(scope_info, name)) continue;
-
- DCHECK(!javascript_frame()->GetParameter(i)->IsTheHole(isolate_));
- Handle<Object> value =
- Object::GetPropertyOrElement(target, name).ToHandleChecked();
- javascript_frame()->SetParameterValue(i, *value);
- }
-
- // Stack locals.
- for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
- Handle<String> name(scope_info->StackLocalName(i));
- if (ScopeInfo::VariableIsSynthetic(*name)) continue;
- int index = scope_info->StackLocalIndex(i);
- if (frame_->GetExpression(index)->IsTheHole(isolate_)) continue;
- Handle<Object> value =
- Object::GetPropertyOrElement(target, name).ToHandleChecked();
- frame_->SetExpression(index, *value);
- }
-}
-
+bool FrameInspector::IsJavaScript() { return frame_->is_java_script(); }
bool FrameInspector::ParameterIsShadowedByContextLocal(
Handle<ScopeInfo> info, Handle<String> parameter_name) {
VariableMode mode;
InitializationFlag init_flag;
MaybeAssignedFlag maybe_assigned_flag;
- return ScopeInfo::ContextSlotIndex(info, parameter_name, &mode, &init_flag,
- &maybe_assigned_flag) != -1;
+ IsStaticFlag is_static_flag;
+ return ScopeInfo::ContextSlotIndex(*info, *parameter_name, &mode, &init_flag,
+ &maybe_assigned_flag,
+ &is_static_flag) != -1;
}
-SaveContext* DebugFrameHelper::FindSavedContextForFrame(Isolate* isolate,
- StandardFrame* frame) {
- SaveContext* save = isolate->save_context();
- while (save != NULL && !save->IsBelowFrame(frame)) {
- save = save->prev();
+RedirectActiveFunctions::RedirectActiveFunctions(SharedFunctionInfo shared,
+ Mode mode)
+ : shared_(shared), mode_(mode) {
+ DCHECK(shared.HasBytecodeArray());
+ if (mode == Mode::kUseDebugBytecode) {
+ DCHECK(shared.HasDebugInfo());
}
- DCHECK(save != NULL);
- return save;
}
-int DebugFrameHelper::FindIndexedNonNativeFrame(StackTraceFrameIterator* it,
- int index) {
- int count = -1;
- for (; !it->done(); it->Advance()) {
- List<FrameSummary> frames(FLAG_max_inlining_levels + 1);
- it->frame()->Summarize(&frames);
- for (int i = frames.length() - 1; i >= 0; i--) {
- // Omit functions from native and extension scripts.
- if (!frames[i].is_subject_to_debugging()) continue;
- if (++count == index) return i;
- }
+void RedirectActiveFunctions::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 bytecode = mode_ == Mode::kUseDebugBytecode
+ ? shared_.GetDebugInfo().DebugBytecodeArray()
+ : shared_.GetBytecodeArray();
+ interpreted_frame->PatchBytecodeArray(bytecode);
}
- return -1;
}
-
} // namespace internal
} // namespace v8
diff --git a/src/debug/debug-frames.h b/src/debug/debug-frames.h
index 2c9e43f..c554ca1 100644
--- a/src/debug/debug-frames.h
+++ b/src/debug/debug-frames.h
@@ -5,89 +5,80 @@
#ifndef V8_DEBUG_DEBUG_FRAMES_H_
#define V8_DEBUG_DEBUG_FRAMES_H_
-#include "src/deoptimizer.h"
-#include "src/frames.h"
-#include "src/isolate.h"
-#include "src/objects.h"
+#include <memory>
+
+#include "src/deoptimizer/deoptimizer.h"
+#include "src/execution/isolate.h"
+#include "src/execution/v8threads.h"
+#include "src/objects/objects.h"
namespace v8 {
namespace internal {
-// Forward declaration:
-namespace wasm {
-class InterpretedFrame;
-}
+class JavaScriptFrame;
+class CommonFrame;
+class WasmFrame;
class FrameInspector {
public:
- FrameInspector(StandardFrame* frame, int inlined_frame_index,
- Isolate* isolate);
+ FrameInspector(CommonFrame* frame, int inlined_frame_index, Isolate* isolate);
~FrameInspector();
- FrameSummary& summary() { return frame_summary_; }
-
- int GetParametersCount();
- Handle<JSFunction> GetFunction();
- Handle<Script> GetScript();
+ Handle<JSFunction> GetFunction() const { return function_; }
+ Handle<Script> GetScript() { return script_; }
Handle<Object> GetParameter(int index);
Handle<Object> GetExpression(int index);
- int GetSourcePosition();
- bool IsConstructor();
+ int GetSourcePosition() { return source_position_; }
+ bool IsConstructor() { return is_constructor_; }
Handle<Object> GetContext();
+ Handle<Object> GetReceiver() { return receiver_; }
- inline JavaScriptFrame* javascript_frame() {
- return frame_->is_arguments_adaptor() ? ArgumentsAdaptorFrame::cast(frame_)
- : JavaScriptFrame::cast(frame_);
- }
+ Handle<String> GetFunctionName() { return function_name_; }
- JavaScriptFrame* GetArgumentsFrame() { return javascript_frame(); }
- void SetArgumentsFrame(StandardFrame* frame);
+ bool IsWasm();
+ bool IsJavaScript();
- void MaterializeStackLocals(Handle<JSObject> target,
- Handle<ScopeInfo> scope_info);
+ JavaScriptFrame* javascript_frame();
- void MaterializeStackLocals(Handle<JSObject> target,
- Handle<JSFunction> function);
-
- void UpdateStackLocalsFromMaterializedObject(Handle<JSObject> object,
- Handle<ScopeInfo> scope_info);
+ int inlined_frame_index() const { return inlined_frame_index_; }
private:
bool ParameterIsShadowedByContextLocal(Handle<ScopeInfo> info,
Handle<String> parameter_name);
- StandardFrame* frame_;
- FrameSummary frame_summary_;
+ CommonFrame* frame_;
+ int inlined_frame_index_;
std::unique_ptr<DeoptimizedFrameInfo> deoptimized_frame_;
- std::unique_ptr<wasm::InterpretedFrame> wasm_interpreted_frame_;
Isolate* isolate_;
- bool is_optimized_;
- bool is_interpreted_;
- bool is_bottommost_;
- bool has_adapted_arguments_;
+ Handle<Script> script_;
+ Handle<Object> receiver_;
+ Handle<JSFunction> function_;
+ Handle<String> function_name_;
+ int source_position_ = -1;
+ bool is_optimized_ = false;
+ bool is_interpreted_ = false;
+ bool has_adapted_arguments_ = false;
+ bool is_constructor_ = false;
DISALLOW_COPY_AND_ASSIGN(FrameInspector);
};
-
-class DebugFrameHelper : public AllStatic {
+class RedirectActiveFunctions : public ThreadVisitor {
public:
- static SaveContext* FindSavedContextForFrame(Isolate* isolate,
- StandardFrame* frame);
- // Advances the iterator to the frame that matches the index and returns the
- // inlined frame index, or -1 if not found. Skips native JS functions.
- static int FindIndexedNonNativeFrame(StackTraceFrameIterator* it, int index);
+ enum class Mode {
+ kUseOriginalBytecode,
+ kUseDebugBytecode,
+ };
- // Helper functions for wrapping and unwrapping stack frame ids.
- static Smi* WrapFrameId(StackFrame::Id id) {
- DCHECK(IsAligned(OffsetFrom(id), static_cast<intptr_t>(4)));
- return Smi::FromInt(id >> 2);
- }
+ explicit RedirectActiveFunctions(SharedFunctionInfo shared, Mode mode);
- static StackFrame::Id UnwrapFrameId(int wrapped) {
- return static_cast<StackFrame::Id>(wrapped << 2);
- }
+ void VisitThread(Isolate* isolate, ThreadLocalTop* top) override;
+
+ private:
+ SharedFunctionInfo shared_;
+ Mode mode_;
+ DisallowHeapAllocation no_gc_;
};
} // namespace internal
diff --git a/src/debug/debug-interface.h b/src/debug/debug-interface.h
index be8ed90..ded8a31 100644
--- a/src/debug/debug-interface.h
+++ b/src/debug/debug-interface.h
@@ -5,71 +5,46 @@
#ifndef V8_DEBUG_DEBUG_INTERFACE_H_
#define V8_DEBUG_DEBUG_INTERFACE_H_
-#include <functional>
+#include <memory>
-#include "include/v8-debug.h"
+#include "include/v8-inspector.h"
#include "include/v8-util.h"
#include "include/v8.h"
-
+#include "src/base/platform/time.h"
+#include "src/common/globals.h"
#include "src/debug/interface-types.h"
-#include "src/globals.h"
+#include "src/utils/vector.h"
namespace v8 {
namespace internal {
+struct CoverageBlock;
struct CoverageFunction;
struct CoverageScript;
+struct TypeProfileEntry;
+struct TypeProfileScript;
class Coverage;
+class DisableBreak;
+class PostponeInterruptsScope;
class Script;
-}
+class TypeProfile;
+} // namespace internal
namespace debug {
-/**
- * Debugger is running in its own context which is entered while debugger
- * messages are being dispatched. This is an explicit getter for this
- * debugger context. Note that the content of the debugger context is subject
- * to change. The Context exists only when the debugger is active, i.e. at
- * least one DebugEventListener or MessageHandler is set.
- */
-Local<Context> GetDebugContext(Isolate* isolate);
+void SetContextId(Local<Context> context, int id);
+int GetContextId(Local<Context> context);
-/**
- * Run a JavaScript function in the debugger.
- * \param fun the function to call
- * \param data passed as second argument to the function
- * With this call the debugger is entered and the function specified is called
- * with the execution state as the first argument. This makes it possible to
- * get access to information otherwise not available during normal JavaScript
- * execution e.g. details on stack frames. Receiver of the function call will
- * be the debugger context global object, however this is a subject to change.
- * The following example shows a JavaScript function which when passed to
- * v8::Debug::Call will return the current line of JavaScript execution.
- *
- * \code
- * function frame_source_line(exec_state) {
- * return exec_state.frame(0).sourceLine();
- * }
- * \endcode
- */
-// TODO(dcarney): data arg should be a MaybeLocal
-MaybeLocal<Value> Call(Local<Context> context, v8::Local<v8::Function> fun,
- Local<Value> data = Local<Value>());
+void SetInspector(Isolate* isolate, v8_inspector::V8Inspector*);
+v8_inspector::V8Inspector* GetInspector(Isolate* isolate);
-/**
- * Enable/disable LiveEdit functionality for the given Isolate
- * (default Isolate if not provided). V8 will abort if LiveEdit is
- * unexpectedly used. LiveEdit is enabled by default.
- */
-void SetLiveEditEnabled(Isolate* isolate, bool enable);
-
-// Schedule a debugger break to happen when JavaScript code is run
-// in the given isolate.
-void DebugBreak(Isolate* isolate);
+// Schedule a debugger break to happen when function is called inside given
+// isolate.
+V8_EXPORT_PRIVATE void SetBreakOnNextFunctionCall(Isolate* isolate);
// Remove scheduled debugger break in given isolate if it has not
// happened yet.
-void CancelDebugBreak(Isolate* isolate);
+V8_EXPORT_PRIVATE void ClearBreakOnNextFunctionCall(Isolate* isolate);
/**
* Returns array of internal properties specific to the value type. Result has
@@ -78,6 +53,28 @@
*/
MaybeLocal<Array> GetInternalProperties(Isolate* isolate, Local<Value> value);
+/**
+ * Returns through the out parameters names_out a vector of names
+ * in v8::String for private members, including fields, methods,
+ * accessors specific to the value type.
+ * The values are returned through the out parameter values_out in the
+ * corresponding indices. Private fields and methods are returned directly
+ * while accessors are returned as v8::debug::AccessorPair. Missing components
+ * in the accessor pairs are null.
+ * If an exception occurs, false is returned. Otherwise true is returned.
+ * Results will be allocated in the current context and handle scope.
+ */
+V8_EXPORT_PRIVATE bool GetPrivateMembers(Local<Context> context,
+ Local<Object> value,
+ std::vector<Local<Value>>* names_out,
+ std::vector<Local<Value>>* values_out);
+
+/**
+ * Forwards to v8::Object::CreationContext, but with special handling for
+ * JSGlobalProxy objects.
+ */
+Local<Context> GetCreationContext(Local<Object> value);
+
enum ExceptionBreakState {
NoBreakOnException = 0,
BreakOnUncaughtException = 1,
@@ -92,6 +89,7 @@
*/
void ChangeBreakOnException(Isolate* isolate, ExceptionBreakState state);
+void RemoveBreakpoint(Isolate* isolate, BreakpointId id);
void SetBreakPointsActive(Isolate* isolate, bool is_active);
enum StepAction {
@@ -102,18 +100,40 @@
};
void PrepareStep(Isolate* isolate, StepAction action);
+void ClearStepping(Isolate* isolate);
+V8_EXPORT_PRIVATE void BreakRightNow(Isolate* isolate);
-bool HasNonBlackboxedFrameOnStack(Isolate* isolate);
+// Use `SetTerminateOnResume` to indicate that an TerminateExecution interrupt
+// should be set shortly before resuming, i.e. shortly before returning into
+// the JavaScript stack frames on the stack. In contrast to setting the
+// interrupt with `RequestTerminateExecution` directly, this flag allows
+// the isolate to be entered for further JavaScript execution.
+V8_EXPORT_PRIVATE void SetTerminateOnResume(Isolate* isolate);
-/**
- * Out-of-memory callback function.
- * The function is invoked when the heap size is close to the hard limit.
- *
- * \param data the parameter provided during callback installation.
- */
-typedef void (*OutOfMemoryCallback)(void* data);
-void SetOutOfMemoryCallback(Isolate* isolate, OutOfMemoryCallback callback,
- void* data);
+bool AllFramesOnStackAreBlackboxed(Isolate* isolate);
+
+class Script;
+
+struct LiveEditResult {
+ enum Status {
+ OK,
+ COMPILE_ERROR,
+ BLOCKED_BY_RUNNING_GENERATOR,
+ BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME,
+ BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME,
+ BLOCKED_BY_ACTIVE_FUNCTION,
+ BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME,
+ FRAME_RESTART_IS_NOT_SUPPORTED
+ };
+ Status status = OK;
+ bool stack_changed = false;
+ // Available only for OK.
+ v8::Local<v8::debug::Script> script;
+ // Fields below are available only for COMPILE_ERROR.
+ v8::Local<v8::String> message;
+ int line_number = -1;
+ int column_number = -1;
+};
/**
* Native wrapper around v8::internal::Script object.
@@ -124,6 +144,7 @@
ScriptOriginOptions OriginOptions() const;
bool WasCompiled() const;
+ bool IsEmbedded() const;
int Id() const;
int LineOffset() const;
int ColumnOffset() const;
@@ -131,16 +152,22 @@
MaybeLocal<String> Name() const;
MaybeLocal<String> SourceURL() const;
MaybeLocal<String> SourceMappingURL() const;
- MaybeLocal<Value> ContextData() const;
+ Maybe<int> ContextId() const;
MaybeLocal<String> Source() const;
bool IsWasm() const;
bool IsModule() const;
- bool GetPossibleBreakpoints(const debug::Location& start,
- const debug::Location& end,
- std::vector<debug::Location>* locations) const;
-
- private:
- int GetSourcePosition(const debug::Location& location) const;
+ bool GetPossibleBreakpoints(
+ const debug::Location& start, const debug::Location& end,
+ bool restrict_to_function,
+ std::vector<debug::BreakLocation>* locations) const;
+ int GetSourceOffset(const debug::Location& location) const;
+ v8::debug::Location GetSourceLocation(int offset) const;
+ bool SetScriptSource(v8::Local<v8::String> newSource, bool preview,
+ LiveEditResult* result) const;
+ bool SetBreakpoint(v8::Local<v8::String> condition, debug::Location* location,
+ BreakpointId* id) const;
+ void RemoveWasmBreakpoint(BreakpointId id);
+ bool SetBreakpointOnScriptEntry(BreakpointId* id) const;
};
// Specialization for wasm Scripts.
@@ -148,51 +175,88 @@
public:
static WasmScript* Cast(Script* script);
+ enum class DebugSymbolsType { None, SourceMap, EmbeddedDWARF, ExternalDWARF };
+ DebugSymbolsType GetDebugSymbolType() const;
+ MemorySpan<const char> ExternalSymbolsURL() const;
int NumFunctions() const;
int NumImportedFunctions() const;
+ MemorySpan<const uint8_t> Bytecode() const;
std::pair<int, int> GetFunctionRange(int function_index) const;
+ int GetContainingFunction(int byte_offset) const;
- debug::WasmDisassembly DisassembleFunction(int function_index) const;
+ uint32_t GetFunctionHash(int function_index);
+
+ int CodeOffset() const;
+ int CodeLength() const;
};
-void GetLoadedScripts(Isolate* isolate, PersistentValueVector<Script>& scripts);
+V8_EXPORT_PRIVATE void GetLoadedScripts(
+ Isolate* isolate,
+ PersistentValueVector<Script>& scripts); // NOLINT(runtime/references)
MaybeLocal<UnboundScript> CompileInspectorScript(Isolate* isolate,
Local<String> source);
+enum ExceptionType { kException, kPromiseRejection };
+
class DebugDelegate {
public:
- virtual ~DebugDelegate() {}
- virtual void PromiseEventOccurred(debug::PromiseDebugActionType type, int id,
- int parent_id) {}
- virtual void ScriptCompiled(v8::Local<Script> script,
+ virtual ~DebugDelegate() = default;
+ virtual void ScriptCompiled(v8::Local<Script> script, bool is_live_edited,
bool has_compile_error) {}
- virtual void BreakProgramRequested(v8::Local<v8::Context> paused_context,
- v8::Local<v8::Object> exec_state,
- v8::Local<v8::Value> break_points_hit) {}
+ // |inspector_break_points_hit| contains id of breakpoints installed with
+ // debug::Script::SetBreakpoint API.
+ virtual void BreakProgramRequested(
+ v8::Local<v8::Context> paused_context,
+ const std::vector<debug::BreakpointId>& inspector_break_points_hit) {}
virtual void 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) {
- }
+ v8::Local<v8::Value> promise, bool is_uncaught,
+ ExceptionType exception_type) {}
virtual bool IsFunctionBlackboxed(v8::Local<debug::Script> script,
const debug::Location& start,
const debug::Location& end) {
return false;
}
+ virtual bool ShouldBeSkipped(v8::Local<v8::debug::Script> script, int line,
+ int column) {
+ return false;
+ }
};
-void SetDebugDelegate(Isolate* isolate, DebugDelegate* listener);
+V8_EXPORT_PRIVATE void SetDebugDelegate(Isolate* isolate,
+ DebugDelegate* listener);
+
+V8_EXPORT_PRIVATE void TierDownAllModulesPerIsolate(Isolate* isolate);
+V8_EXPORT_PRIVATE void TierUpAllModulesPerIsolate(Isolate* isolate);
+
+class AsyncEventDelegate {
+ public:
+ virtual ~AsyncEventDelegate() = default;
+ virtual void AsyncEventOccurred(debug::DebugAsyncActionType type, int id,
+ bool is_blackboxed) = 0;
+};
+
+void SetAsyncEventDelegate(Isolate* isolate, AsyncEventDelegate* delegate);
void ResetBlackboxedStateCache(Isolate* isolate,
v8::Local<debug::Script> script);
int EstimatedValueSize(Isolate* isolate, v8::Local<v8::Value> value);
-v8::MaybeLocal<v8::Array> EntriesPreview(Isolate* isolate,
- v8::Local<v8::Value> value,
- bool* is_key_value);
+enum Builtin { kStringToLowerCase };
+
+Local<Function> GetBuiltin(Isolate* isolate, Builtin builtin);
+
+V8_EXPORT_PRIVATE void SetConsoleDelegate(Isolate* isolate,
+ ConsoleDelegate* delegate);
+
+V8_DEPRECATED("See http://crbug.com/v8/10566.")
+int GetStackFrameId(v8::Local<v8::StackFrame> frame);
+
+v8::Local<v8::StackTrace> GetDetailedStackTrace(Isolate* isolate,
+ v8::Local<v8::Object> error);
/**
* Native wrapper around v8::internal::JSGeneratorObject object.
@@ -212,52 +276,379 @@
*/
class V8_EXPORT_PRIVATE Coverage {
public:
- class ScriptData; // Forward declaration.
+ MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(Coverage);
+
+ // Forward declarations.
+ class ScriptData;
+ class FunctionData;
+
+ class V8_EXPORT_PRIVATE BlockData {
+ public:
+ MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(BlockData);
+
+ int StartOffset() const;
+ int EndOffset() const;
+ uint32_t Count() const;
+
+ private:
+ explicit BlockData(i::CoverageBlock* block,
+ std::shared_ptr<i::Coverage> coverage)
+ : block_(block), coverage_(std::move(coverage)) {}
+
+ i::CoverageBlock* block_;
+ std::shared_ptr<i::Coverage> coverage_;
+
+ friend class v8::debug::Coverage::FunctionData;
+ };
class V8_EXPORT_PRIVATE FunctionData {
public:
- // 0-based line and colum numbers.
- Location Start() { return start_; }
- Location End() { return end_; }
- uint32_t Count();
- MaybeLocal<String> Name();
+ MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(FunctionData);
+
+ int StartOffset() const;
+ int EndOffset() const;
+ uint32_t Count() const;
+ MaybeLocal<String> Name() const;
+ size_t BlockCount() const;
+ bool HasBlockCoverage() const;
+ BlockData GetBlockData(size_t i) const;
private:
- FunctionData(i::CoverageFunction* function, Local<debug::Script> script);
+ explicit FunctionData(i::CoverageFunction* function,
+ std::shared_ptr<i::Coverage> coverage)
+ : function_(function), coverage_(std::move(coverage)) {}
+
i::CoverageFunction* function_;
- Location start_;
- Location end_;
+ std::shared_ptr<i::Coverage> coverage_;
friend class v8::debug::Coverage::ScriptData;
};
class V8_EXPORT_PRIVATE ScriptData {
public:
- Local<debug::Script> GetScript();
- size_t FunctionCount();
- FunctionData GetFunctionData(size_t i);
+ MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ScriptData);
+
+ Local<debug::Script> GetScript() const;
+ size_t FunctionCount() const;
+ FunctionData GetFunctionData(size_t i) const;
private:
- explicit ScriptData(i::CoverageScript* script) : script_(script) {}
+ explicit ScriptData(size_t index, std::shared_ptr<i::Coverage> c);
+
i::CoverageScript* script_;
+ std::shared_ptr<i::Coverage> coverage_;
friend class v8::debug::Coverage;
};
- static Coverage Collect(Isolate* isolate, bool reset_count);
+ static Coverage CollectPrecise(Isolate* isolate);
+ static Coverage CollectBestEffort(Isolate* isolate);
- static void TogglePrecise(Isolate* isolate, bool enable);
+ static void SelectMode(Isolate* isolate, CoverageMode mode);
- size_t ScriptCount();
- ScriptData GetScriptData(size_t i);
- bool IsEmpty() { return coverage_ == nullptr; }
-
- ~Coverage();
+ size_t ScriptCount() const;
+ ScriptData GetScriptData(size_t i) const;
+ bool IsEmpty() const { return coverage_ == nullptr; }
private:
- explicit Coverage(i::Coverage* coverage) : coverage_(coverage) {}
- i::Coverage* coverage_;
+ explicit Coverage(std::shared_ptr<i::Coverage> coverage)
+ : coverage_(std::move(coverage)) {}
+ std::shared_ptr<i::Coverage> coverage_;
};
+
+/*
+ * Provide API layer between inspector and type profile.
+ */
+class V8_EXPORT_PRIVATE TypeProfile {
+ public:
+ MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeProfile);
+
+ class ScriptData; // Forward declaration.
+
+ class V8_EXPORT_PRIVATE Entry {
+ public:
+ MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(Entry);
+
+ int SourcePosition() const;
+ std::vector<MaybeLocal<String>> Types() const;
+
+ private:
+ explicit Entry(const i::TypeProfileEntry* entry,
+ std::shared_ptr<i::TypeProfile> type_profile)
+ : entry_(entry), type_profile_(std::move(type_profile)) {}
+
+ const i::TypeProfileEntry* entry_;
+ std::shared_ptr<i::TypeProfile> type_profile_;
+
+ friend class v8::debug::TypeProfile::ScriptData;
+ };
+
+ class V8_EXPORT_PRIVATE ScriptData {
+ public:
+ MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ScriptData);
+
+ Local<debug::Script> GetScript() const;
+ std::vector<Entry> Entries() const;
+
+ private:
+ explicit ScriptData(size_t index,
+ std::shared_ptr<i::TypeProfile> type_profile);
+
+ i::TypeProfileScript* script_;
+ std::shared_ptr<i::TypeProfile> type_profile_;
+
+ friend class v8::debug::TypeProfile;
+ };
+
+ static TypeProfile Collect(Isolate* isolate);
+
+ static void SelectMode(Isolate* isolate, TypeProfileMode mode);
+
+ size_t ScriptCount() const;
+ ScriptData GetScriptData(size_t i) const;
+
+ private:
+ explicit TypeProfile(std::shared_ptr<i::TypeProfile> type_profile)
+ : type_profile_(std::move(type_profile)) {}
+
+ std::shared_ptr<i::TypeProfile> type_profile_;
+};
+
+class V8_EXPORT_PRIVATE ScopeIterator {
+ public:
+ static std::unique_ptr<ScopeIterator> CreateForFunction(
+ v8::Isolate* isolate, v8::Local<v8::Function> func);
+ static std::unique_ptr<ScopeIterator> CreateForGeneratorObject(
+ v8::Isolate* isolate, v8::Local<v8::Object> generator);
+
+ ScopeIterator() = default;
+ virtual ~ScopeIterator() = default;
+
+ enum ScopeType {
+ ScopeTypeGlobal = 0,
+ ScopeTypeLocal,
+ ScopeTypeWith,
+ ScopeTypeClosure,
+ ScopeTypeCatch,
+ ScopeTypeBlock,
+ ScopeTypeScript,
+ ScopeTypeEval,
+ ScopeTypeModule,
+ ScopeTypeWasmExpressionStack
+ };
+
+ virtual bool Done() = 0;
+ virtual void Advance() = 0;
+ virtual ScopeType GetType() = 0;
+ virtual v8::Local<v8::Object> GetObject() = 0;
+ virtual v8::Local<v8::Value> GetFunctionDebugName() = 0;
+ virtual int GetScriptId() = 0;
+ virtual bool HasLocationInfo() = 0;
+ virtual debug::Location GetStartLocation() = 0;
+ virtual debug::Location GetEndLocation() = 0;
+
+ virtual bool SetVariableValue(v8::Local<v8::String> name,
+ v8::Local<v8::Value> value) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopeIterator);
+};
+
+class V8_EXPORT_PRIVATE StackTraceIterator {
+ public:
+ static bool SupportsWasmDebugEvaluate();
+ static std::unique_ptr<StackTraceIterator> Create(Isolate* isolate,
+ int index = 0);
+ StackTraceIterator() = default;
+ virtual ~StackTraceIterator() = default;
+
+ virtual bool Done() const = 0;
+ virtual void Advance() = 0;
+
+ virtual int GetContextId() const = 0;
+ virtual v8::MaybeLocal<v8::Value> GetReceiver() const = 0;
+ virtual v8::Local<v8::Value> GetReturnValue() const = 0;
+ virtual v8::Local<v8::String> GetFunctionDebugName() const = 0;
+ virtual v8::Local<v8::debug::Script> GetScript() const = 0;
+ virtual debug::Location GetSourceLocation() const = 0;
+ virtual v8::Local<v8::Function> GetFunction() const = 0;
+ virtual std::unique_ptr<ScopeIterator> GetScopeIterator() const = 0;
+
+ virtual bool Restart() = 0;
+ virtual v8::MaybeLocal<v8::Value> Evaluate(v8::Local<v8::String> source,
+ bool throw_on_side_effect) = 0;
+ virtual v8::MaybeLocal<v8::String> EvaluateWasm(
+ internal::Vector<const internal::byte> source, int frame_index) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StackTraceIterator);
+};
+
+class QueryObjectPredicate {
+ public:
+ virtual ~QueryObjectPredicate() = default;
+ virtual bool Filter(v8::Local<v8::Object> object) = 0;
+};
+
+void QueryObjects(v8::Local<v8::Context> context,
+ QueryObjectPredicate* predicate,
+ v8::PersistentValueVector<v8::Object>* objects);
+
+void GlobalLexicalScopeNames(v8::Local<v8::Context> context,
+ v8::PersistentValueVector<v8::String>* names);
+
+void SetReturnValue(v8::Isolate* isolate, v8::Local<v8::Value> value);
+
+enum class NativeAccessorType {
+ None = 0,
+ HasGetter = 1 << 0,
+ HasSetter = 1 << 1
+};
+
+int64_t GetNextRandomInt64(v8::Isolate* isolate);
+
+using RuntimeCallCounterCallback =
+ std::function<void(const char* name, int64_t count, base::TimeDelta time)>;
+void EnumerateRuntimeCallCounters(v8::Isolate* isolate,
+ RuntimeCallCounterCallback callback);
+
+enum class EvaluateGlobalMode {
+ kDefault,
+ kDisableBreaks,
+ kDisableBreaksAndThrowOnSideEffect
+};
+
+V8_EXPORT_PRIVATE v8::MaybeLocal<v8::Value> EvaluateGlobal(
+ v8::Isolate* isolate, v8::Local<v8::String> source, EvaluateGlobalMode mode,
+ bool repl_mode = false);
+
+int GetDebuggingId(v8::Local<v8::Function> function);
+
+bool SetFunctionBreakpoint(v8::Local<v8::Function> function,
+ v8::Local<v8::String> condition, BreakpointId* id);
+
+v8::Platform* GetCurrentPlatform();
+
+void ForceGarbageCollection(
+ v8::Isolate* isolate,
+ v8::EmbedderHeapTracer::EmbedderStackState embedder_stack_state);
+
+class PostponeInterruptsScope {
+ public:
+ explicit PostponeInterruptsScope(v8::Isolate* isolate);
+ ~PostponeInterruptsScope();
+
+ private:
+ std::unique_ptr<i::PostponeInterruptsScope> scope_;
+};
+
+class DisableBreakScope {
+ public:
+ explicit DisableBreakScope(v8::Isolate* isolate);
+ ~DisableBreakScope();
+
+ private:
+ std::unique_ptr<i::DisableBreak> scope_;
+};
+
+class WeakMap : public v8::Object {
+ public:
+ WeakMap() = delete;
+ V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT v8::MaybeLocal<v8::Value> Get(
+ v8::Local<v8::Context> context, v8::Local<v8::Value> key);
+ V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT v8::MaybeLocal<WeakMap> Set(
+ v8::Local<v8::Context> context, v8::Local<v8::Value> key,
+ v8::Local<v8::Value> value);
+
+ V8_EXPORT_PRIVATE static Local<WeakMap> New(v8::Isolate* isolate);
+ V8_INLINE static WeakMap* Cast(Value* obj);
+};
+
+/**
+ * Pairs of accessors.
+ *
+ * In the case of private accessors, getters and setters are either null or
+ * Functions.
+ */
+class V8_EXPORT_PRIVATE AccessorPair : public v8::Value {
+ public:
+ AccessorPair() = delete;
+ v8::Local<v8::Value> getter();
+ v8::Local<v8::Value> setter();
+
+ static bool IsAccessorPair(v8::Local<v8::Value> obj);
+ V8_INLINE static AccessorPair* Cast(v8::Value* obj);
+
+ private:
+ static void CheckCast(v8::Value* obj);
+};
+
+struct PropertyDescriptor {
+ bool enumerable : 1;
+ bool has_enumerable : 1;
+ bool configurable : 1;
+ bool has_configurable : 1;
+ bool writable : 1;
+ bool has_writable : 1;
+ v8::Local<v8::Value> value;
+ v8::Local<v8::Value> get;
+ v8::Local<v8::Value> set;
+};
+
+class PropertyIterator {
+ public:
+ static std::unique_ptr<PropertyIterator> Create(v8::Local<v8::Object> object);
+
+ virtual ~PropertyIterator() = default;
+
+ virtual bool Done() const = 0;
+ virtual void Advance() = 0;
+
+ virtual v8::Local<v8::Name> name() const = 0;
+
+ virtual bool is_native_accessor() = 0;
+ virtual bool has_native_getter() = 0;
+ virtual bool has_native_setter() = 0;
+ virtual Maybe<PropertyAttribute> attributes() = 0;
+ virtual Maybe<PropertyDescriptor> descriptor() = 0;
+
+ virtual bool is_own() = 0;
+ virtual bool is_array_index() = 0;
+};
+
+// Wrapper around v8::internal::WasmValue.
+class V8_EXPORT_PRIVATE WasmValue : public v8::Value {
+ public:
+ WasmValue() = delete;
+ static bool IsWasmValue(v8::Local<v8::Value> obj);
+ V8_INLINE static WasmValue* Cast(v8::Value* obj);
+ int value_type();
+ // Get the underlying values as a byte array, this is only valid if value_type
+ // is i32, i64, f32, f64, or s128.
+ v8::Local<v8::Array> bytes();
+ // Get the underlying externref, only valid if value_type is externref.
+ v8::Local<v8::Value> ref();
+
+ private:
+ static void CheckCast(v8::Value* obj);
+};
+
+AccessorPair* AccessorPair::Cast(v8::Value* value) {
+#ifdef V8_ENABLE_CHECKS
+ CheckCast(value);
+#endif
+ return static_cast<AccessorPair*>(value);
+}
+
+WasmValue* WasmValue::Cast(v8::Value* value) {
+#ifdef V8_ENABLE_CHECKS
+ CheckCast(value);
+#endif
+ return static_cast<WasmValue*>(value);
+}
+
+MaybeLocal<Message> GetMessageFromPromise(Local<Promise> promise);
+
} // namespace debug
} // namespace v8
diff --git a/src/debug/debug-property-iterator.cc b/src/debug/debug-property-iterator.cc
new file mode 100644
index 0000000..a3605df
--- /dev/null
+++ b/src/debug/debug-property-iterator.cc
@@ -0,0 +1,212 @@
+// Copyright 2018 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-property-iterator.h"
+
+#include "src/api/api-inl.h"
+#include "src/base/flags.h"
+#include "src/objects/js-array-buffer-inl.h"
+#include "src/objects/keys.h"
+#include "src/objects/property-descriptor.h"
+#include "src/objects/property-details.h"
+
+namespace v8 {
+
+std::unique_ptr<debug::PropertyIterator> debug::PropertyIterator::Create(
+ v8::Local<v8::Object> v8_object) {
+ internal::Isolate* isolate =
+ reinterpret_cast<internal::Isolate*>(v8_object->GetIsolate());
+ return std::unique_ptr<debug::PropertyIterator>(
+ new internal::DebugPropertyIterator(isolate,
+ Utils::OpenHandle(*v8_object)));
+}
+
+namespace internal {
+
+DebugPropertyIterator::DebugPropertyIterator(Isolate* isolate,
+ Handle<JSReceiver> receiver)
+ : isolate_(isolate),
+ prototype_iterator_(isolate, receiver, kStartAtReceiver,
+ PrototypeIterator::END_AT_NULL) {
+ if (receiver->IsJSProxy()) {
+ is_own_ = false;
+ prototype_iterator_.AdvanceIgnoringProxies();
+ }
+ if (prototype_iterator_.IsAtEnd()) return;
+ FillKeysForCurrentPrototypeAndStage();
+ if (should_move_to_next_stage()) Advance();
+}
+
+bool DebugPropertyIterator::Done() const {
+ return prototype_iterator_.IsAtEnd();
+}
+
+void DebugPropertyIterator::Advance() {
+ ++current_key_index_;
+ calculated_native_accessor_flags_ = false;
+ while (should_move_to_next_stage()) {
+ switch (stage_) {
+ case Stage::kExoticIndices:
+ stage_ = Stage::kEnumerableStrings;
+ break;
+ case Stage::kEnumerableStrings:
+ stage_ = Stage::kAllProperties;
+ break;
+ case Stage::kAllProperties:
+ stage_ = kExoticIndices;
+ is_own_ = false;
+ prototype_iterator_.AdvanceIgnoringProxies();
+ break;
+ }
+ FillKeysForCurrentPrototypeAndStage();
+ }
+}
+
+bool DebugPropertyIterator::is_native_accessor() {
+ if (stage_ == kExoticIndices) return false;
+ CalculateNativeAccessorFlags();
+ return native_accessor_flags_;
+}
+
+bool DebugPropertyIterator::has_native_getter() {
+ if (stage_ == kExoticIndices) return false;
+ CalculateNativeAccessorFlags();
+ return native_accessor_flags_ &
+ static_cast<int>(debug::NativeAccessorType::HasGetter);
+}
+
+bool DebugPropertyIterator::has_native_setter() {
+ if (stage_ == kExoticIndices) return false;
+ CalculateNativeAccessorFlags();
+ return native_accessor_flags_ &
+ static_cast<int>(debug::NativeAccessorType::HasSetter);
+}
+
+Handle<Name> DebugPropertyIterator::raw_name() const {
+ DCHECK(!Done());
+ if (stage_ == kExoticIndices) {
+ return isolate_->factory()->SizeToString(current_key_index_);
+ } else {
+ return Handle<Name>::cast(FixedArray::get(
+ *keys_, static_cast<int>(current_key_index_), isolate_));
+ }
+}
+
+v8::Local<v8::Name> DebugPropertyIterator::name() const {
+ return Utils::ToLocal(raw_name());
+}
+
+v8::Maybe<v8::PropertyAttribute> DebugPropertyIterator::attributes() {
+ Handle<JSReceiver> receiver =
+ PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_);
+ auto result = JSReceiver::GetPropertyAttributes(receiver, raw_name());
+ if (result.IsNothing()) return Nothing<v8::PropertyAttribute>();
+ DCHECK(result.FromJust() != ABSENT);
+ return Just(static_cast<v8::PropertyAttribute>(result.FromJust()));
+}
+
+v8::Maybe<v8::debug::PropertyDescriptor> DebugPropertyIterator::descriptor() {
+ Handle<JSReceiver> receiver =
+ PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_);
+
+ PropertyDescriptor descriptor;
+ Maybe<bool> did_get_descriptor = JSReceiver::GetOwnPropertyDescriptor(
+ isolate_, receiver, raw_name(), &descriptor);
+ if (did_get_descriptor.IsNothing()) {
+ return Nothing<v8::debug::PropertyDescriptor>();
+ }
+ DCHECK(did_get_descriptor.FromJust());
+ return Just(v8::debug::PropertyDescriptor{
+ descriptor.enumerable(), descriptor.has_enumerable(),
+ descriptor.configurable(), descriptor.has_configurable(),
+ descriptor.writable(), descriptor.has_writable(),
+ descriptor.has_value() ? Utils::ToLocal(descriptor.value())
+ : v8::Local<v8::Value>(),
+ descriptor.has_get() ? Utils::ToLocal(descriptor.get())
+ : v8::Local<v8::Value>(),
+ descriptor.has_set() ? Utils::ToLocal(descriptor.set())
+ : v8::Local<v8::Value>(),
+ });
+}
+
+bool DebugPropertyIterator::is_own() { return is_own_; }
+
+bool DebugPropertyIterator::is_array_index() {
+ if (stage_ == kExoticIndices) return true;
+ uint32_t index = 0;
+ return raw_name()->AsArrayIndex(&index);
+}
+
+void DebugPropertyIterator::FillKeysForCurrentPrototypeAndStage() {
+ current_key_index_ = 0;
+ exotic_length_ = 0;
+ keys_ = Handle<FixedArray>::null();
+ if (prototype_iterator_.IsAtEnd()) return;
+ Handle<JSReceiver> receiver =
+ PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_);
+ bool has_exotic_indices = receiver->IsJSTypedArray();
+ if (stage_ == kExoticIndices) {
+ if (!has_exotic_indices) return;
+ Handle<JSTypedArray> typed_array = Handle<JSTypedArray>::cast(receiver);
+ exotic_length_ = typed_array->WasDetached() ? 0 : typed_array->length();
+ return;
+ }
+ bool skip_indices = has_exotic_indices;
+ PropertyFilter filter =
+ stage_ == kEnumerableStrings ? ENUMERABLE_STRINGS : ALL_PROPERTIES;
+ if (!KeyAccumulator::GetKeys(receiver, KeyCollectionMode::kOwnOnly, filter,
+ GetKeysConversion::kConvertToString, false,
+ skip_indices)
+ .ToHandle(&keys_)) {
+ keys_ = Handle<FixedArray>::null();
+ }
+}
+
+bool DebugPropertyIterator::should_move_to_next_stage() const {
+ if (prototype_iterator_.IsAtEnd()) return false;
+ if (stage_ == kExoticIndices) return current_key_index_ >= exotic_length_;
+ return keys_.is_null() ||
+ current_key_index_ >= static_cast<size_t>(keys_->length());
+}
+
+namespace {
+base::Flags<debug::NativeAccessorType, int> GetNativeAccessorDescriptorInternal(
+ Handle<JSReceiver> object, Handle<Name> name) {
+ Isolate* isolate = object->GetIsolate();
+ LookupIterator::Key key(isolate, name);
+ if (key.is_element()) return debug::NativeAccessorType::None;
+ LookupIterator it(isolate, object, key, LookupIterator::OWN);
+ if (!it.IsFound()) return debug::NativeAccessorType::None;
+ if (it.state() != LookupIterator::ACCESSOR) {
+ return debug::NativeAccessorType::None;
+ }
+ Handle<Object> structure = it.GetAccessors();
+ if (!structure->IsAccessorInfo()) return debug::NativeAccessorType::None;
+ base::Flags<debug::NativeAccessorType, int> result;
+#define IS_BUILTIN_ACCESSOR(_, name, ...) \
+ if (*structure == *isolate->factory()->name##_accessor()) \
+ return debug::NativeAccessorType::None;
+ ACCESSOR_INFO_LIST_GENERATOR(IS_BUILTIN_ACCESSOR, /* not used */)
+#undef IS_BUILTIN_ACCESSOR
+ Handle<AccessorInfo> accessor_info = Handle<AccessorInfo>::cast(structure);
+ if (accessor_info->getter() != Object()) {
+ result |= debug::NativeAccessorType::HasGetter;
+ }
+ if (accessor_info->setter() != Object()) {
+ result |= debug::NativeAccessorType::HasSetter;
+ }
+ return result;
+}
+} // anonymous namespace
+
+void DebugPropertyIterator::CalculateNativeAccessorFlags() {
+ if (calculated_native_accessor_flags_) return;
+ Handle<JSReceiver> receiver =
+ PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_);
+ native_accessor_flags_ =
+ GetNativeAccessorDescriptorInternal(receiver, raw_name());
+ calculated_native_accessor_flags_ = true;
+}
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug-property-iterator.h b/src/debug/debug-property-iterator.h
new file mode 100644
index 0000000..67d3cb8
--- /dev/null
+++ b/src/debug/debug-property-iterator.h
@@ -0,0 +1,62 @@
+// Copyright 2018 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.
+
+#ifndef V8_DEBUG_DEBUG_PROPERTY_ITERATOR_H_
+#define V8_DEBUG_DEBUG_PROPERTY_ITERATOR_H_
+
+#include "src/debug/debug-interface.h"
+#include "src/execution/isolate.h"
+#include "src/handles/handles.h"
+#include "src/objects/prototype.h"
+
+#include "include/v8.h"
+
+namespace v8 {
+namespace internal {
+
+class JSReceiver;
+
+class DebugPropertyIterator final : public debug::PropertyIterator {
+ public:
+ DebugPropertyIterator(Isolate* isolate, Handle<JSReceiver> receiver);
+ ~DebugPropertyIterator() override = default;
+
+ bool Done() const override;
+ void Advance() override;
+
+ v8::Local<v8::Name> name() const override;
+ bool is_native_accessor() override;
+ bool has_native_getter() override;
+ bool has_native_setter() override;
+ v8::Maybe<v8::PropertyAttribute> attributes() override;
+ v8::Maybe<v8::debug::PropertyDescriptor> descriptor() override;
+
+ bool is_own() override;
+ bool is_array_index() override;
+
+ private:
+ void FillKeysForCurrentPrototypeAndStage();
+ bool should_move_to_next_stage() const;
+ void CalculateNativeAccessorFlags();
+ Handle<Name> raw_name() const;
+
+ Isolate* isolate_;
+ PrototypeIterator prototype_iterator_;
+ enum Stage { kExoticIndices = 0, kEnumerableStrings = 1, kAllProperties = 2 };
+ Stage stage_ = kExoticIndices;
+
+ size_t current_key_index_ = 0;
+ Handle<FixedArray> keys_;
+ size_t exotic_length_ = 0;
+
+ bool calculated_native_accessor_flags_ = false;
+ int native_accessor_flags_ = 0;
+ bool is_own_ = true;
+
+ DISALLOW_COPY_AND_ASSIGN(DebugPropertyIterator);
+};
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_PROPERTY_ITERATOR_H_
diff --git a/src/debug/debug-scope-iterator.cc b/src/debug/debug-scope-iterator.cc
new file mode 100644
index 0000000..ab3191d
--- /dev/null
+++ b/src/debug/debug-scope-iterator.cc
@@ -0,0 +1,220 @@
+// Copyright 2017 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-scope-iterator.h"
+
+#include "src/api/api-inl.h"
+#include "src/debug/debug.h"
+#include "src/debug/liveedit.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/isolate.h"
+#include "src/objects/js-generator-inl.h"
+#include "src/wasm/wasm-debug.h"
+#include "src/wasm/wasm-objects-inl.h"
+
+namespace v8 {
+
+std::unique_ptr<debug::ScopeIterator> debug::ScopeIterator::CreateForFunction(
+ v8::Isolate* v8_isolate, v8::Local<v8::Function> v8_func) {
+ internal::Handle<internal::JSReceiver> receiver =
+ internal::Handle<internal::JSReceiver>::cast(Utils::OpenHandle(*v8_func));
+
+ // Besides JSFunction and JSBoundFunction, {v8_func} could be an
+ // ObjectTemplate with a CallAsFunctionHandler. We only handle plain
+ // JSFunctions.
+ if (!receiver->IsJSFunction()) return nullptr;
+
+ internal::Handle<internal::JSFunction> function =
+ internal::Handle<internal::JSFunction>::cast(receiver);
+
+ // Blink has function objects with callable map, JS_SPECIAL_API_OBJECT_TYPE
+ // but without context on heap.
+ if (!function->has_context()) return nullptr;
+ return std::unique_ptr<debug::ScopeIterator>(new internal::DebugScopeIterator(
+ reinterpret_cast<internal::Isolate*>(v8_isolate), function));
+}
+
+std::unique_ptr<debug::ScopeIterator>
+debug::ScopeIterator::CreateForGeneratorObject(
+ v8::Isolate* v8_isolate, v8::Local<v8::Object> v8_generator) {
+ internal::Handle<internal::Object> generator =
+ Utils::OpenHandle(*v8_generator);
+ DCHECK(generator->IsJSGeneratorObject());
+ return std::unique_ptr<debug::ScopeIterator>(new internal::DebugScopeIterator(
+ reinterpret_cast<internal::Isolate*>(v8_isolate),
+ internal::Handle<internal::JSGeneratorObject>::cast(generator)));
+}
+
+namespace internal {
+
+DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
+ FrameInspector* frame_inspector)
+ : iterator_(
+ isolate, frame_inspector,
+ ::v8::internal::ScopeIterator::ReparseStrategy::kFunctionLiteral) {
+ if (!Done() && ShouldIgnore()) Advance();
+}
+
+DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
+ Handle<JSFunction> function)
+ : iterator_(isolate, function) {
+ if (!Done() && ShouldIgnore()) Advance();
+}
+
+DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
+ Handle<JSGeneratorObject> generator)
+ : iterator_(isolate, generator) {
+ if (!Done() && ShouldIgnore()) Advance();
+}
+
+bool DebugScopeIterator::Done() { return iterator_.Done(); }
+
+void DebugScopeIterator::Advance() {
+ DCHECK(!Done());
+ iterator_.Next();
+ while (!Done() && ShouldIgnore()) {
+ iterator_.Next();
+ }
+}
+
+bool DebugScopeIterator::ShouldIgnore() {
+ if (GetType() == debug::ScopeIterator::ScopeTypeLocal) return false;
+ return !iterator_.DeclaresLocals(i::ScopeIterator::Mode::ALL);
+}
+
+v8::debug::ScopeIterator::ScopeType DebugScopeIterator::GetType() {
+ DCHECK(!Done());
+ return static_cast<v8::debug::ScopeIterator::ScopeType>(iterator_.Type());
+}
+
+v8::Local<v8::Object> DebugScopeIterator::GetObject() {
+ DCHECK(!Done());
+ Handle<JSObject> value = iterator_.ScopeObject(i::ScopeIterator::Mode::ALL);
+ return Utils::ToLocal(value);
+}
+
+int DebugScopeIterator::GetScriptId() {
+ DCHECK(!Done());
+ return iterator_.GetScript()->id();
+}
+
+v8::Local<v8::Value> DebugScopeIterator::GetFunctionDebugName() {
+ DCHECK(!Done());
+ Handle<Object> name = iterator_.GetFunctionDebugName();
+ return Utils::ToLocal(name);
+}
+
+bool DebugScopeIterator::HasLocationInfo() {
+ return iterator_.HasPositionInfo();
+}
+
+debug::Location DebugScopeIterator::GetStartLocation() {
+ DCHECK(!Done());
+ return ToApiHandle<v8::debug::Script>(iterator_.GetScript())
+ ->GetSourceLocation(iterator_.start_position());
+}
+
+debug::Location DebugScopeIterator::GetEndLocation() {
+ DCHECK(!Done());
+ return ToApiHandle<v8::debug::Script>(iterator_.GetScript())
+ ->GetSourceLocation(iterator_.end_position());
+}
+
+bool DebugScopeIterator::SetVariableValue(v8::Local<v8::String> name,
+ v8::Local<v8::Value> value) {
+ DCHECK(!Done());
+ return iterator_.SetVariableValue(Utils::OpenHandle(*name),
+ Utils::OpenHandle(*value));
+}
+
+DebugWasmScopeIterator::DebugWasmScopeIterator(Isolate* isolate,
+ WasmFrame* frame)
+ : isolate_(isolate),
+ frame_(frame),
+ type_(debug::ScopeIterator::ScopeTypeModule) {}
+
+bool DebugWasmScopeIterator::Done() {
+ return type_ == debug::ScopeIterator::ScopeTypeWith;
+}
+
+void DebugWasmScopeIterator::Advance() {
+ DCHECK(!Done());
+ switch (type_) {
+ case ScopeTypeModule:
+ // Skip local scope and expression stack scope if the frame is not
+ // inspectable.
+ type_ = frame_->is_inspectable() ? debug::ScopeIterator::ScopeTypeLocal
+ : debug::ScopeIterator::ScopeTypeWith;
+ break;
+ case ScopeTypeLocal:
+ type_ = debug::ScopeIterator::ScopeTypeWasmExpressionStack;
+ break;
+ case ScopeTypeWasmExpressionStack:
+ // We use ScopeTypeWith type as marker for done.
+ type_ = debug::ScopeIterator::ScopeTypeWith;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+v8::debug::ScopeIterator::ScopeType DebugWasmScopeIterator::GetType() {
+ DCHECK(!Done());
+ return type_;
+}
+
+v8::Local<v8::Object> DebugWasmScopeIterator::GetObject() {
+ DCHECK(!Done());
+ switch (type_) {
+ case debug::ScopeIterator::ScopeTypeModule: {
+ Handle<WasmInstanceObject> instance =
+ FrameSummary::GetTop(frame_).AsWasm().wasm_instance();
+ return Utils::ToLocal(wasm::GetModuleScopeObject(instance));
+ }
+ case debug::ScopeIterator::ScopeTypeLocal: {
+ DCHECK(frame_->is_wasm());
+ wasm::DebugInfo* debug_info = frame_->native_module()->GetDebugInfo();
+ return Utils::ToLocal(debug_info->GetLocalScopeObject(
+ isolate_, frame_->pc(), frame_->fp(), frame_->callee_fp()));
+ }
+ case debug::ScopeIterator::ScopeTypeWasmExpressionStack: {
+ DCHECK(frame_->is_wasm());
+ wasm::DebugInfo* debug_info = frame_->native_module()->GetDebugInfo();
+ return Utils::ToLocal(debug_info->GetStackScopeObject(
+ isolate_, frame_->pc(), frame_->fp(), frame_->callee_fp()));
+ }
+ default:
+ return {};
+ }
+}
+
+int DebugWasmScopeIterator::GetScriptId() {
+ DCHECK(!Done());
+ return -1;
+}
+
+v8::Local<v8::Value> DebugWasmScopeIterator::GetFunctionDebugName() {
+ DCHECK(!Done());
+ return Utils::ToLocal(isolate_->factory()->empty_string());
+}
+
+bool DebugWasmScopeIterator::HasLocationInfo() { return false; }
+
+debug::Location DebugWasmScopeIterator::GetStartLocation() {
+ DCHECK(!Done());
+ return debug::Location();
+}
+
+debug::Location DebugWasmScopeIterator::GetEndLocation() {
+ DCHECK(!Done());
+ return debug::Location();
+}
+
+bool DebugWasmScopeIterator::SetVariableValue(v8::Local<v8::String> name,
+ v8::Local<v8::Value> value) {
+ DCHECK(!Done());
+ return false;
+}
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug-scope-iterator.h b/src/debug/debug-scope-iterator.h
new file mode 100644
index 0000000..a2b5ebc
--- /dev/null
+++ b/src/debug/debug-scope-iterator.h
@@ -0,0 +1,64 @@
+// Copyright 2017 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.
+
+#ifndef V8_DEBUG_DEBUG_SCOPE_ITERATOR_H_
+#define V8_DEBUG_DEBUG_SCOPE_ITERATOR_H_
+
+#include "src/debug/debug-frames.h"
+#include "src/debug/debug-interface.h"
+#include "src/debug/debug-scopes.h"
+
+namespace v8 {
+namespace internal {
+
+class DebugScopeIterator final : public debug::ScopeIterator {
+ public:
+ DebugScopeIterator(Isolate* isolate, FrameInspector* frame_inspector);
+ DebugScopeIterator(Isolate* isolate, Handle<JSFunction> function);
+ DebugScopeIterator(Isolate* isolate, Handle<JSGeneratorObject> generator);
+
+ bool Done() override;
+ void Advance() override;
+ ScopeType GetType() override;
+ v8::Local<v8::Object> GetObject() override;
+ v8::Local<v8::Value> GetFunctionDebugName() override;
+ int GetScriptId() override;
+ bool HasLocationInfo() override;
+ debug::Location GetStartLocation() override;
+ debug::Location GetEndLocation() override;
+
+ bool SetVariableValue(v8::Local<v8::String> name,
+ v8::Local<v8::Value> value) override;
+
+ private:
+ bool ShouldIgnore();
+
+ v8::internal::ScopeIterator iterator_;
+};
+
+class DebugWasmScopeIterator final : public debug::ScopeIterator {
+ public:
+ DebugWasmScopeIterator(Isolate* isolate, WasmFrame* frame);
+
+ bool Done() override;
+ void Advance() override;
+ ScopeType GetType() override;
+ v8::Local<v8::Object> GetObject() override;
+ v8::Local<v8::Value> GetFunctionDebugName() override;
+ int GetScriptId() override;
+ bool HasLocationInfo() override;
+ debug::Location GetStartLocation() override;
+ debug::Location GetEndLocation() override;
+
+ bool SetVariableValue(v8::Local<v8::String> name,
+ v8::Local<v8::Value> value) override;
+ private:
+ Isolate* isolate_;
+ WasmFrame* frame_;
+ ScopeType type_;
+};
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_SCOPE_ITERATOR_H_
diff --git a/src/debug/debug-scopes.cc b/src/debug/debug-scopes.cc
index cf957bc..3142240 100644
--- a/src/debug/debug-scopes.cc
+++ b/src/debug/debug-scopes.cc
@@ -8,60 +8,224 @@
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
+#include "src/common/globals.h"
#include "src/debug/debug.h"
-#include "src/frames-inl.h"
-#include "src/globals.h"
-#include "src/isolate-inl.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/isolate-inl.h"
+#include "src/objects/js-generator-inl.h"
+#include "src/objects/source-text-module.h"
+#include "src/objects/string-set-inl.h"
#include "src/parsing/parse-info.h"
#include "src/parsing/parsing.h"
#include "src/parsing/rewriter.h"
+#include "src/utils/ostreams.h"
namespace v8 {
namespace internal {
ScopeIterator::ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector,
- ScopeIterator::Option option)
+ ReparseStrategy strategy)
: isolate_(isolate),
frame_inspector_(frame_inspector),
- nested_scope_chain_(4),
- seen_script_scope_(false) {
+ function_(frame_inspector_->GetFunction()),
+ script_(frame_inspector_->GetScript()) {
if (!frame_inspector->GetContext()->IsContext()) {
// Optimized frame, context or function cannot be materialized. Give up.
return;
}
-
context_ = Handle<Context>::cast(frame_inspector->GetContext());
// We should not instantiate a ScopeIterator for wasm frames.
- DCHECK(frame_inspector->GetScript()->type() != Script::TYPE_WASM);
+ DCHECK_NE(Script::TYPE_WASM, frame_inspector->GetScript()->type());
- // Catch the case when the debugger stops in an internal function.
- Handle<JSFunction> function = GetFunction();
- Handle<SharedFunctionInfo> shared_info(function->shared());
- Handle<ScopeInfo> scope_info(shared_info->scope_info());
- if (shared_info->script()->IsUndefined(isolate)) {
- while (context_->closure() == *function) {
- context_ = Handle<Context>(context_->previous(), isolate_);
+ TryParseAndRetrieveScopes(strategy);
+}
+
+ScopeIterator::~ScopeIterator() = default;
+
+Handle<Object> ScopeIterator::GetFunctionDebugName() const {
+ if (!function_.is_null()) return JSFunction::GetDebugName(function_);
+
+ if (!context_->IsNativeContext()) {
+ DisallowHeapAllocation no_gc;
+ ScopeInfo closure_info = context_->closure_context().scope_info();
+ Handle<String> debug_name(closure_info.FunctionDebugName(), isolate_);
+ if (debug_name->length() > 0) return debug_name;
+ }
+ return isolate_->factory()->undefined_value();
+}
+
+ScopeIterator::ScopeIterator(Isolate* isolate, Handle<JSFunction> function)
+ : isolate_(isolate), context_(function->context(), isolate) {
+ if (!function->shared().IsSubjectToDebugging()) {
+ context_ = Handle<Context>();
+ return;
+ }
+ script_ = handle(Script::cast(function->shared().script()), isolate);
+ UnwrapEvaluationContext();
+}
+
+ScopeIterator::ScopeIterator(Isolate* isolate,
+ Handle<JSGeneratorObject> generator)
+ : isolate_(isolate),
+ generator_(generator),
+ function_(generator->function(), isolate),
+ context_(generator->context(), isolate),
+ script_(Script::cast(function_->shared().script()), isolate) {
+ CHECK(function_->shared().IsSubjectToDebugging());
+ TryParseAndRetrieveScopes(ReparseStrategy::kFunctionLiteral);
+}
+
+void ScopeIterator::Restart() {
+ DCHECK_NOT_NULL(frame_inspector_);
+ function_ = frame_inspector_->GetFunction();
+ context_ = Handle<Context>::cast(frame_inspector_->GetContext());
+ current_scope_ = start_scope_;
+ DCHECK_NOT_NULL(current_scope_);
+ UnwrapEvaluationContext();
+}
+
+namespace {
+
+// Takes the scope of a parsed script, a function and a break location
+// inside the function. The result is the innermost lexical scope around
+// the break point, which serves as the starting point of the ScopeIterator.
+// And the scope of the function that was passed in (called closure scope).
+//
+// The start scope is guaranteed to be either the closure scope itself,
+// or a child of the closure scope.
+class ScopeChainRetriever {
+ public:
+ ScopeChainRetriever(DeclarationScope* scope, Handle<JSFunction> function,
+ int position)
+ : scope_(scope),
+ break_scope_start_(function->shared().StartPosition()),
+ break_scope_end_(function->shared().EndPosition()),
+ is_default_constructor_(
+ IsDefaultConstructor(function->shared().kind())),
+ position_(position) {
+ DCHECK_NOT_NULL(scope);
+ RetrieveScopes();
+ }
+
+ DeclarationScope* ClosureScope() { return closure_scope_; }
+ Scope* StartScope() { return start_scope_; }
+
+ private:
+ DeclarationScope* scope_;
+ const int break_scope_start_;
+ const int break_scope_end_;
+ const bool is_default_constructor_;
+ const int position_;
+
+ DeclarationScope* closure_scope_ = nullptr;
+ Scope* start_scope_ = nullptr;
+
+ void RetrieveScopes() {
+ if (is_default_constructor_) {
+ // Even though the DefaultBaseConstructor is a child of a Class scope, the
+ // source positions are *not* nested. This means the actual scope for the
+ // DefaultBaseConstructor needs to be found by doing a DFS.
+ RetrieveScopeChainDefaultConstructor(scope_);
+ } else {
+ RetrieveScopeChain();
}
+ DCHECK_NOT_NULL(closure_scope_);
+ DCHECK_NOT_NULL(start_scope_);
+ }
+
+ bool RetrieveScopeChainDefaultConstructor(Scope* scope) {
+ const int beg_pos = scope->start_position();
+ const int end_pos = scope->end_position();
+ if (beg_pos == position_ && end_pos == position_) {
+ DCHECK(scope->is_function_scope());
+ DCHECK(
+ IsDefaultConstructor(scope->AsDeclarationScope()->function_kind()));
+ start_scope_ = scope;
+ closure_scope_ = scope->AsDeclarationScope();
+ return true;
+ }
+
+ for (Scope* inner_scope = scope->inner_scope(); inner_scope != nullptr;
+ inner_scope = inner_scope->sibling()) {
+ if (RetrieveScopeChainDefaultConstructor(inner_scope)) return true;
+ }
+ return false;
+ }
+
+ void RetrieveScopeChain() {
+ Scope* parent = nullptr;
+ Scope* current = scope_;
+ SetClosureScopeIfFound(current);
+
+ while (parent != current) {
+ parent = current;
+ for (Scope* inner_scope = current->inner_scope(); inner_scope != nullptr;
+ inner_scope = inner_scope->sibling()) {
+ if (SetClosureScopeIfFound(inner_scope) ||
+ ContainsPosition(inner_scope)) {
+ current = inner_scope;
+ break;
+ }
+ }
+ }
+ start_scope_ = current;
+ }
+
+ bool SetClosureScopeIfFound(Scope* scope) {
+ const int start = scope->start_position();
+ const int end = scope->end_position();
+ if (start == break_scope_start_ && end == break_scope_end_) {
+ closure_scope_ = scope->AsDeclarationScope();
+ return true;
+ }
+ return false;
+ }
+
+ bool ContainsPosition(Scope* scope) {
+ const int start = scope->start_position();
+ const int end = scope->end_position();
+ // In case the closure_scope_ hasn't been found yet, we are less strict
+ // about recursing downwards. This might be the case for nested arrow
+ // functions that have the same end position.
+ const bool position_fits_end =
+ closure_scope_ ? position_ < end : position_ <= end;
+ return start < position_ && position_fits_end;
+ }
+};
+
+} // namespace
+
+void ScopeIterator::TryParseAndRetrieveScopes(ReparseStrategy strategy) {
+ // Catch the case when the debugger stops in an internal function.
+ Handle<SharedFunctionInfo> shared_info(function_->shared(), isolate_);
+ Handle<ScopeInfo> scope_info(shared_info->scope_info(), isolate_);
+ if (shared_info->script().IsUndefined(isolate_)) {
+ current_scope_ = closure_scope_ = nullptr;
+ context_ = handle(function_->context(), isolate_);
+ function_ = Handle<JSFunction>();
return;
}
- // Currently it takes too much time to find nested scopes due to script
- // parsing. Sometimes we want to run the ScopeIterator as fast as possible
- // (for example, while collecting async call stacks on every
- // addEventListener call), even if we drop some nested scopes.
- // Later we may optimize getting the nested scopes (cache the result?)
- // and include nested scopes into the "fast" iteration case as well.
- bool ignore_nested_scopes = (option == IGNORE_NESTED_SCOPES);
- bool collect_non_locals = (option == COLLECT_NON_LOCALS);
- if (!ignore_nested_scopes && shared_info->HasDebugInfo()) {
+ // Class fields initializer functions don't have any scope
+ // information. We short circuit the parsing of the class literal
+ // and return an empty context here.
+ if (IsClassMembersInitializerFunction(shared_info->kind())) {
+ current_scope_ = closure_scope_ = nullptr;
+ context_ = Handle<Context>();
+ function_ = Handle<JSFunction>();
+ return;
+ }
+
+ bool ignore_nested_scopes = false;
+ if (shared_info->HasBreakInfo() && frame_inspector_ != nullptr) {
// The source position at return is always the end of the function,
// which is not consistent with the current scope chain. Therefore all
// nested with, catch and block contexts are skipped, and we can only
// inspect the function scope.
// This can only happen if we set a break point inside right before the
// return, which requires a debug info to be available.
- Handle<DebugInfo> debug_info(shared_info->GetDebugInfo());
+ Handle<DebugInfo> debug_info(shared_info->GetDebugInfo(), isolate_);
// Find the break point where execution has stopped.
BreakLocation location = BreakLocation::FromFrame(debug_info, GetFrame());
@@ -69,219 +233,289 @@
ignore_nested_scopes = location.IsReturn();
}
- if (ignore_nested_scopes) {
- if (scope_info->HasContext()) {
- context_ = Handle<Context>(context_->declaration_context(), isolate_);
- } else {
- while (context_->closure() == *function) {
- context_ = Handle<Context>(context_->previous(), isolate_);
- }
+ // Reparse the code and analyze the scopes.
+ // Depending on the choosen strategy, the whole script or just
+ // the closure is re-parsed for function scopes.
+ Handle<Script> script(Script::cast(shared_info->script()), isolate_);
+
+ // Pick between flags for a single function compilation, or an eager
+ // compilation of the whole script.
+ UnoptimizedCompileFlags flags =
+ (scope_info->scope_type() == FUNCTION_SCOPE &&
+ strategy == ReparseStrategy::kFunctionLiteral)
+ ? UnoptimizedCompileFlags::ForFunctionCompile(isolate_, *shared_info)
+ : UnoptimizedCompileFlags::ForScriptCompile(isolate_, *script)
+ .set_is_eager(true);
+
+ MaybeHandle<ScopeInfo> maybe_outer_scope;
+ if (scope_info->scope_type() == EVAL_SCOPE || script->is_wrapped()) {
+ flags.set_is_eval(true);
+ if (!context_->IsNativeContext()) {
+ maybe_outer_scope = handle(context_->scope_info(), isolate_);
}
- if (scope_info->scope_type() == FUNCTION_SCOPE) {
- nested_scope_chain_.Add(ExtendedScopeInfo(scope_info,
- shared_info->start_position(),
- shared_info->end_position()));
- }
- if (!collect_non_locals) return;
+ // Language mode may be inherited from the eval caller.
+ // Retrieve it from shared function info.
+ flags.set_outer_language_mode(shared_info->language_mode());
+ } else if (scope_info->scope_type() == MODULE_SCOPE) {
+ DCHECK(flags.is_module());
+ } else {
+ DCHECK(scope_info->scope_type() == SCRIPT_SCOPE ||
+ scope_info->scope_type() == FUNCTION_SCOPE);
}
- // Reparse the code and analyze the scopes.
- // Check whether we are in global, eval or function code.
- std::unique_ptr<ParseInfo> info;
- if (scope_info->scope_type() != FUNCTION_SCOPE) {
- // Global or eval code.
- Handle<Script> script(Script::cast(shared_info->script()));
- info.reset(new ParseInfo(script));
- if (scope_info->scope_type() == EVAL_SCOPE) {
- info->set_eval();
- if (!function->context()->IsNativeContext()) {
- info->set_outer_scope_info(handle(function->context()->scope_info()));
+ UnoptimizedCompileState compile_state(isolate_);
+
+ info_ = std::make_unique<ParseInfo>(isolate_, flags, &compile_state);
+
+ const bool parse_result =
+ flags.is_toplevel()
+ ? parsing::ParseProgram(info_.get(), script, maybe_outer_scope,
+ isolate_, parsing::ReportStatisticsMode::kNo)
+ : parsing::ParseFunction(info_.get(), shared_info, isolate_,
+ parsing::ReportStatisticsMode::kNo);
+
+ if (parse_result) {
+ DeclarationScope* literal_scope = info_->literal()->scope();
+
+ ScopeChainRetriever scope_chain_retriever(literal_scope, function_,
+ GetSourcePosition());
+ start_scope_ = scope_chain_retriever.StartScope();
+ current_scope_ = start_scope_;
+
+ // In case of a FUNCTION_SCOPE, the ScopeIterator expects
+ // {closure_scope_} to be set to the scope of the function.
+ closure_scope_ = scope_info->scope_type() == FUNCTION_SCOPE
+ ? scope_chain_retriever.ClosureScope()
+ : literal_scope;
+
+ if (ignore_nested_scopes) {
+ current_scope_ = closure_scope_;
+ start_scope_ = current_scope_;
+ // ignore_nested_scopes is only used for the return-position breakpoint,
+ // so we can safely assume that the closure context for the current
+ // function exists if it needs one.
+ if (closure_scope_->NeedsContext()) {
+ context_ = handle(context_->closure_context(), isolate_);
}
- // Language mode may be inherited from the eval caller.
- // Retrieve it from shared function info.
- info->set_language_mode(shared_info->language_mode());
- } else if (scope_info->scope_type() == MODULE_SCOPE) {
- info->set_module();
- } else {
- DCHECK(scope_info->scope_type() == SCRIPT_SCOPE);
}
- } else {
- // Inner function.
- info.reset(new ParseInfo(shared_info));
- }
- if (parsing::ParseAny(info.get()) && Rewriter::Rewrite(info.get())) {
- DeclarationScope* scope = info->literal()->scope();
- if (!ignore_nested_scopes || collect_non_locals) {
- CollectNonLocals(info.get(), scope);
- }
- if (!ignore_nested_scopes) {
- DeclarationScope::Analyze(info.get(), AnalyzeMode::kDebugger);
- RetrieveScopeChain(scope);
- }
+
+ UnwrapEvaluationContext();
} else {
// A failed reparse indicates that the preparser has diverged from the
- // parser or that the preparse data given to the initial parse has been
- // faulty. We fail in debug mode but in release mode we only provide the
- // information we get from the context chain but nothing about
- // completely stack allocated scopes or stack allocated locals.
- // Or it could be due to stack overflow.
+ // parser, that the preparse data given to the initial parse was faulty, or
+ // a stack overflow.
+ // TODO(leszeks): This error is pretty unexpected, so we could report the
+ // error in debug mode. Better to not fail in release though, in case it's
+ // just a stack overflow.
+
// Silently fail by presenting an empty context chain.
- CHECK(isolate_->has_pending_exception());
- isolate_->clear_pending_exception();
context_ = Handle<Context>();
}
- UnwrapEvaluationContext();
-}
-
-ScopeIterator::ScopeIterator(Isolate* isolate, Handle<JSFunction> function)
- : isolate_(isolate),
- frame_inspector_(NULL),
- context_(function->context()),
- seen_script_scope_(false) {
- if (!function->shared()->IsSubjectToDebugging()) context_ = Handle<Context>();
- UnwrapEvaluationContext();
-}
-
-ScopeIterator::ScopeIterator(Isolate* isolate,
- Handle<JSGeneratorObject> generator)
- : isolate_(isolate),
- frame_inspector_(NULL),
- context_(generator->context()),
- seen_script_scope_(false) {
- if (!generator->function()->shared()->IsSubjectToDebugging()) {
- context_ = Handle<Context>();
- }
- UnwrapEvaluationContext();
}
void ScopeIterator::UnwrapEvaluationContext() {
- while (true) {
- if (context_.is_null()) return;
- if (!context_->IsDebugEvaluateContext()) return;
- Handle<Object> wrapped(context_->get(Context::WRAPPED_CONTEXT_INDEX),
- isolate_);
- if (wrapped->IsContext()) {
- context_ = Handle<Context>::cast(wrapped);
+ if (!context_->IsDebugEvaluateContext()) return;
+ Context current = *context_;
+ do {
+ Object wrapped = current.get(Context::WRAPPED_CONTEXT_INDEX);
+ if (wrapped.IsContext()) {
+ current = Context::cast(wrapped);
} else {
- context_ = Handle<Context>(context_->previous(), isolate_);
+ DCHECK(!current.previous().is_null());
+ current = current.previous();
}
- }
+ } while (current.IsDebugEvaluateContext());
+ context_ = handle(current, isolate_);
}
-
-MUST_USE_RESULT MaybeHandle<JSObject> ScopeIterator::MaterializeScopeDetails() {
+Handle<JSObject> ScopeIterator::MaterializeScopeDetails() {
// Calculate the size of the result.
Handle<FixedArray> details =
isolate_->factory()->NewFixedArray(kScopeDetailsSize);
// Fill in scope details.
details->set(kScopeDetailsTypeIndex, Smi::FromInt(Type()));
- Handle<JSObject> scope_object;
- ASSIGN_RETURN_ON_EXCEPTION(isolate_, scope_object, ScopeObject(), JSObject);
+ Handle<JSObject> scope_object = ScopeObject(Mode::ALL);
details->set(kScopeDetailsObjectIndex, *scope_object);
- Handle<JSFunction> js_function = HasContext()
- ? handle(CurrentContext()->closure())
- : Handle<JSFunction>::null();
if (Type() == ScopeTypeGlobal || Type() == ScopeTypeScript) {
return isolate_->factory()->NewJSArrayWithElements(details);
- }
-
- int start_position = 0;
- int end_position = 0;
- if (!nested_scope_chain_.is_empty()) {
- js_function = GetFunction();
- start_position = nested_scope_chain_.last().start_position;
- end_position = nested_scope_chain_.last().end_position;
- } else if (!js_function.is_null()) {
- start_position = js_function->shared()->start_position();
- end_position = js_function->shared()->end_position();
- }
-
- if (!js_function.is_null()) {
- Handle<String> closure_name = JSFunction::GetDebugName(js_function);
- if (!closure_name.is_null() && closure_name->length() != 0) {
- details->set(kScopeDetailsNameIndex, *closure_name);
+ } else if (HasContext()) {
+ Handle<Object> closure_name = GetFunctionDebugName();
+ details->set(kScopeDetailsNameIndex, *closure_name);
+ details->set(kScopeDetailsStartPositionIndex,
+ Smi::FromInt(start_position()));
+ details->set(kScopeDetailsEndPositionIndex, Smi::FromInt(end_position()));
+ if (InInnerScope()) {
+ details->set(kScopeDetailsFunctionIndex, *function_);
}
- details->set(kScopeDetailsStartPositionIndex, Smi::FromInt(start_position));
- details->set(kScopeDetailsEndPositionIndex, Smi::FromInt(end_position));
- details->set(kScopeDetailsFunctionIndex, *js_function);
}
return isolate_->factory()->NewJSArrayWithElements(details);
}
+bool ScopeIterator::HasPositionInfo() {
+ return InInnerScope() || !context_->IsNativeContext();
+}
+
+int ScopeIterator::start_position() {
+ if (InInnerScope()) return current_scope_->start_position();
+ if (context_->IsNativeContext()) return 0;
+ return context_->closure_context().scope_info().StartPosition();
+}
+
+int ScopeIterator::end_position() {
+ if (InInnerScope()) return current_scope_->end_position();
+ if (context_->IsNativeContext()) return 0;
+ return context_->closure_context().scope_info().EndPosition();
+}
+
+bool ScopeIterator::DeclaresLocals(Mode mode) const {
+ ScopeType type = Type();
+
+ if (type == ScopeTypeWith) return mode == Mode::ALL;
+ if (type == ScopeTypeGlobal) return mode == Mode::ALL;
+
+ bool declares_local = false;
+ auto visitor = [&](Handle<String> name, Handle<Object> value,
+ ScopeType scope_type) {
+ declares_local = true;
+ return true;
+ };
+ VisitScope(visitor, mode);
+ return declares_local;
+}
+
+bool ScopeIterator::HasContext() const {
+ return !InInnerScope() || NeedsAndHasContext();
+}
+
+bool ScopeIterator::NeedsAndHasContext() const {
+ if (!current_scope_->NeedsContext()) return false;
+ // Generally, if a scope needs a context, then we can assume that it has a
+ // context. However, the stack check during function entry happens before the
+ // function has a chance to create and push its own context, so we must check
+ // for the case where the function is executing in its parent context. This
+ // case is only possible in function scopes; top-level code (modules and
+ // non-module scripts) begin execution in the context they need and don't have
+ // a separate step to push the correct context.
+ return !(current_scope_ == closure_scope_ &&
+ current_scope_->is_function_scope() && !function_.is_null() &&
+ function_->context() == *context_);
+}
+
+void ScopeIterator::AdvanceOneScope() {
+ if (NeedsAndHasContext()) {
+ DCHECK(!context_->previous().is_null());
+ context_ = handle(context_->previous(), isolate_);
+ }
+ DCHECK(current_scope_->outer_scope() != nullptr);
+ current_scope_ = current_scope_->outer_scope();
+}
+
+void ScopeIterator::AdvanceToNonHiddenScope() {
+ do {
+ AdvanceOneScope();
+ } while (current_scope_->is_hidden());
+}
+
+void ScopeIterator::AdvanceContext() {
+ DCHECK(!context_->IsNativeContext());
+ context_ = handle(context_->previous(), isolate_);
+
+ // While advancing one context, we need to advance at least one
+ // scope, but until we hit the next scope that actually requires
+ // a context. All the locals collected along the way build the
+ // blocklist for debug-evaluate for this context.
+ locals_ = StringSet::New(isolate_);
+ do {
+ if (!current_scope_ || !current_scope_->outer_scope()) break;
+
+ current_scope_ = current_scope_->outer_scope();
+ CollectLocalsFromCurrentScope();
+ } while (!NeedsAndHasContext());
+}
void ScopeIterator::Next() {
DCHECK(!Done());
+
ScopeType scope_type = Type();
+
if (scope_type == ScopeTypeGlobal) {
// The global scope is always the last in the chain.
DCHECK(context_->IsNativeContext());
context_ = Handle<Context>();
- } else if (scope_type == ScopeTypeScript) {
+ DCHECK(Done());
+ return;
+ }
+
+ bool leaving_closure = current_scope_ == closure_scope_;
+
+ if (scope_type == ScopeTypeScript) {
+ DCHECK_IMPLIES(InInnerScope() && !leaving_closure,
+ current_scope_->is_script_scope());
seen_script_scope_ = true;
if (context_->IsScriptContext()) {
- context_ = Handle<Context>(context_->previous(), isolate_);
+ context_ = handle(context_->previous(), isolate_);
}
- if (!nested_scope_chain_.is_empty()) {
- DCHECK_EQ(nested_scope_chain_.last().scope_info->scope_type(),
- SCRIPT_SCOPE);
- nested_scope_chain_.RemoveLast();
- DCHECK(nested_scope_chain_.is_empty());
- }
- CHECK(context_->IsNativeContext());
- } else if (nested_scope_chain_.is_empty()) {
- context_ = Handle<Context>(context_->previous(), isolate_);
+ } else if (!InInnerScope()) {
+ AdvanceContext();
} else {
- do {
- if (nested_scope_chain_.last().scope_info->HasContext()) {
- DCHECK(context_->previous() != NULL);
- context_ = Handle<Context>(context_->previous(), isolate_);
- }
- nested_scope_chain_.RemoveLast();
- if (nested_scope_chain_.is_empty()) break;
- // Repeat to skip hidden scopes.
- } while (nested_scope_chain_.last().is_hidden());
+ DCHECK_NOT_NULL(current_scope_);
+ AdvanceToNonHiddenScope();
+
+ if (leaving_closure) {
+ DCHECK(current_scope_ != closure_scope_);
+ // Edge case when we just go past {closure_scope_}. This case
+ // already needs to start collecting locals for the blocklist.
+ locals_ = StringSet::New(isolate_);
+ CollectLocalsFromCurrentScope();
+ }
}
+
+ if (leaving_closure) function_ = Handle<JSFunction>();
+
UnwrapEvaluationContext();
}
-
// Return the type of the current scope.
-ScopeIterator::ScopeType ScopeIterator::Type() {
+ScopeIterator::ScopeType ScopeIterator::Type() const {
DCHECK(!Done());
- if (!nested_scope_chain_.is_empty()) {
- Handle<ScopeInfo> scope_info = nested_scope_chain_.last().scope_info;
- switch (scope_info->scope_type()) {
+ if (InInnerScope()) {
+ switch (current_scope_->scope_type()) {
case FUNCTION_SCOPE:
- DCHECK(context_->IsFunctionContext() || !scope_info->HasContext());
+ DCHECK_IMPLIES(NeedsAndHasContext(),
+ context_->IsFunctionContext() ||
+ context_->IsDebugEvaluateContext());
return ScopeTypeLocal;
case MODULE_SCOPE:
- DCHECK(context_->IsModuleContext());
+ DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsModuleContext());
return ScopeTypeModule;
case SCRIPT_SCOPE:
- DCHECK(context_->IsScriptContext() || context_->IsNativeContext());
+ DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsScriptContext() ||
+ context_->IsNativeContext());
return ScopeTypeScript;
case WITH_SCOPE:
- DCHECK(context_->IsWithContext() || context_->IsDebugEvaluateContext());
+ DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsWithContext());
return ScopeTypeWith;
case CATCH_SCOPE:
DCHECK(context_->IsCatchContext());
return ScopeTypeCatch;
case BLOCK_SCOPE:
- DCHECK(!scope_info->HasContext() || context_->IsBlockContext());
+ case CLASS_SCOPE:
+ DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsBlockContext());
return ScopeTypeBlock;
case EVAL_SCOPE:
- DCHECK(!scope_info->HasContext() || context_->IsEvalContext());
+ DCHECK_IMPLIES(NeedsAndHasContext(), context_->IsEvalContext());
return ScopeTypeEval;
}
UNREACHABLE();
}
if (context_->IsNativeContext()) {
- DCHECK(context_->global_object()->IsJSGlobalObject());
+ DCHECK(context_->global_object().IsJSGlobalObject());
// If we are at the native context and have not yet seen script scope,
// fake it.
return seen_script_scope_ ? ScopeTypeGlobal : ScopeTypeScript;
}
- if (context_->IsFunctionContext() || context_->IsEvalContext()) {
+ if (context_->IsFunctionContext() || context_->IsEvalContext() ||
+ context_->IsDebugEvaluateContext()) {
return ScopeTypeClosure;
}
if (context_->IsCatchContext()) {
@@ -296,126 +530,142 @@
if (context_->IsScriptContext()) {
return ScopeTypeScript;
}
- DCHECK(context_->IsWithContext() || context_->IsDebugEvaluateContext());
+ DCHECK(context_->IsWithContext());
return ScopeTypeWith;
}
-
-MaybeHandle<JSObject> ScopeIterator::ScopeObject() {
+Handle<JSObject> ScopeIterator::ScopeObject(Mode mode) {
DCHECK(!Done());
- switch (Type()) {
- case ScopeIterator::ScopeTypeGlobal:
- return Handle<JSObject>(CurrentContext()->global_proxy());
- case ScopeIterator::ScopeTypeScript:
- return MaterializeScriptScope();
- case ScopeIterator::ScopeTypeLocal:
- // Materialize the content of the local scope into a JSObject.
- DCHECK(nested_scope_chain_.length() == 1);
- return MaterializeLocalScope();
- case ScopeIterator::ScopeTypeWith:
- return WithContextExtension();
- case ScopeIterator::ScopeTypeCatch:
- return MaterializeCatchScope();
- case ScopeIterator::ScopeTypeClosure:
- // Materialize the content of the closure scope into a JSObject.
- return MaterializeClosure();
- case ScopeIterator::ScopeTypeBlock:
- case ScopeIterator::ScopeTypeEval:
- return MaterializeInnerScope();
- case ScopeIterator::ScopeTypeModule:
- return MaterializeModuleScope();
- }
- UNREACHABLE();
- return Handle<JSObject>();
-}
-
-bool ScopeIterator::HasContext() {
ScopeType type = Type();
- if (type == ScopeTypeBlock || type == ScopeTypeLocal ||
- type == ScopeTypeEval) {
- if (!nested_scope_chain_.is_empty()) {
- return nested_scope_chain_.last().scope_info->HasContext();
- }
+ if (type == ScopeTypeGlobal) {
+ DCHECK_EQ(Mode::ALL, mode);
+ return handle(context_->global_proxy(), isolate_);
}
- return true;
+ if (type == ScopeTypeWith) {
+ DCHECK_EQ(Mode::ALL, mode);
+ return WithContextExtension();
+ }
+
+ Handle<JSObject> scope = isolate_->factory()->NewJSObjectWithNullProto();
+ auto visitor = [=](Handle<String> name, Handle<Object> value,
+ ScopeType scope_type) {
+ if (value->IsTheHole(isolate_)) {
+ // Reflect variables under TDZ as undefined in scope object.
+ if (scope_type == ScopeTypeScript &&
+ JSReceiver::HasOwnProperty(scope, name).FromMaybe(true)) {
+ // We also use the hole to represent overridden let-declarations via
+ // REPL mode in a script context. Catch this case.
+ return false;
+ }
+ value = isolate_->factory()->undefined_value();
+ }
+ JSObject::AddProperty(isolate_, scope, name, value, NONE);
+ return false;
+ };
+
+ VisitScope(visitor, mode);
+ return scope;
}
-
-bool ScopeIterator::SetVariableValue(Handle<String> variable_name,
- Handle<Object> new_value) {
- DCHECK(!Done());
+void ScopeIterator::VisitScope(const Visitor& visitor, Mode mode) const {
switch (Type()) {
- case ScopeIterator::ScopeTypeGlobal:
+ case ScopeTypeLocal:
+ case ScopeTypeClosure:
+ case ScopeTypeCatch:
+ case ScopeTypeBlock:
+ case ScopeTypeEval:
+ return VisitLocalScope(visitor, mode, Type());
+ case ScopeTypeModule:
+ if (InInnerScope()) {
+ return VisitLocalScope(visitor, mode, Type());
+ }
+ DCHECK_EQ(Mode::ALL, mode);
+ return VisitModuleScope(visitor);
+ case ScopeTypeScript:
+ DCHECK_EQ(Mode::ALL, mode);
+ return VisitScriptScope(visitor);
+ case ScopeTypeWith:
+ case ScopeTypeGlobal:
+ UNREACHABLE();
+ }
+}
+
+bool ScopeIterator::SetVariableValue(Handle<String> name,
+ Handle<Object> value) {
+ DCHECK(!Done());
+ name = isolate_->factory()->InternalizeString(name);
+ switch (Type()) {
+ case ScopeTypeGlobal:
+ case ScopeTypeWith:
break;
- case ScopeIterator::ScopeTypeLocal:
- return SetLocalVariableValue(variable_name, new_value);
- case ScopeIterator::ScopeTypeWith:
- break;
- case ScopeIterator::ScopeTypeCatch:
- return SetCatchVariableValue(variable_name, new_value);
- case ScopeIterator::ScopeTypeClosure:
- return SetClosureVariableValue(variable_name, new_value);
- case ScopeIterator::ScopeTypeScript:
- return SetScriptVariableValue(variable_name, new_value);
- case ScopeIterator::ScopeTypeBlock:
- case ScopeIterator::ScopeTypeEval:
- return SetInnerScopeVariableValue(variable_name, new_value);
- case ScopeIterator::ScopeTypeModule:
- // TODO(neis): Implement.
- break;
+
+ case ScopeTypeEval:
+ case ScopeTypeBlock:
+ case ScopeTypeCatch:
+ case ScopeTypeModule:
+ if (InInnerScope()) return SetLocalVariableValue(name, value);
+ if (Type() == ScopeTypeModule && SetModuleVariableValue(name, value)) {
+ return true;
+ }
+ return SetContextVariableValue(name, value);
+
+ case ScopeTypeLocal:
+ case ScopeTypeClosure:
+ if (InInnerScope()) {
+ DCHECK_EQ(ScopeTypeLocal, Type());
+ if (SetLocalVariableValue(name, value)) return true;
+ // There may not be an associated context since we're InInnerScope().
+ if (!NeedsAndHasContext()) return false;
+ } else {
+ DCHECK_EQ(ScopeTypeClosure, Type());
+ if (SetContextVariableValue(name, value)) return true;
+ }
+ // The above functions only set variables statically declared in the
+ // function. There may be eval-introduced variables. Check them in
+ // SetContextExtensionValue.
+ return SetContextExtensionValue(name, value);
+
+ case ScopeTypeScript:
+ return SetScriptVariableValue(name, value);
}
return false;
}
-
-Handle<ScopeInfo> ScopeIterator::CurrentScopeInfo() {
- DCHECK(!Done());
- if (!nested_scope_chain_.is_empty()) {
- return nested_scope_chain_.last().scope_info;
- } else if (context_->IsBlockContext() || context_->IsFunctionContext() ||
- context_->IsEvalContext()) {
- return Handle<ScopeInfo>(context_->scope_info());
- }
- return Handle<ScopeInfo>::null();
+bool ScopeIterator::ClosureScopeHasThisReference() const {
+ return !closure_scope_->has_this_declaration() &&
+ closure_scope_->HasThisReference();
}
-
-Handle<Context> ScopeIterator::CurrentContext() {
- DCHECK(!Done());
- if (Type() == ScopeTypeGlobal || Type() == ScopeTypeScript ||
- nested_scope_chain_.is_empty()) {
- return context_;
- } else if (nested_scope_chain_.last().scope_info->HasContext()) {
- return context_;
- } else {
- return Handle<Context>();
+void ScopeIterator::CollectLocalsFromCurrentScope() {
+ DCHECK(locals_->IsStringSet());
+ for (Variable* var : *current_scope_->locals()) {
+ if (var->location() == VariableLocation::PARAMETER ||
+ var->location() == VariableLocation::LOCAL) {
+ locals_ = StringSet::Add(isolate_, locals_, var->name());
+ }
}
}
-Handle<StringSet> ScopeIterator::GetNonLocals() { return non_locals_; }
-
#ifdef DEBUG
// Debug print of the content of the current scope.
void ScopeIterator::DebugPrint() {
- OFStream os(stdout);
+ StdoutStream os;
DCHECK(!Done());
switch (Type()) {
case ScopeIterator::ScopeTypeGlobal:
os << "Global:\n";
- CurrentContext()->Print(os);
+ context_->Print(os);
break;
case ScopeIterator::ScopeTypeLocal: {
os << "Local:\n";
- GetFunction()->shared()->scope_info()->Print();
- if (!CurrentContext().is_null()) {
- CurrentContext()->Print(os);
- if (CurrentContext()->has_extension()) {
- Handle<HeapObject> extension(CurrentContext()->extension(), isolate_);
- if (extension->IsJSContextExtensionObject()) {
- extension->Print(os);
- }
+ if (NeedsAndHasContext()) {
+ context_->Print(os);
+ if (context_->has_extension()) {
+ Handle<HeapObject> extension(context_->extension(), isolate_);
+ DCHECK(extension->IsJSContextExtensionObject());
+ extension->Print(os);
}
}
break;
@@ -423,33 +673,29 @@
case ScopeIterator::ScopeTypeWith:
os << "With:\n";
- CurrentContext()->extension()->Print(os);
+ context_->extension().Print(os);
break;
case ScopeIterator::ScopeTypeCatch:
os << "Catch:\n";
- CurrentContext()->extension()->Print(os);
- CurrentContext()->get(Context::THROWN_OBJECT_INDEX)->Print(os);
+ context_->extension().Print(os);
+ context_->get(Context::THROWN_OBJECT_INDEX).Print(os);
break;
case ScopeIterator::ScopeTypeClosure:
os << "Closure:\n";
- CurrentContext()->Print(os);
- if (CurrentContext()->has_extension()) {
- Handle<HeapObject> extension(CurrentContext()->extension(), isolate_);
- if (extension->IsJSContextExtensionObject()) {
- extension->Print(os);
- }
+ context_->Print(os);
+ if (context_->has_extension()) {
+ Handle<HeapObject> extension(context_->extension(), isolate_);
+ DCHECK(extension->IsJSContextExtensionObject());
+ extension->Print(os);
}
break;
case ScopeIterator::ScopeTypeScript:
os << "Script:\n";
- CurrentContext()
- ->global_object()
- ->native_context()
- ->script_context_table()
- ->Print(os);
+ context_->global_object().native_context().script_context_table().Print(
+ os);
break;
default:
@@ -459,290 +705,395 @@
}
#endif
-void ScopeIterator::RetrieveScopeChain(DeclarationScope* scope) {
- DCHECK_NOT_NULL(scope);
- int source_position = frame_inspector_->GetSourcePosition();
- GetNestedScopeChain(isolate_, scope, source_position);
+int ScopeIterator::GetSourcePosition() {
+ if (frame_inspector_) {
+ return frame_inspector_->GetSourcePosition();
+ } else {
+ DCHECK(!generator_.is_null());
+ SharedFunctionInfo::EnsureSourcePositionsAvailable(
+ isolate_, handle(generator_->function().shared(), isolate_));
+ return generator_->source_position();
+ }
}
-void ScopeIterator::CollectNonLocals(ParseInfo* info, DeclarationScope* scope) {
- DCHECK_NOT_NULL(scope);
- DCHECK(non_locals_.is_null());
- non_locals_ = scope->CollectNonLocals(info, StringSet::New(isolate_));
-}
-
-
-MaybeHandle<JSObject> ScopeIterator::MaterializeScriptScope() {
- Handle<JSGlobalObject> global(CurrentContext()->global_object());
+void ScopeIterator::VisitScriptScope(const Visitor& visitor) const {
+ Handle<JSGlobalObject> global(context_->global_object(), isolate_);
Handle<ScriptContextTable> script_contexts(
- global->native_context()->script_context_table());
+ global->native_context().script_context_table(), isolate_);
- Handle<JSObject> script_scope =
- isolate_->factory()->NewJSObjectWithNullProto();
-
- for (int context_index = 0; context_index < script_contexts->used();
- context_index++) {
- Handle<Context> context =
- ScriptContextTable::GetContext(script_contexts, context_index);
- Handle<ScopeInfo> scope_info(context->scope_info());
- CopyContextLocalsToScopeObject(scope_info, context, script_scope);
+ // Skip the first script since that just declares 'this'.
+ for (int context_index = 1;
+ context_index < script_contexts->synchronized_used(); context_index++) {
+ Handle<Context> context = ScriptContextTable::GetContext(
+ isolate_, script_contexts, context_index);
+ Handle<ScopeInfo> scope_info(context->scope_info(), isolate_);
+ if (VisitContextLocals(visitor, scope_info, context, ScopeTypeScript))
+ return;
}
- return script_scope;
}
+void ScopeIterator::VisitModuleScope(const Visitor& visitor) const {
+ DCHECK(context_->IsModuleContext());
-MaybeHandle<JSObject> ScopeIterator::MaterializeLocalScope() {
- Handle<JSFunction> function = GetFunction();
+ Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);
+ if (VisitContextLocals(visitor, scope_info, context_, ScopeTypeModule))
+ return;
- Handle<JSObject> local_scope =
- isolate_->factory()->NewJSObjectWithNullProto();
- frame_inspector_->MaterializeStackLocals(local_scope, function);
+ int count_index = scope_info->ModuleVariableCountIndex();
+ int module_variable_count = Smi::cast(scope_info->get(count_index)).value();
- Handle<Context> frame_context =
- Handle<Context>::cast(frame_inspector_->GetContext());
+ Handle<SourceTextModule> module(context_->module(), isolate_);
- HandleScope scope(isolate_);
- Handle<SharedFunctionInfo> shared(function->shared());
- Handle<ScopeInfo> scope_info(shared->scope_info());
+ for (int i = 0; i < module_variable_count; ++i) {
+ int index;
+ Handle<String> name;
+ {
+ String raw_name;
+ scope_info->ModuleVariable(i, &raw_name, &index);
+ if (ScopeInfo::VariableIsSynthetic(raw_name)) continue;
+ name = handle(raw_name, isolate_);
+ }
+ Handle<Object> value =
+ SourceTextModule::LoadVariable(isolate_, module, index);
- if (!scope_info->HasContext()) return local_scope;
-
- // Fill all context locals.
- Handle<Context> function_context(frame_context->closure_context());
- CopyContextLocalsToScopeObject(scope_info, function_context, local_scope);
-
- // Finally copy any properties from the function context extension.
- // These will be variables introduced by eval.
- if (function_context->closure() == *function &&
- !function_context->IsNativeContext()) {
- CopyContextExtensionToScopeObject(function_context, local_scope,
- KeyCollectionMode::kIncludePrototypes);
+ if (visitor(name, value, ScopeTypeModule)) return;
}
-
- return local_scope;
}
-
-// Create a plain JSObject which materializes the closure content for the
-// context.
-Handle<JSObject> ScopeIterator::MaterializeClosure() {
- Handle<Context> context = CurrentContext();
- DCHECK(context->IsFunctionContext() || context->IsEvalContext());
-
- Handle<SharedFunctionInfo> shared(context->closure()->shared());
- Handle<ScopeInfo> scope_info(shared->scope_info());
-
- // Allocate and initialize a JSObject with all the content of this function
- // closure.
- Handle<JSObject> closure_scope =
- isolate_->factory()->NewJSObjectWithNullProto();
-
+bool ScopeIterator::VisitContextLocals(const Visitor& visitor,
+ Handle<ScopeInfo> scope_info,
+ Handle<Context> context,
+ ScopeType scope_type) const {
// Fill all context locals to the context extension.
- CopyContextLocalsToScopeObject(scope_info, context, closure_scope);
-
- // Finally copy any properties from the function context extension. This will
- // be variables introduced by eval.
- CopyContextExtensionToScopeObject(context, closure_scope,
- KeyCollectionMode::kOwnOnly);
-
- return closure_scope;
+ for (int i = 0; i < scope_info->ContextLocalCount(); ++i) {
+ Handle<String> name(scope_info->ContextLocalName(i), isolate_);
+ if (ScopeInfo::VariableIsSynthetic(*name)) continue;
+ int context_index = scope_info->ContextHeaderLength() + i;
+ Handle<Object> value(context->get(context_index), isolate_);
+ if (visitor(name, value, scope_type)) return true;
+ }
+ return false;
}
+bool ScopeIterator::VisitLocals(const Visitor& visitor, Mode mode,
+ ScopeType scope_type) const {
+ if (mode == Mode::STACK && current_scope_->is_declaration_scope() &&
+ current_scope_->AsDeclarationScope()->has_this_declaration()) {
+ // TODO(bmeurer): We should refactor the general variable lookup
+ // around "this", since the current way is rather hacky when the
+ // receiver is context-allocated.
+ auto this_var = current_scope_->AsDeclarationScope()->receiver();
+ Handle<Object> receiver =
+ this_var->location() == VariableLocation::CONTEXT
+ ? handle(context_->get(this_var->index()), isolate_)
+ : frame_inspector_ == nullptr
+ ? handle(generator_->receiver(), isolate_)
+ : frame_inspector_->GetReceiver();
+ if (receiver->IsOptimizedOut(isolate_)) {
+ receiver = isolate_->factory()->undefined_value();
+ }
+ if (visitor(isolate_->factory()->this_string(), receiver, scope_type))
+ return true;
+ }
-// Create a plain JSObject which materializes the scope for the specified
-// catch context.
-Handle<JSObject> ScopeIterator::MaterializeCatchScope() {
- Handle<Context> context = CurrentContext();
- DCHECK(context->IsCatchContext());
- Handle<String> name(context->catch_name());
- Handle<Object> thrown_object(context->get(Context::THROWN_OBJECT_INDEX),
- isolate_);
- Handle<JSObject> catch_scope =
- isolate_->factory()->NewJSObjectWithNullProto();
- JSObject::SetOwnPropertyIgnoreAttributes(catch_scope, name, thrown_object,
- NONE)
- .Check();
- return catch_scope;
+ if (current_scope_->is_function_scope()) {
+ Variable* function_var =
+ current_scope_->AsDeclarationScope()->function_var();
+ if (function_var != nullptr) {
+ Handle<JSFunction> function = frame_inspector_ == nullptr
+ ? function_
+ : frame_inspector_->GetFunction();
+ Handle<String> name = function_var->name();
+ if (visitor(name, function, scope_type)) return true;
+ }
+ }
+
+ for (Variable* var : *current_scope_->locals()) {
+ DCHECK(!var->is_this());
+ if (ScopeInfo::VariableIsSynthetic(*var->name())) continue;
+
+ int index = var->index();
+ Handle<Object> value;
+ switch (var->location()) {
+ case VariableLocation::LOOKUP:
+ UNREACHABLE();
+ break;
+
+ case VariableLocation::REPL_GLOBAL:
+ // REPL declared variables are ignored for now.
+ case VariableLocation::UNALLOCATED:
+ continue;
+
+ case VariableLocation::PARAMETER: {
+ if (frame_inspector_ == nullptr) {
+ // Get the variable from the suspended generator.
+ DCHECK(!generator_.is_null());
+ FixedArray parameters_and_registers =
+ generator_->parameters_and_registers();
+ DCHECK_LT(index, parameters_and_registers.length());
+ value = handle(parameters_and_registers.get(index), isolate_);
+ } else {
+ value = frame_inspector_->GetParameter(index);
+
+ if (value->IsOptimizedOut(isolate_)) {
+ value = isolate_->factory()->undefined_value();
+ }
+ }
+ break;
+ }
+
+ case VariableLocation::LOCAL:
+ if (frame_inspector_ == nullptr) {
+ // Get the variable from the suspended generator.
+ DCHECK(!generator_.is_null());
+ FixedArray parameters_and_registers =
+ generator_->parameters_and_registers();
+ int parameter_count =
+ function_->shared().scope_info().ParameterCount();
+ index += parameter_count;
+ DCHECK_LT(index, parameters_and_registers.length());
+ value = handle(parameters_and_registers.get(index), isolate_);
+ } else {
+ value = frame_inspector_->GetExpression(index);
+ if (value->IsOptimizedOut(isolate_)) {
+ // We'll rematerialize this later.
+ if (current_scope_->is_declaration_scope() &&
+ current_scope_->AsDeclarationScope()->arguments() == var) {
+ continue;
+ }
+ value = isolate_->factory()->undefined_value();
+ }
+ }
+ break;
+
+ case VariableLocation::CONTEXT:
+ if (mode == Mode::STACK) continue;
+ DCHECK(var->IsContextSlot());
+ value = handle(context_->get(index), isolate_);
+ break;
+
+ case VariableLocation::MODULE: {
+ if (mode == Mode::STACK) continue;
+ // if (var->IsExport()) continue;
+ Handle<SourceTextModule> module(context_->module(), isolate_);
+ value = SourceTextModule::LoadVariable(isolate_, module, var->index());
+ break;
+ }
+ }
+
+ if (visitor(var->name(), value, scope_type)) return true;
+ }
+ return false;
}
// Retrieve the with-context extension object. If the extension object is
// a proxy, return an empty object.
Handle<JSObject> ScopeIterator::WithContextExtension() {
- Handle<Context> context = CurrentContext();
- DCHECK(context->IsWithContext());
- if (context->extension_receiver()->IsJSProxy()) {
+ DCHECK(context_->IsWithContext());
+ if (context_->extension_receiver().IsJSProxy()) {
return isolate_->factory()->NewJSObjectWithNullProto();
}
- return handle(JSObject::cast(context->extension_receiver()));
+ return handle(JSObject::cast(context_->extension_receiver()), isolate_);
}
// Create a plain JSObject which materializes the block scope for the specified
// block context.
-Handle<JSObject> ScopeIterator::MaterializeInnerScope() {
- Handle<JSObject> inner_scope =
- isolate_->factory()->NewJSObjectWithNullProto();
-
- Handle<Context> context = Handle<Context>::null();
- if (!nested_scope_chain_.is_empty()) {
- Handle<ScopeInfo> scope_info = nested_scope_chain_.last().scope_info;
- frame_inspector_->MaterializeStackLocals(inner_scope, scope_info);
- if (scope_info->HasContext()) context = CurrentContext();
+void ScopeIterator::VisitLocalScope(const Visitor& visitor, Mode mode,
+ ScopeType scope_type) const {
+ if (InInnerScope()) {
+ if (VisitLocals(visitor, mode, scope_type)) return;
+ if (mode == Mode::STACK && Type() == ScopeTypeLocal) {
+ // Hide |this| in arrow functions that may be embedded in other functions
+ // but don't force |this| to be context-allocated. Otherwise we'd find the
+ // wrong |this| value.
+ if (!closure_scope_->has_this_declaration() &&
+ !closure_scope_->HasThisReference()) {
+ if (visitor(isolate_->factory()->this_string(),
+ isolate_->factory()->undefined_value(), scope_type))
+ return;
+ }
+ // Add |arguments| to the function scope even if it wasn't used.
+ // Currently we don't yet support materializing the arguments object of
+ // suspended generators. We'd need to read the arguments out from the
+ // suspended generator rather than from an activation as
+ // FunctionGetArguments does.
+ if (frame_inspector_ != nullptr && !closure_scope_->is_arrow_scope() &&
+ (closure_scope_->arguments() == nullptr ||
+ frame_inspector_->GetExpression(closure_scope_->arguments()->index())
+ ->IsOptimizedOut(isolate_))) {
+ JavaScriptFrame* frame = GetFrame();
+ Handle<JSObject> arguments = Accessors::FunctionGetArguments(
+ frame, frame_inspector_->inlined_frame_index());
+ if (visitor(isolate_->factory()->arguments_string(), arguments,
+ scope_type))
+ return;
+ }
+ }
} else {
- context = CurrentContext();
+ DCHECK_EQ(Mode::ALL, mode);
+ Handle<ScopeInfo> scope_info(context_->scope_info(), isolate_);
+ if (VisitContextLocals(visitor, scope_info, context_, scope_type)) return;
}
- if (!context.is_null()) {
- // Fill all context locals.
- CopyContextLocalsToScopeObject(CurrentScopeInfo(), context, inner_scope);
- CopyContextExtensionToScopeObject(context, inner_scope,
- KeyCollectionMode::kOwnOnly);
- }
- return inner_scope;
-}
+ if (mode == Mode::ALL && HasContext()) {
+ DCHECK(!context_->IsScriptContext());
+ DCHECK(!context_->IsNativeContext());
+ DCHECK(!context_->IsWithContext());
+ if (!context_->scope_info().SloppyEvalCanExtendVars()) return;
+ if (context_->extension_object().is_null()) return;
+ Handle<JSObject> extension(context_->extension_object(), isolate_);
+ Handle<FixedArray> keys =
+ KeyAccumulator::GetKeys(extension, KeyCollectionMode::kOwnOnly,
+ ENUMERABLE_STRINGS)
+ .ToHandleChecked();
-
-// Create a plain JSObject which materializes the module scope for the specified
-// module context.
-MaybeHandle<JSObject> ScopeIterator::MaterializeModuleScope() {
- Handle<Context> context = CurrentContext();
- DCHECK(context->IsModuleContext());
- Handle<ScopeInfo> scope_info(context->scope_info());
- Handle<JSObject> module_scope =
- isolate_->factory()->NewJSObjectWithNullProto();
- CopyContextLocalsToScopeObject(scope_info, context, module_scope);
- CopyModuleVarsToScopeObject(scope_info, context, module_scope);
- return module_scope;
-}
-
-bool ScopeIterator::SetParameterValue(Handle<ScopeInfo> scope_info,
- JavaScriptFrame* frame,
- Handle<String> parameter_name,
- Handle<Object> new_value) {
- // Setting stack locals of optimized frames is not supported.
- if (frame->is_optimized()) return false;
- HandleScope scope(isolate_);
- for (int i = 0; i < scope_info->ParameterCount(); ++i) {
- if (String::Equals(handle(scope_info->ParameterName(i)), parameter_name)) {
- frame->SetParameterValue(i, *new_value);
- return true;
+ for (int i = 0; i < keys->length(); i++) {
+ // Names of variables introduced by eval are strings.
+ DCHECK(keys->get(i).IsString());
+ Handle<String> key(String::cast(keys->get(i)), isolate_);
+ Handle<Object> value = JSReceiver::GetDataProperty(extension, key);
+ if (visitor(key, value, scope_type)) return;
}
}
- return false;
-}
-
-bool ScopeIterator::SetStackVariableValue(Handle<ScopeInfo> scope_info,
- Handle<String> variable_name,
- Handle<Object> new_value) {
- if (frame_inspector_ == nullptr) return false;
- JavaScriptFrame* frame = GetFrame();
- // Setting stack locals of optimized frames is not supported.
- if (frame->is_optimized()) return false;
- HandleScope scope(isolate_);
- for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
- if (String::Equals(handle(scope_info->StackLocalName(i)), variable_name)) {
- frame->SetExpression(scope_info->StackLocalIndex(i), *new_value);
- return true;
- }
- }
- return false;
-}
-
-bool ScopeIterator::SetContextVariableValue(Handle<ScopeInfo> scope_info,
- Handle<Context> context,
- Handle<String> variable_name,
- Handle<Object> new_value) {
- HandleScope scope(isolate_);
- for (int i = 0; i < scope_info->ContextLocalCount(); i++) {
- Handle<String> next_name(scope_info->ContextLocalName(i));
- if (String::Equals(variable_name, next_name)) {
- VariableMode mode;
- InitializationFlag init_flag;
- MaybeAssignedFlag maybe_assigned_flag;
- int context_index = ScopeInfo::ContextSlotIndex(
- scope_info, next_name, &mode, &init_flag, &maybe_assigned_flag);
- context->set(context_index, *new_value);
- return true;
- }
- }
-
- if (context->has_extension()) {
- Handle<JSObject> ext(context->extension_object());
- Maybe<bool> maybe = JSReceiver::HasOwnProperty(ext, variable_name);
- DCHECK(maybe.IsJust());
- if (maybe.FromJust()) {
- // We don't expect this to do anything except replacing property value.
- JSObject::SetOwnPropertyIgnoreAttributes(ext, variable_name, new_value,
- NONE)
- .Check();
- return true;
- }
- }
-
- return false;
}
bool ScopeIterator::SetLocalVariableValue(Handle<String> variable_name,
Handle<Object> new_value) {
- JavaScriptFrame* frame = GetFrame();
- Handle<ScopeInfo> scope_info(frame->function()->shared()->scope_info());
+ // TODO(verwaest): Walk parameters backwards, not forwards.
+ // TODO(verwaest): Use VariableMap rather than locals() list for lookup.
+ for (Variable* var : *current_scope_->locals()) {
+ if (String::Equals(isolate_, var->name(), variable_name)) {
+ int index = var->index();
+ switch (var->location()) {
+ case VariableLocation::LOOKUP:
+ case VariableLocation::UNALLOCATED:
+ // Drop assignments to unallocated locals.
+ DCHECK(var->is_this() ||
+ *variable_name == ReadOnlyRoots(isolate_).arguments_string());
+ return false;
- // Parameter might be shadowed in context. Don't stop here.
- bool result = SetParameterValue(scope_info, frame, variable_name, new_value);
+ case VariableLocation::REPL_GLOBAL:
+ // Assignments to REPL declared variables are ignored for now.
+ return false;
- // Stack locals.
- if (SetStackVariableValue(scope_info, variable_name, new_value)) {
- return true;
- }
+ case VariableLocation::PARAMETER: {
+ if (var->is_this()) return false;
+ if (frame_inspector_ == nullptr) {
+ // Set the variable in the suspended generator.
+ DCHECK(!generator_.is_null());
+ Handle<FixedArray> parameters_and_registers(
+ generator_->parameters_and_registers(), isolate_);
+ DCHECK_LT(index, parameters_and_registers->length());
+ parameters_and_registers->set(index, *new_value);
+ } else {
+ JavaScriptFrame* frame = GetFrame();
+ if (frame->is_optimized()) return false;
- if (scope_info->HasContext() &&
- SetContextVariableValue(scope_info, CurrentContext(), variable_name,
- new_value)) {
- return true;
- }
+ frame->SetParameterValue(index, *new_value);
+ }
+ return true;
+ }
- return result;
-}
+ case VariableLocation::LOCAL:
+ if (frame_inspector_ == nullptr) {
+ // Set the variable in the suspended generator.
+ DCHECK(!generator_.is_null());
+ int parameter_count =
+ function_->shared().scope_info().ParameterCount();
+ index += parameter_count;
+ Handle<FixedArray> parameters_and_registers(
+ generator_->parameters_and_registers(), isolate_);
+ DCHECK_LT(index, parameters_and_registers->length());
+ parameters_and_registers->set(index, *new_value);
+ } else {
+ // Set the variable on the stack.
+ JavaScriptFrame* frame = GetFrame();
+ if (frame->is_optimized()) return false;
-bool ScopeIterator::SetInnerScopeVariableValue(Handle<String> variable_name,
- Handle<Object> new_value) {
- Handle<ScopeInfo> scope_info = CurrentScopeInfo();
- DCHECK(scope_info->scope_type() == BLOCK_SCOPE ||
- scope_info->scope_type() == EVAL_SCOPE);
+ frame->SetExpression(index, *new_value);
+ }
+ return true;
- // Setting stack locals of optimized frames is not supported.
- if (SetStackVariableValue(scope_info, variable_name, new_value)) {
- return true;
- }
+ case VariableLocation::CONTEXT:
+ DCHECK(var->IsContextSlot());
+ context_->set(index, *new_value);
+ return true;
- if (HasContext() && SetContextVariableValue(scope_info, CurrentContext(),
- variable_name, new_value)) {
- return true;
+ case VariableLocation::MODULE:
+ if (!var->IsExport()) return false;
+ Handle<SourceTextModule> module(context_->module(), isolate_);
+ SourceTextModule::StoreVariable(module, var->index(), new_value);
+ return true;
+ }
+ UNREACHABLE();
+ }
}
return false;
}
-// This method copies structure of MaterializeClosure method above.
-bool ScopeIterator::SetClosureVariableValue(Handle<String> variable_name,
+bool ScopeIterator::SetContextExtensionValue(Handle<String> variable_name,
+ Handle<Object> new_value) {
+ if (!context_->has_extension()) return false;
+
+ DCHECK(context_->extension_object().IsJSContextExtensionObject());
+ Handle<JSObject> ext(context_->extension_object(), isolate_);
+ LookupIterator it(isolate_, ext, variable_name, LookupIterator::OWN);
+ Maybe<bool> maybe = JSReceiver::HasProperty(&it);
+ DCHECK(maybe.IsJust());
+ if (!maybe.FromJust()) return false;
+
+ CHECK(Object::SetDataProperty(&it, new_value).ToChecked());
+ return true;
+}
+
+bool ScopeIterator::SetContextVariableValue(Handle<String> variable_name,
Handle<Object> new_value) {
- DCHECK(CurrentContext()->IsFunctionContext() ||
- CurrentContext()->IsEvalContext());
- return SetContextVariableValue(CurrentScopeInfo(), CurrentContext(),
- variable_name, new_value);
+ DisallowHeapAllocation no_gc;
+ VariableMode mode;
+ InitializationFlag flag;
+ MaybeAssignedFlag maybe_assigned_flag;
+ IsStaticFlag is_static_flag;
+ int slot_index =
+ ScopeInfo::ContextSlotIndex(context_->scope_info(), *variable_name, &mode,
+ &flag, &maybe_assigned_flag, &is_static_flag);
+ if (slot_index < 0) return false;
+
+ context_->set(slot_index, *new_value);
+ return true;
+}
+
+bool ScopeIterator::SetModuleVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value) {
+ DisallowHeapAllocation no_gc;
+ int cell_index;
+ VariableMode mode;
+ InitializationFlag init_flag;
+ MaybeAssignedFlag maybe_assigned_flag;
+ cell_index = context_->scope_info().ModuleIndex(
+ *variable_name, &mode, &init_flag, &maybe_assigned_flag);
+
+ // Setting imports is currently not supported.
+ if (SourceTextModuleDescriptor::GetCellIndexKind(cell_index) !=
+ SourceTextModuleDescriptor::kExport) {
+ return false;
+ }
+
+ Handle<SourceTextModule> module(context_->module(), isolate_);
+ SourceTextModule::StoreVariable(module, cell_index, new_value);
+ return true;
}
bool ScopeIterator::SetScriptVariableValue(Handle<String> variable_name,
Handle<Object> new_value) {
- Handle<String> internalized_variable_name =
- isolate_->factory()->InternalizeString(variable_name);
- Handle<Context> context = CurrentContext();
Handle<ScriptContextTable> script_contexts(
- context->global_object()->native_context()->script_context_table());
+ context_->global_object().native_context().script_context_table(),
+ isolate_);
ScriptContextTable::LookupResult lookup_result;
- if (ScriptContextTable::Lookup(script_contexts, internalized_variable_name,
+ if (ScriptContextTable::Lookup(isolate_, *script_contexts, *variable_name,
&lookup_result)) {
Handle<Context> script_context = ScriptContextTable::GetContext(
- script_contexts, lookup_result.context_index);
+ isolate_, script_contexts, lookup_result.context_index);
script_context->set(lookup_result.slot_index, *new_value);
return true;
}
@@ -750,120 +1101,5 @@
return false;
}
-bool ScopeIterator::SetCatchVariableValue(Handle<String> variable_name,
- Handle<Object> new_value) {
- Handle<Context> context = CurrentContext();
- DCHECK(context->IsCatchContext());
- Handle<String> name(context->catch_name());
- if (!String::Equals(name, variable_name)) {
- return false;
- }
- context->set(Context::THROWN_OBJECT_INDEX, *new_value);
- return true;
-}
-
-
-void ScopeIterator::CopyContextLocalsToScopeObject(
- Handle<ScopeInfo> scope_info, Handle<Context> context,
- Handle<JSObject> scope_object) {
- Isolate* isolate = scope_info->GetIsolate();
- int local_count = scope_info->ContextLocalCount();
- if (local_count == 0) return;
- // Fill all context locals to the context extension.
- for (int i = 0; i < local_count; ++i) {
- Handle<String> name(scope_info->ContextLocalName(i));
- if (ScopeInfo::VariableIsSynthetic(*name)) continue;
- int context_index = Context::MIN_CONTEXT_SLOTS + i;
- Handle<Object> value = Handle<Object>(context->get(context_index), isolate);
- // Reflect variables under TDZ as undefined in scope object.
- if (value->IsTheHole(isolate)) continue;
- // This should always succeed.
- // TODO(verwaest): Use AddDataProperty instead.
- JSObject::SetOwnPropertyIgnoreAttributes(scope_object, name, value, NONE)
- .Check();
- }
-}
-
-void ScopeIterator::CopyModuleVarsToScopeObject(Handle<ScopeInfo> scope_info,
- Handle<Context> context,
- Handle<JSObject> scope_object) {
- Isolate* isolate = scope_info->GetIsolate();
-
- int module_variable_count =
- Smi::cast(scope_info->get(scope_info->ModuleVariableCountIndex()))
- ->value();
- for (int i = 0; i < module_variable_count; ++i) {
- Handle<String> local_name;
- Handle<Object> value;
- {
- String* name;
- int index;
- scope_info->ModuleVariable(i, &name, &index);
- CHECK(!ScopeInfo::VariableIsSynthetic(name));
- local_name = handle(name, isolate);
- value = Module::LoadVariable(handle(context->module(), isolate), index);
- }
-
- // Reflect variables under TDZ as undefined in scope object.
- if (value->IsTheHole(isolate)) continue;
- // This should always succeed.
- // TODO(verwaest): Use AddDataProperty instead.
- JSObject::SetOwnPropertyIgnoreAttributes(scope_object, local_name, value,
- NONE)
- .Check();
- }
-}
-
-void ScopeIterator::CopyContextExtensionToScopeObject(
- Handle<Context> context, Handle<JSObject> scope_object,
- KeyCollectionMode mode) {
- if (context->extension_object() == nullptr) return;
- Handle<JSObject> extension(context->extension_object());
- Handle<FixedArray> keys =
- KeyAccumulator::GetKeys(extension, mode, ENUMERABLE_STRINGS)
- .ToHandleChecked();
-
- for (int i = 0; i < keys->length(); i++) {
- // Names of variables introduced by eval are strings.
- DCHECK(keys->get(i)->IsString());
- Handle<String> key(String::cast(keys->get(i)));
- Handle<Object> value =
- Object::GetPropertyOrElement(extension, key).ToHandleChecked();
- JSObject::SetOwnPropertyIgnoreAttributes(scope_object, key, value, NONE)
- .Check();
- }
-}
-
-void ScopeIterator::GetNestedScopeChain(Isolate* isolate, Scope* scope,
- int position) {
- if (scope->is_function_scope()) {
- // Do not collect scopes of nested inner functions inside the current one.
- // Nested arrow functions could have the same end positions.
- Handle<JSFunction> function = frame_inspector_->GetFunction();
- if (scope->start_position() > function->shared()->start_position() &&
- scope->end_position() <= function->shared()->end_position()) {
- return;
- }
- }
- if (scope->is_hidden()) {
- // We need to add this chain element in case the scope has a context
- // associated. We need to keep the scope chain and context chain in sync.
- nested_scope_chain_.Add(ExtendedScopeInfo(scope->scope_info()));
- } else {
- nested_scope_chain_.Add(ExtendedScopeInfo(
- scope->scope_info(), scope->start_position(), scope->end_position()));
- }
- for (Scope* inner_scope = scope->inner_scope(); inner_scope != nullptr;
- inner_scope = inner_scope->sibling()) {
- int beg_pos = inner_scope->start_position();
- int end_pos = inner_scope->end_position();
- DCHECK((beg_pos >= 0 && end_pos >= 0) || inner_scope->is_hidden());
- if (beg_pos <= position && position < end_pos) {
- GetNestedScopeChain(isolate, inner_scope, position);
- return;
- }
- }
-}
-
} // namespace internal
} // namespace v8
diff --git a/src/debug/debug-scopes.h b/src/debug/debug-scopes.h
index d187f3e..590e9e9 100644
--- a/src/debug/debug-scopes.h
+++ b/src/debug/debug-scopes.h
@@ -5,12 +5,14 @@
#ifndef V8_DEBUG_DEBUG_SCOPES_H_
#define V8_DEBUG_DEBUG_SCOPES_H_
+#include <vector>
+
#include "src/debug/debug-frames.h"
-#include "src/frames.h"
namespace v8 {
namespace internal {
+class JavaScriptFrame;
class ParseInfo;
// Iterate over the actual scopes visible from a stack frame or from a closure.
@@ -39,127 +41,132 @@
static const int kScopeDetailsFunctionIndex = 5;
static const int kScopeDetailsSize = 6;
- enum Option { DEFAULT, IGNORE_NESTED_SCOPES, COLLECT_NON_LOCALS };
+ enum class ReparseStrategy {
+ kScript,
+ kFunctionLiteral,
+ };
ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector,
- Option options = DEFAULT);
+ ReparseStrategy strategy);
ScopeIterator(Isolate* isolate, Handle<JSFunction> function);
ScopeIterator(Isolate* isolate, Handle<JSGeneratorObject> generator);
+ ~ScopeIterator();
- MUST_USE_RESULT MaybeHandle<JSObject> MaterializeScopeDetails();
+ Handle<JSObject> MaterializeScopeDetails();
// More scopes?
- bool Done() { return context_.is_null(); }
+ bool Done() const { return context_.is_null(); }
// Move to the next scope.
void Next();
+ // Restart to the first scope and context.
+ void Restart();
+
// Return the type of the current scope.
- ScopeType Type();
+ ScopeType Type() const;
+
+ // Indicates which variables should be visited. Either only variables from the
+ // scope that are available on the stack, or all variables.
+ enum class Mode { STACK, ALL };
// Return the JavaScript object with the content of the current scope.
- MaybeHandle<JSObject> ScopeObject();
+ Handle<JSObject> ScopeObject(Mode mode);
- bool HasContext();
+ // Returns whether the current scope declares any variables.
+ bool DeclaresLocals(Mode mode) const;
// Set variable value and return true on success.
bool SetVariableValue(Handle<String> variable_name, Handle<Object> new_value);
- Handle<ScopeInfo> CurrentScopeInfo();
-
- // Return the context for this scope. For the local context there might not
- // be an actual context.
- Handle<Context> CurrentContext();
+ bool ClosureScopeHasThisReference() const;
// Populate the set with collected non-local variable names.
- Handle<StringSet> GetNonLocals();
+ Handle<StringSet> GetLocals() { return locals_; }
+
+ // Similar to JSFunction::GetName return the function's name or it's inferred
+ // name.
+ Handle<Object> GetFunctionDebugName() const;
+
+ Handle<Script> GetScript() const { return script_; }
+
+ bool HasPositionInfo();
+ int start_position();
+ int end_position();
#ifdef DEBUG
// Debug print of the content of the current scope.
void DebugPrint();
#endif
+ bool InInnerScope() const { return !function_.is_null(); }
+ bool HasContext() const;
+ bool NeedsAndHasContext() const;
+ Handle<Context> CurrentContext() const {
+ DCHECK(HasContext());
+ return context_;
+ }
+
private:
- struct ExtendedScopeInfo {
- ExtendedScopeInfo(Handle<ScopeInfo> info, int start, int end)
- : scope_info(info), start_position(start), end_position(end) {}
- explicit ExtendedScopeInfo(Handle<ScopeInfo> info)
- : scope_info(info), start_position(-1), end_position(-1) {}
- Handle<ScopeInfo> scope_info;
- int start_position;
- int end_position;
- bool is_hidden() { return start_position == -1 && end_position == -1; }
- };
-
Isolate* isolate_;
- FrameInspector* const frame_inspector_;
+ std::unique_ptr<ParseInfo> info_;
+ FrameInspector* const frame_inspector_ = nullptr;
+ Handle<JSGeneratorObject> generator_;
+
+ // The currently-executing function from the inspected frame, or null if this
+ // ScopeIterator has already iterated to any Scope outside that function.
+ Handle<JSFunction> function_;
+
Handle<Context> context_;
- List<ExtendedScopeInfo> nested_scope_chain_;
- Handle<StringSet> non_locals_;
- bool seen_script_scope_;
+ Handle<Script> script_;
+ Handle<StringSet> locals_;
+ DeclarationScope* closure_scope_ = nullptr;
+ Scope* start_scope_ = nullptr;
+ Scope* current_scope_ = nullptr;
+ bool seen_script_scope_ = false;
- inline JavaScriptFrame* GetFrame() {
- return frame_inspector_->GetArgumentsFrame();
+ inline JavaScriptFrame* GetFrame() const {
+ return frame_inspector_->javascript_frame();
}
- inline Handle<JSFunction> GetFunction() {
- return frame_inspector_->GetFunction();
- }
+ void AdvanceOneScope();
+ void AdvanceToNonHiddenScope();
+ void AdvanceContext();
+ void CollectLocalsFromCurrentScope();
- void RetrieveScopeChain(DeclarationScope* scope);
+ int GetSourcePosition();
- void CollectNonLocals(ParseInfo* info, DeclarationScope* scope);
+ void TryParseAndRetrieveScopes(ReparseStrategy strategy);
void UnwrapEvaluationContext();
- MUST_USE_RESULT MaybeHandle<JSObject> MaterializeScriptScope();
- MUST_USE_RESULT MaybeHandle<JSObject> MaterializeLocalScope();
- MUST_USE_RESULT MaybeHandle<JSObject> MaterializeModuleScope();
- Handle<JSObject> MaterializeClosure();
- Handle<JSObject> MaterializeCatchScope();
- Handle<JSObject> MaterializeInnerScope();
+ using Visitor = std::function<bool(Handle<String> name, Handle<Object> value,
+ ScopeType scope_type)>;
+
Handle<JSObject> WithContextExtension();
bool SetLocalVariableValue(Handle<String> variable_name,
Handle<Object> new_value);
- bool SetInnerScopeVariableValue(Handle<String> variable_name,
- Handle<Object> new_value);
- bool SetClosureVariableValue(Handle<String> variable_name,
+ bool SetContextVariableValue(Handle<String> variable_name,
Handle<Object> new_value);
+ bool SetContextExtensionValue(Handle<String> variable_name,
+ Handle<Object> new_value);
bool SetScriptVariableValue(Handle<String> variable_name,
Handle<Object> new_value);
- bool SetCatchVariableValue(Handle<String> variable_name,
- Handle<Object> new_value);
+ bool SetModuleVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value);
// Helper functions.
- bool SetParameterValue(Handle<ScopeInfo> scope_info, JavaScriptFrame* frame,
- Handle<String> parameter_name,
- Handle<Object> new_value);
- bool SetStackVariableValue(Handle<ScopeInfo> scope_info,
- Handle<String> variable_name,
- Handle<Object> new_value);
- bool SetContextVariableValue(Handle<ScopeInfo> scope_info,
- Handle<Context> context,
- Handle<String> variable_name,
- Handle<Object> new_value);
-
- void CopyContextLocalsToScopeObject(Handle<ScopeInfo> scope_info,
- Handle<Context> context,
- Handle<JSObject> scope_object);
- void CopyModuleVarsToScopeObject(Handle<ScopeInfo> scope_info,
- Handle<Context> context,
- Handle<JSObject> scope_object);
- void CopyContextExtensionToScopeObject(Handle<Context> context,
- Handle<JSObject> scope_object,
- KeyCollectionMode mode);
-
- // Get the chain of nested scopes within this scope for the source statement
- // position. The scopes will be added to the list from the outermost scope to
- // the innermost scope. Only nested block, catch or with scopes are tracked
- // and will be returned, but no inner function scopes.
- void GetNestedScopeChain(Isolate* isolate, Scope* scope,
- int statement_position);
+ void VisitScope(const Visitor& visitor, Mode mode) const;
+ void VisitLocalScope(const Visitor& visitor, Mode mode,
+ ScopeType scope_type) const;
+ void VisitScriptScope(const Visitor& visitor) const;
+ void VisitModuleScope(const Visitor& visitor) const;
+ bool VisitLocals(const Visitor& visitor, Mode mode,
+ ScopeType scope_type) const;
+ bool VisitContextLocals(const Visitor& visitor, Handle<ScopeInfo> scope_info,
+ Handle<Context> context, ScopeType scope_type) const;
DISALLOW_IMPLICIT_CONSTRUCTORS(ScopeIterator);
};
diff --git a/src/debug/debug-stack-trace-iterator.cc b/src/debug/debug-stack-trace-iterator.cc
new file mode 100644
index 0000000..ea0f4d3
--- /dev/null
+++ b/src/debug/debug-stack-trace-iterator.cc
@@ -0,0 +1,233 @@
+// Copyright 2017 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-stack-trace-iterator.h"
+
+#include "src/api/api-inl.h"
+#include "src/debug/debug-evaluate.h"
+#include "src/debug/debug-interface.h"
+#include "src/debug/debug-scope-iterator.h"
+#include "src/debug/debug.h"
+#include "src/debug/liveedit.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/frames.h"
+#include "src/execution/isolate.h"
+#include "src/wasm/wasm-debug-evaluate.h"
+#include "src/wasm/wasm-debug.h"
+
+namespace v8 {
+
+bool debug::StackTraceIterator::SupportsWasmDebugEvaluate() {
+ return i::FLAG_wasm_expose_debug_eval;
+}
+
+std::unique_ptr<debug::StackTraceIterator> debug::StackTraceIterator::Create(
+ v8::Isolate* isolate, int index) {
+ return std::unique_ptr<debug::StackTraceIterator>(
+ new internal::DebugStackTraceIterator(
+ reinterpret_cast<internal::Isolate*>(isolate), index));
+}
+
+namespace internal {
+
+DebugStackTraceIterator::DebugStackTraceIterator(Isolate* isolate, int index)
+ : isolate_(isolate),
+ iterator_(isolate, isolate->debug()->break_frame_id()),
+ is_top_frame_(true) {
+ if (iterator_.done()) return;
+ std::vector<FrameSummary> frames;
+ iterator_.frame()->Summarize(&frames);
+ inlined_frame_index_ = static_cast<int>(frames.size());
+ Advance();
+ for (; !Done() && index > 0; --index) Advance();
+}
+
+DebugStackTraceIterator::~DebugStackTraceIterator() = default;
+
+bool DebugStackTraceIterator::Done() const { return iterator_.done(); }
+
+void DebugStackTraceIterator::Advance() {
+ while (true) {
+ --inlined_frame_index_;
+ for (; inlined_frame_index_ >= 0; --inlined_frame_index_) {
+ // Omit functions from native and extension scripts.
+ if (FrameSummary::Get(iterator_.frame(), inlined_frame_index_)
+ .is_subject_to_debugging()) {
+ break;
+ }
+ is_top_frame_ = false;
+ }
+ if (inlined_frame_index_ >= 0) {
+ frame_inspector_.reset(new FrameInspector(
+ iterator_.frame(), inlined_frame_index_, isolate_));
+ break;
+ }
+ is_top_frame_ = false;
+ frame_inspector_.reset();
+ iterator_.Advance();
+ if (iterator_.done()) break;
+ std::vector<FrameSummary> frames;
+ iterator_.frame()->Summarize(&frames);
+ inlined_frame_index_ = static_cast<int>(frames.size());
+ }
+}
+
+int DebugStackTraceIterator::GetContextId() const {
+ DCHECK(!Done());
+ Handle<Object> context = frame_inspector_->GetContext();
+ if (context->IsContext()) {
+ Object value = Context::cast(*context).native_context().debug_context_id();
+ if (value.IsSmi()) return Smi::ToInt(value);
+ }
+ return 0;
+}
+
+v8::MaybeLocal<v8::Value> DebugStackTraceIterator::GetReceiver() const {
+ DCHECK(!Done());
+ if (frame_inspector_->IsJavaScript() &&
+ frame_inspector_->GetFunction()->shared().kind() == kArrowFunction) {
+ // FrameInspector is not able to get receiver for arrow function.
+ // So let's try to fetch it using same logic as is used to retrieve 'this'
+ // during DebugEvaluate::Local.
+ Handle<JSFunction> function = frame_inspector_->GetFunction();
+ Handle<Context> context(function->context(), isolate_);
+ // Arrow function defined in top level function without references to
+ // variables may have NativeContext as context.
+ if (!context->IsFunctionContext()) return v8::MaybeLocal<v8::Value>();
+ ScopeIterator scope_iterator(
+ isolate_, frame_inspector_.get(),
+ ScopeIterator::ReparseStrategy::kFunctionLiteral);
+ // We lookup this variable in function context only when it is used in arrow
+ // function otherwise V8 can optimize it out.
+ if (!scope_iterator.ClosureScopeHasThisReference()) {
+ return v8::MaybeLocal<v8::Value>();
+ }
+ DisallowHeapAllocation no_gc;
+ VariableMode mode;
+ InitializationFlag flag;
+ MaybeAssignedFlag maybe_assigned_flag;
+ IsStaticFlag is_static_flag;
+ int slot_index = ScopeInfo::ContextSlotIndex(
+ context->scope_info(), ReadOnlyRoots(isolate_->heap()).this_string(),
+ &mode, &flag, &maybe_assigned_flag, &is_static_flag);
+ if (slot_index < 0) return v8::MaybeLocal<v8::Value>();
+ Handle<Object> value = handle(context->get(slot_index), isolate_);
+ if (value->IsTheHole(isolate_)) return v8::MaybeLocal<v8::Value>();
+ return Utils::ToLocal(value);
+ }
+
+ Handle<Object> value = frame_inspector_->GetReceiver();
+ if (value.is_null() || (value->IsSmi() || !value->IsTheHole(isolate_))) {
+ return Utils::ToLocal(value);
+ }
+ return v8::MaybeLocal<v8::Value>();
+}
+
+v8::Local<v8::Value> DebugStackTraceIterator::GetReturnValue() const {
+ CHECK(!Done());
+ if (frame_inspector_ && frame_inspector_->IsWasm()) {
+ return v8::Local<v8::Value>();
+ }
+ CHECK_NOT_NULL(iterator_.frame());
+ bool is_optimized = iterator_.frame()->is_optimized();
+ if (is_optimized || !is_top_frame_ ||
+ !isolate_->debug()->IsBreakAtReturn(iterator_.javascript_frame())) {
+ return v8::Local<v8::Value>();
+ }
+ return Utils::ToLocal(isolate_->debug()->return_value_handle());
+}
+
+v8::Local<v8::String> DebugStackTraceIterator::GetFunctionDebugName() const {
+ DCHECK(!Done());
+ return Utils::ToLocal(frame_inspector_->GetFunctionName());
+}
+
+v8::Local<v8::debug::Script> DebugStackTraceIterator::GetScript() const {
+ DCHECK(!Done());
+ Handle<Object> value = frame_inspector_->GetScript();
+ if (!value->IsScript()) return v8::Local<v8::debug::Script>();
+ return ToApiHandle<debug::Script>(Handle<Script>::cast(value));
+}
+
+debug::Location DebugStackTraceIterator::GetSourceLocation() const {
+ DCHECK(!Done());
+ v8::Local<v8::debug::Script> script = GetScript();
+ if (script.IsEmpty()) return v8::debug::Location();
+ return script->GetSourceLocation(frame_inspector_->GetSourcePosition());
+}
+
+v8::Local<v8::Function> DebugStackTraceIterator::GetFunction() const {
+ DCHECK(!Done());
+ if (!frame_inspector_->IsJavaScript()) return v8::Local<v8::Function>();
+ return Utils::ToLocal(frame_inspector_->GetFunction());
+}
+
+std::unique_ptr<v8::debug::ScopeIterator>
+DebugStackTraceIterator::GetScopeIterator() const {
+ DCHECK(!Done());
+ CommonFrame* frame = iterator_.frame();
+ if (frame->is_wasm()) {
+ return std::make_unique<DebugWasmScopeIterator>(isolate_,
+ WasmFrame::cast(frame));
+ }
+ return std::make_unique<DebugScopeIterator>(isolate_, frame_inspector_.get());
+}
+
+bool DebugStackTraceIterator::Restart() {
+ DCHECK(!Done());
+ if (iterator_.is_wasm()) return false;
+ return LiveEdit::RestartFrame(iterator_.javascript_frame());
+}
+
+v8::MaybeLocal<v8::Value> DebugStackTraceIterator::Evaluate(
+ v8::Local<v8::String> source, bool throw_on_side_effect) {
+ DCHECK(!Done());
+ Handle<Object> value;
+
+ i::SafeForInterruptsScope safe_for_interrupt_scope(isolate_);
+ bool success = false;
+ if (iterator_.is_wasm()) {
+ FrameSummary summary = FrameSummary::Get(iterator_.frame(), 0);
+ const FrameSummary::WasmFrameSummary& wasmSummary = summary.AsWasm();
+ Handle<WasmInstanceObject> instance = wasmSummary.wasm_instance();
+
+ success = DebugEvaluate::WebAssembly(instance, iterator_.frame()->id(),
+ Utils::OpenHandle(*source),
+ throw_on_side_effect)
+ .ToHandle(&value);
+ } else {
+ success = DebugEvaluate::Local(
+ isolate_, iterator_.frame()->id(), inlined_frame_index_,
+ Utils::OpenHandle(*source), throw_on_side_effect)
+ .ToHandle(&value);
+ }
+ if (!success) {
+ isolate_->OptionalRescheduleException(false);
+ return v8::MaybeLocal<v8::Value>();
+ }
+ return Utils::ToLocal(value);
+}
+
+v8::MaybeLocal<v8::String> DebugStackTraceIterator::EvaluateWasm(
+ internal::Vector<const internal::byte> source, int frame_index) {
+ DCHECK(!Done());
+ if (!i::FLAG_wasm_expose_debug_eval || !iterator_.is_wasm()) {
+ return v8::MaybeLocal<v8::String>();
+ }
+ Handle<String> value;
+ i::SafeForInterruptsScope safe_for_interrupt_scope(isolate_);
+
+ FrameSummary summary = FrameSummary::Get(iterator_.frame(), 0);
+ const FrameSummary::WasmFrameSummary& wasmSummary = summary.AsWasm();
+ Handle<WasmInstanceObject> instance = wasmSummary.wasm_instance();
+
+ if (!v8::internal::wasm::DebugEvaluate(source, instance, iterator_.frame())
+ .ToHandle(&value)) {
+ isolate_->OptionalRescheduleException(false);
+ return v8::MaybeLocal<v8::String>();
+ }
+ return Utils::ToLocal(value);
+}
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug-stack-trace-iterator.h b/src/debug/debug-stack-trace-iterator.h
new file mode 100644
index 0000000..2317af3
--- /dev/null
+++ b/src/debug/debug-stack-trace-iterator.h
@@ -0,0 +1,50 @@
+// Copyright 2017 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.
+
+#ifndef V8_DEBUG_DEBUG_STACK_TRACE_ITERATOR_H_
+#define V8_DEBUG_DEBUG_STACK_TRACE_ITERATOR_H_
+
+#include <memory>
+
+#include "src/debug/debug-frames.h"
+#include "src/debug/debug-interface.h"
+#include "src/execution/frames.h"
+
+namespace v8 {
+namespace internal {
+
+class DebugStackTraceIterator final : public debug::StackTraceIterator {
+ public:
+ DebugStackTraceIterator(Isolate* isolate, int index);
+ ~DebugStackTraceIterator() override;
+
+ bool Done() const override;
+ void Advance() override;
+
+ int GetContextId() const override;
+ v8::MaybeLocal<v8::Value> GetReceiver() const override;
+ v8::Local<v8::Value> GetReturnValue() const override;
+ v8::Local<v8::String> GetFunctionDebugName() const override;
+ v8::Local<v8::debug::Script> GetScript() const override;
+ debug::Location GetSourceLocation() const override;
+ v8::Local<v8::Function> GetFunction() const override;
+ std::unique_ptr<v8::debug::ScopeIterator> GetScopeIterator() const override;
+
+ bool Restart() override;
+ v8::MaybeLocal<v8::Value> Evaluate(v8::Local<v8::String> source,
+ bool throw_on_side_effect) override;
+ v8::MaybeLocal<v8::String> EvaluateWasm(
+ internal::Vector<const internal::byte> source, int frame_index) override;
+
+ private:
+ Isolate* isolate_;
+ StackTraceFrameIterator iterator_;
+ std::unique_ptr<FrameInspector> frame_inspector_;
+ int inlined_frame_index_;
+ bool is_top_frame_;
+};
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_STACK_TRACE_ITERATOR_H_
diff --git a/src/debug/debug-type-profile.cc b/src/debug/debug-type-profile.cc
new file mode 100644
index 0000000..c0ba96c
--- /dev/null
+++ b/src/debug/debug-type-profile.cc
@@ -0,0 +1,122 @@
+// Copyright 2017 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-type-profile.h"
+
+#include "src/execution/isolate.h"
+#include "src/objects/feedback-vector.h"
+#include "src/objects/objects-inl.h"
+#include "src/objects/objects.h"
+
+namespace v8 {
+namespace internal {
+
+std::unique_ptr<TypeProfile> TypeProfile::Collect(Isolate* isolate) {
+ std::unique_ptr<TypeProfile> result(new TypeProfile());
+
+ // Feedback vectors are already listed to prevent losing them to GC.
+ DCHECK(isolate->factory()
+ ->feedback_vectors_for_profiling_tools()
+ ->IsArrayList());
+ Handle<ArrayList> list = Handle<ArrayList>::cast(
+ isolate->factory()->feedback_vectors_for_profiling_tools());
+
+ Script::Iterator scripts(isolate);
+
+ for (Script script = scripts.Next(); !script.is_null();
+ script = scripts.Next()) {
+ if (!script.IsUserJavaScript()) {
+ continue;
+ }
+
+ Handle<Script> script_handle(script, isolate);
+
+ TypeProfileScript type_profile_script(script_handle);
+ std::vector<TypeProfileEntry>* entries = &type_profile_script.entries;
+
+ // TODO(franzih): Sort the vectors by script first instead of iterating
+ // the list multiple times.
+ for (int i = 0; i < list->Length(); i++) {
+ FeedbackVector vector = FeedbackVector::cast(list->Get(i));
+ SharedFunctionInfo info = vector.shared_function_info();
+ DCHECK(info.IsSubjectToDebugging());
+
+ // Match vectors with script.
+ if (script != info.script()) {
+ continue;
+ }
+ if (!info.HasFeedbackMetadata() || info.feedback_metadata().is_empty() ||
+ !info.feedback_metadata().HasTypeProfileSlot()) {
+ continue;
+ }
+ FeedbackSlot slot = vector.GetTypeProfileSlot();
+ FeedbackNexus nexus(vector, slot);
+ Handle<String> name(info.DebugName(), isolate);
+ std::vector<int> source_positions = nexus.GetSourcePositions();
+ for (int position : source_positions) {
+ DCHECK_GE(position, 0);
+ entries->emplace_back(position, nexus.GetTypesForSourcePositions(
+ static_cast<uint32_t>(position)));
+ }
+
+ // Releases type profile data collected so far.
+ nexus.ResetTypeProfile();
+ }
+ if (!entries->empty()) {
+ result->emplace_back(type_profile_script);
+ }
+ }
+ return result;
+}
+
+void TypeProfile::SelectMode(Isolate* isolate, debug::TypeProfileMode mode) {
+ if (mode != isolate->type_profile_mode()) {
+ // Changing the type profile mode can change the bytecode that would be
+ // generated for a function, which can interfere with lazy source positions,
+ // so just force source position collection whenever there's such a change.
+ isolate->CollectSourcePositionsForAllBytecodeArrays();
+ }
+
+ HandleScope handle_scope(isolate);
+
+ if (mode == debug::TypeProfileMode::kNone) {
+ if (!isolate->factory()
+ ->feedback_vectors_for_profiling_tools()
+ ->IsUndefined(isolate)) {
+ // Release type profile data collected so far.
+
+ // Feedback vectors are already listed to prevent losing them to GC.
+ DCHECK(isolate->factory()
+ ->feedback_vectors_for_profiling_tools()
+ ->IsArrayList());
+ Handle<ArrayList> list = Handle<ArrayList>::cast(
+ isolate->factory()->feedback_vectors_for_profiling_tools());
+
+ for (int i = 0; i < list->Length(); i++) {
+ FeedbackVector vector = FeedbackVector::cast(list->Get(i));
+ SharedFunctionInfo info = vector.shared_function_info();
+ DCHECK(info.IsSubjectToDebugging());
+ if (info.feedback_metadata().HasTypeProfileSlot()) {
+ FeedbackSlot slot = vector.GetTypeProfileSlot();
+ FeedbackNexus nexus(vector, slot);
+ nexus.ResetTypeProfile();
+ }
+ }
+
+ // Delete the feedback vectors from the list if they're not used by code
+ // coverage.
+ if (isolate->is_best_effort_code_coverage()) {
+ isolate->SetFeedbackVectorsForProfilingTools(
+ ReadOnlyRoots(isolate).undefined_value());
+ }
+ }
+ } else {
+ DCHECK_EQ(debug::TypeProfileMode::kCollect, mode);
+ isolate->MaybeInitializeVectorListFromHeap();
+ }
+ isolate->set_type_profile_mode(mode);
+}
+
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug-type-profile.h b/src/debug/debug-type-profile.h
new file mode 100644
index 0000000..f06af0c
--- /dev/null
+++ b/src/debug/debug-type-profile.h
@@ -0,0 +1,47 @@
+// Copyright 2017 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.
+
+#ifndef V8_DEBUG_DEBUG_TYPE_PROFILE_H_
+#define V8_DEBUG_DEBUG_TYPE_PROFILE_H_
+
+#include <memory>
+#include <vector>
+
+#include "src/debug/debug-interface.h"
+#include "src/handles/handles.h"
+#include "src/objects/objects.h"
+
+namespace v8 {
+namespace internal {
+
+// Forward declaration.
+class Isolate;
+
+struct TypeProfileEntry {
+ explicit TypeProfileEntry(
+ int pos, std::vector<v8::internal::Handle<internal::String>> t)
+ : position(pos), types(std::move(t)) {}
+ int position;
+ std::vector<v8::internal::Handle<internal::String>> types;
+};
+
+struct TypeProfileScript {
+ explicit TypeProfileScript(Handle<Script> s) : script(s) {}
+ Handle<Script> script;
+ std::vector<TypeProfileEntry> entries;
+};
+
+class TypeProfile : public std::vector<TypeProfileScript> {
+ public:
+ static std::unique_ptr<TypeProfile> Collect(Isolate* isolate);
+ static void SelectMode(Isolate* isolate, debug::TypeProfileMode mode);
+
+ private:
+ TypeProfile() = default;
+};
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_TYPE_PROFILE_H_
diff --git a/src/debug/debug.cc b/src/debug/debug.cc
index dd1f5cf..a65c1b3 100644
--- a/src/debug/debug.cc
+++ b/src/debug/debug.cc
@@ -5,87 +5,146 @@
#include "src/debug/debug.h"
#include <memory>
+#include <unordered_set>
-#include "src/api.h"
-#include "src/arguments.h"
-#include "src/assembler-inl.h"
-#include "src/bootstrapper.h"
-#include "src/code-stubs.h"
-#include "src/codegen.h"
-#include "src/compilation-cache.h"
-#include "src/compiler-dispatcher/optimizing-compile-dispatcher.h"
-#include "src/compiler.h"
+#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.h"
-#include "src/execution.h"
-#include "src/frames-inl.h"
-#include "src/full-codegen/full-codegen.h"
-#include "src/global-handles.h"
-#include "src/globals.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/isolate-inl.h"
-#include "src/list.h"
-#include "src/log.h"
-#include "src/messages.h"
-#include "src/snapshot/natives.h"
-#include "src/wasm/wasm-module.h"
-#include "src/wasm/wasm-objects.h"
-
-#include "include/v8-debug.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-debug.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)
- : debug_context_(Handle<Context>()),
- is_active_(false),
+ : is_active_(false),
hook_on_function_call_(false),
is_suppressed_(false),
- live_edit_enabled_(true), // TODO(yangguo): set to false by default.
break_disabled_(false),
break_points_active_(true),
break_on_exception_(false),
break_on_uncaught_exception_(false),
side_effect_check_failed_(false),
- debug_info_list_(NULL),
+ 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();
- if (abstract_code->IsCode()) offset = offset - 1;
- auto it = BreakIterator::GetIterator(debug_info, abstract_code);
- it->SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset));
- return it->GetBreakLocation();
+ BreakIterator it(debug_info);
+ it.SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset));
+ return it.GetBreakLocation();
}
-void BreakLocation::AllAtCurrentStatement(Handle<DebugInfo> debug_info,
- JavaScriptFrame* frame,
- List<BreakLocation>* result_out) {
+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;
{
- auto it = BreakIterator::GetIterator(debug_info, abstract_code);
- it->SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset));
- statement_position = it->statement_position();
+ BreakIterator it(debug_info);
+ it.SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset));
+ statement_position = it.statement_position();
}
- for (auto it = BreakIterator::GetIterator(debug_info, abstract_code);
- !it->Done(); it->Next()) {
- if (it->statement_position() == statement_position) {
- result_out->Add(it->GetBreakLocation());
+ 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) {
@@ -93,12 +152,11 @@
int closest_break = 0;
int distance = kMaxInt;
DCHECK(0 <= offset && offset < abstract_code->Size());
- for (auto it = BreakIterator::GetIterator(debug_info, abstract_code);
- !it->Done(); it->Next()) {
+ 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();
+ 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;
}
@@ -106,56 +164,57 @@
return closest_break;
}
-bool BreakLocation::HasBreakPoint(Handle<DebugInfo> debug_info) const {
+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(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.
- if (abstract_code_->IsCode()) {
- DCHECK_EQ(debug_info->DebugCode(), abstract_code_->GetCode());
- CodeBreakIterator it(debug_info);
- it.SkipToPosition(position_, BREAK_POSITION_ALIGNED);
- return it.code_offset() == code_offset_;
+ 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());
- BytecodeArrayBreakIterator it(debug_info);
- it.SkipToPosition(position_, BREAK_POSITION_ALIGNED);
+ BreakIterator it(debug_info);
+ it.SkipToPosition(position_);
return it.code_offset() == code_offset_;
}
}
-std::unique_ptr<BreakIterator> BreakIterator::GetIterator(
- Handle<DebugInfo> debug_info, Handle<AbstractCode> abstract_code) {
- if (abstract_code->IsBytecodeArray()) {
- DCHECK(debug_info->HasDebugBytecodeArray());
- return std::unique_ptr<BreakIterator>(
- new BytecodeArrayBreakIterator(debug_info));
- } else {
- DCHECK(abstract_code->IsCode());
- DCHECK(debug_info->HasDebugCode());
- return std::unique_ptr<BreakIterator>(new CodeBreakIterator(debug_info));
+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) {
- position_ = debug_info->shared()->start_position();
+ : 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,
- BreakPositionAlignment alignment) {
+int BreakIterator::BreakIndexFromPosition(int source_position) {
int distance = kMaxInt;
int closest_break = break_index();
while (!Done()) {
- int next_position;
- if (alignment == STATEMENT_ALIGNED) {
- next_position = statement_position();
- } else {
- DCHECK(alignment == BREAK_POSITION_ALIGNED);
- next_position = position();
- }
+ int next_position = position();
if (source_position <= next_position &&
next_position - source_position < distance) {
closest_break = break_index();
@@ -168,108 +227,7 @@
return closest_break;
}
-CodeBreakIterator::CodeBreakIterator(Handle<DebugInfo> debug_info)
- : BreakIterator(debug_info),
- reloc_iterator_(debug_info->DebugCode(), GetModeMask()),
- source_position_iterator_(
- debug_info->DebugCode()->source_position_table()) {
- // There is at least one break location.
- DCHECK(!Done());
- Next();
-}
-
-int CodeBreakIterator::GetModeMask() {
- int mask = 0;
- mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_RETURN);
- mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_CALL);
- mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_TAIL_CALL);
- mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_POSITION);
- return mask;
-}
-
-void CodeBreakIterator::Next() {
- DisallowHeapAllocation no_gc;
- DCHECK(!Done());
-
- // Iterate through reloc info stopping at each breakable code target.
- bool first = break_index_ == -1;
-
- if (!first) reloc_iterator_.next();
- first = false;
- if (Done()) return;
-
- int offset = code_offset();
- while (!source_position_iterator_.done() &&
- source_position_iterator_.code_offset() <= offset) {
- position_ = source_position_iterator_.source_position().ScriptOffset();
- if (source_position_iterator_.is_statement()) {
- statement_position_ = position_;
- }
- source_position_iterator_.Advance();
- }
-
- DCHECK(RelocInfo::IsDebugBreakSlot(rmode()));
- break_index_++;
-}
-
-DebugBreakType CodeBreakIterator::GetDebugBreakType() {
- if (RelocInfo::IsDebugBreakSlotAtReturn(rmode())) {
- return DEBUG_BREAK_SLOT_AT_RETURN;
- } else if (RelocInfo::IsDebugBreakSlotAtCall(rmode())) {
- return DEBUG_BREAK_SLOT_AT_CALL;
- } else if (RelocInfo::IsDebugBreakSlotAtTailCall(rmode())) {
- return isolate()->is_tail_call_elimination_enabled()
- ? DEBUG_BREAK_SLOT_AT_TAIL_CALL
- : DEBUG_BREAK_SLOT_AT_CALL;
- } else if (RelocInfo::IsDebugBreakSlot(rmode())) {
- return DEBUG_BREAK_SLOT;
- } else {
- return NOT_DEBUG_BREAK;
- }
-}
-
-void CodeBreakIterator::SkipToPosition(int position,
- BreakPositionAlignment alignment) {
- CodeBreakIterator it(debug_info_);
- SkipTo(it.BreakIndexFromPosition(position, alignment));
-}
-
-void CodeBreakIterator::SetDebugBreak() {
- DebugBreakType debug_break_type = GetDebugBreakType();
- DCHECK(debug_break_type >= DEBUG_BREAK_SLOT);
- Builtins* builtins = isolate()->builtins();
- Handle<Code> target = debug_break_type == DEBUG_BREAK_SLOT_AT_RETURN
- ? builtins->Return_DebugBreak()
- : builtins->Slot_DebugBreak();
- DebugCodegen::PatchDebugBreakSlot(isolate(), rinfo()->pc(), target);
-}
-
-void CodeBreakIterator::ClearDebugBreak() {
- DCHECK(GetDebugBreakType() >= DEBUG_BREAK_SLOT);
- DebugCodegen::ClearDebugBreakSlot(isolate(), rinfo()->pc());
-}
-
-bool CodeBreakIterator::IsDebugBreak() {
- DCHECK(GetDebugBreakType() >= DEBUG_BREAK_SLOT);
- return DebugCodegen::DebugBreakSlotIsPatched(rinfo()->pc());
-}
-
-BreakLocation CodeBreakIterator::GetBreakLocation() {
- Handle<AbstractCode> code(AbstractCode::cast(debug_info_->DebugCode()));
- return BreakLocation(code, GetDebugBreakType(), code_offset(), position_);
-}
-
-BytecodeArrayBreakIterator::BytecodeArrayBreakIterator(
- Handle<DebugInfo> debug_info)
- : BreakIterator(debug_info),
- source_position_iterator_(
- debug_info->DebugBytecodeArray()->source_position_table()) {
- // There is at least one break location.
- DCHECK(!Done());
- Next();
-}
-
-void BytecodeArrayBreakIterator::Next() {
+void BreakIterator::Next() {
DisallowHeapAllocation no_gc;
DCHECK(!Done());
bool first = break_index_ == -1;
@@ -281,8 +239,8 @@
if (source_position_iterator_.is_statement()) {
statement_position_ = position_;
}
- DCHECK(position_ >= 0);
- DCHECK(statement_position_ >= 0);
+ DCHECK_LE(0, position_);
+ DCHECK_LE(0, statement_position_);
DebugBreakType type = GetDebugBreakType();
if (type != NOT_DEBUG_BREAK) break;
@@ -290,19 +248,23 @@
break_index_++;
}
-DebugBreakType BytecodeArrayBreakIterator::GetDebugBreakType() {
- BytecodeArray* bytecode_array = debug_info_->OriginalBytecodeArray();
+DebugBreakType BreakIterator::GetDebugBreakType() {
+ BytecodeArray bytecode_array = debug_info_->OriginalBytecodeArray();
interpreter::Bytecode bytecode =
- interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset()));
+ 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::kTailCall) {
- return isolate()->is_tail_call_elimination_enabled()
- ? DEBUG_BREAK_SLOT_AT_TAIL_CALL
- : DEBUG_BREAK_SLOT_AT_CALL;
+ } 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()) {
@@ -312,51 +274,56 @@
}
}
-void BytecodeArrayBreakIterator::SkipToPosition(
- int position, BreakPositionAlignment alignment) {
- BytecodeArrayBreakIterator it(debug_info_);
- SkipTo(it.BreakIndexFromPosition(position, alignment));
+void BreakIterator::SkipToPosition(int position) {
+ BreakIterator it(debug_info_);
+ SkipTo(it.BreakIndexFromPosition(position));
}
-void BytecodeArrayBreakIterator::SetDebugBreak() {
+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();
- 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));
+ BytecodeArray bytecode_array = debug_info_->DebugBytecodeArray();
+ BytecodeArray original = debug_info_->OriginalBytecodeArray();
+ bytecode_array.set(code_offset(), original.get(code_offset()));
}
-void BytecodeArrayBreakIterator::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()));
-}
-
-bool BytecodeArrayBreakIterator::IsDebugBreak() {
- DebugBreakType debug_break_type = GetDebugBreakType();
- if (debug_break_type == DEBUGGER_STATEMENT) return false;
- DCHECK(debug_break_type >= DEBUG_BREAK_SLOT);
- BytecodeArray* bytecode_array = debug_info_->DebugBytecodeArray();
- interpreter::Bytecode bytecode =
- interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset()));
- return interpreter::Bytecodes::IsDebugBreak(bytecode);
-}
-
-BreakLocation BytecodeArrayBreakIterator::GetBreakLocation() {
+BreakLocation BreakIterator::GetBreakLocation() {
Handle<AbstractCode> code(
- AbstractCode::cast(debug_info_->DebugBytecodeArray()));
- return BreakLocation(code, GetDebugBreakType(), code_offset(), position_);
+ 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;
@@ -369,110 +336,95 @@
// 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_.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::kZero;
- thread_local_.async_task_count_ = 0;
+ thread_local_.return_value_ = Smi::zero();
+ thread_local_.last_breakpoint_id_ = 0;
clear_suspended_generator();
- thread_local_.restart_fp_ = nullptr;
- base::NoBarrier_Store(&thread_local_.current_debug_scope_,
- static_cast<base::AtomicWord>(0));
+ 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) {
- // Simply reset state. Don't archive anything.
- ThreadInit();
+ MemCopy(storage, reinterpret_cast<char*>(&thread_local_),
+ ArchiveSpacePerThread());
return storage + ArchiveSpacePerThread();
}
-
char* Debug::RestoreDebug(char* storage) {
- // Simply reset state. Don't restore anything.
- ThreadInit();
+ 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) {
+ int current_frame_count = CurrentFrameCount();
+ int target_frame_count = thread_local_.target_frame_count_;
+ DCHECK(current_frame_count >= target_frame_count);
+ StackTraceFrameIterator frames_it(isolate_);
+ while (current_frame_count > target_frame_count) {
+ current_frame_count -= frames_it.FrameFunctionCount();
+ frames_it.Advance();
+ }
+ DCHECK(current_frame_count == target_frame_count);
+ // Set frame to what it was at Step break
+ thread_local_.break_frame_id_ = frames_it.frame()->id();
+
+ // Reset the previous step action for this thread.
+ PrepareStep(thread_local_.last_step_action_);
+ }
+
return storage + ArchiveSpacePerThread();
}
-int Debug::ArchiveSpacePerThread() { return 0; }
+int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal); }
-void Debug::Iterate(ObjectVisitor* v) {
- v->VisitPointer(&thread_local_.return_value_);
- v->VisitPointer(&thread_local_.suspended_generator_);
+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(DebugInfo* debug_info): next_(NULL) {
+DebugInfoListNode::DebugInfoListNode(Isolate* isolate, 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_ =
- Handle<DebugInfo>::cast(global_handles->Create(debug_info)).location();
+ GlobalHandles* global_handles = isolate->global_handles();
+ debug_info_ = global_handles->Create(debug_info).location();
}
-
DebugInfoListNode::~DebugInfoListNode() {
if (debug_info_ == nullptr) return;
- GlobalHandles::Destroy(reinterpret_cast<Object**>(debug_info_));
+ GlobalHandles::Destroy(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::DeserializeInternalFieldsCallback(),
- DEBUG_CONTEXT);
-
- // Fail if no context could be created.
- if (context.is_null()) return false;
-
- debug_context_ = Handle<Context>::cast(
- isolate_->global_handles()->Create(*context));
-
- feature_tracker()->Track(DebugFeatureTracker::kActive);
-
- return true;
-}
-
-
void Debug::Unload() {
ClearAllBreakPoints();
ClearStepping();
- 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>();
+ RemoveAllCoverageInfos();
+ ClearAllDebuggerHints();
+ debug_delegate_ = nullptr;
}
-void Debug::Break(JavaScriptFrame* frame) {
+void Debug::Break(JavaScriptFrame* frame, Handle<JSFunction> break_target) {
// Initialize LiveEdit.
LiveEdit::InitializeThreadLocal(this);
@@ -481,16 +433,13 @@
// 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 (!EnsureDebugInfo(shared)) return;
+ 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.
@@ -499,22 +448,43 @@
// 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()) {
+ if (!break_points_hit.is_null() || break_on_next_function_call()) {
+ StepAction lastStepAction = last_step_action();
// Clear all current stepping setup.
ClearStepping();
// Notify the debug event listeners.
- Handle<JSArray> jsarr = isolate_->factory()->NewJSArrayWithElements(
- break_points_hit.ToHandleChecked());
- OnDebugBreak(jsarr);
+ OnDebugBreak(!break_points_hit.is_null()
+ ? break_points_hit.ToHandleChecked()
+ : isolate_->factory()->empty_fixed_array(),
+ lastStepAction);
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:
@@ -527,10 +497,17 @@
case StepNext:
// Step next should not break in a deeper frame than target frame.
if (current_frame_count > target_frame_count) return;
- // For step-next, a tail call is like a return and should break.
- step_break = location.IsTailCall();
- // Fall through.
+ 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 ||
@@ -540,12 +517,13 @@
}
}
+ StepAction lastStepAction = last_step_action();
// Clear all current stepping setup.
ClearStepping();
if (step_break) {
// Notify the debug event listeners.
- OnDebugBreak(isolate_->factory()->undefined_value());
+ OnDebugBreak(isolate_->factory()->empty_fixed_array(), lastStepAction);
} else {
// Re-prepare to continue.
PrepareStep(step_action);
@@ -560,13 +538,11 @@
BreakLocation* location,
bool* has_break_points) {
bool has_break_points_to_check =
- break_points_active_ && location->HasBreakPoint(debug_info);
+ 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 {};
- Handle<Object> break_point_objects =
- debug_info->GetBreakPointObjects(location->position());
- return Debug::GetHitBreakPointObjects(break_point_objects);
+ return Debug::GetHitBreakPoints(debug_info, location->position());
}
@@ -580,15 +556,14 @@
FrameSummary summary = FrameSummary::GetTop(frame);
DCHECK(!summary.IsWasm());
Handle<JSFunction> function = summary.AsJavaScript().function();
- if (!function->shared()->HasDebugInfo()) return false;
- Handle<DebugInfo> debug_info(function->shared()->GetDebugInfo());
+ if (!function->shared().HasBreakInfo()) return false;
+ Handle<DebugInfo> debug_info(function->shared().GetDebugInfo(), isolate_);
// Enter the debugger.
DebugScope debug_scope(this);
- if (debug_scope.failed()) return false;
- List<BreakLocation> break_locations;
+ std::vector<BreakLocation> break_locations;
BreakLocation::AllAtCurrentStatement(debug_info, frame, &break_locations);
bool has_break_points_at_all = false;
- for (int i = 0; i < break_locations.length(); i++) {
+ 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);
@@ -598,65 +573,55 @@
return has_break_points_at_all;
}
-
-MaybeHandle<Object> Debug::CallFunction(const char* name, int argc,
- Handle<Object> args[]) {
- 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();
- MaybeHandle<Object> maybe_exception;
- return Execution::TryCall(isolate_, fun, undefined, argc, args,
- Execution::MessageHandling::kReport,
- &maybe_exception);
-}
-
-
// Check whether a single break point object is triggered.
-bool Debug::CheckBreakPoint(Handle<Object> break_point_object) {
- Factory* factory = isolate_->factory();
+bool Debug::CheckBreakPoint(Handle<BreakPoint> break_point,
+ bool is_break_at_entry) {
HandleScope scope(isolate_);
- // 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 };
+ if (!break_point->condition().length()) return true;
+ Handle<String> condition(break_point->condition(), isolate_);
+ MaybeHandle<Object> maybe_result;
Handle<Object> result;
- if (!CallFunction("IsBreakPointTriggered", arraysize(argv), argv)
- .ToHandle(&result)) {
- return false;
+
+ 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);
}
- // Return whether the break point is triggered.
- return result->IsTrue(isolate_);
+ if (!maybe_result.ToHandle(&result)) {
+ if (isolate_->has_pending_exception()) {
+ isolate_->clear_pending_exception();
+ }
+ return false;
+ }
+ return result->BooleanValue(isolate_);
}
-
-bool Debug::SetBreakPoint(Handle<JSFunction> function,
- Handle<Object> break_point_object,
+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.
- Handle<SharedFunctionInfo> shared(function->shared());
- if (!EnsureDebugInfo(shared)) return true;
- Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ if (!EnsureBreakInfo(shared)) return false;
+ PrepareFunctionForDebugExecution(shared);
+
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
// Source positions starts with zero.
- DCHECK(*source_position >= 0);
+ DCHECK_LE(0, *source_position);
// Find the break point and change it.
- *source_position =
- FindBreakablePosition(debug_info, *source_position, STATEMENT_ALIGNED);
- DebugInfo::SetBreakPoint(debug_info, *source_position, break_point_object);
+ *source_position = FindBreakablePosition(debug_info, *source_position);
+ DebugInfo::SetBreakPoint(isolate_, debug_info, *source_position, break_point);
// At least one active break point now.
- DCHECK(debug_info->GetBreakPointCount() > 0);
+ DCHECK_LT(0, debug_info->GetBreakPointCount(isolate_));
ClearBreakPoints(debug_info);
ApplyBreakPoints(debug_info);
@@ -665,16 +630,15 @@
return true;
}
-
bool Debug::SetBreakPointForScript(Handle<Script> script,
- Handle<Object> break_point_object,
- int* source_position,
- BreakPositionAlignment alignment) {
+ 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) {
- Handle<WasmCompiledModule> compiled_module(
- WasmCompiledModule::cast(script->wasm_compiled_module()), isolate_);
- return WasmCompiledModule::SetBreakPoint(compiled_module, source_position,
- break_point_object);
+ RecordWasmScriptWithBreakpoints(script);
+ return WasmScript::SetBreakPoint(script, source_position, break_point);
}
HandleScope scope(isolate_);
@@ -686,22 +650,27 @@
// Make sure the function has set up the debug info.
Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>::cast(result);
- if (!EnsureDebugInfo(shared)) return false;
+ 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->start_position() > *source_position) {
- *source_position = shared->start_position();
+ if (shared->StartPosition() > *source_position) {
+ *source_position = shared->StartPosition();
}
- Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
- // Find the break point and change it.
- *source_position =
- FindBreakablePosition(debug_info, *source_position, alignment);
- DebugInfo::SetBreakPoint(debug_info, *source_position, break_point_object);
+ // 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(debug_info->GetBreakPointCount() > 0);
+ DCHECK_LT(0, debug_info->GetBreakPointCount(isolate_));
ClearBreakPoints(debug_info);
ApplyBreakPoints(debug_info);
@@ -711,73 +680,69 @@
}
int Debug::FindBreakablePosition(Handle<DebugInfo> debug_info,
- int source_position,
- BreakPositionAlignment alignment) {
- int statement_position;
- int position;
- if (debug_info->HasDebugCode()) {
- CodeBreakIterator it(debug_info);
- it.SkipToPosition(source_position, alignment);
- statement_position = it.statement_position();
- position = it.position();
+ int source_position) {
+ if (debug_info->CanBreakAtEntry()) {
+ return kBreakAtEntryPosition;
} else {
- DCHECK(debug_info->HasDebugBytecodeArray());
- BytecodeArrayBreakIterator it(debug_info);
- it.SkipToPosition(source_position, alignment);
- statement_position = it.statement_position();
- position = it.position();
+ DCHECK(debug_info->HasInstrumentedBytecodeArray());
+ BreakIterator it(debug_info);
+ it.SkipToPosition(source_position);
+ return it.position();
}
- return alignment == STATEMENT_ALIGNED ? statement_position : 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;
- if (debug_info->HasDebugCode()) {
- CodeBreakIterator it(debug_info);
- it.SkipToPosition(info->source_position(), BREAK_POSITION_ALIGNED);
- it.SetDebugBreak();
- }
- if (debug_info->HasDebugBytecodeArray()) {
- BytecodeArrayBreakIterator it(debug_info);
- it.SkipToPosition(info->source_position(), BREAK_POSITION_ALIGNED);
+ 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) {
- DisallowHeapAllocation no_gc;
- if (debug_info->HasDebugCode()) {
- for (CodeBreakIterator it(debug_info); !it.Done(); it.Next()) {
- it.ClearDebugBreak();
+ 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;
}
- }
- if (debug_info->HasDebugBytecodeArray()) {
- for (BytecodeArrayBreakIterator it(debug_info); !it.Done(); it.Next()) {
+
+ DisallowHeapAllocation no_gc;
+ for (BreakIterator it(debug_info); !it.Done(); it.Next()) {
it.ClearDebugBreak();
}
}
}
-void Debug::ClearBreakPoint(Handle<Object> break_point_object) {
+void Debug::ClearBreakPoint(Handle<BreakPoint> break_point) {
HandleScope scope(isolate_);
- for (DebugInfoListNode* node = debug_info_list_; node != NULL;
+ for (DebugInfoListNode* node = debug_info_list_; node != nullptr;
node = node->next()) {
- Handle<Object> result =
- DebugInfo::FindBreakPointInfo(node->debug_info(), break_point_object);
+ 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(debug_info, break_point_object)) {
+ if (DebugInfo::ClearBreakPoint(isolate_, debug_info, break_point)) {
ClearBreakPoints(debug_info);
- if (debug_info->GetBreakPointCount() == 0) {
- RemoveDebugInfoAndClearFromShared(debug_info);
+ if (debug_info->GetBreakPointCount(isolate_) == 0) {
+ RemoveBreakInfoAndMaybeFree(debug_info);
} else {
ApplyBreakPoints(debug_info);
}
@@ -786,35 +751,113 @@
}
}
-// Clear out all the debug break code. This is ONLY supposed to be used when
-// shutting down the debugger as it will leave the break point information in
-// DebugInfo even though the code is patched back to the non break point state.
-void Debug::ClearAllBreakPoints() {
- for (DebugInfoListNode* node = debug_info_list_; node != NULL;
- node = node->next()) {
- ClearBreakPoints(node->debug_info());
+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);
}
- // Remove all debug info.
- while (debug_info_list_ != NULL) {
- RemoveDebugInfoAndClearFromShared(debug_info_list_->debug_info());
+ 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);
}
}
-void Debug::FloodWithOneShot(Handle<SharedFunctionInfo> shared) {
- if (!shared->IsSubjectToDebugging() || IsBlackboxed(shared)) return;
- // Make sure the function is compiled and has set up the debug info.
- if (!EnsureDebugInfo(shared)) return;
- Handle<DebugInfo> debug_info(shared->GetDebugInfo());
- // Flood the function with break points.
- if (debug_info->HasDebugCode()) {
- for (CodeBreakIterator it(debug_info); !it.Done(); it.Next()) {
- it.SetDebugBreak();
+void Debug::RecordWasmScriptWithBreakpoints(Handle<Script> script) {
+ if (wasm_scripts_with_breakpoints_.is_null()) {
+ Handle<WeakArrayList> new_list = isolate_->factory()->NewWeakArrayList(4);
+ wasm_scripts_with_breakpoints_ =
+ isolate_->global_handles()->Create(*new_list);
+ }
+ {
+ DisallowHeapAllocation no_gc;
+ for (int idx = wasm_scripts_with_breakpoints_->length() - 1; idx >= 0;
+ --idx) {
+ HeapObject wasm_script;
+ if (wasm_scripts_with_breakpoints_->Get(idx).GetHeapObject(
+ &wasm_script) &&
+ wasm_script == *script) {
+ return;
+ }
}
}
- if (debug_info->HasDebugBytecodeArray()) {
- for (BytecodeArrayBreakIterator it(debug_info); !it.Done(); it.Next()) {
- it.SetDebugBreak();
+ Handle<WeakArrayList> new_list = WeakArrayList::Append(
+ isolate_, wasm_scripts_with_breakpoints_, MaybeObjectHandle{script});
+ if (*new_list != *wasm_scripts_with_breakpoints_) {
+ isolate_->global_handles()->Destroy(
+ wasm_scripts_with_breakpoints_.location());
+ wasm_scripts_with_breakpoints_ =
+ isolate_->global_handles()->Create(*new_list);
+ }
+}
+
+// Clear out all the debug break code.
+void Debug::ClearAllBreakPoints() {
+ ClearAllDebugInfos([=](Handle<DebugInfo> info) {
+ ClearBreakPoints(info);
+ info->ClearBreakInfo(isolate_);
+ });
+ // Clear all wasm breakpoints.
+ if (!wasm_scripts_with_breakpoints_.is_null()) {
+ DisallowHeapAllocation no_gc;
+ for (int idx = wasm_scripts_with_breakpoints_->length() - 1; idx >= 0;
+ --idx) {
+ HeapObject raw_wasm_script;
+ if (wasm_scripts_with_breakpoints_->Get(idx).GetHeapObject(
+ &raw_wasm_script)) {
+ Script wasm_script = Script::cast(raw_wasm_script);
+ WasmScript::ClearAllBreakpoints(wasm_script);
+ wasm_script.wasm_native_module()->GetDebugInfo()->RemoveIsolate(
+ isolate_);
+ }
}
+ wasm_scripts_with_breakpoints_ = Handle<WeakArrayList>{};
+ }
+}
+
+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();
}
}
@@ -835,37 +878,63 @@
}
}
-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 {};
+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_point_objects);
+ break_points_hit->set(0, *break_points);
return break_points_hit;
}
- Handle<FixedArray> array(FixedArray::cast(*break_point_objects));
+ 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_object(array->get(i), isolate_);
- if (CheckBreakPoint(break_point_object)) {
- break_points_hit->set(break_points_hit_count++, *break_point_object);
+ 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(break_points_hit_count);
+ 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);
+ 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 (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_));
}
@@ -877,7 +946,8 @@
thread_local_.last_step_action_ = StepIn;
UpdateHookOnFunctionCall();
Handle<JSFunction> function(
- JSGeneratorObject::cast(thread_local_.suspended_generator_)->function());
+ JSGeneratorObject::cast(thread_local_.suspended_generator_).function(),
+ isolate_);
FloodWithOneShot(Handle<SharedFunctionInfo>(function->shared(), isolate_));
clear_suspended_generator();
}
@@ -897,9 +967,9 @@
while (!it.done()) {
JavaScriptFrame* frame = it.frame();
if (frame->LookupExceptionHandlerInTable(nullptr, nullptr) > 0) break;
- List<SharedFunctionInfo*> infos;
+ std::vector<SharedFunctionInfo> infos;
frame->GetFunctions(&infos);
- current_frame_count -= infos.length();
+ current_frame_count -= infos.size();
it.Advance();
}
@@ -915,22 +985,21 @@
// Deoptimize frame to ensure calls are checked for step-in.
Deoptimizer::DeoptimizeFunction(frame->function());
}
- List<FrameSummary> summaries;
+ std::vector<FrameSummary> summaries;
frame->Summarize(&summaries);
- for (int i = summaries.length() - 1; i >= 0; i--, current_frame_count--) {
+ 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.length() > 1) {
- Handle<AbstractCode> code =
- summaries[i].AsJavaScript().abstract_code();
- CHECK_EQ(AbstractCode::INTERPRETED_FUNCTION, code->kind());
- BytecodeArray* bytecode = code->GetBytecodeArray();
- HandlerTable* table = HandlerTable::cast(bytecode->handler_table());
- int code_offset = summaries[i].code_offset();
+ if (summaries.size() > 1) {
+ Handle<AbstractCode> code = summary.AsJavaScript().abstract_code();
+ CHECK_EQ(CodeKind::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);
+ int index = table.LookupRange(code_offset, nullptr, &prediction);
if (index > 0) found_handler = true;
} else {
found_handler = true;
@@ -945,8 +1014,8 @@
continue;
}
Handle<SharedFunctionInfo> info(
- summaries[i].AsJavaScript().function()->shared());
- if (!info->IsSubjectToDebugging() || IsBlackboxed(info)) continue;
+ summary.AsJavaScript().function()->shared(), isolate_);
+ if (IsBlackboxed(info)) continue;
FloodWithOneShot(info);
return;
}
@@ -954,7 +1023,6 @@
}
}
-
void Debug::PrepareStep(StepAction step_action) {
HandleScope scope(isolate_);
@@ -964,84 +1032,115 @@
// 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();
+ StackFrameId frame_id = break_frame_id();
// If there is no JavaScript stack don't do anything.
- if (frame_id == StackFrame::NO_ID) return;
+ if (frame_id == StackFrameId::NO_ID) return;
feature_tracker()->Track(DebugFeatureTracker::kStepping);
thread_local_.last_step_action_ = step_action;
- UpdateHookOnFunctionCall();
StackTraceFrameIterator frames_it(isolate_, frame_id);
- StandardFrame* frame = frames_it.frame();
+ CommonFrame* 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 (!EnsureDebugInfo(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.
- if (location.IsReturn()) step_action = StepOut;
- // A step-next at a tail call is a step-out.
- if (location.IsTailCall() && step_action == StepNext) step_action = StepOut;
- // 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());
+ BreakLocation location = BreakLocation::Invalid();
+ Handle<SharedFunctionInfo> shared;
int current_frame_count = CurrentFrameCount();
- thread_local_.last_frame_count_ = current_frame_count;
- // No longer perform the current async step.
- clear_suspended_generator();
+
+ 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();
+ } else if (frame->is_wasm()) {
+ // Handle stepping in Liftoff code.
+ WasmFrame* wasm_frame = WasmFrame::cast(frame);
+ wasm::WasmCodeRefScope code_ref_scope;
+ wasm::WasmCode* code = wasm_frame->wasm_code();
+ if (code->is_liftoff()) {
+ wasm_frame->native_module()->GetDebugInfo()->PrepareStep(isolate_,
+ frame_id);
+ }
+ // In case the wasm code returns, prepare the next frame (if JS) to break.
+ step_action = StepOut;
+ UpdateHookOnFunctionCall();
+ }
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 (!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()) {
- // TODO(clemensh): Implement stepping out from JS to WASM.
- if (frames_it.frame()->is_wasm()) continue;
+ 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_);
- List<Handle<SharedFunctionInfo>> infos;
+ std::vector<Handle<SharedFunctionInfo>> infos;
frame->GetFunctions(&infos);
- for (; !infos.is_empty(); current_frame_count--) {
- Handle<SharedFunctionInfo> info = infos.RemoveLast();
+ 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 (!info->IsSubjectToDebugging() || IsBlackboxed(info)) continue;
+ if (IsBlackboxed(info)) continue;
FloodWithOneShot(info);
thread_local_.target_frame_count_ = current_frame_count;
return;
@@ -1051,9 +1150,9 @@
}
case StepNext:
thread_local_.target_frame_count_ = current_frame_count;
- // Fall through.
+ V8_FALLTHROUGH;
case StepIn:
- // TODO(clemensh): Implement stepping from JS into WASM.
+ // TODO(clemensb): Implement stepping from JS into wasm.
FloodWithOneShot(shared);
break;
}
@@ -1061,44 +1160,28 @@
// Simple function for returning the source positions for active break points.
Handle<Object> Debug::GetSourceBreakLocations(
- Handle<SharedFunctionInfo> shared,
- BreakPositionAlignment position_alignment) {
- Isolate* isolate = shared->GetIsolate();
- if (!shared->HasDebugInfo()) {
+ Isolate* isolate, Handle<SharedFunctionInfo> shared) {
+ if (!shared->HasBreakInfo()) {
return isolate->factory()->undefined_value();
}
- Handle<DebugInfo> debug_info(shared->GetDebugInfo());
- if (debug_info->GetBreakPointCount() == 0) {
+
+ 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());
+ 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();
+ 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;
- Smi* position = NULL;
- if (position_alignment == STATEMENT_ALIGNED) {
- if (debug_info->HasDebugCode()) {
- CodeBreakIterator it(debug_info);
- it.SkipToPosition(break_point_info->source_position(),
- BREAK_POSITION_ALIGNED);
- position = Smi::FromInt(it.statement_position());
- } else {
- DCHECK(debug_info->HasDebugBytecodeArray());
- BytecodeArrayBreakIterator it(debug_info);
- it.SkipToPosition(break_point_info->source_position(),
- BREAK_POSITION_ALIGNED);
- position = Smi::FromInt(it.statement_position());
- }
- } else {
- DCHECK_EQ(BREAK_POSITION_ALIGNED, position_alignment);
- position = Smi::FromInt(break_point_info->source_position());
+ for (int j = 0; j < break_points; ++j) {
+ locations->set(count++,
+ Smi::FromInt(break_point_info.source_position()));
}
- for (int j = 0; j < break_points; ++j) locations->set(count++, position);
}
}
return locations;
@@ -1110,8 +1193,11 @@
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();
}
@@ -1123,7 +1209,7 @@
// 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 != NULL;
+ for (DebugInfoListNode* node = debug_info_list_; node != nullptr;
node = node->next()) {
Handle<DebugInfo> debug_info = node->debug_info();
ClearBreakPoints(debug_info);
@@ -1131,303 +1217,290 @@
}
}
+void Debug::DeoptimizeFunction(Handle<SharedFunctionInfo> shared) {
+ // Deoptimize all code compiled from this shared function info including
+ // inlining.
+ isolate_->AbortConcurrentOptimization(BlockingBehavior::kBlock);
-bool MatchingCodeTargets(Code* target1, Code* target2) {
- if (target1 == target2) return true;
- if (target1->kind() != target2->kind()) return false;
- return target1->is_handler() || target1->is_inline_cache_stub();
+ 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;
-// Count the number of calls before the current frame PC to find the
-// corresponding PC in the newly recompiled code.
-static Address ComputeNewPcForRedirect(Code* new_code, Code* old_code,
- Address old_pc) {
- DCHECK_EQ(old_code->kind(), Code::FUNCTION);
- DCHECK_EQ(new_code->kind(), Code::FUNCTION);
- DCHECK(new_code->has_debug_break_slots());
- static const int mask = RelocInfo::kCodeTargetMask;
-
- // Find the target of the current call.
- Code* target = NULL;
- intptr_t delta = 0;
- for (RelocIterator it(old_code, mask); !it.done(); it.next()) {
- RelocInfo* rinfo = it.rinfo();
- Address current_pc = rinfo->pc();
- // The frame PC is behind the call instruction by the call instruction size.
- if (current_pc > old_pc) break;
- delta = old_pc - current_pc;
- target = Code::GetCodeFromTargetAddress(rinfo->target_address());
+ // 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);
- // Count the number of calls to the same target before the current call.
- int index = 0;
- for (RelocIterator it(old_code, mask); !it.done(); it.next()) {
- RelocInfo* rinfo = it.rinfo();
- Address current_pc = rinfo->pc();
- if (current_pc > old_pc) break;
- Code* current = Code::GetCodeFromTargetAddress(rinfo->target_address());
- if (MatchingCodeTargets(target, current)) index++;
+ 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);
}
-
- DCHECK(index > 0);
-
- // Repeat the count on the new code to find corresponding call.
- for (RelocIterator it(new_code, mask); !it.done(); it.next()) {
- RelocInfo* rinfo = it.rinfo();
- Code* current = Code::GetCodeFromTargetAddress(rinfo->target_address());
- if (MatchingCodeTargets(target, current)) index--;
- if (index == 0) return rinfo->pc() + delta;
- }
-
- UNREACHABLE();
- return NULL;
+ debug_info->set_flags(debug_info->flags() |
+ DebugInfo::kPreparedForDebugExecution);
}
-
-class RedirectActiveFunctions : public ThreadVisitor {
- public:
- explicit RedirectActiveFunctions(SharedFunctionInfo* shared)
- : shared_(shared) {
- DCHECK(shared->HasDebugCode());
+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;
+ }
+ }
}
- 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_optimized()) continue;
- if (!function->Inlines(shared_)) continue;
+ if (!needs_to_use_trampoline) return;
- if (frame->is_interpreted()) {
- InterpretedFrame* interpreted_frame =
- reinterpret_cast<InterpretedFrame*>(frame);
- BytecodeArray* debug_copy =
- shared_->GetDebugInfo()->DebugBytecodeArray();
- interpreted_frame->PatchBytecodeArray(debug_copy);
+ 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;
- }
-
- Code* frame_code = frame->LookupCode();
- DCHECK(frame_code->kind() == Code::FUNCTION);
- if (frame_code->has_debug_break_slots()) continue;
-
- Code* new_code = function->shared()->code();
- Address old_pc = frame->pc();
- Address new_pc = ComputeNewPcForRedirect(new_code, frame_code, old_pc);
-
- if (FLAG_trace_deopt) {
- PrintF("Replacing pc for debugging: %08" V8PRIxPTR " => %08" V8PRIxPTR
- "\n",
- reinterpret_cast<intptr_t>(old_pc),
- reinterpret_cast<intptr_t>(new_pc));
- }
-
- if (FLAG_enable_embedded_constant_pool) {
- // Update constant pool pointer for new code.
- frame->set_constant_pool(new_code->constant_pool());
- }
-
- // Patch the return address to return into the code with
- // debug break slots.
- frame->set_pc(new_pc);
- }
- }
-
- private:
- SharedFunctionInfo* shared_;
- DisallowHeapAllocation no_gc_;
-};
-
-
-bool Debug::PrepareFunctionForBreakPoints(Handle<SharedFunctionInfo> shared) {
- DCHECK(shared->is_compiled());
-
- if (isolate_->concurrent_recompilation_enabled()) {
- isolate_->optimizing_compile_dispatcher()->Flush(
- OptimizingCompileDispatcher::BlockingBehavior::kBlock);
- }
-
- List<Handle<JSFunction> > functions;
-
- // Flush all optimized code maps. Note that the below heap iteration does not
- // cover this, because the given function might have been inlined into code
- // for which no JSFunction exists.
- {
- SharedFunctionInfo::GlobalIterator iterator(isolate_);
- while (SharedFunctionInfo* shared = iterator.Next()) {
- shared->ClearCodeFromOptimizedCodeMap();
- }
- }
-
- // The native context also has a list of OSR'd optimized code. Clear it.
- isolate_->ClearOSROptimizedCode();
-
- // Make sure we abort incremental marking.
- isolate_->heap()->CollectAllGarbage(Heap::kMakeHeapIterableMask,
- GarbageCollectionReason::kDebugger);
-
- DCHECK(shared->is_compiled());
- bool baseline_exists = shared->HasBaselineCode();
-
- {
- // TODO(yangguo): with bytecode, we still walk the heap to find all
- // optimized code for the function to deoptimize. We can probably be
- // smarter here and avoid the heap walk.
- HeapIterator iterator(isolate_->heap());
- HeapObject* obj;
-
- while ((obj = iterator.next())) {
- if (obj->IsJSFunction()) {
- JSFunction* function = JSFunction::cast(obj);
- if (!function->Inlines(*shared)) continue;
- if (function->code()->kind() == Code::OPTIMIZED_FUNCTION) {
- Deoptimizer::DeoptimizeFunction(function);
+ } 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);
}
- if (baseline_exists && function->shared() == *shared) {
- functions.Add(handle(function));
+ } else if (obj.IsJSObject()) {
+ JSObject object = JSObject::cast(obj);
+ DescriptorArray descriptors =
+ object.map().instance_descriptors(kRelaxedLoad);
+
+ 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);
+ }
}
}
}
}
- // We do not need to replace code to debug bytecode.
- DCHECK(baseline_exists || functions.is_empty());
-
- // We do not need to recompile to debug bytecode.
- if (baseline_exists && !shared->code()->has_debug_break_slots()) {
- if (!Compiler::CompileDebugCode(shared)) return false;
+ // 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);
+ }
}
- for (Handle<JSFunction> const function : functions) {
- function->ReplaceCode(shared->code());
- JSFunction::EnsureLiterals(function);
+ // 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);
}
-
- // 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);
-
- return true;
}
namespace {
template <typename Iterator>
void GetBreakablePositions(Iterator* it, int start_position, int end_position,
- BreakPositionAlignment alignment,
- std::set<int>* positions) {
- it->SkipToPosition(start_position, alignment);
- while (!it->Done() && it->position() < end_position &&
- it->position() >= start_position) {
- positions->insert(alignment == STATEMENT_ALIGNED ? it->statement_position()
- : it->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, BreakPositionAlignment alignment,
- std::set<int>* positions) {
- if (debug_info->HasDebugCode()) {
- CodeBreakIterator it(debug_info);
- GetBreakablePositions(&it, start_position, end_position, alignment,
- positions);
- } else {
- DCHECK(debug_info->HasDebugBytecodeArray());
- BytecodeArrayBreakIterator it(debug_info);
- GetBreakablePositions(&it, start_position, end_position, alignment,
- positions);
- }
+ 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, std::set<int>* positions) {
+ 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_);
- List<Handle<SharedFunctionInfo>> candidates;
- SharedFunctionInfo::ScriptIterator iterator(script);
- for (SharedFunctionInfo* info = iterator.Next(); info != nullptr;
+ 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->end_position() < start_position ||
- info->start_position() >= end_position) {
+ if (info.EndPosition() < start_position ||
+ info.StartPosition() >= end_position) {
continue;
}
- if (!info->IsSubjectToDebugging()) continue;
- if (!info->HasDebugCode() && !info->allows_lazy_compilation()) continue;
- candidates.Add(i::handle(info));
+ 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 (int i = 0; i < candidates.length(); ++i) {
- // Code that cannot be compiled lazily are internal and not debuggable.
- DCHECK(candidates[i]->allows_lazy_compilation());
- if (!candidates[i]->HasDebugCode()) {
- if (!Compiler::CompileDebugCode(candidates[i])) {
+ for (const auto& candidate : candidates) {
+ IsCompiledScope is_compiled_scope(candidate->is_compiled_scope(isolate_));
+ 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;
}
}
- if (!EnsureDebugInfo(candidates[i])) return false;
+ 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 (int i = 0; i < candidates.length(); ++i) {
- CHECK(candidates[i]->HasDebugInfo());
- Handle<DebugInfo> debug_info(candidates[i]->GetDebugInfo());
+ for (const auto& candidate : candidates) {
+ CHECK(candidate->HasBreakInfo());
+ Handle<DebugInfo> debug_info(candidate->GetDebugInfo(), isolate_);
FindBreakablePositions(debug_info, start_position, end_position,
- BREAK_POSITION_ALIGNED, positions);
+ locations);
}
return true;
}
UNREACHABLE();
- return false;
-}
-
-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_(NULL),
- current_candidate_closure_(NULL),
- current_start_position_(kNoSourcePosition),
+ : current_start_position_(kNoSourcePosition),
target_position_(target_position) {}
- void NewCandidate(SharedFunctionInfo* shared, JSFunction* closure = NULL) {
- if (!shared->IsSubjectToDebugging()) return;
- int start_position = shared->function_token_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->start_position();
+ start_position = shared.StartPosition();
}
if (start_position > target_position_) return;
- if (target_position_ > shared->end_position()) return;
+ if (target_position_ > shared.EndPosition()) return;
- if (current_candidate_ != NULL) {
+ if (!current_candidate_.is_null()) {
if (current_start_position_ == start_position &&
- shared->end_position() == current_candidate_->end_position()) {
+ shared.EndPosition() == current_candidate_.EndPosition()) {
// If we already have a matching closure, do not throw it away.
- if (current_candidate_closure_ != NULL && closure == NULL) return;
+ 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;
+ if (!current_candidate_.is_toplevel() && shared.is_toplevel()) return;
} else if (start_position < current_start_position_ ||
- current_candidate_->end_position() < shared->end_position()) {
+ current_candidate_.EndPosition() < shared.EndPosition()) {
return;
}
}
@@ -1437,13 +1510,13 @@
current_candidate_closure_ = closure;
}
- SharedFunctionInfo* Result() { return current_candidate_; }
+ SharedFunctionInfo Result() { return current_candidate_; }
- JSFunction* ResultClosure() { return current_candidate_closure_; }
+ JSFunction ResultClosure() { return current_candidate_closure_; }
private:
- SharedFunctionInfo* current_candidate_;
- JSFunction* current_candidate_closure_;
+ SharedFunctionInfo current_candidate_;
+ JSFunction current_candidate_closure_;
int current_start_position_;
int target_position_;
DisallowHeapAllocation no_gc_;
@@ -1464,26 +1537,28 @@
// 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;
+ SharedFunctionInfo shared;
+ IsCompiledScope is_compiled_scope;
{
SharedFunctionInfoFinder finder(position);
- SharedFunctionInfo::ScriptIterator iterator(script);
- for (SharedFunctionInfo* info = iterator.Next(); info != nullptr;
+ SharedFunctionInfo::ScriptIterator iterator(isolate_, *script);
+ for (SharedFunctionInfo info = iterator.Next(); !info.is_null();
info = iterator.Next()) {
finder.NewCandidate(info);
}
shared = finder.Result();
- if (shared == NULL) break;
- // We found it if it's already compiled and has debug code.
- if (shared->HasDebugCode()) {
- Handle<SharedFunctionInfo> shared_handle(shared);
+ if (shared.is_null()) break;
+ // We found it if it's already compiled.
+ is_compiled_scope = shared.is_compiled_scope(isolate_);
+ 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 PrepareFunctionForBreakpoints.
+ // info while bypassing PrepareFunctionForDebugExecution.
if (iteration > 1) {
AllowHeapAllocation allow_before_return;
- CreateDebugInfo(shared_handle);
+ CreateBreakInfo(shared_handle);
}
return shared_handle;
}
@@ -1491,83 +1566,163 @@
// 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::CompileDebugCode(handle(shared))) break;
+ 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::EnsureDebugInfo(Handle<SharedFunctionInfo> shared) {
- // Return if we already have the debug info for shared.
- if (shared->HasDebugInfo()) return true;
- if (!shared->IsSubjectToDebugging()) return false;
- if (!shared->is_compiled() && !Compiler::CompileDebugCode(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;
}
-
- // 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.
- CreateDebugInfo(shared);
- CHECK(PrepareFunctionForBreakPoints(shared));
+ IsCompiledScope is_compiled_scope = shared->is_compiled_scope(isolate_);
+ 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);
-void Debug::CreateDebugInfo(Handle<SharedFunctionInfo> shared) {
- // Create the debug info object.
- Handle<DebugInfo> debug_info = isolate_->factory()->NewDebugInfo(shared);
+ // Initialize with break information.
- // Add debug info to the list.
- DebugInfoListNode* node = new DebugInfoListNode(*debug_info);
- node->set_next(debug_info_list_);
- debug_info_list_ = node;
+ 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_);
-void Debug::RemoveDebugInfoAndClearFromShared(Handle<DebugInfo> debug_info) {
+ // 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_);
- Handle<SharedFunctionInfo> shared(debug_info->shared());
-
- DCHECK_NOT_NULL(debug_info_list_);
- // Run through the debug info objects to find this one and remove it.
- DebugInfoListNode* prev = NULL;
- DebugInfoListNode* current = debug_info_list_;
- while (current != NULL) {
- if (current->debug_info().is_identical_to(debug_info)) {
- // Unlink from list. If prev is NULL we are looking at the first element.
- if (prev == NULL) {
- debug_info_list_ = current->next();
- } else {
- prev->set_next(current->next());
- }
- shared->set_debug_info(Smi::FromInt(debug_info->debugger_hints()));
- delete current;
- return;
- }
- // Move to next in list.
- prev = current;
- current = current->next();
+ *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(),
+ kReleaseStore);
+
+ 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());
+ 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->HasDebugInfo()) return false;
+ if (!shared->HasBreakInfo()) return false;
DCHECK(!frame->is_optimized());
- Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo(), isolate_);
BreakLocation location = BreakLocation::FromFrame(debug_info, frame);
- return location.IsReturn() || location.IsTailCall();
+ return location.IsReturn();
}
void Debug::ScheduleFrameRestart(StackFrame* frame) {
@@ -1582,7 +1737,7 @@
// Reset break frame ID to the frame below the restarted frame.
StackTraceFrameIterator it(isolate_);
- thread_local_.break_frame_id_ = StackFrame::NO_ID;
+ 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();
@@ -1591,82 +1746,29 @@
}
}
-
-bool Debug::IsDebugGlobal(JSGlobalObject* global) {
- return is_loaded() && global == debug_context()->global_object();
-}
-
-
Handle<FixedArray> Debug::GetLoadedScripts() {
- isolate_->heap()->CollectAllGarbage(Heap::kFinalizeIncrementalMarkingMask,
+ isolate_->heap()->CollectAllGarbage(Heap::kNoGCFlags,
GarbageCollectionReason::kDebugger);
Factory* factory = isolate_->factory();
- if (!factory->script_list()->IsWeakFixedArray()) {
+ if (!factory->script_list()->IsWeakArrayList()) {
return factory->empty_fixed_array();
}
- Handle<WeakFixedArray> array =
- Handle<WeakFixedArray>::cast(factory->script_list());
- Handle<FixedArray> results = factory->NewFixedArray(array->Length());
+ Handle<WeakArrayList> array =
+ Handle<WeakArrayList>::cast(factory->script_list());
+ Handle<FixedArray> results = factory->NewFixedArray(array->length());
int length = 0;
{
Script::Iterator iterator(isolate_);
- Script* script;
- while ((script = iterator.Next())) {
- if (script->HasValidSource()) results->set(length++, script);
+ for (Script script = iterator.Next(); !script.is_null();
+ script = iterator.Next()) {
+ if (script.HasValidSource()) results->set(length++, script);
}
}
- results->Shrink(length);
- return results;
+ return FixedArray::ShrinkOrEmpty(isolate_, results, length);
}
-
-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;
+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_);
@@ -1675,11 +1777,22 @@
scheduled_exception = handle(isolate_->scheduled_exception(), isolate_);
isolate_->clear_scheduled_exception();
}
- OnException(exception, isolate_->GetPromiseOnStackOnThrow());
+ 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) {
@@ -1690,55 +1803,33 @@
if (!promise->IsJSObject() ||
JSReceiver::GetDataProperty(Handle<JSObject>::cast(promise), key)
->IsUndefined(isolate_)) {
- OnException(value, promise);
+ OnException(value, promise, v8::debug::kPromiseRejection);
}
}
-namespace {
-v8::Local<v8::Context> GetDebugEventContext(Isolate* isolate) {
- Handle<Context> context = isolate->debug()->debugger_entry()->GetContext();
- // Isolate::context() may have been NULL when "script collected" event
- // occured.
- 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) {
- JavaScriptFrameIterator it(isolate_);
- if (it.done()) return false;
// Uncaught exception is blackboxed if all current frames are blackboxed,
// caught exception if top frame is blackboxed.
- bool is_top_frame_blackboxed = IsFrameBlackboxed(it.frame());
+ 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;
- it.Advance();
- while (!it.done()) {
- if (!IsFrameBlackboxed(it.frame())) return false;
- it.Advance();
- }
- return true;
+ return AllFramesOnStackAreBlackboxed();
}
bool Debug::IsFrameBlackboxed(JavaScriptFrame* frame) {
HandleScope scope(isolate_);
- if (!frame->HasInlinedFrames()) {
- Handle<SharedFunctionInfo> shared(frame->function()->shared(), isolate_);
- return IsBlackboxed(shared);
- }
- List<Handle<SharedFunctionInfo>> infos;
+ std::vector<Handle<SharedFunctionInfo>> infos;
frame->GetFunctions(&infos);
- for (const auto& info : 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;
-
+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.
@@ -1749,9 +1840,16 @@
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, STRICT).Assert();
+ 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 (jspromise->IsJSPromise()) {
+ uncaught = !isolate_->PromiseHasUserDefinedRejectHandler(
+ Handle<JSPromise>::cast(jspromise));
+ } else {
+ uncaught = true;
+ }
}
if (!debug_delegate_) return;
@@ -1775,24 +1873,24 @@
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);
- 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;
-
+ Handle<Context> native_context(isolate_->native_context());
debug_delegate_->ExceptionThrown(
- GetDebugEventContext(isolate_),
- v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)),
- v8::Utils::ToLocal(exception), v8::Utils::ToLocal(promise), uncaught);
+ v8::Utils::ToLocal(native_context), v8::Utils::ToLocal(exception),
+ v8::Utils::ToLocal(promise), uncaught, exception_type);
}
-void Debug::OnDebugBreak(Handle<Object> break_points_hit) {
+void Debug::OnDebugBreak(Handle<FixedArray> break_points_hit,
+ StepAction lastStepAction) {
+ DCHECK(!break_points_hit.is_null());
// The caller provided for DebugScope.
AssertDebugContext();
// Bail out if there is no listener for this event
@@ -1803,294 +1901,196 @@
#endif // DEBUG
if (!debug_delegate_) return;
+ DCHECK(in_debug_scope());
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;
-
- debug_delegate_->BreakProgramRequested(
- GetDebugEventContext(isolate_),
- v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)),
- v8::Utils::ToLocal(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 {
-struct CollectedCallbackData {
- Object** location;
- int id;
- Debug* debug;
- Isolate* isolate;
-
- CollectedCallbackData(Object** location, int id, Debug* debug,
- Isolate* isolate)
- : location(location), id(id), debug(debug), isolate(isolate) {}
-};
-
-void SendAsyncTaskEventCancel(const v8::WeakCallbackInfo<void>& info) {
- std::unique_ptr<CollectedCallbackData> data(
- reinterpret_cast<CollectedCallbackData*>(info.GetParameter()));
- if (!data->debug->is_active()) return;
- HandleScope scope(data->isolate);
- data->debug->OnAsyncTaskEvent(debug::kDebugPromiseCollected, data->id, 0);
-}
-
-void ResetPromiseHandle(const v8::WeakCallbackInfo<void>& info) {
- CollectedCallbackData* data =
- reinterpret_cast<CollectedCallbackData*>(info.GetParameter());
- GlobalHandles::Destroy(data->location);
- info.SetSecondPassCallback(&SendAsyncTaskEventCancel);
-}
-
-// 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);
+ if ((lastStepAction == StepAction::StepNext ||
+ lastStepAction == StepAction::StepIn) &&
+ ShouldBeSkipped()) {
+ PrepareStep(lastStepAction);
+ return;
}
- 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;
+ 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;
}
-}
-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, SLOPPY, Object::MAY_BE_STORE_FROM_KEYED)
- .ToChecked();
- Handle<Object> global_handle = isolate_->global_handles()->Create(*promise);
- // We send EnqueueRecurring async task event when promise is fulfilled or
- // rejected, WillHandle and DidHandle for every scheduled microtask for this
- // promise.
- // We need to send a cancel event when no other microtasks can be
- // started for this promise and all current microtasks are finished.
- // Since we holding promise when at least one microtask is scheduled (inside
- // PromiseReactionJobInfo), we can send cancel event in weak callback.
- GlobalHandles::MakeWeak(
- global_handle.location(),
- new CollectedCallbackData(global_handle.location(), async_id->value(),
- this, isolate_),
- &ResetPromiseHandle, v8::WeakCallbackType::kParameter);
- return async_id->value();
+ 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);
- return debug::Location(info.line, info.column);
+ // 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 false;
- if (!shared->computed_debug_is_blackboxed()) {
- bool is_blackboxed = false;
- if (shared->script()->IsScript()) {
+ 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);
- Handle<Script> script(Script::cast(shared->script()));
- if (script->type() == i::Script::TYPE_NORMAL) {
- 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);
- }
+ 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);
}
- shared->set_debug_is_blackboxed(is_blackboxed);
- shared->set_computed_debug_is_blackboxed(true);
+ debug_info->set_debug_is_blackboxed(is_blackboxed);
+ debug_info->set_computed_debug_is_blackboxed(true);
}
- return shared->debug_is_blackboxed();
+ return debug_info->debug_is_blackboxed();
}
-void Debug::OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id,
- int parent_id) {
- if (in_debug_scope() || ignore_events()) return;
- if (!debug_delegate_) return;
+bool Debug::ShouldBeSkipped() {
SuppressDebug while_processing(this);
- DebugScope debug_scope(isolate_->debug());
- if (debug_scope.failed()) return;
- HandleScope scope(isolate_);
PostponeInterruptsScope no_interrupts(isolate_);
DisableBreak no_recursive_break(this);
- debug_delegate_->PromiseEventOccurred(type, id, parent_id);
+
+ StackTraceFrameIterator iterator(isolate_);
+ CommonFrame* frame = iterator.frame();
+ FrameSummary summary = FrameSummary::GetTop(frame);
+ Handle<Object> script_obj = summary.script();
+ if (!script_obj->IsScript()) return false;
+
+ Handle<Script> script = Handle<Script>::cast(script_obj);
+ summary.EnsureSourcePositionsAvailable();
+ int source_position = summary.SourcePosition();
+ int line = Script::GetLineNumber(script, source_position);
+ int column = Script::GetColumnNumber(script, source_position);
+
+ return debug_delegate_->ShouldBeSkipped(ToApiHandle<debug::Script>(script),
+ line, column);
}
-void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
+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) {
+ // Ignore temporary scripts.
+ if (script->id() == Script::kTemporaryScriptId) return;
+ // 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->type() != i::Script::TYPE_NORMAL &&
- script->type() != i::Script::TYPE_WASM) {
+ 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),
- 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_);
+ running_live_edit_, has_compile_error);
}
int Debug::CurrentFrameCount() {
StackTraceFrameIterator it(isolate_);
- if (break_frame_id() != StackFrame::NO_ID) {
+ 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()) {
- List<SharedFunctionInfo*> infos;
- OptimizedFrame::cast(it.frame())->GetFunctions(&infos);
- counter += infos.length();
- } else {
- counter++;
- }
- it.Advance();
+ for (; !it.done(); it.Advance()) {
+ counter += it.FrameFunctionCount();
}
return counter;
}
-void Debug::SetDebugDelegate(debug::DebugDelegate* delegate,
- bool pass_ownership) {
- RemoveDebugDelegate();
+void Debug::SetDebugDelegate(debug::DebugDelegate* delegate) {
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()) {
+ 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()->Disable();
- is_active = Load();
- } else if (is_loaded()) {
- isolate_->compilation_cache()->Enable();
+ isolate_->compilation_cache()->DisableScriptAndEval();
+ is_active = true;
+ feature_tracker()->Track(DebugFeatureTracker::kActive);
+ } else {
+ isolate_->compilation_cache()->EnableScriptAndEval();
Unload();
}
is_active_ = is_active;
- isolate_->DebugStateUpdated();
+ isolate_->PromiseHookStateUpdated();
}
void Debug::UpdateHookOnFunctionCall() {
STATIC_ASSERT(LastStepAction == StepIn);
- hook_on_function_call_ = thread_local_.last_step_action_ == StepIn ||
- isolate_->needs_side_effect_check();
+ hook_on_function_call_ =
+ thread_local_.last_step_action_ == StepIn ||
+ isolate_->debug_execution_mode() == DebugInfo::kSideEffects ||
+ thread_local_.break_on_next_function_call_;
}
-MaybeHandle<Object> Debug::Call(Handle<Object> fun, Handle<Object> data) {
- 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() {
+void Debug::HandleDebugBreak(IgnoreBreakMode ignore_break_mode) {
// Initialize LiveEdit.
LiveEdit::InitializeThreadLocal(this);
// Ignore debug break during bootstrapping.
@@ -2105,42 +2105,30 @@
{ JavaScriptFrameIterator it(isolate_);
DCHECK(!it.done());
- Object* fun = it.frame()->function();
- if (fun && fun->IsJSFunction()) {
+ 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(JSFunction::cast(fun)->shared(),
- isolate_);
- if (!shared->IsSubjectToDebugging() || IsBlackboxed(shared)) {
- // 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.
- Deoptimizer::DeoptimizeFunction(JSFunction::cast(fun));
- return;
- }
- JSGlobalObject* global =
- JSFunction::cast(fun)->context()->global_object();
- // Don't stop in debugger functions.
- if (IsDebugGlobal(global)) return;
+ 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;
}
}
- isolate_->stack_guard()->ClearDebugBreak();
+ StepAction lastStepAction = last_step_action();
// Clear stepping to avoid duplicate breaks.
ClearStepping();
HandleScope scope(isolate_);
DebugScope debug_scope(this);
- if (debug_scope.failed()) return;
- OnDebugBreak(isolate_->factory()->undefined_value());
+ OnDebugBreak(isolate_->factory()->empty_fixed_array(), lastStepAction);
}
#ifdef DEBUG
@@ -2149,8 +2137,9 @@
HandleScope scope(isolate_);
StackTraceFrameIterator iterator(isolate_);
if (iterator.done()) return;
- StandardFrame* frame = iterator.frame();
+ CommonFrame* 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 '");
@@ -2158,21 +2147,21 @@
PrintF("'.\n");
if (script_obj->IsScript()) {
Handle<Script> script = Handle<Script>::cast(script_obj);
- Handle<String> source(String::cast(script->source()));
- Script::InitLineEnds(script);
+ 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()));
- int line_start =
- line == 0 ? 0 : Smi::cast(line_ends->get(line - 1))->value() + 1;
- int line_end = Smi::cast(line_ends->get(line))->value();
+ 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();
+ String::FlatContent content = source->GetFlatContent(no_gc);
if (content.IsOneByte()) {
PrintF("[debug] %.*s\n", line_end - line_start,
- content.ToOneByteVector().start() + line_start);
+ content.ToOneByteVector().begin() + line_start);
PrintF("[debug] ");
for (int i = 0; i < column; i++) PrintF(" ");
PrintF("^\n");
@@ -2185,16 +2174,13 @@
DebugScope::DebugScope(Debug* debug)
: debug_(debug),
- prev_(debug->debugger_entry()),
- save_(debug_->isolate_),
- no_termination_exceptons_(debug_->isolate_,
- StackGuard::TERMINATE_EXECUTION) {
+ prev_(reinterpret_cast<DebugScope*>(
+ base::Relaxed_Load(&debug->thread_local_.current_debug_scope_))),
+ no_interrupts_(debug_->isolate_) {
// Link recursive debugger entry.
- base::NoBarrier_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();
+ 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
@@ -2202,25 +2188,29 @@
StackTraceFrameIterator it(isolate());
bool has_frames = !it.done();
debug_->thread_local_.break_frame_id_ =
- has_frames ? it.frame()->id() : StackFrame::NO_ID;
- debug_->SetNextBreakId();
+ has_frames ? it.frame()->id() : StackFrameId::NO_ID;
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());
}
+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::NoBarrier_Store(&debug_->thread_local_.current_debug_scope_,
- reinterpret_cast<base::AtomicWord>(prev_));
+ 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();
}
@@ -2233,27 +2223,200 @@
debug_->set_return_value(*return_value_);
}
-bool Debug::PerformSideEffectCheck(Handle<JSFunction> function) {
- DCHECK(isolate_->needs_side_effect_check());
- DisallowJavascriptExecution no_js(isolate_);
- if (!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());
+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);
+ }
}
- side_effect_check_failed_ = true;
- // Throw an uncatchable termination exception.
- isolate_->TerminateExecution();
- return false;
+ current = current->next();
}
- return true;
}
-bool Debug::PerformSideEffectCheckForCallback(Address function) {
- DCHECK(isolate_->needs_side_effect_check());
- if (DebugEvaluate::CallbackHasNoSideEffect(function)) return true;
+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(isolate_));
+ 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 allowlisted 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();
@@ -2261,158 +2424,49 @@
return false;
}
-void LegacyDebugDelegate::PromiseEventOccurred(
- v8::debug::PromiseDebugActionType type, int id, int parent_id) {
- Handle<Object> event_data;
- if (isolate_->debug()->MakeAsyncTaskEvent(type, id).ToHandle(&event_data)) {
- ProcessDebugEvent(v8::AsyncTaskEvent, Handle<JSObject>::cast(event_data));
+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);
}
-void LegacyDebugDelegate::ScriptCompiled(v8::Local<v8::debug::Script> script,
- 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));
+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;
}
-}
-void LegacyDebugDelegate::BreakProgramRequested(
- v8::Local<v8::Context> paused_context, v8::Local<v8::Object> exec_state,
- v8::Local<v8::Value> break_points_hit) {
- 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)));
+ 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;
}
-
-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_ = Handle<JSFunction>::cast(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) {
- 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
diff --git a/src/debug/debug.h b/src/debug/debug.h
index 43338d7..c95affc 100644
--- a/src/debug/debug.h
+++ b/src/debug/debug.h
@@ -5,32 +5,28 @@
#ifndef V8_DEBUG_DEBUG_H_
#define V8_DEBUG_DEBUG_H_
-#include "src/allocation.h"
-#include "src/assembler.h"
-#include "src/base/atomicops.h"
-#include "src/base/hashmap.h"
-#include "src/base/platform/platform.h"
+#include <memory>
+#include <vector>
+
+#include "src/codegen/source-position-table.h"
+#include "src/common/globals.h"
#include "src/debug/debug-interface.h"
#include "src/debug/interface-types.h"
-#include "src/execution.h"
-#include "src/factory.h"
-#include "src/flags.h"
-#include "src/frames.h"
-#include "src/globals.h"
-#include "src/runtime/runtime.h"
-#include "src/source-position-table.h"
-#include "src/string-stream.h"
-#include "src/v8threads.h"
-
-#include "include/v8-debug.h"
+#include "src/execution/interrupts-scope.h"
+#include "src/execution/isolate.h"
+#include "src/handles/handles.h"
+#include "src/objects/debug-objects.h"
namespace v8 {
namespace internal {
-
// Forward declarations.
+class AbstractCode;
class DebugScope;
-
+class InterpretedFrame;
+class JavaScriptFrame;
+class JSGeneratorObject;
+class StackFrame;
// Step actions. NOTE: These values are in macros.py as well.
enum StepAction : int8_t {
@@ -48,56 +44,72 @@
BreakUncaughtException = 1
};
-
-// The different types of breakpoint position alignments.
-// Must match Debug.BreakPositionAlignment in debug.js
-enum BreakPositionAlignment {
- STATEMENT_ALIGNED = 0,
- BREAK_POSITION_ALIGNED = 1
-};
-
enum DebugBreakType {
NOT_DEBUG_BREAK,
DEBUGGER_STATEMENT,
DEBUG_BREAK_SLOT,
DEBUG_BREAK_SLOT_AT_CALL,
DEBUG_BREAK_SLOT_AT_RETURN,
- DEBUG_BREAK_SLOT_AT_TAIL_CALL,
+ DEBUG_BREAK_SLOT_AT_SUSPEND,
+ DEBUG_BREAK_AT_ENTRY,
+};
+
+enum IgnoreBreakMode {
+ kIgnoreIfAllFramesBlackboxed,
+ kIgnoreIfTopFrameBlackboxed
};
class BreakLocation {
public:
+ static BreakLocation Invalid() { return BreakLocation(-1, NOT_DEBUG_BREAK); }
static BreakLocation FromFrame(Handle<DebugInfo> debug_info,
JavaScriptFrame* frame);
static void AllAtCurrentStatement(Handle<DebugInfo> debug_info,
JavaScriptFrame* frame,
- List<BreakLocation>* result_out);
+ std::vector<BreakLocation>* result_out);
+ inline bool IsSuspend() const { return type_ == DEBUG_BREAK_SLOT_AT_SUSPEND; }
inline bool IsReturn() const { return type_ == DEBUG_BREAK_SLOT_AT_RETURN; }
- inline bool IsCall() const { return type_ == DEBUG_BREAK_SLOT_AT_CALL; }
- inline bool IsTailCall() const {
- return type_ == DEBUG_BREAK_SLOT_AT_TAIL_CALL;
+ inline bool IsReturnOrSuspend() const {
+ return type_ >= DEBUG_BREAK_SLOT_AT_RETURN;
}
+ inline bool IsCall() const { return type_ == DEBUG_BREAK_SLOT_AT_CALL; }
inline bool IsDebugBreakSlot() const { return type_ >= DEBUG_BREAK_SLOT; }
inline bool IsDebuggerStatement() const {
return type_ == DEBUGGER_STATEMENT;
}
+ inline bool IsDebugBreakAtEntry() const {
+ bool result = type_ == DEBUG_BREAK_AT_ENTRY;
+ return result;
+ }
- bool HasBreakPoint(Handle<DebugInfo> debug_info) const;
+ bool HasBreakPoint(Isolate* isolate, Handle<DebugInfo> debug_info) const;
inline int position() const { return position_; }
+ debug::BreakLocationType type() const;
+
+ JSGeneratorObject GetGeneratorObjectForSuspendedFrame(
+ JavaScriptFrame* frame) const;
+
private:
BreakLocation(Handle<AbstractCode> abstract_code, DebugBreakType type,
- int code_offset, int position)
+ int code_offset, int position, int generator_obj_reg_index)
: abstract_code_(abstract_code),
code_offset_(code_offset),
type_(type),
- position_(position) {
+ position_(position),
+ generator_obj_reg_index_(generator_obj_reg_index) {
DCHECK_NE(NOT_DEBUG_BREAK, type_);
}
+ BreakLocation(int position, DebugBreakType type)
+ : code_offset_(0),
+ type_(type),
+ position_(position),
+ generator_obj_reg_index_(0) {}
+
static int BreakIndexFromCodeOffset(Handle<DebugInfo> debug_info,
Handle<AbstractCode> abstract_code,
int offset);
@@ -109,113 +121,54 @@
int code_offset_;
DebugBreakType type_;
int position_;
+ int generator_obj_reg_index_;
- friend class CodeBreakIterator;
- friend class BytecodeArrayBreakIterator;
+ friend class BreakIterator;
};
-class BreakIterator {
+class V8_EXPORT_PRIVATE BreakIterator {
public:
- static std::unique_ptr<BreakIterator> GetIterator(
- Handle<DebugInfo> debug_info, Handle<AbstractCode> abstract_code);
+ explicit BreakIterator(Handle<DebugInfo> debug_info);
- virtual ~BreakIterator() {}
+ BreakLocation GetBreakLocation();
+ bool Done() const { return source_position_iterator_.done(); }
+ void Next();
- virtual BreakLocation GetBreakLocation() = 0;
- virtual bool Done() const = 0;
- virtual void Next() = 0;
-
+ void SkipToPosition(int position);
void SkipTo(int count) {
while (count-- > 0) Next();
}
- virtual int code_offset() = 0;
+ int code_offset() { return source_position_iterator_.code_offset(); }
int break_index() const { return break_index_; }
inline int position() const { return position_; }
inline int statement_position() const { return statement_position_; }
- virtual bool IsDebugBreak() = 0;
- virtual void ClearDebugBreak() = 0;
- virtual void SetDebugBreak() = 0;
+ void ClearDebugBreak();
+ void SetDebugBreak();
- protected:
- explicit BreakIterator(Handle<DebugInfo> debug_info);
+ private:
+ int BreakIndexFromPosition(int position);
- int BreakIndexFromPosition(int position, BreakPositionAlignment alignment);
+ Isolate* isolate();
- Isolate* isolate() { return debug_info_->GetIsolate(); }
+ DebugBreakType GetDebugBreakType();
Handle<DebugInfo> debug_info_;
int break_index_;
int position_;
int statement_position_;
-
- private:
+ SourcePositionTableIterator source_position_iterator_;
DisallowHeapAllocation no_gc_;
+
DISALLOW_COPY_AND_ASSIGN(BreakIterator);
};
-class CodeBreakIterator : public BreakIterator {
- public:
- explicit CodeBreakIterator(Handle<DebugInfo> debug_info);
- ~CodeBreakIterator() override {}
-
- BreakLocation GetBreakLocation() override;
- bool Done() const override { return reloc_iterator_.done(); }
- void Next() override;
-
- bool IsDebugBreak() override;
- void ClearDebugBreak() override;
- void SetDebugBreak() override;
-
- void SkipToPosition(int position, BreakPositionAlignment alignment);
-
- int code_offset() override {
- return static_cast<int>(rinfo()->pc() -
- debug_info_->DebugCode()->instruction_start());
- }
-
- private:
- int GetModeMask();
- DebugBreakType GetDebugBreakType();
-
- RelocInfo::Mode rmode() { return reloc_iterator_.rinfo()->rmode(); }
- RelocInfo* rinfo() { return reloc_iterator_.rinfo(); }
-
- RelocIterator reloc_iterator_;
- SourcePositionTableIterator source_position_iterator_;
- DISALLOW_COPY_AND_ASSIGN(CodeBreakIterator);
-};
-
-class BytecodeArrayBreakIterator : public BreakIterator {
- public:
- explicit BytecodeArrayBreakIterator(Handle<DebugInfo> debug_info);
- ~BytecodeArrayBreakIterator() override {}
-
- BreakLocation GetBreakLocation() override;
- bool Done() const override { return source_position_iterator_.done(); }
- void Next() override;
-
- bool IsDebugBreak() override;
- void ClearDebugBreak() override;
- void SetDebugBreak() override;
-
- void SkipToPosition(int position, BreakPositionAlignment alignment);
-
- int code_offset() override { return source_position_iterator_.code_offset(); }
-
- private:
- DebugBreakType GetDebugBreakType();
-
- SourcePositionTableIterator source_position_iterator_;
- DISALLOW_COPY_AND_ASSIGN(BytecodeArrayBreakIterator);
-};
-
// Linked list holding debug info objects. The debug info objects are kept as
// weak handles to avoid a debug info object to keep a function alive.
class DebugInfoListNode {
public:
- explicit DebugInfoListNode(DebugInfo* debug_info);
+ DebugInfoListNode(Isolate* isolate, DebugInfo debug_info);
~DebugInfoListNode();
DebugInfoListNode* next() { return next_; }
@@ -224,7 +177,7 @@
private:
// Global (weak) handle to the debug info object.
- DebugInfo** debug_info_;
+ Address* debug_info_;
// Next pointer for linked list.
DebugInfoListNode* next_;
@@ -259,48 +212,49 @@
// active breakpoints in them. This debug info is held in the heap root object
// debug_info which is a FixedArray. Each entry in this list is of class
// DebugInfo.
-class Debug {
+class V8_EXPORT_PRIVATE Debug {
public:
// Debug event triggers.
- void OnDebugBreak(Handle<Object> break_points_hit);
+ void OnDebugBreak(Handle<FixedArray> break_points_hit, StepAction stepAction);
- void OnThrow(Handle<Object> exception);
+ base::Optional<Object> OnThrow(Handle<Object> exception)
+ V8_WARN_UNUSED_RESULT;
void OnPromiseReject(Handle<Object> promise, Handle<Object> value);
void OnCompileError(Handle<Script> script);
void OnAfterCompile(Handle<Script> script);
- void OnAsyncTaskEvent(debug::PromiseDebugActionType type, int id,
- int parent_id);
- MUST_USE_RESULT MaybeHandle<Object> Call(Handle<Object> fun,
- Handle<Object> data);
- Handle<Context> GetDebugContext();
- void HandleDebugBreak();
+ void HandleDebugBreak(IgnoreBreakMode ignore_break_mode);
- // Internal logic
- bool Load();
- void Break(JavaScriptFrame* frame);
+ // The break target may not be the top-most frame, since we may be
+ // breaking before entering a function that cannot contain break points.
+ void Break(JavaScriptFrame* frame, Handle<JSFunction> break_target);
// Scripts handling.
Handle<FixedArray> GetLoadedScripts();
// Break point handling.
- bool SetBreakPoint(Handle<JSFunction> function,
- Handle<Object> break_point_object,
- int* source_position);
- bool SetBreakPointForScript(Handle<Script> script,
- Handle<Object> break_point_object,
- int* source_position,
- BreakPositionAlignment alignment);
- void ClearBreakPoint(Handle<Object> break_point_object);
+ bool SetBreakpoint(Handle<SharedFunctionInfo> shared,
+ Handle<BreakPoint> break_point, int* source_position);
+ void ClearBreakPoint(Handle<BreakPoint> break_point);
void ChangeBreakOnException(ExceptionBreakType type, bool enable);
bool IsBreakOnException(ExceptionBreakType type);
- // The parameter is either a BreakPointInfo object, or a FixedArray of
- // BreakPointInfo objects.
- // Returns an empty handle if no breakpoint is hit, or a FixedArray with all
- // hit breakpoints.
- MaybeHandle<FixedArray> GetHitBreakPointObjects(
- Handle<Object> break_point_objects);
+ void SetTerminateOnResume();
+
+ bool SetBreakPointForScript(Handle<Script> script, Handle<String> condition,
+ int* source_position, int* id);
+ bool SetBreakpointForFunction(Handle<SharedFunctionInfo> shared,
+ Handle<String> condition, int* id);
+ void RemoveBreakpoint(int id);
+ void RemoveBreakpointForWasmScript(Handle<Script> script, int id);
+
+ void RecordWasmScriptWithBreakpoints(Handle<Script> script);
+
+ // Find breakpoints from the debug info and the break location and check
+ // whether they are hit. Return an empty handle if not, or a FixedArray with
+ // hit BreakPoint objects.
+ MaybeHandle<FixedArray> GetHitBreakPoints(Handle<DebugInfo> debug_info,
+ int position);
// Stepping handling.
void PrepareStep(StepAction step_action);
@@ -308,41 +262,39 @@
void PrepareStepInSuspendedGenerator();
void PrepareStepOnThrow();
void ClearStepping();
- void ClearStepOut();
- bool PrepareFunctionForBreakPoints(Handle<SharedFunctionInfo> shared);
+ void SetBreakOnNextFunctionCall();
+ void ClearBreakOnNextFunctionCall();
+
+ void DeoptimizeFunction(Handle<SharedFunctionInfo> shared);
+ void PrepareFunctionForDebugExecution(Handle<SharedFunctionInfo> shared);
+ void InstallDebugBreakTrampoline();
bool GetPossibleBreakpoints(Handle<Script> script, int start_position,
- int end_position, std::set<int>* positions);
-
- void RecordGenerator(Handle<JSGeneratorObject> generator_object);
-
- void RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
- Handle<Object> parent);
-
- int NextAsyncTaskId(Handle<JSObject> promise);
+ int end_position, bool restrict_to_function,
+ std::vector<BreakLocation>* locations);
bool IsBlackboxed(Handle<SharedFunctionInfo> shared);
+ bool ShouldBeSkipped();
- void SetDebugDelegate(debug::DebugDelegate* delegate, bool pass_ownership);
+ bool CanBreakAtEntry(Handle<SharedFunctionInfo> shared);
+
+ void SetDebugDelegate(debug::DebugDelegate* delegate);
// Returns whether the operation succeeded.
- bool EnsureDebugInfo(Handle<SharedFunctionInfo> shared);
- void CreateDebugInfo(Handle<SharedFunctionInfo> shared);
- static Handle<DebugInfo> GetDebugInfo(Handle<SharedFunctionInfo> shared);
+ bool EnsureBreakInfo(Handle<SharedFunctionInfo> shared);
+ void CreateBreakInfo(Handle<SharedFunctionInfo> shared);
+ Handle<DebugInfo> GetOrCreateDebugInfo(Handle<SharedFunctionInfo> shared);
- template <typename C>
- bool CompileToRevealInnerFunctions(C* compilable);
+ void InstallCoverageInfo(Handle<SharedFunctionInfo> shared,
+ Handle<CoverageInfo> coverage_info);
+ void RemoveAllCoverageInfos();
// This function is used in FunctionNameUsing* tests.
Handle<Object> FindSharedFunctionInfoInScript(Handle<Script> script,
int position);
static Handle<Object> GetSourceBreakLocations(
- Handle<SharedFunctionInfo> shared,
- BreakPositionAlignment position_aligment);
-
- // Check whether a global object is the debug global object.
- bool IsDebugGlobal(JSGlobalObject* global);
+ Isolate* isolate, Handle<SharedFunctionInfo> shared);
// Check whether this frame is just about to return.
bool IsBreakAtReturn(JavaScriptFrame* frame);
@@ -350,54 +302,60 @@
// Support for LiveEdit
void ScheduleFrameRestart(StackFrame* frame);
- bool IsFrameBlackboxed(JavaScriptFrame* frame);
+ bool AllFramesOnStackAreBlackboxed();
+
+ // Set new script source, throw an exception if error occurred. When preview
+ // is true: try to set source, throw exception if any without actual script
+ // change. stack_changed is true if after editing script on pause stack is
+ // changed and client should request stack trace again.
+ bool SetScriptSource(Handle<Script> script, Handle<String> source,
+ bool preview, debug::LiveEditResult* result);
+
+ int GetFunctionDebuggingId(Handle<JSFunction> function);
// Threading support.
char* ArchiveDebug(char* to);
char* RestoreDebug(char* from);
static int ArchiveSpacePerThread();
void FreeThreadResources() { }
- void Iterate(ObjectVisitor* v);
+ void Iterate(RootVisitor* v);
+ void InitThread(const ExecutionAccess& lock) { ThreadInit(); }
- bool CheckExecutionState(int id) {
- return CheckExecutionState() && break_id() == id;
- }
+ bool CheckExecutionState() { return is_active(); }
- bool CheckExecutionState() {
- return is_active() && !debug_context().is_null() && break_id() != 0;
- }
+ void StartSideEffectCheckMode();
+ void StopSideEffectCheckMode();
- bool PerformSideEffectCheck(Handle<JSFunction> function);
- bool PerformSideEffectCheckForCallback(Address function);
+ void ApplySideEffectChecks(Handle<DebugInfo> debug_info);
+ void ClearSideEffectChecks(Handle<DebugInfo> debug_info);
+
+ bool PerformSideEffectCheck(Handle<JSFunction> function,
+ Handle<Object> receiver);
+
+ enum AccessorKind { kNotAccessor, kGetter, kSetter };
+ bool PerformSideEffectCheckForCallback(Handle<Object> callback_info,
+ Handle<Object> receiver,
+ AccessorKind accessor_kind);
+ bool PerformSideEffectCheckAtBytecode(InterpretedFrame* frame);
+ bool PerformSideEffectCheckForObject(Handle<Object> object);
// Flags and states.
- DebugScope* debugger_entry() {
- return reinterpret_cast<DebugScope*>(
- base::NoBarrier_Load(&thread_local_.current_debug_scope_));
- }
- inline Handle<Context> debug_context() { return debug_context_; }
-
- void set_live_edit_enabled(bool v) { live_edit_enabled_ = v; }
- bool live_edit_enabled() const {
- return FLAG_enable_liveedit && live_edit_enabled_;
- }
-
inline bool is_active() const { return is_active_; }
- inline bool is_loaded() const { return !debug_context_.is_null(); }
inline bool in_debug_scope() const {
- return !!base::NoBarrier_Load(&thread_local_.current_debug_scope_);
+ return !!base::Relaxed_Load(&thread_local_.current_debug_scope_);
}
+ inline bool needs_check_on_function_call() const {
+ return hook_on_function_call_;
+ }
+
void set_break_points_active(bool v) { break_points_active_ = v; }
bool break_points_active() const { return break_points_active_; }
- StackFrame::Id break_frame_id() { return thread_local_.break_frame_id_; }
- int break_id() { return thread_local_.break_id_; }
+ StackFrameId break_frame_id() { return thread_local_.break_frame_id_; }
- Handle<Object> return_value_handle() {
- return handle(thread_local_.return_value_, isolate_);
- }
- Object* return_value() { return thread_local_.return_value_; }
- void set_return_value(Object* value) { thread_local_.return_value_ = value; }
+ Handle<Object> return_value_handle();
+ Object return_value() { return thread_local_.return_value_; }
+ void set_return_value(Object value) { thread_local_.return_value_ = value; }
// Support for embedding into generated code.
Address is_active_address() {
@@ -408,10 +366,6 @@
return reinterpret_cast<Address>(&hook_on_function_call_);
}
- Address last_step_action_address() {
- return reinterpret_cast<Address>(&thread_local_.last_step_action_);
- }
-
Address suspended_generator_address() {
return reinterpret_cast<Address>(&thread_local_.suspended_generator_);
}
@@ -419,62 +373,59 @@
Address restart_fp_address() {
return reinterpret_cast<Address>(&thread_local_.restart_fp_);
}
+ bool will_restart() const {
+ return thread_local_.restart_fp_ != kNullAddress;
+ }
StepAction last_step_action() { return thread_local_.last_step_action_; }
+ bool break_on_next_function_call() const {
+ return thread_local_.break_on_next_function_call_;
+ }
+
+ inline bool break_disabled() const { return break_disabled_; }
DebugFeatureTracker* feature_tracker() { return &feature_tracker_; }
+ // For functions in which we cannot set a break point, use a canonical
+ // source position for break points.
+ static const int kBreakAtEntryPosition = 0;
+
+ void RemoveBreakInfoAndMaybeFree(Handle<DebugInfo> debug_info);
+
private:
explicit Debug(Isolate* isolate);
- ~Debug() { DCHECK_NULL(debug_delegate_); }
+ ~Debug();
+ void UpdateDebugInfosForExecutionMode();
void UpdateState();
void UpdateHookOnFunctionCall();
- void RemoveDebugDelegate();
void Unload();
- void SetNextBreakId() {
- thread_local_.break_id_ = ++thread_local_.break_count_;
- }
// Return the number of virtual frames below debugger entry.
int CurrentFrameCount();
inline bool ignore_events() const {
- return is_suppressed_ || !is_active_ || isolate_->needs_side_effect_check();
+ return is_suppressed_ || !is_active_ ||
+ isolate_->debug_execution_mode() == DebugInfo::kSideEffects;
}
- inline bool break_disabled() const { return break_disabled_; }
void clear_suspended_generator() {
- thread_local_.suspended_generator_ = Smi::kZero;
+ thread_local_.suspended_generator_ = Smi::zero();
}
bool has_suspended_generator() const {
- return thread_local_.suspended_generator_ != Smi::kZero;
+ return thread_local_.suspended_generator_ != Smi::zero();
}
bool IsExceptionBlackboxed(bool uncaught);
- void OnException(Handle<Object> exception, Handle<Object> promise);
+ void OnException(Handle<Object> exception, Handle<Object> promise,
+ v8::debug::ExceptionType exception_type);
- // Constructors for debug event objects.
- MUST_USE_RESULT MaybeHandle<Object> MakeExecutionState();
- MUST_USE_RESULT MaybeHandle<Object> MakeBreakEvent(
- Handle<Object> break_points_hit);
- MUST_USE_RESULT MaybeHandle<Object> MakeExceptionEvent(
- Handle<Object> exception,
- bool uncaught,
- Handle<Object> promise);
- MUST_USE_RESULT MaybeHandle<Object> MakeCompileEvent(
- Handle<Script> script, v8::DebugEvent type);
- MUST_USE_RESULT MaybeHandle<Object> MakeAsyncTaskEvent(
- v8::debug::PromiseDebugActionType type, int id);
-
- void ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script);
- void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data);
+ void ProcessCompileEvent(bool has_compile_error, Handle<Script> script);
// Find the closest source position for a break point for a given position.
- int FindBreakablePosition(Handle<DebugInfo> debug_info, int source_position,
- BreakPositionAlignment alignment);
+ int FindBreakablePosition(Handle<DebugInfo> debug_info, int source_position);
// Instrument code to break at break points.
void ApplyBreakPoints(Handle<DebugInfo> debug_info);
// Clear code from instrumentation.
@@ -482,22 +433,23 @@
// Clear all code from instrumentation.
void ClearAllBreakPoints();
// Instrument a function with one-shots.
- void FloodWithOneShot(Handle<SharedFunctionInfo> function);
+ void FloodWithOneShot(Handle<SharedFunctionInfo> function,
+ bool returns_only = false);
// Clear all one-shot instrumentations, but restore break points.
void ClearOneShot();
+ bool IsFrameBlackboxed(JavaScriptFrame* frame);
+
void ActivateStepOut(StackFrame* frame);
- void RemoveDebugInfoAndClearFromShared(Handle<DebugInfo> debug_info);
MaybeHandle<FixedArray> CheckBreakPoints(Handle<DebugInfo> debug_info,
BreakLocation* location,
bool* has_break_points = nullptr);
bool IsMutedAtCurrentLocation(JavaScriptFrame* frame);
- bool CheckBreakPoint(Handle<Object> break_point_object);
- MaybeHandle<Object> CallFunction(const char* name, int argc,
- Handle<Object> args[]);
+ // Check whether a BreakPoint object is hit. Evaluate condition depending
+ // on whether this is a regular break location or a break at function entry.
+ bool CheckBreakPoint(Handle<BreakPoint> break_point, bool is_break_at_entry);
inline void AssertDebugContext() {
- DCHECK(isolate_->context() == *debug_context());
DCHECK(in_debug_scope());
}
@@ -505,11 +457,17 @@
void PrintBreakLocation();
- // Global handles.
- Handle<Context> debug_context_;
+ void ClearAllDebuggerHints();
+
+ // Wraps logic for clearing and maybe freeing all debug infos.
+ using DebugInfoClearFunction = std::function<void(Handle<DebugInfo>)>;
+ void ClearAllDebugInfos(const DebugInfoClearFunction& clear_function);
+
+ void FindDebugInfo(Handle<DebugInfo> debug_info, DebugInfoListNode** prev,
+ DebugInfoListNode** curr);
+ void FreeDebugInfoListNode(DebugInfoListNode* prev, DebugInfoListNode* node);
debug::DebugDelegate* debug_delegate_ = nullptr;
- bool owns_debug_delegate_ = false;
// Debugger is active, i.e. there is a debug event listener attached.
bool is_active_;
@@ -518,8 +476,8 @@
bool hook_on_function_call_;
// Suppress debug events.
bool is_suppressed_;
- // LiveEdit is enabled.
- bool live_edit_enabled_;
+ // Running liveedit.
+ bool running_live_edit_ = false;
// Do not trigger debug break events.
bool break_disabled_;
// Do not break on break points.
@@ -534,6 +492,12 @@
// List of active debug info objects.
DebugInfoListNode* debug_info_list_;
+ // Used for side effect check to mark temporary objects.
+ class TemporaryObjectsTracker;
+ std::unique_ptr<TemporaryObjectsTracker> temporary_objects_;
+
+ Handle<RegExpMatchInfo> regexp_match_info_;
+
// Used to collect histogram data on debugger feature usage.
DebugFeatureTracker feature_tracker_;
@@ -543,18 +507,19 @@
// Top debugger entry.
base::AtomicWord current_debug_scope_;
- // Counter for generating next break id.
- int break_count_;
-
- // Current break id.
- int break_id_;
-
// Frame id for the frame of the current break.
- StackFrame::Id break_frame_id_;
+ StackFrameId break_frame_id_;
// Step action for last step performed.
StepAction last_step_action_;
+ // If set, next PrepareStepIn will ignore this function until stepped into
+ // another function, at which point this will be cleared.
+ Object ignore_step_into_function_;
+
+ // If set then we need to repeat StepOut action at return.
+ bool fast_forward_to_return_;
+
// Source statement position from last step next action.
int last_statement_position_;
@@ -565,20 +530,28 @@
int target_frame_count_;
// Value of the accumulator at the point of entering the debugger.
- Object* return_value_;
+ Object return_value_;
// The suspended generator object to track when stepping.
- Object* suspended_generator_;
+ Object suspended_generator_;
// The new frame pointer to drop to when restarting a frame.
Address restart_fp_;
- int async_task_count_;
+ // Last used inspector breakpoint id.
+ int last_breakpoint_id_;
+
+ // This flag is true when SetBreakOnNextFunctionCall is called and it forces
+ // debugger to break on next function call.
+ bool break_on_next_function_call_;
};
// Storage location for registers when handling debug break calls
ThreadLocal thread_local_;
+ // This is a global handle, lazily initialized.
+ Handle<WeakArrayList> wasm_scripts_with_breakpoints_;
+
Isolate* isolate_;
friend class Isolate;
@@ -586,118 +559,31 @@
friend class DisableBreak;
friend class LiveEdit;
friend class SuppressDebug;
- friend class NoSideEffectScope;
- friend class LegacyDebugDelegate;
friend Handle<FixedArray> GetDebuggedFunctions(); // In test-debug.cc
- friend void CheckDebuggerUnloaded(bool check_functions); // In test-debug.cc
+ friend void CheckDebuggerUnloaded(); // In test-debug.cc
DISALLOW_COPY_AND_ASSIGN(Debug);
};
-class LegacyDebugDelegate : public v8::debug::DebugDelegate {
- public:
- explicit LegacyDebugDelegate(Isolate* isolate) : isolate_(isolate) {}
- void PromiseEventOccurred(v8::debug::PromiseDebugActionType type, int id,
- int parent_id) override;
- void ScriptCompiled(v8::Local<v8::debug::Script> script,
- bool has_compile_error) override;
- void BreakProgramRequested(v8::Local<v8::Context> paused_context,
- v8::Local<v8::Object> exec_state,
- v8::Local<v8::Value> break_points_hit) override;
- void 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) override;
- bool IsFunctionBlackboxed(v8::Local<v8::debug::Script> script,
- const v8::debug::Location& start,
- const v8::debug::Location& end) override {
- return false;
- }
-
- protected:
- Isolate* isolate_;
-
- private:
- void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data);
- virtual void ProcessDebugEvent(v8::DebugEvent event,
- Handle<JSObject> event_data,
- Handle<JSObject> exec_state) = 0;
-};
-
-class JavaScriptDebugDelegate : public LegacyDebugDelegate {
- public:
- JavaScriptDebugDelegate(Isolate* isolate, Handle<JSFunction> listener,
- Handle<Object> data);
- virtual ~JavaScriptDebugDelegate();
-
- private:
- void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data,
- Handle<JSObject> exec_state) override;
-
- Handle<JSFunction> listener_;
- Handle<Object> data_;
-};
-
-class NativeDebugDelegate : public LegacyDebugDelegate {
- public:
- NativeDebugDelegate(Isolate* isolate, v8::Debug::EventCallback callback,
- Handle<Object> data);
- virtual ~NativeDebugDelegate();
-
- private:
- // Details of the debug event delivered to the debug event listener.
- class EventDetails : public v8::Debug::EventDetails {
- public:
- EventDetails(DebugEvent event, Handle<JSObject> exec_state,
- Handle<JSObject> event_data, Handle<Object> callback_data);
- virtual DebugEvent GetEvent() const;
- virtual v8::Local<v8::Object> GetExecutionState() const;
- virtual v8::Local<v8::Object> GetEventData() const;
- virtual v8::Local<v8::Context> GetEventContext() const;
- virtual v8::Local<v8::Value> GetCallbackData() const;
- virtual v8::Debug::ClientData* GetClientData() const { return nullptr; }
- virtual v8::Isolate* GetIsolate() const;
-
- private:
- DebugEvent event_; // Debug event causing the break.
- Handle<JSObject> exec_state_; // Current execution state.
- Handle<JSObject> event_data_; // Data associated with the event.
- Handle<Object> callback_data_; // User data passed with the callback
- // when it was registered.
- };
-
- void ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data,
- Handle<JSObject> exec_state) override;
-
- v8::Debug::EventCallback callback_;
- Handle<Object> data_;
-};
-
// This scope is used to load and enter the debug context and create a new
// break state. Leaving the scope will restore the previous state.
-// On failure to load, FailedToEnter returns true.
-class DebugScope BASE_EMBEDDED {
+class DebugScope {
public:
explicit DebugScope(Debug* debug);
~DebugScope();
- // Check whether loading was successful.
- inline bool failed() { return failed_; }
-
- // Get the active context from before entering the debugger.
- inline Handle<Context> GetContext() { return save_.context(); }
+ void set_terminate_on_resume();
private:
Isolate* isolate() { return debug_->isolate_; }
Debug* debug_;
DebugScope* prev_; // Previous scope if entered recursively.
- StackFrame::Id break_frame_id_; // Previous break frame id.
- int break_id_; // Previous break id.
- bool failed_; // Did the debug context fail to load?
- SaveContext save_; // Saves previous context.
- PostponeInterruptsScope no_termination_exceptons_;
+ StackFrameId break_frame_id_; // Previous break frame id.
+ PostponeInterruptsScope no_interrupts_;
+ // This is used as a boolean.
+ bool terminate_on_resume_ = false;
};
// This scope is used to handle return values in nested debug break points.
@@ -715,11 +601,11 @@
};
// Stack allocated class for disabling break.
-class DisableBreak BASE_EMBEDDED {
+class DisableBreak {
public:
- explicit DisableBreak(Debug* debug)
+ explicit DisableBreak(Debug* debug, bool disable = true)
: debug_(debug), previous_break_disabled_(debug->break_disabled_) {
- debug_->break_disabled_ = true;
+ debug_->break_disabled_ = disable;
}
~DisableBreak() {
debug_->break_disabled_ = previous_break_disabled_;
@@ -731,8 +617,7 @@
DISALLOW_COPY_AND_ASSIGN(DisableBreak);
};
-
-class SuppressDebug BASE_EMBEDDED {
+class SuppressDebug {
public:
explicit SuppressDebug(Debug* debug)
: debug_(debug), old_state_(debug->is_suppressed_) {
@@ -746,24 +631,6 @@
DISALLOW_COPY_AND_ASSIGN(SuppressDebug);
};
-class NoSideEffectScope {
- public:
- NoSideEffectScope(Isolate* isolate, bool disallow_side_effects)
- : isolate_(isolate),
- old_needs_side_effect_check_(isolate->needs_side_effect_check()) {
- isolate->set_needs_side_effect_check(old_needs_side_effect_check_ ||
- disallow_side_effects);
- isolate->debug()->UpdateHookOnFunctionCall();
- isolate->debug()->side_effect_check_failed_ = false;
- }
- ~NoSideEffectScope();
-
- private:
- Isolate* isolate_;
- bool old_needs_side_effect_check_;
- DISALLOW_COPY_AND_ASSIGN(NoSideEffectScope);
-};
-
// Code generator routines.
class DebugCodegen : public AllStatic {
public:
@@ -772,11 +639,6 @@
IGNORE_RESULT_REGISTER
};
- static void GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode);
-
- static void GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode);
-
// Builtin to drop frames to restart function.
static void GenerateFrameDropperTrampoline(MacroAssembler* masm);
@@ -784,10 +646,8 @@
// drop frames to restart function if necessary.
static void GenerateHandleDebuggerStatement(MacroAssembler* masm);
- static void PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code);
- static bool DebugBreakSlotIsPatched(Address pc);
- static void ClearDebugBreakSlot(Isolate* isolate, Address pc);
+ // Builtin to trigger a debug break before entering the function.
+ static void GenerateDebugBreakTrampoline(MacroAssembler* masm);
};
diff --git a/src/debug/debug.js b/src/debug/debug.js
deleted file mode 100644
index 6993274..0000000
--- a/src/debug/debug.js
+++ /dev/null
@@ -1,1030 +0,0 @@
-// 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.
-
-(function (global, utils) {
-"use strict";
-
-// ----------------------------------------------------------------------------
-// Imports
-
-var FrameMirror = global.FrameMirror;
-var GlobalArray = global.Array;
-var GlobalRegExp = global.RegExp;
-var IsNaN = global.isNaN;
-var MakeMirror = global.MakeMirror;
-var MathMin = global.Math.min;
-var Mirror = global.Mirror;
-var ValueMirror = global.ValueMirror;
-
-//----------------------------------------------------------------------------
-
-// Default number of frames to include in the response to backtrace request.
-var kDefaultBacktraceLength = 10;
-
-var Debug = {};
-
-// Regular expression to skip "crud" at the beginning of a source line which is
-// not really code. Currently the regular expression matches whitespace and
-// comments.
-var sourceLineBeginningSkip = /^(?:\s*(?:\/\*.*?\*\/)*)*/;
-
-// Debug events which can occour in the V8 JavaScript engine. These originate
-// from the API include file debug.h.
-Debug.DebugEvent = { Break: 1,
- Exception: 2,
- AfterCompile: 3,
- CompileError: 4,
- AsyncTaskEvent: 5 };
-
-// Types of exceptions that can be broken upon.
-Debug.ExceptionBreak = { Caught : 0,
- Uncaught: 1 };
-
-// The different types of steps.
-Debug.StepAction = { StepOut: 0,
- StepNext: 1,
- StepIn: 2 };
-
-// The different types of scripts matching enum ScriptType in objects.h.
-Debug.ScriptType = { Native: 0,
- Extension: 1,
- Normal: 2,
- Wasm: 3};
-
-// The different types of script compilations matching enum
-// Script::CompilationType in objects.h.
-Debug.ScriptCompilationType = { Host: 0,
- Eval: 1,
- JSON: 2 };
-
-// The different script break point types.
-Debug.ScriptBreakPointType = { ScriptId: 0,
- ScriptName: 1,
- ScriptRegExp: 2 };
-
-// The different types of breakpoint position alignments.
-// Must match BreakPositionAlignment in debug.h.
-Debug.BreakPositionAlignment = {
- Statement: 0,
- BreakPosition: 1
-};
-
-function ScriptTypeFlag(type) {
- return (1 << type);
-}
-
-// Globals.
-var next_response_seq = 0;
-var next_break_point_number = 1;
-var break_points = [];
-var script_break_points = [];
-var debugger_flags = {
- breakPointsActive: {
- value: true,
- getValue: function() { return this.value; },
- setValue: function(value) {
- this.value = !!value;
- %SetBreakPointsActive(this.value);
- }
- },
- breakOnCaughtException: {
- getValue: function() { return Debug.isBreakOnException(); },
- setValue: function(value) {
- if (value) {
- Debug.setBreakOnException();
- } else {
- Debug.clearBreakOnException();
- }
- }
- },
- breakOnUncaughtException: {
- getValue: function() { return Debug.isBreakOnUncaughtException(); },
- setValue: function(value) {
- if (value) {
- Debug.setBreakOnUncaughtException();
- } else {
- Debug.clearBreakOnUncaughtException();
- }
- }
- },
-};
-
-
-// Create a new break point object and add it to the list of break points.
-function MakeBreakPoint(source_position, opt_script_break_point) {
- var break_point = new BreakPoint(source_position, opt_script_break_point);
- break_points.push(break_point);
- return break_point;
-}
-
-
-// Object representing a break point.
-// NOTE: This object does not have a reference to the function having break
-// point as this would cause function not to be garbage collected when it is
-// not used any more. We do not want break points to keep functions alive.
-function BreakPoint(source_position, opt_script_break_point) {
- this.source_position_ = source_position;
- if (opt_script_break_point) {
- this.script_break_point_ = opt_script_break_point;
- } else {
- this.number_ = next_break_point_number++;
- }
- this.active_ = true;
- this.condition_ = null;
-}
-
-
-BreakPoint.prototype.number = function() {
- return this.number_;
-};
-
-
-BreakPoint.prototype.func = function() {
- return this.func_;
-};
-
-
-BreakPoint.prototype.source_position = function() {
- return this.source_position_;
-};
-
-
-BreakPoint.prototype.active = function() {
- if (this.script_break_point()) {
- return this.script_break_point().active();
- }
- return this.active_;
-};
-
-
-BreakPoint.prototype.condition = function() {
- if (this.script_break_point() && this.script_break_point().condition()) {
- return this.script_break_point().condition();
- }
- return this.condition_;
-};
-
-
-BreakPoint.prototype.script_break_point = function() {
- return this.script_break_point_;
-};
-
-
-BreakPoint.prototype.enable = function() {
- this.active_ = true;
-};
-
-
-BreakPoint.prototype.disable = function() {
- this.active_ = false;
-};
-
-
-BreakPoint.prototype.setCondition = function(condition) {
- this.condition_ = condition;
-};
-
-
-BreakPoint.prototype.isTriggered = function(exec_state) {
- // Break point not active - not triggered.
- if (!this.active()) return false;
-
- // Check for conditional break point.
- if (this.condition()) {
- // If break point has condition try to evaluate it in the top frame.
- try {
- var mirror = exec_state.frame(0).evaluate(this.condition());
- // If no sensible mirror or non true value break point not triggered.
- if (!(mirror instanceof ValueMirror) || !mirror.value_) {
- return false;
- }
- } catch (e) {
- // Exception evaluating condition counts as not triggered.
- return false;
- }
- }
-
- // Break point triggered.
- return true;
-};
-
-
-// Function called from the runtime when a break point is hit. Returns true if
-// the break point is triggered and supposed to break execution.
-function IsBreakPointTriggered(break_id, break_point) {
- return break_point.isTriggered(MakeExecutionState(break_id));
-}
-
-
-// Object representing a script break point. The script is referenced by its
-// script name or script id and the break point is represented as line and
-// column.
-function ScriptBreakPoint(type, script_id_or_name, opt_line, opt_column,
- opt_groupId, opt_position_alignment) {
- this.type_ = type;
- if (type == Debug.ScriptBreakPointType.ScriptId) {
- this.script_id_ = script_id_or_name;
- } else if (type == Debug.ScriptBreakPointType.ScriptName) {
- this.script_name_ = script_id_or_name;
- } else if (type == Debug.ScriptBreakPointType.ScriptRegExp) {
- this.script_regexp_object_ = new GlobalRegExp(script_id_or_name);
- } else {
- throw %make_error(kDebugger, "Unexpected breakpoint type " + type);
- }
- this.line_ = opt_line || 0;
- this.column_ = opt_column;
- this.groupId_ = opt_groupId;
- this.position_alignment_ = IS_UNDEFINED(opt_position_alignment)
- ? Debug.BreakPositionAlignment.Statement : opt_position_alignment;
- this.active_ = true;
- this.condition_ = null;
- this.break_points_ = [];
-}
-
-
-ScriptBreakPoint.prototype.number = function() {
- return this.number_;
-};
-
-
-ScriptBreakPoint.prototype.groupId = function() {
- return this.groupId_;
-};
-
-
-ScriptBreakPoint.prototype.type = function() {
- return this.type_;
-};
-
-
-ScriptBreakPoint.prototype.script_id = function() {
- return this.script_id_;
-};
-
-
-ScriptBreakPoint.prototype.script_name = function() {
- return this.script_name_;
-};
-
-
-ScriptBreakPoint.prototype.script_regexp_object = function() {
- return this.script_regexp_object_;
-};
-
-
-ScriptBreakPoint.prototype.line = function() {
- return this.line_;
-};
-
-
-ScriptBreakPoint.prototype.column = function() {
- return this.column_;
-};
-
-
-ScriptBreakPoint.prototype.actual_locations = function() {
- var locations = [];
- for (var i = 0; i < this.break_points_.length; i++) {
- locations.push(this.break_points_[i].actual_location);
- }
- return locations;
-};
-
-
-ScriptBreakPoint.prototype.update_positions = function(line, column) {
- this.line_ = line;
- this.column_ = column;
-};
-
-
-ScriptBreakPoint.prototype.active = function() {
- return this.active_;
-};
-
-
-ScriptBreakPoint.prototype.condition = function() {
- return this.condition_;
-};
-
-
-ScriptBreakPoint.prototype.enable = function() {
- this.active_ = true;
-};
-
-
-ScriptBreakPoint.prototype.disable = function() {
- this.active_ = false;
-};
-
-
-ScriptBreakPoint.prototype.setCondition = function(condition) {
- this.condition_ = condition;
-};
-
-
-// Check whether a script matches this script break point. Currently this is
-// only based on script name.
-ScriptBreakPoint.prototype.matchesScript = function(script) {
- if (this.type_ == Debug.ScriptBreakPointType.ScriptId) {
- return this.script_id_ == script.id;
- } else {
- // We might want to account columns here as well.
- if (!(script.line_offset <= this.line_ &&
- this.line_ < script.line_offset + %ScriptLineCount(script))) {
- return false;
- }
- if (this.type_ == Debug.ScriptBreakPointType.ScriptName) {
- return this.script_name_ == script.nameOrSourceURL();
- } else if (this.type_ == Debug.ScriptBreakPointType.ScriptRegExp) {
- return this.script_regexp_object_.test(script.nameOrSourceURL());
- } else {
- throw %make_error(kDebugger, "Unexpected breakpoint type " + this.type_);
- }
- }
-};
-
-
-// Set the script break point in a script.
-ScriptBreakPoint.prototype.set = function (script) {
- var column = this.column();
- var line = this.line();
- // If the column is undefined the break is on the line. To help locate the
- // first piece of breakable code on the line try to find the column on the
- // line which contains some source.
- if (IS_UNDEFINED(column)) {
- var source_line = %ScriptSourceLine(script, line || script.line_offset);
-
- // Allocate array for caching the columns where the actual source starts.
- if (!script.sourceColumnStart_) {
- script.sourceColumnStart_ = new GlobalArray(%ScriptLineCount(script));
- }
-
- // Fill cache if needed and get column where the actual source starts.
- if (IS_UNDEFINED(script.sourceColumnStart_[line])) {
- script.sourceColumnStart_[line] =
- source_line.match(sourceLineBeginningSkip)[0].length;
- }
- column = script.sourceColumnStart_[line];
- }
-
- // Convert the line and column into an absolute position within the script.
- var position = Debug.findScriptSourcePosition(script, this.line(), column);
-
- // If the position is not found in the script (the script might be shorter
- // than it used to be) just ignore it.
- if (IS_NULL(position)) return;
-
- // Create a break point object and set the break point.
- var break_point = MakeBreakPoint(position, this);
- var actual_position = %SetScriptBreakPoint(script, position,
- this.position_alignment_,
- break_point);
- if (IS_UNDEFINED(actual_position)) {
- actual_position = position;
- }
- var actual_location = script.locationFromPosition(actual_position, true);
- break_point.actual_location = { line: actual_location.line,
- column: actual_location.column,
- script_id: script.id };
- this.break_points_.push(break_point);
- return break_point;
-};
-
-
-// Clear all the break points created from this script break point
-ScriptBreakPoint.prototype.clear = function () {
- var remaining_break_points = [];
- for (var i = 0; i < break_points.length; i++) {
- if (break_points[i].script_break_point() &&
- break_points[i].script_break_point() === this) {
- %ClearBreakPoint(break_points[i]);
- } else {
- remaining_break_points.push(break_points[i]);
- }
- }
- break_points = remaining_break_points;
- this.break_points_ = [];
-};
-
-
-Debug.setListener = function(listener, opt_data) {
- if (!IS_FUNCTION(listener) && !IS_UNDEFINED(listener) && !IS_NULL(listener)) {
- throw %make_type_error(kDebuggerType);
- }
- %SetDebugEventListener(listener, opt_data);
-};
-
-
-// Returns a Script object. If the parameter is a function the return value
-// is the script in which the function is defined. If the parameter is a string
-// the return value is the script for which the script name has that string
-// value. If it is a regexp and there is a unique script whose name matches
-// we return that, otherwise undefined.
-Debug.findScript = function(func_or_script_name) {
- if (IS_FUNCTION(func_or_script_name)) {
- return %FunctionGetScript(func_or_script_name);
- } else if (%IsRegExp(func_or_script_name)) {
- var scripts = this.scripts();
- var last_result = null;
- var result_count = 0;
- for (var i in scripts) {
- var script = scripts[i];
- if (func_or_script_name.test(script.name)) {
- last_result = script;
- result_count++;
- }
- }
- // Return the unique script matching the regexp. If there are more
- // than one we don't return a value since there is no good way to
- // decide which one to return. Returning a "random" one, say the
- // first, would introduce nondeterminism (or something close to it)
- // because the order is the heap iteration order.
- if (result_count == 1) {
- return last_result;
- } else {
- return UNDEFINED;
- }
- } else {
- return %GetScript(func_or_script_name);
- }
-};
-
-// Returns the script source. If the parameter is a function the return value
-// is the script source for the script in which the function is defined. If the
-// parameter is a string the return value is the script for which the script
-// name has that string value.
-Debug.scriptSource = function(func_or_script_name) {
- return this.findScript(func_or_script_name).source;
-};
-
-
-Debug.source = function(f) {
- if (!IS_FUNCTION(f)) throw %make_type_error(kDebuggerType);
- return %FunctionGetSourceCode(f);
-};
-
-
-Debug.sourcePosition = function(f) {
- if (!IS_FUNCTION(f)) throw %make_type_error(kDebuggerType);
- return %FunctionGetScriptSourcePosition(f);
-};
-
-
-Debug.findFunctionSourceLocation = function(func, opt_line, opt_column) {
- var script = %FunctionGetScript(func);
- var script_offset = %FunctionGetScriptSourcePosition(func);
- return %ScriptLocationFromLine(script, opt_line, opt_column, script_offset);
-};
-
-
-// Returns the character position in a script based on a line number and an
-// optional position within that line.
-Debug.findScriptSourcePosition = function(script, opt_line, opt_column) {
- var location = %ScriptLocationFromLine(script, opt_line, opt_column, 0);
- return location ? location.position : null;
-};
-
-
-Debug.findBreakPoint = function(break_point_number, remove) {
- var break_point;
- for (var i = 0; i < break_points.length; i++) {
- if (break_points[i].number() == break_point_number) {
- break_point = break_points[i];
- // Remove the break point from the list if requested.
- if (remove) {
- break_points.splice(i, 1);
- }
- break;
- }
- }
- if (break_point) {
- return break_point;
- } else {
- return this.findScriptBreakPoint(break_point_number, remove);
- }
-};
-
-Debug.findBreakPointActualLocations = function(break_point_number) {
- for (var i = 0; i < script_break_points.length; i++) {
- if (script_break_points[i].number() == break_point_number) {
- return script_break_points[i].actual_locations();
- }
- }
- for (var i = 0; i < break_points.length; i++) {
- if (break_points[i].number() == break_point_number) {
- return [break_points[i].actual_location];
- }
- }
- return [];
-};
-
-Debug.setBreakPoint = function(func, opt_line, opt_column, opt_condition) {
- if (!IS_FUNCTION(func)) throw %make_type_error(kDebuggerType);
- // Break points in API functions are not supported.
- if (%FunctionIsAPIFunction(func)) {
- throw %make_error(kDebugger, 'Cannot set break point in native code.');
- }
- // Find source position.
- var source_position =
- this.findFunctionSourceLocation(func, opt_line, opt_column).position;
- // Find the script for the function.
- var script = %FunctionGetScript(func);
- // Break in builtin JavaScript code is not supported.
- if (script.type == Debug.ScriptType.Native) {
- throw %make_error(kDebugger, 'Cannot set break point in native code.');
- }
- // If the script for the function has a name convert this to a script break
- // point.
- if (script && script.id) {
- // Find line and column for the position in the script and set a script
- // break point from that.
- var location = script.locationFromPosition(source_position, false);
- return this.setScriptBreakPointById(script.id,
- location.line, location.column,
- opt_condition);
- } else {
- // Set a break point directly on the function.
- var break_point = MakeBreakPoint(source_position);
- var actual_position =
- %SetFunctionBreakPoint(func, source_position, break_point);
- var actual_location = script.locationFromPosition(actual_position, true);
- break_point.actual_location = { line: actual_location.line,
- column: actual_location.column,
- script_id: script.id };
- break_point.setCondition(opt_condition);
- return break_point.number();
- }
-};
-
-
-Debug.setBreakPointByScriptIdAndPosition = function(script_id, position,
- condition, enabled,
- opt_position_alignment)
-{
- var break_point = MakeBreakPoint(position);
- break_point.setCondition(condition);
- if (!enabled) {
- break_point.disable();
- }
- var script = scriptById(script_id);
- if (script) {
- var position_alignment = IS_UNDEFINED(opt_position_alignment)
- ? Debug.BreakPositionAlignment.Statement : opt_position_alignment;
- break_point.actual_position = %SetScriptBreakPoint(script, position,
- position_alignment, break_point);
- }
- return break_point;
-};
-
-
-Debug.enableBreakPoint = function(break_point_number) {
- var break_point = this.findBreakPoint(break_point_number, false);
- // Only enable if the breakpoint hasn't been deleted:
- if (break_point) {
- break_point.enable();
- }
-};
-
-
-Debug.disableBreakPoint = function(break_point_number) {
- var break_point = this.findBreakPoint(break_point_number, false);
- // Only enable if the breakpoint hasn't been deleted:
- if (break_point) {
- break_point.disable();
- }
-};
-
-
-Debug.changeBreakPointCondition = function(break_point_number, condition) {
- var break_point = this.findBreakPoint(break_point_number, false);
- break_point.setCondition(condition);
-};
-
-
-Debug.clearBreakPoint = function(break_point_number) {
- var break_point = this.findBreakPoint(break_point_number, true);
- if (break_point) {
- return %ClearBreakPoint(break_point);
- } else {
- break_point = this.findScriptBreakPoint(break_point_number, true);
- if (!break_point) throw %make_error(kDebugger, 'Invalid breakpoint');
- }
-};
-
-
-Debug.clearAllBreakPoints = function() {
- for (var i = 0; i < break_points.length; i++) {
- var break_point = break_points[i];
- %ClearBreakPoint(break_point);
- }
- break_points = [];
-};
-
-
-Debug.disableAllBreakPoints = function() {
- // Disable all user defined breakpoints:
- for (var i = 1; i < next_break_point_number; i++) {
- Debug.disableBreakPoint(i);
- }
- // Disable all exception breakpoints:
- %ChangeBreakOnException(Debug.ExceptionBreak.Caught, false);
- %ChangeBreakOnException(Debug.ExceptionBreak.Uncaught, false);
-};
-
-
-Debug.findScriptBreakPoint = function(break_point_number, remove) {
- var script_break_point;
- for (var i = 0; i < script_break_points.length; i++) {
- if (script_break_points[i].number() == break_point_number) {
- script_break_point = script_break_points[i];
- // Remove the break point from the list if requested.
- if (remove) {
- script_break_point.clear();
- script_break_points.splice(i,1);
- }
- break;
- }
- }
- return script_break_point;
-};
-
-
-// Sets a breakpoint in a script identified through id or name at the
-// specified source line and column within that line.
-Debug.setScriptBreakPoint = function(type, script_id_or_name,
- opt_line, opt_column, opt_condition,
- opt_groupId, opt_position_alignment) {
- // Create script break point object.
- var script_break_point =
- new ScriptBreakPoint(type, script_id_or_name, opt_line, opt_column,
- opt_groupId, opt_position_alignment);
-
- // Assign number to the new script break point and add it.
- script_break_point.number_ = next_break_point_number++;
- script_break_point.setCondition(opt_condition);
- script_break_points.push(script_break_point);
-
- // Run through all scripts to see if this script break point matches any
- // loaded scripts.
- var scripts = this.scripts();
- for (var i = 0; i < scripts.length; i++) {
- if (script_break_point.matchesScript(scripts[i])) {
- script_break_point.set(scripts[i]);
- }
- }
-
- return script_break_point.number();
-};
-
-
-Debug.setScriptBreakPointById = function(script_id,
- opt_line, opt_column,
- opt_condition, opt_groupId,
- opt_position_alignment) {
- return this.setScriptBreakPoint(Debug.ScriptBreakPointType.ScriptId,
- script_id, opt_line, opt_column,
- opt_condition, opt_groupId,
- opt_position_alignment);
-};
-
-
-Debug.setScriptBreakPointByName = function(script_name,
- opt_line, opt_column,
- opt_condition, opt_groupId) {
- return this.setScriptBreakPoint(Debug.ScriptBreakPointType.ScriptName,
- script_name, opt_line, opt_column,
- opt_condition, opt_groupId);
-};
-
-
-Debug.setScriptBreakPointByRegExp = function(script_regexp,
- opt_line, opt_column,
- opt_condition, opt_groupId) {
- return this.setScriptBreakPoint(Debug.ScriptBreakPointType.ScriptRegExp,
- script_regexp, opt_line, opt_column,
- opt_condition, opt_groupId);
-};
-
-
-Debug.enableScriptBreakPoint = function(break_point_number) {
- var script_break_point = this.findScriptBreakPoint(break_point_number, false);
- script_break_point.enable();
-};
-
-
-Debug.disableScriptBreakPoint = function(break_point_number) {
- var script_break_point = this.findScriptBreakPoint(break_point_number, false);
- script_break_point.disable();
-};
-
-
-Debug.changeScriptBreakPointCondition = function(
- break_point_number, condition) {
- var script_break_point = this.findScriptBreakPoint(break_point_number, false);
- script_break_point.setCondition(condition);
-};
-
-
-Debug.scriptBreakPoints = function() {
- return script_break_points;
-};
-
-
-Debug.clearStepping = function() {
- %ClearStepping();
-};
-
-Debug.setBreakOnException = function() {
- return %ChangeBreakOnException(Debug.ExceptionBreak.Caught, true);
-};
-
-Debug.clearBreakOnException = function() {
- return %ChangeBreakOnException(Debug.ExceptionBreak.Caught, false);
-};
-
-Debug.isBreakOnException = function() {
- return !!%IsBreakOnException(Debug.ExceptionBreak.Caught);
-};
-
-Debug.setBreakOnUncaughtException = function() {
- return %ChangeBreakOnException(Debug.ExceptionBreak.Uncaught, true);
-};
-
-Debug.clearBreakOnUncaughtException = function() {
- return %ChangeBreakOnException(Debug.ExceptionBreak.Uncaught, false);
-};
-
-Debug.isBreakOnUncaughtException = function() {
- return !!%IsBreakOnException(Debug.ExceptionBreak.Uncaught);
-};
-
-Debug.showBreakPoints = function(f, full, opt_position_alignment) {
- if (!IS_FUNCTION(f)) throw %make_error(kDebuggerType);
- var source = full ? this.scriptSource(f) : this.source(f);
- var offset = full ? 0 : this.sourcePosition(f);
- var position_alignment = IS_UNDEFINED(opt_position_alignment)
- ? Debug.BreakPositionAlignment.Statement : opt_position_alignment;
- var locations = %GetBreakLocations(f, position_alignment);
- if (!locations) return source;
- locations.sort(function(x, y) { return x - y; });
- var result = "";
- var prev_pos = 0;
- var pos;
- for (var i = 0; i < locations.length; i++) {
- pos = locations[i] - offset;
- result += source.slice(prev_pos, pos);
- result += "[B" + i + "]";
- prev_pos = pos;
- }
- pos = source.length;
- result += source.substring(prev_pos, pos);
- return result;
-};
-
-
-// Get all the scripts currently loaded. Locating all the scripts is based on
-// scanning the heap.
-Debug.scripts = function() {
- // Collect all scripts in the heap.
- return %DebugGetLoadedScripts();
-};
-
-
-// Get a specific script currently loaded. This is based on scanning the heap.
-// TODO(clemensh): Create a runtime function for this.
-function scriptById(scriptId) {
- var scripts = Debug.scripts();
- for (var script of scripts) {
- if (script.id == scriptId) return script;
- }
- return UNDEFINED;
-};
-
-
-Debug.debuggerFlags = function() {
- return debugger_flags;
-};
-
-Debug.MakeMirror = MakeMirror;
-
-function MakeExecutionState(break_id) {
- return new ExecutionState(break_id);
-}
-
-function ExecutionState(break_id) {
- this.break_id = break_id;
- this.selected_frame = 0;
-}
-
-ExecutionState.prototype.prepareStep = function(action) {
- if (action === Debug.StepAction.StepIn ||
- action === Debug.StepAction.StepOut ||
- action === Debug.StepAction.StepNext) {
- return %PrepareStep(this.break_id, action);
- }
- throw %make_type_error(kDebuggerType);
-};
-
-ExecutionState.prototype.evaluateGlobal = function(source) {
- return MakeMirror(%DebugEvaluateGlobal(this.break_id, source));
-};
-
-ExecutionState.prototype.frameCount = function() {
- return %GetFrameCount(this.break_id);
-};
-
-ExecutionState.prototype.frame = function(opt_index) {
- // If no index supplied return the selected frame.
- if (opt_index == null) opt_index = this.selected_frame;
- if (opt_index < 0 || opt_index >= this.frameCount()) {
- throw %make_type_error(kDebuggerFrame);
- }
- return new FrameMirror(this.break_id, opt_index);
-};
-
-ExecutionState.prototype.setSelectedFrame = function(index) {
- var i = TO_NUMBER(index);
- if (i < 0 || i >= this.frameCount()) {
- throw %make_type_error(kDebuggerFrame);
- }
- this.selected_frame = i;
-};
-
-ExecutionState.prototype.selectedFrame = function() {
- return this.selected_frame;
-};
-
-function MakeBreakEvent(break_id, break_points_hit) {
- return new BreakEvent(break_id, break_points_hit);
-}
-
-
-function BreakEvent(break_id, break_points_hit) {
- this.frame_ = new FrameMirror(break_id, 0);
- this.break_points_hit_ = break_points_hit;
-}
-
-
-BreakEvent.prototype.eventType = function() {
- return Debug.DebugEvent.Break;
-};
-
-
-BreakEvent.prototype.func = function() {
- return this.frame_.func();
-};
-
-
-BreakEvent.prototype.sourceLine = function() {
- return this.frame_.sourceLine();
-};
-
-
-BreakEvent.prototype.sourceColumn = function() {
- return this.frame_.sourceColumn();
-};
-
-
-BreakEvent.prototype.sourceLineText = function() {
- return this.frame_.sourceLineText();
-};
-
-
-BreakEvent.prototype.breakPointsHit = function() {
- return this.break_points_hit_;
-};
-
-
-function MakeExceptionEvent(break_id, exception, uncaught, promise) {
- return new ExceptionEvent(break_id, exception, uncaught, promise);
-}
-
-
-function ExceptionEvent(break_id, exception, uncaught, promise) {
- this.exec_state_ = new ExecutionState(break_id);
- this.exception_ = exception;
- this.uncaught_ = uncaught;
- this.promise_ = promise;
-}
-
-
-ExceptionEvent.prototype.eventType = function() {
- return Debug.DebugEvent.Exception;
-};
-
-
-ExceptionEvent.prototype.exception = function() {
- return this.exception_;
-};
-
-
-ExceptionEvent.prototype.uncaught = function() {
- return this.uncaught_;
-};
-
-
-ExceptionEvent.prototype.promise = function() {
- return this.promise_;
-};
-
-
-ExceptionEvent.prototype.func = function() {
- return this.exec_state_.frame(0).func();
-};
-
-
-ExceptionEvent.prototype.sourceLine = function() {
- return this.exec_state_.frame(0).sourceLine();
-};
-
-
-ExceptionEvent.prototype.sourceColumn = function() {
- return this.exec_state_.frame(0).sourceColumn();
-};
-
-
-ExceptionEvent.prototype.sourceLineText = function() {
- return this.exec_state_.frame(0).sourceLineText();
-};
-
-
-function MakeCompileEvent(script, type) {
- return new CompileEvent(script, type);
-}
-
-
-function CompileEvent(script, type) {
- this.script_ = MakeMirror(script);
- this.type_ = type;
-}
-
-
-CompileEvent.prototype.eventType = function() {
- return this.type_;
-};
-
-
-CompileEvent.prototype.script = function() {
- return this.script_;
-};
-
-
-function MakeScriptObject_(script, include_source) {
- var o = { id: script.id(),
- name: script.name(),
- lineOffset: script.lineOffset(),
- columnOffset: script.columnOffset(),
- lineCount: script.lineCount(),
- };
- if (!IS_UNDEFINED(script.data())) {
- o.data = script.data();
- }
- if (include_source) {
- o.source = script.source();
- }
- return o;
-}
-
-
-function MakeAsyncTaskEvent(type, id) {
- return new AsyncTaskEvent(type, id);
-}
-
-
-function AsyncTaskEvent(type, id) {
- this.type_ = type;
- this.id_ = id;
-}
-
-
-AsyncTaskEvent.prototype.type = function() {
- return this.type_;
-}
-
-
-AsyncTaskEvent.prototype.id = function() {
- return this.id_;
-}
-
-// -------------------------------------------------------------------
-// Exports
-
-utils.InstallConstants(global, [
- "Debug", Debug,
- "BreakEvent", BreakEvent,
- "CompileEvent", CompileEvent,
- "BreakPoint", BreakPoint,
-]);
-
-// Functions needed by the debugger runtime.
-utils.InstallFunctions(utils, DONT_ENUM, [
- "MakeExecutionState", MakeExecutionState,
- "MakeExceptionEvent", MakeExceptionEvent,
- "MakeBreakEvent", MakeBreakEvent,
- "MakeCompileEvent", MakeCompileEvent,
- "MakeAsyncTaskEvent", MakeAsyncTaskEvent,
- "IsBreakPointTriggered", IsBreakPointTriggered,
-]);
-
-})
diff --git a/src/debug/ia32/debug-ia32.cc b/src/debug/ia32/debug-ia32.cc
index 0ce9874..309614c 100644
--- a/src/debug/ia32/debug-ia32.cc
+++ b/src/debug/ia32/debug-ia32.cc
@@ -6,99 +6,15 @@
#include "src/debug/debug.h"
-#include "src/codegen.h"
+#include "src/codegen/macro-assembler.h"
#include "src/debug/liveedit.h"
-#include "src/ia32/frames-ia32.h"
+#include "src/execution/frames-inl.h"
namespace v8 {
namespace internal {
#define __ ACCESS_MASM(masm)
-
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_codesize;
- __ bind(&check_codesize);
- __ Nop(Assembler::kDebugBreakSlotLength);
- DCHECK_EQ(Assembler::kDebugBreakSlotLength,
- masm->SizeOfCodeGeneratedSince(&check_codesize));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction.
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotLength);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- static const int kSize = Assembler::kDebugBreakSlotLength;
- CodePatcher patcher(isolate, pc, kSize);
-
- // Add a label for checking the size of the code used for returning.
- Label check_codesize;
- patcher.masm()->bind(&check_codesize);
- patcher.masm()->call(code->entry(), RelocInfo::NONE32);
- // Check that the size of the code generated is as expected.
- DCHECK_EQ(kSize, patcher.masm()->SizeOfCodeGeneratedSince(&check_codesize));
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- return !Assembler::IsNop(pc);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
-
- // Enter an internal frame.
- {
- FrameScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ push(eax);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ Move(eax, Immediate(1));
- __ mov(ebx,
- Immediate(ExternalReference(
- Runtime::FunctionForId(Runtime::kDebugBreak), masm->isolate())));
-
- CEntryStub ceb(masm->isolate(), 1);
- __ CallStub(&ceb);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; ++i) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber eax if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function.
- if (!(reg.is(eax) && (mode == SAVE_RESULT_REGISTER))) {
- __ Move(reg, Immediate(kDebugZapValue));
- }
- }
- }
- // Get rid of the internal frame.
- }
-
- __ MaybeDropFrames();
-
- // Return to caller.
- __ ret(0);
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -112,24 +28,24 @@
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
- // - Drop to the target frame specified by ebx.
+ // - Drop to the target frame specified by eax.
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
- __ mov(ebp, ebx);
- __ mov(edi, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset));
+ __ mov(ebp, eax);
+ __ mov(edi, Operand(ebp, StandardFrameConstants::kFunctionOffset));
__ leave();
- __ mov(ebx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
- __ mov(ebx,
- FieldOperand(ebx, SharedFunctionInfo::kFormalParameterCountOffset));
+ __ mov(eax, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
+ __ movzx_w(
+ eax, FieldOperand(eax, SharedFunctionInfo::kFormalParameterCountOffset));
- ParameterCount dummy(ebx);
- __ InvokeFunction(edi, dummy, dummy, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ // The expected and actual argument counts don't matter as long as they match
+ // and we don't enter the ArgumentsAdaptorTrampoline.
+ __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
+ __ InvokeFunctionCode(edi, no_reg, eax, eax, JUMP_FUNCTION);
}
-
const bool LiveEdit::kFrameDropperSupported = true;
#undef __
diff --git a/src/debug/interface-types.h b/src/debug/interface-types.h
index b86986d..a2645d3 100644
--- a/src/debug/interface-types.h
+++ b/src/debug/interface-types.h
@@ -9,9 +9,15 @@
#include <string>
#include <vector>
-#include "src/globals.h"
+#include "include/v8.h"
+#include "src/common/globals.h"
namespace v8 {
+
+namespace internal {
+class BuiltinArguments;
+} // namespace internal
+
namespace debug {
/**
@@ -33,44 +39,136 @@
private:
int line_number_;
int column_number_;
+ bool is_empty_;
};
-/**
- * The result of disassembling a wasm function.
- * Consists of the disassembly string and an offset table mapping wasm byte
- * offsets to line and column in the disassembly.
- * The offset table entries are ordered by the byte_offset.
- * All numbers are 0-based.
- */
-struct WasmDisassemblyOffsetTableEntry {
- WasmDisassemblyOffsetTableEntry(uint32_t byte_offset, int line, int column)
- : byte_offset(byte_offset), line(line), column(column) {}
-
- uint32_t byte_offset;
- int line;
- int column;
-};
-struct WasmDisassembly {
- using OffsetTable = std::vector<WasmDisassemblyOffsetTableEntry>;
- WasmDisassembly() {}
- WasmDisassembly(std::string disassembly, OffsetTable offset_table)
- : disassembly(std::move(disassembly)),
- offset_table(std::move(offset_table)) {}
-
- std::string disassembly;
- OffsetTable offset_table;
-};
-
-enum PromiseDebugActionType {
- kDebugPromiseCreated,
- kDebugEnqueueAsyncFunction,
- kDebugEnqueuePromiseResolve,
- kDebugEnqueuePromiseReject,
- kDebugPromiseCollected,
+enum DebugAsyncActionType {
+ kDebugPromiseThen,
+ kDebugPromiseCatch,
+ kDebugPromiseFinally,
kDebugWillHandle,
kDebugDidHandle,
+ kAsyncFunctionSuspended,
+ kAsyncFunctionFinished
};
+enum BreakLocationType {
+ kCallBreakLocation,
+ kReturnBreakLocation,
+ kDebuggerStatementBreakLocation,
+ kCommonBreakLocation
+};
+
+enum class CoverageMode {
+ // Make use of existing information in feedback vectors on the heap.
+ // Only return a yes/no result. Optimization and GC are not affected.
+ // Collecting best effort coverage does not reset counters.
+ kBestEffort,
+ // Disable optimization and prevent feedback vectors from being garbage
+ // collected in order to preserve precise invocation counts. Collecting
+ // precise count coverage resets counters to get incremental updates.
+ kPreciseCount,
+ // We are only interested in a yes/no result for the function. Optimization
+ // and GC can be allowed once a function has been invoked. Collecting
+ // precise binary coverage resets counters for incremental updates.
+ kPreciseBinary,
+ // Similar to the precise coverage modes but provides coverage at a
+ // lower granularity. Design doc: goo.gl/lA2swZ.
+ kBlockCount,
+ kBlockBinary,
+};
+
+enum class TypeProfileMode {
+ kNone,
+ kCollect,
+};
+
+class V8_EXPORT_PRIVATE BreakLocation : public Location {
+ public:
+ BreakLocation(int line_number, int column_number, BreakLocationType type)
+ : Location(line_number, column_number), type_(type) {}
+
+ BreakLocationType type() const { return type_; }
+
+ private:
+ BreakLocationType type_;
+};
+
+class ConsoleCallArguments : private v8::FunctionCallbackInfo<v8::Value> {
+ public:
+ int Length() const { return v8::FunctionCallbackInfo<v8::Value>::Length(); }
+ V8_INLINE Local<Value> operator[](int i) const {
+ return v8::FunctionCallbackInfo<v8::Value>::operator[](i);
+ }
+
+ explicit ConsoleCallArguments(const v8::FunctionCallbackInfo<v8::Value>&);
+ explicit ConsoleCallArguments(const internal::BuiltinArguments&);
+};
+
+class ConsoleContext {
+ public:
+ ConsoleContext(int id, v8::Local<v8::String> name) : id_(id), name_(name) {}
+ ConsoleContext() : id_(0) {}
+
+ int id() const { return id_; }
+ v8::Local<v8::String> name() const { return name_; }
+
+ private:
+ int id_;
+ v8::Local<v8::String> name_;
+};
+
+class ConsoleDelegate {
+ public:
+ virtual void Debug(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Error(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Info(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Log(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Warn(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Dir(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void DirXml(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Table(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Trace(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Group(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void GroupCollapsed(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void GroupEnd(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Clear(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Count(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void CountReset(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Assert(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Profile(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void ProfileEnd(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void Time(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void TimeLog(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void TimeEnd(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual void TimeStamp(const ConsoleCallArguments& args,
+ const ConsoleContext& context) {}
+ virtual ~ConsoleDelegate() = default;
+};
+
+using BreakpointId = int;
+
} // namespace debug
} // namespace v8
diff --git a/src/debug/liveedit.cc b/src/debug/liveedit.cc
index fa70e77..981692c 100644
--- a/src/debug/liveedit.cc
+++ b/src/debug/liveedit.cc
@@ -4,35 +4,59 @@
#include "src/debug/liveedit.h"
-#include "src/assembler-inl.h"
+#include "src/api/api-inl.h"
+#include "src/ast/ast-traversal-visitor.h"
+#include "src/ast/ast.h"
#include "src/ast/scopes.h"
-#include "src/code-stubs.h"
-#include "src/compilation-cache.h"
-#include "src/compiler.h"
+#include "src/codegen/compilation-cache.h"
+#include "src/codegen/compiler.h"
+#include "src/codegen/source-position-table.h"
+#include "src/common/globals.h"
+#include "src/debug/debug-interface.h"
#include "src/debug/debug.h"
-#include "src/deoptimizer.h"
-#include "src/frames-inl.h"
-#include "src/global-handles.h"
-#include "src/isolate-inl.h"
-#include "src/messages.h"
-#include "src/objects-inl.h"
-#include "src/source-position-table.h"
-#include "src/v8.h"
-#include "src/v8memory.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/isolate-inl.h"
+#include "src/execution/v8threads.h"
+#include "src/init/v8.h"
+#include "src/logging/log.h"
+#include "src/objects/hash-table-inl.h"
+#include "src/objects/js-generator-inl.h"
+#include "src/objects/objects-inl.h"
+#include "src/parsing/parse-info.h"
+#include "src/parsing/parsing.h"
namespace v8 {
namespace internal {
+namespace {
+// A general-purpose comparator between 2 arrays.
+class Comparator {
+ public:
+ // Holds 2 arrays of some elements allowing to compare any pair of
+ // element from the first array and element from the second array.
+ class Input {
+ public:
+ virtual int GetLength1() = 0;
+ virtual int GetLength2() = 0;
+ virtual bool Equals(int index1, int index2) = 0;
-void SetElementSloppy(Handle<JSObject> object,
- uint32_t index,
- Handle<Object> value) {
- // Ignore return value from SetElement. It can only be a failure if there
- // are element setters causing exceptions and the debugger context has none
- // of these.
- Object::SetElement(object->GetIsolate(), object, index, value, SLOPPY)
- .Assert();
-}
+ protected:
+ virtual ~Input() = default;
+ };
+ // Receives compare result as a series of chunks.
+ class Output {
+ public:
+ // Puts another chunk in result list. Note that technically speaking
+ // only 3 arguments actually needed with 4th being derivable.
+ virtual void AddChunk(int pos1, int pos2, int len1, int len2) = 0;
+
+ protected:
+ virtual ~Output() = default;
+ };
+
+ // Finds the difference between 2 arrays of elements.
+ static void CalculateDifference(Input* input, Output* result_writer);
+};
// A simple implementation of dynamic programming algorithm. It solves
// the problem of finding the difference of 2 arrays. It uses a table of results
@@ -163,7 +187,7 @@
// Each cell keeps a value plus direction. Value is multiplied by 4.
void set_value4_and_dir(int i1, int i2, int value4, Direction dir) {
- DCHECK((value4 & kDirectionMask) == 0);
+ DCHECK_EQ(0, value4 & kDirectionMask);
get_cell(i1, i2) = value4 | dir;
}
@@ -233,7 +257,6 @@
};
};
-
void Comparator::CalculateDifference(Comparator::Input* input,
Comparator::Output* result_writer) {
Differencer differencer(input);
@@ -242,18 +265,14 @@
differencer.SaveResult(result_writer);
}
-
-static bool CompareSubstrings(Handle<String> s1, int pos1,
- Handle<String> s2, int pos2, int len) {
+bool CompareSubstrings(Handle<String> s1, int pos1, Handle<String> s2, int pos2,
+ int len) {
for (int i = 0; i < len; i++) {
- if (s1->Get(i + pos1) != s2->Get(i + pos2)) {
- return false;
- }
+ if (s1->Get(i + pos1) != s2->Get(i + pos2)) return false;
}
return true;
}
-
// Additional to Input interface. Lets switch Input range to subrange.
// More elegant way would be to wrap one Input as another Input object
// and translate positions there, but that would cost us additional virtual
@@ -271,16 +290,9 @@
virtual void SetSubrange2(int offset, int len) = 0;
};
-
-static int min(int a, int b) {
- return a < b ? a : b;
-}
-
-
// Finds common prefix and suffix in input. This parts shouldn't take space in
// linear programming table. Enable subranging in input and output.
-static void NarrowDownInput(SubrangableInput* input,
- SubrangableOutput* output) {
+void NarrowDownInput(SubrangableInput* input, SubrangableOutput* output) {
const int len1 = input->GetLength1();
const int len2 = input->GetLength2();
@@ -289,14 +301,15 @@
{
common_prefix_len = 0;
- int prefix_limit = min(len1, len2);
+ int prefix_limit = std::min(len1, len2);
while (common_prefix_len < prefix_limit &&
input->Equals(common_prefix_len, common_prefix_len)) {
common_prefix_len++;
}
common_suffix_len = 0;
- int suffix_limit = min(len1 - common_prefix_len, len2 - common_prefix_len);
+ int suffix_limit =
+ std::min(len1 - common_prefix_len, len2 - common_prefix_len);
while (common_suffix_len < suffix_limit &&
input->Equals(len1 - common_suffix_len - 1,
@@ -317,40 +330,6 @@
}
}
-
-// A helper class that writes chunk numbers into JSArray.
-// Each chunk is stored as 3 array elements: (pos1_begin, pos1_end, pos2_end).
-class CompareOutputArrayWriter {
- public:
- explicit CompareOutputArrayWriter(Isolate* isolate)
- : array_(isolate->factory()->NewJSArray(10)), current_size_(0) {}
-
- Handle<JSArray> GetResult() {
- return array_;
- }
-
- void WriteChunk(int char_pos1, int char_pos2, int char_len1, int char_len2) {
- Isolate* isolate = array_->GetIsolate();
- SetElementSloppy(array_,
- current_size_,
- Handle<Object>(Smi::FromInt(char_pos1), isolate));
- SetElementSloppy(array_,
- current_size_ + 1,
- Handle<Object>(Smi::FromInt(char_pos1 + char_len1),
- isolate));
- SetElementSloppy(array_,
- current_size_ + 2,
- Handle<Object>(Smi::FromInt(char_pos2 + char_len2),
- isolate));
- current_size_ += 3;
- }
-
- private:
- Handle<JSArray> array_;
- int current_size_;
-};
-
-
// Represents 2 strings as 2 arrays of tokens.
// TODO(LiveEdit): Currently it's actually an array of charactres.
// Make array of tokens instead.
@@ -361,13 +340,9 @@
: s1_(s1), offset1_(offset1), len1_(len1),
s2_(s2), offset2_(offset2), len2_(len2) {
}
- virtual int GetLength1() {
- return len1_;
- }
- virtual int GetLength2() {
- return len2_;
- }
- bool Equals(int index1, int index2) {
+ int GetLength1() override { return len1_; }
+ int GetLength2() override { return len2_; }
+ bool Equals(int index1, int index2) override {
return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2);
}
@@ -380,47 +355,39 @@
int len2_;
};
-
-// Stores compare result in JSArray. Converts substring positions
+// Stores compare result in std::vector. Converts substring positions
// to absolute positions.
class TokensCompareOutput : public Comparator::Output {
public:
- TokensCompareOutput(CompareOutputArrayWriter* array_writer,
- int offset1, int offset2)
- : array_writer_(array_writer), offset1_(offset1), offset2_(offset2) {
- }
+ TokensCompareOutput(int offset1, int offset2,
+ std::vector<SourceChangeRange>* output)
+ : output_(output), offset1_(offset1), offset2_(offset2) {}
- void AddChunk(int pos1, int pos2, int len1, int len2) {
- array_writer_->WriteChunk(pos1 + offset1_, pos2 + offset2_, len1, len2);
+ void AddChunk(int pos1, int pos2, int len1, int len2) override {
+ output_->emplace_back(
+ SourceChangeRange{pos1 + offset1_, pos1 + len1 + offset1_,
+ pos2 + offset2_, pos2 + offset2_ + len2});
}
private:
- CompareOutputArrayWriter* array_writer_;
+ std::vector<SourceChangeRange>* output_;
int offset1_;
int offset2_;
};
-
// Wraps raw n-elements line_ends array as a list of n+1 lines. The last line
// never has terminating new line character.
class LineEndsWrapper {
public:
- explicit LineEndsWrapper(Handle<String> string)
- : ends_array_(String::CalculateLineEnds(string, false)),
- string_len_(string->length()) {
- }
+ explicit LineEndsWrapper(Isolate* isolate, Handle<String> string)
+ : ends_array_(String::CalculateLineEnds(isolate, string, false)),
+ string_len_(string->length()) {}
int length() {
return ends_array_->length() + 1;
}
// Returns start for any line including start of the imaginary line after
// the last line.
- int GetLineStart(int index) {
- if (index == 0) {
- return 0;
- } else {
- return GetLineEnd(index - 1);
- }
- }
+ int GetLineStart(int index) { return index == 0 ? 0 : GetLineEnd(index - 1); }
int GetLineEnd(int index) {
if (index == ends_array_->length()) {
// End of the last line is always an end of the whole string.
@@ -437,11 +404,10 @@
int string_len_;
int GetPosAfterNewLine(int index) {
- return Smi::cast(ends_array_->get(index))->value() + 1;
+ return Smi::ToInt(ends_array_->get(index)) + 1;
}
};
-
// Represents 2 strings as 2 arrays of lines.
class LineArrayCompareInput : public SubrangableInput {
public:
@@ -453,13 +419,9 @@
subrange_len1_(line_ends1_.length()),
subrange_len2_(line_ends2_.length()) {
}
- int GetLength1() {
- return subrange_len1_;
- }
- int GetLength2() {
- return subrange_len2_;
- }
- bool Equals(int index1, int index2) {
+ int GetLength1() override { return subrange_len1_; }
+ int GetLength2() override { return subrange_len2_; }
+ bool Equals(int index1, int index2) override {
index1 += subrange_offset1_;
index2 += subrange_offset2_;
@@ -475,11 +437,11 @@
return CompareSubstrings(s1_, line_start1, s2_, line_start2,
len1);
}
- void SetSubrange1(int offset, int len) {
+ void SetSubrange1(int offset, int len) override {
subrange_offset1_ = offset;
subrange_len1_ = len;
}
- void SetSubrange2(int offset, int len) {
+ void SetSubrange2(int offset, int len) override {
subrange_offset2_ = offset;
subrange_len2_ = len;
}
@@ -495,20 +457,25 @@
int subrange_len2_;
};
-
-// Stores compare result in JSArray. For each chunk tries to conduct
+// Stores compare result in std::vector. For each chunk tries to conduct
// a fine-grained nested diff token-wise.
class TokenizingLineArrayCompareOutput : public SubrangableOutput {
public:
- TokenizingLineArrayCompareOutput(LineEndsWrapper line_ends1,
+ TokenizingLineArrayCompareOutput(Isolate* isolate, LineEndsWrapper line_ends1,
LineEndsWrapper line_ends2,
- Handle<String> s1, Handle<String> s2)
- : array_writer_(s1->GetIsolate()),
- line_ends1_(line_ends1), line_ends2_(line_ends2), s1_(s1), s2_(s2),
- subrange_offset1_(0), subrange_offset2_(0) {
- }
+ Handle<String> s1, Handle<String> s2,
+ std::vector<SourceChangeRange>* output)
+ : isolate_(isolate),
+ line_ends1_(line_ends1),
+ line_ends2_(line_ends2),
+ s1_(s1),
+ s2_(s2),
+ subrange_offset1_(0),
+ subrange_offset2_(0),
+ output_(output) {}
- void AddChunk(int line_pos1, int line_pos2, int line_len1, int line_len2) {
+ void AddChunk(int line_pos1, int line_pos2, int line_len1,
+ int line_len2) override {
line_pos1 += subrange_offset1_;
line_pos2 += subrange_offset2_;
@@ -519,1137 +486,870 @@
if (char_len1 < CHUNK_LEN_LIMIT && char_len2 < CHUNK_LEN_LIMIT) {
// Chunk is small enough to conduct a nested token-level diff.
- HandleScope subTaskScope(s1_->GetIsolate());
+ HandleScope subTaskScope(isolate_);
TokensCompareInput tokens_input(s1_, char_pos1, char_len1,
s2_, char_pos2, char_len2);
- TokensCompareOutput tokens_output(&array_writer_, char_pos1,
- char_pos2);
+ TokensCompareOutput tokens_output(char_pos1, char_pos2, output_);
Comparator::CalculateDifference(&tokens_input, &tokens_output);
} else {
- array_writer_.WriteChunk(char_pos1, char_pos2, char_len1, char_len2);
+ output_->emplace_back(SourceChangeRange{
+ char_pos1, char_pos1 + char_len1, char_pos2, char_pos2 + char_len2});
}
}
- void SetSubrange1(int offset, int len) {
+ void SetSubrange1(int offset, int len) override {
subrange_offset1_ = offset;
}
- void SetSubrange2(int offset, int len) {
+ void SetSubrange2(int offset, int len) override {
subrange_offset2_ = offset;
}
- Handle<JSArray> GetResult() {
- return array_writer_.GetResult();
- }
-
private:
static const int CHUNK_LEN_LIMIT = 800;
- CompareOutputArrayWriter array_writer_;
+ Isolate* isolate_;
LineEndsWrapper line_ends1_;
LineEndsWrapper line_ends2_;
Handle<String> s1_;
Handle<String> s2_;
int subrange_offset1_;
int subrange_offset2_;
+ std::vector<SourceChangeRange>* output_;
};
+struct SourcePositionEvent {
+ enum Type { LITERAL_STARTS, LITERAL_ENDS, DIFF_STARTS, DIFF_ENDS };
-Handle<JSArray> LiveEdit::CompareStrings(Handle<String> s1,
- Handle<String> s2) {
- s1 = String::Flatten(s1);
- s2 = String::Flatten(s2);
+ int position;
+ Type type;
- LineEndsWrapper line_ends1(s1);
- LineEndsWrapper line_ends2(s2);
+ union {
+ FunctionLiteral* literal;
+ int pos_diff;
+ };
- LineArrayCompareInput input(s1, s2, line_ends1, line_ends2);
- TokenizingLineArrayCompareOutput output(line_ends1, line_ends2, s1, s2);
+ SourcePositionEvent(FunctionLiteral* literal, bool is_start)
+ : position(is_start ? literal->start_position()
+ : literal->end_position()),
+ type(is_start ? LITERAL_STARTS : LITERAL_ENDS),
+ literal(literal) {}
+ SourcePositionEvent(const SourceChangeRange& change, bool is_start)
+ : position(is_start ? change.start_position : change.end_position),
+ type(is_start ? DIFF_STARTS : DIFF_ENDS),
+ pos_diff((change.new_end_position - change.new_start_position) -
+ (change.end_position - change.start_position)) {}
- NarrowDownInput(&input, &output);
-
- Comparator::CalculateDifference(&input, &output);
-
- return output.GetResult();
-}
-
-
-// Unwraps JSValue object, returning its field "value"
-static Handle<Object> UnwrapJSValue(Handle<JSValue> jsValue) {
- return Handle<Object>(jsValue->value(), jsValue->GetIsolate());
-}
-
-
-// Wraps any object into a OpaqueReference, that will hide the object
-// from JavaScript.
-static Handle<JSValue> WrapInJSValue(Handle<HeapObject> object) {
- Isolate* isolate = object->GetIsolate();
- Handle<JSFunction> constructor = isolate->opaque_reference_function();
- Handle<JSValue> result =
- Handle<JSValue>::cast(isolate->factory()->NewJSObject(constructor));
- result->set_value(*object);
- return result;
-}
-
-
-static Handle<SharedFunctionInfo> UnwrapSharedFunctionInfoFromJSValue(
- Handle<JSValue> jsValue) {
- Object* shared = jsValue->value();
- CHECK(shared->IsSharedFunctionInfo());
- return Handle<SharedFunctionInfo>(SharedFunctionInfo::cast(shared));
-}
-
-
-static int GetArrayLength(Handle<JSArray> array) {
- Object* length = array->length();
- CHECK(length->IsSmi());
- return Smi::cast(length)->value();
-}
-
-void FunctionInfoWrapper::SetInitialProperties(Handle<String> name,
- int start_position,
- int end_position, int param_num,
- int parent_index,
- int function_literal_id) {
- HandleScope scope(isolate());
- this->SetField(kFunctionNameOffset_, name);
- this->SetSmiValueField(kStartPositionOffset_, start_position);
- this->SetSmiValueField(kEndPositionOffset_, end_position);
- this->SetSmiValueField(kParamNumOffset_, param_num);
- this->SetSmiValueField(kParentIndexOffset_, parent_index);
- this->SetSmiValueField(kFunctionLiteralIdOffset_, function_literal_id);
-}
-
-void FunctionInfoWrapper::SetSharedFunctionInfo(
- Handle<SharedFunctionInfo> info) {
- Handle<JSValue> info_holder = WrapInJSValue(info);
- this->SetField(kSharedFunctionInfoOffset_, info_holder);
-}
-
-Handle<SharedFunctionInfo> FunctionInfoWrapper::GetSharedFunctionInfo() {
- Handle<Object> element = this->GetField(kSharedFunctionInfoOffset_);
- Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element);
- Handle<Object> raw_result = UnwrapJSValue(value_wrapper);
- CHECK(raw_result->IsSharedFunctionInfo());
- return Handle<SharedFunctionInfo>::cast(raw_result);
-}
-
-void SharedInfoWrapper::SetProperties(Handle<String> name,
- int start_position,
- int end_position,
- Handle<SharedFunctionInfo> info) {
- HandleScope scope(isolate());
- this->SetField(kFunctionNameOffset_, name);
- Handle<JSValue> info_holder = WrapInJSValue(info);
- this->SetField(kSharedInfoOffset_, info_holder);
- this->SetSmiValueField(kStartPositionOffset_, start_position);
- this->SetSmiValueField(kEndPositionOffset_, end_position);
-}
-
-
-Handle<SharedFunctionInfo> SharedInfoWrapper::GetInfo() {
- Handle<Object> element = this->GetField(kSharedInfoOffset_);
- Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element);
- return UnwrapSharedFunctionInfoFromJSValue(value_wrapper);
-}
-
-
-void LiveEdit::InitializeThreadLocal(Debug* debug) {
- debug->thread_local_.restart_fp_ = 0;
-}
-
-
-MaybeHandle<JSArray> LiveEdit::GatherCompileInfo(Handle<Script> script,
- Handle<String> source) {
- Isolate* isolate = script->GetIsolate();
-
- MaybeHandle<JSArray> infos;
- Handle<Object> original_source =
- Handle<Object>(script->source(), isolate);
- script->set_source(*source);
-
- {
- // Creating verbose TryCatch from public API is currently the only way to
- // force code save location. We do not use this the object directly.
- v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
- try_catch.SetVerbose(true);
-
- // A logical 'try' section.
- infos = Compiler::CompileForLiveEdit(script);
- }
-
- // A logical 'catch' section.
- Handle<JSObject> rethrow_exception;
- if (isolate->has_pending_exception()) {
- Handle<Object> exception(isolate->pending_exception(), isolate);
- MessageLocation message_location = isolate->GetMessageLocation();
-
- isolate->clear_pending_message();
- isolate->clear_pending_exception();
-
- // If possible, copy positions from message object to exception object.
- if (exception->IsJSObject() && !message_location.script().is_null()) {
- rethrow_exception = Handle<JSObject>::cast(exception);
-
- Factory* factory = isolate->factory();
- Handle<String> start_pos_key = factory->InternalizeOneByteString(
- STATIC_CHAR_VECTOR("startPosition"));
- Handle<String> end_pos_key =
- factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("endPosition"));
- Handle<String> script_obj_key =
- factory->InternalizeOneByteString(STATIC_CHAR_VECTOR("scriptObject"));
- Handle<Smi> start_pos(
- Smi::FromInt(message_location.start_pos()), isolate);
- Handle<Smi> end_pos(Smi::FromInt(message_location.end_pos()), isolate);
- Handle<JSObject> script_obj =
- Script::GetWrapper(message_location.script());
- Object::SetProperty(rethrow_exception, start_pos_key, start_pos, SLOPPY)
- .Assert();
- Object::SetProperty(rethrow_exception, end_pos_key, end_pos, SLOPPY)
- .Assert();
- Object::SetProperty(rethrow_exception, script_obj_key, script_obj, SLOPPY)
- .Assert();
+ static bool LessThan(const SourcePositionEvent& a,
+ const SourcePositionEvent& b) {
+ if (a.position != b.position) return a.position < b.position;
+ if (a.type != b.type) return a.type < b.type;
+ if (a.type == LITERAL_STARTS && b.type == LITERAL_STARTS) {
+ // If the literals start in the same position, we want the one with the
+ // furthest (i.e. largest) end position to be first.
+ if (a.literal->end_position() != b.literal->end_position()) {
+ return a.literal->end_position() > b.literal->end_position();
+ }
+ // If they also end in the same position, we want the first in order of
+ // literal ids to be first.
+ return a.literal->function_literal_id() <
+ b.literal->function_literal_id();
+ } else if (a.type == LITERAL_ENDS && b.type == LITERAL_ENDS) {
+ // If the literals end in the same position, we want the one with the
+ // nearest (i.e. largest) start position to be first.
+ if (a.literal->start_position() != b.literal->start_position()) {
+ return a.literal->start_position() > b.literal->start_position();
+ }
+ // If they also end in the same position, we want the last in order of
+ // literal ids to be first.
+ return a.literal->function_literal_id() >
+ b.literal->function_literal_id();
+ } else {
+ return a.pos_diff < b.pos_diff;
}
}
+};
- // A logical 'finally' section.
- script->set_source(*original_source);
+struct FunctionLiteralChange {
+ // If any of start/end position is kNoSourcePosition, this literal is
+ // considered damaged and will not be mapped and edited at all.
+ int new_start_position;
+ int new_end_position;
+ bool has_changes;
+ FunctionLiteral* outer_literal;
- if (rethrow_exception.is_null()) {
- return infos.ToHandleChecked();
- } else {
- return isolate->Throw<JSArray>(rethrow_exception);
+ explicit FunctionLiteralChange(int new_start_position, FunctionLiteral* outer)
+ : new_start_position(new_start_position),
+ new_end_position(kNoSourcePosition),
+ has_changes(false),
+ outer_literal(outer) {}
+};
+
+using FunctionLiteralChanges =
+ std::unordered_map<FunctionLiteral*, FunctionLiteralChange>;
+void CalculateFunctionLiteralChanges(
+ const std::vector<FunctionLiteral*>& literals,
+ const std::vector<SourceChangeRange>& diffs,
+ FunctionLiteralChanges* result) {
+ std::vector<SourcePositionEvent> events;
+ events.reserve(literals.size() * 2 + diffs.size() * 2);
+ for (FunctionLiteral* literal : literals) {
+ events.emplace_back(literal, true);
+ events.emplace_back(literal, false);
}
-}
-
-// Finds all references to original and replaces them with substitution.
-static void ReplaceCodeObject(Handle<Code> original,
- Handle<Code> substitution) {
- // Perform a full GC in order to ensure that we are not in the middle of an
- // incremental marking phase when we are replacing the code object.
- // Since we are not in an incremental marking phase we can write pointers
- // to code objects (that are never in new space) without worrying about
- // write barriers.
- Heap* heap = original->GetHeap();
- HeapIterator iterator(heap, HeapIterator::kFilterUnreachable);
- // Now iterate over all pointers of all objects, including code_target
- // implicit pointers.
- for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) {
- if (obj->IsJSFunction()) {
- JSFunction* fun = JSFunction::cast(obj);
- if (fun->code() == *original) fun->ReplaceCode(*substitution);
- } else if (obj->IsSharedFunctionInfo()) {
- SharedFunctionInfo* info = SharedFunctionInfo::cast(obj);
- if (info->code() == *original) info->set_code(*substitution);
+ for (const SourceChangeRange& diff : diffs) {
+ events.emplace_back(diff, true);
+ events.emplace_back(diff, false);
+ }
+ std::sort(events.begin(), events.end(), SourcePositionEvent::LessThan);
+ bool inside_diff = false;
+ int delta = 0;
+ std::stack<std::pair<FunctionLiteral*, FunctionLiteralChange>> literal_stack;
+ for (const SourcePositionEvent& event : events) {
+ switch (event.type) {
+ case SourcePositionEvent::DIFF_ENDS:
+ DCHECK(inside_diff);
+ inside_diff = false;
+ delta += event.pos_diff;
+ break;
+ case SourcePositionEvent::LITERAL_ENDS: {
+ DCHECK_EQ(literal_stack.top().first, event.literal);
+ FunctionLiteralChange& change = literal_stack.top().second;
+ change.new_end_position = inside_diff
+ ? kNoSourcePosition
+ : event.literal->end_position() + delta;
+ result->insert(literal_stack.top());
+ literal_stack.pop();
+ break;
+ }
+ case SourcePositionEvent::LITERAL_STARTS:
+ literal_stack.push(std::make_pair(
+ event.literal,
+ FunctionLiteralChange(
+ inside_diff ? kNoSourcePosition
+ : event.literal->start_position() + delta,
+ literal_stack.empty() ? nullptr : literal_stack.top().first)));
+ break;
+ case SourcePositionEvent::DIFF_STARTS:
+ DCHECK(!inside_diff);
+ inside_diff = true;
+ if (!literal_stack.empty()) {
+ // Note that outer literal has not necessarily changed, unless the
+ // diff goes past the end of this literal. In this case, we'll mark
+ // this function as damaged and parent as changed later in
+ // MapLiterals.
+ literal_stack.top().second.has_changes = true;
+ }
+ break;
}
}
}
-// Patch function feedback vector.
-// The feedback vector is a cache for complex object boilerplates and for a
-// native context. We must clean cached values, or if the structure of the
-// vector itself changes we need to allocate a new one.
-class FeedbackVectorFixer {
+// Function which has not changed itself, but if any variable in its
+// outer context has been added/removed, we must consider this function
+// as damaged and not update references to it.
+// This is because old compiled function has hardcoded references to
+// it's outer context.
+bool HasChangedScope(FunctionLiteral* a, FunctionLiteral* b) {
+ Scope* scope_a = a->scope()->outer_scope();
+ Scope* scope_b = b->scope()->outer_scope();
+ while (scope_a && scope_b) {
+ std::unordered_map<int, Handle<String>> vars;
+ for (Variable* var : *scope_a->locals()) {
+ if (!var->IsContextSlot()) continue;
+ vars[var->index()] = var->name();
+ }
+ for (Variable* var : *scope_b->locals()) {
+ if (!var->IsContextSlot()) continue;
+ auto it = vars.find(var->index());
+ if (it == vars.end()) return true;
+ if (*it->second != *var->name()) return true;
+ }
+ scope_a = scope_a->outer_scope();
+ scope_b = scope_b->outer_scope();
+ }
+ return scope_a != scope_b;
+}
+
+enum ChangeState { UNCHANGED, CHANGED, DAMAGED };
+
+using LiteralMap = std::unordered_map<FunctionLiteral*, FunctionLiteral*>;
+void MapLiterals(const FunctionLiteralChanges& changes,
+ const std::vector<FunctionLiteral*>& new_literals,
+ LiteralMap* unchanged, LiteralMap* changed) {
+ // Track the top-level script function separately as it can overlap fully with
+ // another function, e.g. the script "()=>42".
+ const std::pair<int, int> kTopLevelMarker = std::make_pair(-1, -1);
+ std::map<std::pair<int, int>, FunctionLiteral*> position_to_new_literal;
+ for (FunctionLiteral* literal : new_literals) {
+ DCHECK(literal->start_position() != kNoSourcePosition);
+ DCHECK(literal->end_position() != kNoSourcePosition);
+ std::pair<int, int> key =
+ literal->function_literal_id() == kFunctionLiteralIdTopLevel
+ ? kTopLevelMarker
+ : std::make_pair(literal->start_position(),
+ literal->end_position());
+ // Make sure there are no duplicate keys.
+ DCHECK_EQ(position_to_new_literal.find(key), position_to_new_literal.end());
+ position_to_new_literal[key] = literal;
+ }
+ LiteralMap mappings;
+ std::unordered_map<FunctionLiteral*, ChangeState> change_state;
+ for (const auto& change_pair : changes) {
+ FunctionLiteral* literal = change_pair.first;
+ const FunctionLiteralChange& change = change_pair.second;
+ std::pair<int, int> key =
+ literal->function_literal_id() == kFunctionLiteralIdTopLevel
+ ? kTopLevelMarker
+ : std::make_pair(change.new_start_position,
+ change.new_end_position);
+ auto it = position_to_new_literal.find(key);
+ if (it == position_to_new_literal.end() ||
+ HasChangedScope(literal, it->second)) {
+ change_state[literal] = ChangeState::DAMAGED;
+ if (!change.outer_literal) continue;
+ if (change_state[change.outer_literal] != ChangeState::DAMAGED) {
+ change_state[change.outer_literal] = ChangeState::CHANGED;
+ }
+ } else {
+ mappings[literal] = it->second;
+ if (change_state.find(literal) == change_state.end()) {
+ change_state[literal] =
+ change.has_changes ? ChangeState::CHANGED : ChangeState::UNCHANGED;
+ }
+ }
+ }
+ for (const auto& mapping : mappings) {
+ if (change_state[mapping.first] == ChangeState::UNCHANGED) {
+ (*unchanged)[mapping.first] = mapping.second;
+ } else if (change_state[mapping.first] == ChangeState::CHANGED) {
+ (*changed)[mapping.first] = mapping.second;
+ }
+ }
+}
+
+class CollectFunctionLiterals final
+ : public AstTraversalVisitor<CollectFunctionLiterals> {
public:
- static void PatchFeedbackVector(FunctionInfoWrapper* compile_info_wrapper,
- Handle<SharedFunctionInfo> shared_info,
- Isolate* isolate) {
- // When feedback metadata changes, we have to create new array instances.
- // Since we cannot create instances when iterating heap, we should first
- // collect all functions and fix their literal arrays.
- Handle<FixedArray> function_instances =
- CollectJSFunctions(shared_info, isolate);
-
- for (int i = 0; i < function_instances->length(); i++) {
- Handle<JSFunction> fun(JSFunction::cast(function_instances->get(i)));
- Handle<Cell> new_cell = isolate->factory()->NewManyClosuresCell(
- isolate->factory()->undefined_value());
- fun->set_feedback_vector_cell(*new_cell);
- // Only create feedback vectors if we already have the metadata.
- if (shared_info->is_compiled()) JSFunction::EnsureLiterals(fun);
- }
+ CollectFunctionLiterals(Isolate* isolate, AstNode* root)
+ : AstTraversalVisitor<CollectFunctionLiterals>(isolate, root) {}
+ void VisitFunctionLiteral(FunctionLiteral* lit) {
+ AstTraversalVisitor::VisitFunctionLiteral(lit);
+ literals_->push_back(lit);
+ }
+ void Run(std::vector<FunctionLiteral*>* literals) {
+ literals_ = literals;
+ AstTraversalVisitor::Run();
+ literals_ = nullptr;
}
private:
- // Iterates all function instances in the HEAP that refers to the
- // provided shared_info.
- template<typename Visitor>
- static void IterateJSFunctions(Handle<SharedFunctionInfo> shared_info,
- Visitor* visitor) {
- HeapIterator iterator(shared_info->GetHeap());
- for (HeapObject* obj = iterator.next(); obj != NULL;
- obj = iterator.next()) {
- if (obj->IsJSFunction()) {
- JSFunction* function = JSFunction::cast(obj);
- if (function->shared() == *shared_info) {
- visitor->visit(function);
+ std::vector<FunctionLiteral*>* literals_;
+};
+
+bool ParseScript(Isolate* isolate, Handle<Script> script, ParseInfo* parse_info,
+ bool compile_as_well, std::vector<FunctionLiteral*>* literals,
+ debug::LiveEditResult* result) {
+ v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
+ Handle<SharedFunctionInfo> shared;
+ bool success = false;
+ if (compile_as_well) {
+ success = Compiler::CompileForLiveEdit(parse_info, script, isolate)
+ .ToHandle(&shared);
+ } else {
+ success = parsing::ParseProgram(parse_info, script, isolate,
+ parsing::ReportStatisticsMode::kYes);
+ if (!success) {
+ // Throw the parser error.
+ parse_info->pending_error_handler()->PrepareErrors(
+ isolate, parse_info->ast_value_factory());
+ parse_info->pending_error_handler()->ReportErrors(isolate, script);
+ }
+ }
+ if (!success) {
+ isolate->OptionalRescheduleException(false);
+ DCHECK(try_catch.HasCaught());
+ result->message = try_catch.Message()->Get();
+ auto self = Utils::OpenHandle(*try_catch.Message());
+ auto msg = i::Handle<i::JSMessageObject>::cast(self);
+ result->line_number = msg->GetLineNumber();
+ result->column_number = msg->GetColumnNumber();
+ result->status = debug::LiveEditResult::COMPILE_ERROR;
+ return false;
+ }
+ CollectFunctionLiterals(isolate, parse_info->literal()).Run(literals);
+ return true;
+}
+
+struct FunctionData {
+ FunctionData(FunctionLiteral* literal, bool should_restart)
+ : literal(literal),
+ stack_position(NOT_ON_STACK),
+ should_restart(should_restart) {}
+
+ FunctionLiteral* literal;
+ MaybeHandle<SharedFunctionInfo> shared;
+ std::vector<Handle<JSFunction>> js_functions;
+ std::vector<Handle<JSGeneratorObject>> running_generators;
+ // In case of multiple functions with different stack position, the latest
+ // one (in the order below) is used, since it is the most restrictive.
+ // This is important only for functions to be restarted.
+ enum StackPosition {
+ NOT_ON_STACK,
+ ABOVE_BREAK_FRAME,
+ PATCHABLE,
+ BELOW_NON_DROPPABLE_FRAME,
+ ARCHIVED_THREAD,
+ };
+ StackPosition stack_position;
+ bool should_restart;
+};
+
+class FunctionDataMap : public ThreadVisitor {
+ public:
+ void AddInterestingLiteral(int script_id, FunctionLiteral* literal,
+ bool should_restart) {
+ map_.emplace(GetFuncId(script_id, literal),
+ FunctionData{literal, should_restart});
+ }
+
+ bool Lookup(SharedFunctionInfo sfi, FunctionData** data) {
+ int start_position = sfi.StartPosition();
+ if (!sfi.script().IsScript() || start_position == -1) {
+ return false;
+ }
+ Script script = Script::cast(sfi.script());
+ return Lookup(GetFuncId(script.id(), sfi), data);
+ }
+
+ bool Lookup(Handle<Script> script, FunctionLiteral* literal,
+ FunctionData** data) {
+ return Lookup(GetFuncId(script->id(), literal), data);
+ }
+
+ void Fill(Isolate* isolate, Address* restart_frame_fp) {
+ {
+ HeapObjectIterator iterator(isolate->heap(),
+ HeapObjectIterator::kFilterUnreachable);
+ for (HeapObject obj = iterator.Next(); !obj.is_null();
+ obj = iterator.Next()) {
+ if (obj.IsSharedFunctionInfo()) {
+ SharedFunctionInfo sfi = SharedFunctionInfo::cast(obj);
+ FunctionData* data = nullptr;
+ if (!Lookup(sfi, &data)) continue;
+ data->shared = handle(sfi, isolate);
+ } else if (obj.IsJSFunction()) {
+ JSFunction js_function = JSFunction::cast(obj);
+ SharedFunctionInfo sfi = js_function.shared();
+ FunctionData* data = nullptr;
+ if (!Lookup(sfi, &data)) continue;
+ data->js_functions.emplace_back(js_function, isolate);
+ } else if (obj.IsJSGeneratorObject()) {
+ JSGeneratorObject gen = JSGeneratorObject::cast(obj);
+ if (gen.is_closed()) continue;
+ SharedFunctionInfo sfi = gen.function().shared();
+ FunctionData* data = nullptr;
+ if (!Lookup(sfi, &data)) continue;
+ data->running_generators.emplace_back(gen, isolate);
}
}
}
- }
-
- // Finds all instances of JSFunction that refers to the provided shared_info
- // and returns array with them.
- static Handle<FixedArray> CollectJSFunctions(
- Handle<SharedFunctionInfo> shared_info, Isolate* isolate) {
- CountVisitor count_visitor;
- count_visitor.count = 0;
- IterateJSFunctions(shared_info, &count_visitor);
- int size = count_visitor.count;
-
- Handle<FixedArray> result = isolate->factory()->NewFixedArray(size);
- if (size > 0) {
- CollectVisitor collect_visitor(result);
- IterateJSFunctions(shared_info, &collect_visitor);
- }
- return result;
- }
-
- class CountVisitor {
- public:
- void visit(JSFunction* fun) {
- count++;
- }
- int count;
- };
-
- class CollectVisitor {
- public:
- explicit CollectVisitor(Handle<FixedArray> output)
- : m_output(output), m_pos(0) {}
-
- void visit(JSFunction* fun) {
- m_output->set(m_pos, fun);
- m_pos++;
- }
- private:
- Handle<FixedArray> m_output;
- int m_pos;
- };
-};
-
-
-// Marks code that shares the same shared function info or has inlined
-// code that shares the same function info.
-class DependentFunctionMarker: public OptimizedFunctionVisitor {
- public:
- SharedFunctionInfo* shared_info_;
- bool found_;
-
- explicit DependentFunctionMarker(SharedFunctionInfo* shared_info)
- : shared_info_(shared_info), found_(false) { }
-
- virtual void EnterContext(Context* context) { } // Don't care.
- virtual void LeaveContext(Context* context) { } // Don't care.
- virtual void VisitFunction(JSFunction* function) {
- // It should be guaranteed by the iterator that everything is optimized.
- DCHECK(function->code()->kind() == Code::OPTIMIZED_FUNCTION);
- if (function->Inlines(shared_info_)) {
- // Mark the code for deoptimization.
- function->code()->set_marked_for_deoptimization(true);
- found_ = true;
- }
- }
-};
-
-
-static void DeoptimizeDependentFunctions(SharedFunctionInfo* function_info) {
- DisallowHeapAllocation no_allocation;
- DependentFunctionMarker marker(function_info);
- // TODO(titzer): need to traverse all optimized code to find OSR code here.
- Deoptimizer::VisitAllOptimizedFunctions(function_info->GetIsolate(), &marker);
-
- if (marker.found_) {
- // Only go through with the deoptimization if something was found.
- Deoptimizer::DeoptimizeMarkedCode(function_info->GetIsolate());
- }
-}
-
-
-void LiveEdit::ReplaceFunctionCode(
- Handle<JSArray> new_compile_info_array,
- Handle<JSArray> shared_info_array) {
- Isolate* isolate = new_compile_info_array->GetIsolate();
-
- FunctionInfoWrapper compile_info_wrapper(new_compile_info_array);
- SharedInfoWrapper shared_info_wrapper(shared_info_array);
-
- Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo();
- Handle<SharedFunctionInfo> new_shared_info =
- compile_info_wrapper.GetSharedFunctionInfo();
-
- if (shared_info->is_compiled()) {
- // Take whatever code we can get from the new shared function info. We
- // expect activations of neither the old bytecode nor old FCG code, since
- // the lowest activation is going to be restarted.
- Handle<Code> old_code(shared_info->code());
- Handle<Code> new_code(new_shared_info->code());
- // Clear old bytecode. This will trigger self-healing if we do not install
- // new bytecode.
- shared_info->ClearBytecodeArray();
- if (!shared_info->HasBaselineCode()) {
- // Every function from this SFI is interpreted.
- if (!new_shared_info->HasBaselineCode()) {
- // We have newly compiled bytecode. Simply replace the old one.
- shared_info->set_bytecode_array(new_shared_info->bytecode_array());
- } else {
- // Rely on self-healing for places that used to run bytecode.
- shared_info->ReplaceCode(*new_code);
+ FunctionData::StackPosition stack_position =
+ isolate->debug()->break_frame_id() == StackFrameId::NO_ID
+ ? FunctionData::PATCHABLE
+ : FunctionData::ABOVE_BREAK_FRAME;
+ for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
+ StackFrame* frame = it.frame();
+ if (stack_position == FunctionData::ABOVE_BREAK_FRAME) {
+ if (frame->id() == isolate->debug()->break_frame_id()) {
+ stack_position = FunctionData::PATCHABLE;
+ }
}
- } else {
- // Functions from this SFI can be either interpreted or running FCG.
- DCHECK(old_code->kind() == Code::FUNCTION);
- if (new_shared_info->HasBytecodeArray()) {
- // Start using new bytecode everywhere.
- shared_info->set_bytecode_array(new_shared_info->bytecode_array());
- ReplaceCodeObject(old_code,
- isolate->builtins()->InterpreterEntryTrampoline());
- } else {
- // Start using new FCG code everywhere.
- // Rely on self-healing for places that used to run bytecode.
- DCHECK(new_code->kind() == Code::FUNCTION);
- ReplaceCodeObject(old_code, new_code);
+ if (stack_position == FunctionData::PATCHABLE &&
+ (frame->is_exit() || frame->is_builtin_exit())) {
+ stack_position = FunctionData::BELOW_NON_DROPPABLE_FRAME;
+ continue;
+ }
+ if (!frame->is_java_script()) continue;
+ std::vector<Handle<SharedFunctionInfo>> sfis;
+ JavaScriptFrame::cast(frame)->GetFunctions(&sfis);
+ for (auto& sfi : sfis) {
+ if (stack_position == FunctionData::PATCHABLE &&
+ IsResumableFunction(sfi->kind())) {
+ stack_position = FunctionData::BELOW_NON_DROPPABLE_FRAME;
+ }
+ FunctionData* data = nullptr;
+ if (!Lookup(*sfi, &data)) continue;
+ if (!data->should_restart) continue;
+ data->stack_position = stack_position;
+ *restart_frame_fp = frame->fp();
}
}
- if (shared_info->HasDebugInfo()) {
- // Existing break points will be re-applied. Reset the debug info here.
- isolate->debug()->RemoveDebugInfoAndClearFromShared(
- handle(shared_info->GetDebugInfo()));
+ isolate->thread_manager()->IterateArchivedThreads(this);
+ }
+
+ private:
+ // Unique id for a function: script_id + start_position, where start_position
+ // is special cased to -1 for top-level so that it does not overlap with a
+ // function whose start position is 0.
+ using FuncId = std::pair<int, int>;
+
+ FuncId GetFuncId(int script_id, FunctionLiteral* literal) {
+ int start_position = literal->start_position();
+ if (literal->function_literal_id() == 0) {
+ // This is the top-level script function literal, so special case its
+ // start position
+ DCHECK_EQ(start_position, 0);
+ start_position = -1;
}
- shared_info->set_scope_info(new_shared_info->scope_info());
- shared_info->set_outer_scope_info(new_shared_info->outer_scope_info());
- shared_info->DisableOptimization(kLiveEdit);
- // Update the type feedback vector, if needed.
- Handle<FeedbackMetadata> new_feedback_metadata(
- new_shared_info->feedback_metadata());
- shared_info->set_feedback_metadata(*new_feedback_metadata);
- } else {
- shared_info->set_feedback_metadata(
- FeedbackMetadata::cast(isolate->heap()->empty_fixed_array()));
+ return FuncId(script_id, start_position);
}
- int start_position = compile_info_wrapper.GetStartPosition();
- int end_position = compile_info_wrapper.GetEndPosition();
- shared_info->set_start_position(start_position);
- shared_info->set_end_position(end_position);
-
- FeedbackVectorFixer::PatchFeedbackVector(&compile_info_wrapper, shared_info,
- isolate);
-
- DeoptimizeDependentFunctions(*shared_info);
- isolate->compilation_cache()->Remove(shared_info);
-}
-
-void LiveEdit::FunctionSourceUpdated(Handle<JSArray> shared_info_array,
- int new_function_literal_id) {
- SharedInfoWrapper shared_info_wrapper(shared_info_array);
- Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo();
-
- shared_info->set_function_literal_id(new_function_literal_id);
- DeoptimizeDependentFunctions(*shared_info);
- shared_info_array->GetIsolate()->compilation_cache()->Remove(shared_info);
-}
-
-void LiveEdit::FixupScript(Handle<Script> script, int max_function_literal_id) {
- Isolate* isolate = script->GetIsolate();
- Handle<FixedArray> old_infos(script->shared_function_infos(), isolate);
- Handle<FixedArray> new_infos(
- isolate->factory()->NewFixedArray(max_function_literal_id + 1));
- script->set_shared_function_infos(*new_infos);
- SharedFunctionInfo::ScriptIterator iterator(isolate, old_infos);
- while (SharedFunctionInfo* shared = iterator.Next()) {
- // We can't use SharedFunctionInfo::SetScript(info, undefined_value()) here,
- // as we severed the link from the Script to the SharedFunctionInfo above.
- Handle<SharedFunctionInfo> info(shared, isolate);
- info->set_script(isolate->heap()->undefined_value());
- Handle<Object> new_noscript_list = WeakFixedArray::Add(
- isolate->factory()->noscript_shared_function_infos(), info);
- isolate->heap()->SetRootNoScriptSharedFunctionInfos(*new_noscript_list);
-
- // Put the SharedFunctionInfo at its new, correct location.
- SharedFunctionInfo::SetScript(info, script);
+ FuncId GetFuncId(int script_id, SharedFunctionInfo sfi) {
+ DCHECK_EQ(script_id, Script::cast(sfi.script()).id());
+ int start_position = sfi.StartPosition();
+ DCHECK_NE(start_position, -1);
+ if (sfi.is_toplevel()) {
+ // This is the top-level function, so special case its start position
+ DCHECK_EQ(start_position, 0);
+ start_position = -1;
+ }
+ return FuncId(script_id, start_position);
}
+
+ bool Lookup(FuncId id, FunctionData** data) {
+ auto it = map_.find(id);
+ if (it == map_.end()) return false;
+ *data = &it->second;
+ return true;
+ }
+
+ void VisitThread(Isolate* isolate, ThreadLocalTop* top) override {
+ for (JavaScriptFrameIterator it(isolate, top); !it.done(); it.Advance()) {
+ std::vector<Handle<SharedFunctionInfo>> sfis;
+ it.frame()->GetFunctions(&sfis);
+ for (auto& sfi : sfis) {
+ FunctionData* data = nullptr;
+ if (!Lookup(*sfi, &data)) continue;
+ data->stack_position = FunctionData::ARCHIVED_THREAD;
+ }
+ }
+ }
+
+ std::map<FuncId, FunctionData> map_;
+};
+
+bool CanPatchScript(
+ const LiteralMap& changed, Handle<Script> script, Handle<Script> new_script,
+ FunctionDataMap& function_data_map, // NOLINT(runtime/references)
+ debug::LiveEditResult* result) {
+ debug::LiveEditResult::Status status = debug::LiveEditResult::OK;
+ for (const auto& mapping : changed) {
+ FunctionData* data = nullptr;
+ function_data_map.Lookup(script, mapping.first, &data);
+ FunctionData* new_data = nullptr;
+ function_data_map.Lookup(new_script, mapping.second, &new_data);
+ Handle<SharedFunctionInfo> sfi;
+ if (!data->shared.ToHandle(&sfi)) {
+ continue;
+ } else if (!data->should_restart) {
+ UNREACHABLE();
+ } else if (data->stack_position == FunctionData::ABOVE_BREAK_FRAME) {
+ status = debug::LiveEditResult::BLOCKED_BY_FUNCTION_ABOVE_BREAK_FRAME;
+ } else if (data->stack_position ==
+ FunctionData::BELOW_NON_DROPPABLE_FRAME) {
+ status =
+ debug::LiveEditResult::BLOCKED_BY_FUNCTION_BELOW_NON_DROPPABLE_FRAME;
+ } else if (!data->running_generators.empty()) {
+ status = debug::LiveEditResult::BLOCKED_BY_RUNNING_GENERATOR;
+ } else if (data->stack_position == FunctionData::ARCHIVED_THREAD) {
+ status = debug::LiveEditResult::BLOCKED_BY_ACTIVE_FUNCTION;
+ }
+ if (status != debug::LiveEditResult::OK) {
+ result->status = status;
+ return false;
+ }
+ }
+ return true;
}
-void LiveEdit::SetFunctionScript(Handle<JSValue> function_wrapper,
- Handle<Object> script_handle) {
- Handle<SharedFunctionInfo> shared_info =
- UnwrapSharedFunctionInfoFromJSValue(function_wrapper);
- Isolate* isolate = function_wrapper->GetIsolate();
- CHECK(script_handle->IsScript() || script_handle->IsUndefined(isolate));
- SharedFunctionInfo::SetScript(shared_info, script_handle);
- shared_info->DisableOptimization(kLiveEdit);
-
- function_wrapper->GetIsolate()->compilation_cache()->Remove(shared_info);
-}
-
-namespace {
-// For a script text change (defined as position_change_array), translates
-// position in unchanged text to position in changed text.
-// Text change is a set of non-overlapping regions in text, that have changed
-// their contents and length. It is specified as array of groups of 3 numbers:
-// (change_begin, change_end, change_end_new_position).
-// Each group describes a change in text; groups are sorted by change_begin.
-// Only position in text beyond any changes may be successfully translated.
-// If a positions is inside some region that changed, result is currently
-// undefined.
-static int TranslatePosition(int original_position,
- Handle<JSArray> position_change_array) {
- int position_diff = 0;
- int array_len = GetArrayLength(position_change_array);
- Isolate* isolate = position_change_array->GetIsolate();
- // TODO(635): binary search may be used here
- for (int i = 0; i < array_len; i += 3) {
- HandleScope scope(isolate);
- Handle<Object> element =
- JSReceiver::GetElement(isolate, position_change_array, i)
- .ToHandleChecked();
- CHECK(element->IsSmi());
- int chunk_start = Handle<Smi>::cast(element)->value();
- if (original_position < chunk_start) {
+bool CanRestartFrame(
+ Isolate* isolate, Address fp,
+ FunctionDataMap& function_data_map, // NOLINT(runtime/references)
+ const LiteralMap& changed, debug::LiveEditResult* result) {
+ DCHECK_GT(fp, 0);
+ StackFrame* restart_frame = nullptr;
+ StackFrameIterator it(isolate);
+ for (; !it.done(); it.Advance()) {
+ if (it.frame()->fp() == fp) {
+ restart_frame = it.frame();
break;
}
- element = JSReceiver::GetElement(isolate, position_change_array, i + 1)
- .ToHandleChecked();
- CHECK(element->IsSmi());
- int chunk_end = Handle<Smi>::cast(element)->value();
- // Position mustn't be inside a chunk.
- DCHECK(original_position >= chunk_end);
- element = JSReceiver::GetElement(isolate, position_change_array, i + 2)
- .ToHandleChecked();
- CHECK(element->IsSmi());
- int chunk_changed_end = Handle<Smi>::cast(element)->value();
- position_diff = chunk_changed_end - chunk_end;
}
-
- return original_position + position_diff;
+ DCHECK(restart_frame && restart_frame->is_java_script());
+ if (!LiveEdit::kFrameDropperSupported) {
+ result->status = debug::LiveEditResult::FRAME_RESTART_IS_NOT_SUPPORTED;
+ return false;
+ }
+ std::vector<Handle<SharedFunctionInfo>> sfis;
+ JavaScriptFrame::cast(restart_frame)->GetFunctions(&sfis);
+ for (auto& sfi : sfis) {
+ FunctionData* data = nullptr;
+ if (!function_data_map.Lookup(*sfi, &data)) continue;
+ auto new_literal_it = changed.find(data->literal);
+ if (new_literal_it == changed.end()) continue;
+ if (new_literal_it->second->scope()->new_target_var()) {
+ result->status =
+ debug::LiveEditResult::BLOCKED_BY_NEW_TARGET_IN_RESTART_FRAME;
+ return false;
+ }
+ }
+ return true;
}
-void TranslateSourcePositionTable(Handle<AbstractCode> code,
- Handle<JSArray> position_change_array) {
- Isolate* isolate = code->GetIsolate();
+void TranslateSourcePositionTable(Isolate* isolate, Handle<BytecodeArray> code,
+ const std::vector<SourceChangeRange>& diffs) {
Zone zone(isolate->allocator(), ZONE_NAME);
SourcePositionTableBuilder builder(&zone);
- Handle<ByteArray> source_position_table(code->source_position_table());
+ Handle<ByteArray> source_position_table(code->SourcePositionTable(), isolate);
for (SourcePositionTableIterator iterator(*source_position_table);
!iterator.done(); iterator.Advance()) {
SourcePosition position = iterator.source_position();
position.SetScriptOffset(
- TranslatePosition(position.ScriptOffset(), position_change_array));
+ LiveEdit::TranslatePosition(diffs, position.ScriptOffset()));
builder.AddPosition(iterator.code_offset(), position,
iterator.is_statement());
}
Handle<ByteArray> new_source_position_table(
- builder.ToSourcePositionTable(isolate, code));
- code->set_source_position_table(*new_source_position_table);
+ builder.ToSourcePositionTable(isolate));
+ code->set_source_position_table(*new_source_position_table, kReleaseStore);
+ LOG_CODE_EVENT(isolate,
+ CodeLinePosInfoRecordEvent(code->GetFirstBytecodeAddress(),
+ *new_source_position_table));
}
-} // namespace
-void LiveEdit::PatchFunctionPositions(Handle<JSArray> shared_info_array,
- Handle<JSArray> position_change_array) {
- SharedInfoWrapper shared_info_wrapper(shared_info_array);
- Handle<SharedFunctionInfo> info = shared_info_wrapper.GetInfo();
-
- int old_function_start = info->start_position();
- int new_function_start = TranslatePosition(old_function_start,
- position_change_array);
- int new_function_end = TranslatePosition(info->end_position(),
- position_change_array);
- int new_function_token_pos =
- TranslatePosition(info->function_token_position(), position_change_array);
-
- info->set_start_position(new_function_start);
- info->set_end_position(new_function_end);
- info->set_function_token_position(new_function_token_pos);
-
- if (info->HasBytecodeArray()) {
+void UpdatePositions(Isolate* isolate, Handle<SharedFunctionInfo> sfi,
+ const std::vector<SourceChangeRange>& diffs) {
+ int old_start_position = sfi->StartPosition();
+ int new_start_position =
+ LiveEdit::TranslatePosition(diffs, old_start_position);
+ int new_end_position = LiveEdit::TranslatePosition(diffs, sfi->EndPosition());
+ int new_function_token_position =
+ LiveEdit::TranslatePosition(diffs, sfi->function_token_position());
+ sfi->SetPosition(new_start_position, new_end_position);
+ sfi->SetFunctionTokenPosition(new_function_token_position,
+ new_start_position);
+ if (sfi->HasBytecodeArray()) {
TranslateSourcePositionTable(
- Handle<AbstractCode>(AbstractCode::cast(info->bytecode_array())),
- position_change_array);
- }
- if (info->code()->kind() == Code::FUNCTION) {
- TranslateSourcePositionTable(
- Handle<AbstractCode>(AbstractCode::cast(info->code())),
- position_change_array);
- }
- if (info->HasDebugInfo()) {
- // Existing break points will be re-applied. Reset the debug info here.
- info->GetIsolate()->debug()->RemoveDebugInfoAndClearFromShared(
- handle(info->GetDebugInfo()));
+ isolate, handle(sfi->GetBytecodeArray(), isolate), diffs);
}
}
+} // anonymous namespace
-
-static Handle<Script> CreateScriptCopy(Handle<Script> original) {
- Isolate* isolate = original->GetIsolate();
-
- Handle<String> original_source(String::cast(original->source()));
- Handle<Script> copy = isolate->factory()->NewScript(original_source);
-
- copy->set_name(original->name());
- copy->set_line_offset(original->line_offset());
- copy->set_column_offset(original->column_offset());
- copy->set_type(original->type());
- copy->set_context_data(original->context_data());
- copy->set_eval_from_shared(original->eval_from_shared());
- copy->set_eval_from_position(original->eval_from_position());
-
- Handle<FixedArray> infos(isolate->factory()->NewFixedArray(
- original->shared_function_infos()->length()));
- copy->set_shared_function_infos(*infos);
-
- // Copy all the flags, but clear compilation state.
- copy->set_flags(original->flags());
- copy->set_compilation_state(Script::COMPILATION_STATE_INITIAL);
-
- return copy;
-}
-
-Handle<Object> LiveEdit::ChangeScriptSource(Handle<Script> original_script,
- Handle<String> new_source,
- Handle<Object> old_script_name) {
- Isolate* isolate = original_script->GetIsolate();
- Handle<Object> old_script_object;
- if (old_script_name->IsString()) {
- Handle<Script> old_script = CreateScriptCopy(original_script);
- old_script->set_name(String::cast(*old_script_name));
- old_script_object = old_script;
- isolate->debug()->OnAfterCompile(old_script);
- } else {
- old_script_object = isolate->factory()->null_value();
+void LiveEdit::PatchScript(Isolate* isolate, Handle<Script> script,
+ Handle<String> new_source, bool preview,
+ debug::LiveEditResult* result) {
+ std::vector<SourceChangeRange> diffs;
+ LiveEdit::CompareStrings(isolate,
+ handle(String::cast(script->source()), isolate),
+ new_source, &diffs);
+ if (diffs.empty()) {
+ result->status = debug::LiveEditResult::OK;
+ return;
}
- original_script->set_source(*new_source);
+ UnoptimizedCompileState compile_state(isolate);
+ UnoptimizedCompileFlags flags =
+ UnoptimizedCompileFlags::ForScriptCompile(isolate, *script);
+ flags.set_is_eager(true);
+ ParseInfo parse_info(isolate, flags, &compile_state);
+ std::vector<FunctionLiteral*> literals;
+ if (!ParseScript(isolate, script, &parse_info, false, &literals, result))
+ return;
- // Drop line ends so that they will be recalculated.
- original_script->set_line_ends(isolate->heap()->undefined_value());
+ Handle<Script> new_script = isolate->factory()->CloneScript(script);
+ new_script->set_source(*new_source);
+ UnoptimizedCompileState new_compile_state(isolate);
+ UnoptimizedCompileFlags new_flags =
+ UnoptimizedCompileFlags::ForScriptCompile(isolate, *new_script);
+ new_flags.set_is_eager(true);
+ ParseInfo new_parse_info(isolate, new_flags, &new_compile_state);
+ std::vector<FunctionLiteral*> new_literals;
+ if (!ParseScript(isolate, new_script, &new_parse_info, true, &new_literals,
+ result)) {
+ return;
+ }
- return old_script_object;
-}
+ FunctionLiteralChanges literal_changes;
+ CalculateFunctionLiteralChanges(literals, diffs, &literal_changes);
+ LiteralMap changed;
+ LiteralMap unchanged;
+ MapLiterals(literal_changes, new_literals, &unchanged, &changed);
+ FunctionDataMap function_data_map;
+ for (const auto& mapping : changed) {
+ function_data_map.AddInterestingLiteral(script->id(), mapping.first, true);
+ function_data_map.AddInterestingLiteral(new_script->id(), mapping.second,
+ false);
+ }
+ for (const auto& mapping : unchanged) {
+ function_data_map.AddInterestingLiteral(script->id(), mapping.first, false);
+ }
+ Address restart_frame_fp = 0;
+ function_data_map.Fill(isolate, &restart_frame_fp);
-void LiveEdit::ReplaceRefToNestedFunction(
- Handle<JSValue> parent_function_wrapper,
- Handle<JSValue> orig_function_wrapper,
- Handle<JSValue> subst_function_wrapper) {
+ if (!CanPatchScript(changed, script, new_script, function_data_map, result)) {
+ return;
+ }
+ if (restart_frame_fp &&
+ !CanRestartFrame(isolate, restart_frame_fp, function_data_map, changed,
+ result)) {
+ return;
+ }
- Handle<SharedFunctionInfo> parent_shared =
- UnwrapSharedFunctionInfoFromJSValue(parent_function_wrapper);
- Handle<SharedFunctionInfo> orig_shared =
- UnwrapSharedFunctionInfoFromJSValue(orig_function_wrapper);
- Handle<SharedFunctionInfo> subst_shared =
- UnwrapSharedFunctionInfoFromJSValue(subst_function_wrapper);
+ if (preview) {
+ result->status = debug::LiveEditResult::OK;
+ return;
+ }
- for (RelocIterator it(parent_shared->code()); !it.done(); it.next()) {
- if (it.rinfo()->rmode() == RelocInfo::EMBEDDED_OBJECT) {
- if (it.rinfo()->target_object() == *orig_shared) {
- it.rinfo()->set_target_object(*subst_shared);
+ std::map<int, int> start_position_to_unchanged_id;
+ for (const auto& mapping : unchanged) {
+ FunctionData* data = nullptr;
+ if (!function_data_map.Lookup(script, mapping.first, &data)) continue;
+ Handle<SharedFunctionInfo> sfi;
+ if (!data->shared.ToHandle(&sfi)) continue;
+ DCHECK_EQ(sfi->script(), *script);
+
+ isolate->compilation_cache()->Remove(sfi);
+ isolate->debug()->DeoptimizeFunction(sfi);
+ if (sfi->HasDebugInfo()) {
+ Handle<DebugInfo> debug_info(sfi->GetDebugInfo(), isolate);
+ isolate->debug()->RemoveBreakInfoAndMaybeFree(debug_info);
+ }
+ SharedFunctionInfo::EnsureSourcePositionsAvailable(isolate, sfi);
+ UpdatePositions(isolate, sfi, diffs);
+
+ sfi->set_script(*new_script);
+ sfi->set_function_literal_id(mapping.second->function_literal_id());
+ new_script->shared_function_infos().Set(
+ mapping.second->function_literal_id(), HeapObjectReference::Weak(*sfi));
+ DCHECK_EQ(sfi->function_literal_id(),
+ mapping.second->function_literal_id());
+
+ // Save the new start_position -> id mapping, so that we can recover it when
+ // iterating over changed functions' constant pools.
+ start_position_to_unchanged_id[mapping.second->start_position()] =
+ mapping.second->function_literal_id();
+
+ if (sfi->HasUncompiledDataWithPreparseData()) {
+ sfi->ClearPreparseData();
+ }
+
+ for (auto& js_function : data->js_functions) {
+ js_function->set_raw_feedback_cell(
+ *isolate->factory()->many_closures_cell());
+ if (!js_function->is_compiled()) continue;
+ IsCompiledScope is_compiled_scope(
+ js_function->shared().is_compiled_scope(isolate));
+ JSFunction::EnsureFeedbackVector(js_function, &is_compiled_scope);
+ }
+
+ if (!sfi->HasBytecodeArray()) continue;
+ FixedArray constants = sfi->GetBytecodeArray().constant_pool();
+ for (int i = 0; i < constants.length(); ++i) {
+ if (!constants.get(i).IsSharedFunctionInfo()) continue;
+ FunctionData* data = nullptr;
+ if (!function_data_map.Lookup(SharedFunctionInfo::cast(constants.get(i)),
+ &data)) {
+ continue;
+ }
+ auto change_it = changed.find(data->literal);
+ if (change_it == changed.end()) continue;
+ if (!function_data_map.Lookup(new_script, change_it->second, &data)) {
+ continue;
+ }
+ Handle<SharedFunctionInfo> new_sfi;
+ if (!data->shared.ToHandle(&new_sfi)) continue;
+ constants.set(i, *new_sfi);
+ }
+ }
+ for (const auto& mapping : changed) {
+ FunctionData* data = nullptr;
+ if (!function_data_map.Lookup(new_script, mapping.second, &data)) continue;
+ Handle<SharedFunctionInfo> new_sfi = data->shared.ToHandleChecked();
+ DCHECK_EQ(new_sfi->script(), *new_script);
+
+ if (!function_data_map.Lookup(script, mapping.first, &data)) continue;
+ Handle<SharedFunctionInfo> sfi;
+ if (!data->shared.ToHandle(&sfi)) continue;
+
+ isolate->debug()->DeoptimizeFunction(sfi);
+ isolate->compilation_cache()->Remove(sfi);
+ for (auto& js_function : data->js_functions) {
+ js_function->set_shared(*new_sfi);
+ js_function->set_code(js_function->shared().GetCode());
+
+ js_function->set_raw_feedback_cell(
+ *isolate->factory()->many_closures_cell());
+ if (!js_function->is_compiled()) continue;
+ IsCompiledScope is_compiled_scope(
+ js_function->shared().is_compiled_scope(isolate));
+ JSFunction::EnsureFeedbackVector(js_function, &is_compiled_scope);
+ }
+ }
+ SharedFunctionInfo::ScriptIterator it(isolate, *new_script);
+ for (SharedFunctionInfo sfi = it.Next(); !sfi.is_null(); sfi = it.Next()) {
+ if (!sfi.HasBytecodeArray()) continue;
+ FixedArray constants = sfi.GetBytecodeArray().constant_pool();
+ for (int i = 0; i < constants.length(); ++i) {
+ if (!constants.get(i).IsSharedFunctionInfo()) continue;
+ SharedFunctionInfo inner_sfi = SharedFunctionInfo::cast(constants.get(i));
+ // See if there is a mapping from this function's start position to a
+ // unchanged function's id.
+ auto unchanged_it =
+ start_position_to_unchanged_id.find(inner_sfi.StartPosition());
+ if (unchanged_it == start_position_to_unchanged_id.end()) continue;
+
+ // Grab that function id from the new script's SFI list, which should have
+ // already been updated in in the unchanged pass.
+ SharedFunctionInfo old_unchanged_inner_sfi =
+ SharedFunctionInfo::cast(new_script->shared_function_infos()
+ .Get(unchanged_it->second)
+ ->GetHeapObject());
+ if (old_unchanged_inner_sfi == inner_sfi) continue;
+ DCHECK_NE(old_unchanged_inner_sfi, inner_sfi);
+ // Now some sanity checks. Make sure that the unchanged SFI has already
+ // been processed and patched to be on the new script ...
+ DCHECK_EQ(old_unchanged_inner_sfi.script(), *new_script);
+ constants.set(i, old_unchanged_inner_sfi);
+ }
+ }
+#ifdef DEBUG
+ {
+ // Check that all the functions in the new script are valid, that their
+ // function literals match what is expected, and that start positions are
+ // unique.
+ DisallowHeapAllocation no_gc;
+
+ SharedFunctionInfo::ScriptIterator it(isolate, *new_script);
+ std::set<int> start_positions;
+ for (SharedFunctionInfo sfi = it.Next(); !sfi.is_null(); sfi = it.Next()) {
+ DCHECK_EQ(sfi.script(), *new_script);
+ DCHECK_EQ(sfi.function_literal_id(), it.CurrentIndex());
+ // Don't check the start position of the top-level function, as it can
+ // overlap with a function in the script.
+ if (sfi.is_toplevel()) {
+ DCHECK_EQ(start_positions.find(sfi.StartPosition()),
+ start_positions.end());
+ start_positions.insert(sfi.StartPosition());
+ }
+
+ if (!sfi.HasBytecodeArray()) continue;
+ // Check that all the functions in this function's constant pool are also
+ // on the new script, and that their id matches their index in the new
+ // scripts function list.
+ FixedArray constants = sfi.GetBytecodeArray().constant_pool();
+ for (int i = 0; i < constants.length(); ++i) {
+ if (!constants.get(i).IsSharedFunctionInfo()) continue;
+ SharedFunctionInfo inner_sfi =
+ SharedFunctionInfo::cast(constants.get(i));
+ DCHECK_EQ(inner_sfi.script(), *new_script);
+ DCHECK_EQ(inner_sfi, new_script->shared_function_infos()
+ .Get(inner_sfi.function_literal_id())
+ ->GetHeapObject());
}
}
}
+#endif
+
+ if (restart_frame_fp) {
+ for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
+ if (it.frame()->fp() == restart_frame_fp) {
+ isolate->debug()->ScheduleFrameRestart(it.frame());
+ result->stack_changed = true;
+ break;
+ }
+ }
+ }
+
+ int script_id = script->id();
+ script->set_id(new_script->id());
+ new_script->set_id(script_id);
+ result->status = debug::LiveEditResult::OK;
+ result->script = ToApiHandle<v8::debug::Script>(new_script);
}
+void LiveEdit::InitializeThreadLocal(Debug* debug) {
+ debug->thread_local_.restart_fp_ = 0;
+}
-// Check an activation against list of functions. If there is a function
-// that matches, its status in result array is changed to status argument value.
-static bool CheckActivation(Handle<JSArray> shared_info_array,
- Handle<JSArray> result,
- StackFrame* frame,
- LiveEdit::FunctionPatchabilityStatus status) {
- if (!frame->is_java_script()) return false;
-
- Handle<JSFunction> function(JavaScriptFrame::cast(frame)->function());
-
- Isolate* isolate = shared_info_array->GetIsolate();
- int len = GetArrayLength(shared_info_array);
- for (int i = 0; i < len; i++) {
- HandleScope scope(isolate);
- Handle<Object> element =
- JSReceiver::GetElement(isolate, shared_info_array, i).ToHandleChecked();
- Handle<JSValue> jsvalue = Handle<JSValue>::cast(element);
- Handle<SharedFunctionInfo> shared =
- UnwrapSharedFunctionInfoFromJSValue(jsvalue);
-
- if (function->Inlines(*shared)) {
- SetElementSloppy(result, i, Handle<Smi>(Smi::FromInt(status), isolate));
- return true;
+bool LiveEdit::RestartFrame(JavaScriptFrame* frame) {
+ if (!LiveEdit::kFrameDropperSupported) return false;
+ Isolate* isolate = frame->isolate();
+ StackFrameId break_frame_id = isolate->debug()->break_frame_id();
+ bool break_frame_found = break_frame_id == StackFrameId::NO_ID;
+ for (StackFrameIterator it(isolate); !it.done(); it.Advance()) {
+ StackFrame* current = it.frame();
+ break_frame_found = break_frame_found || break_frame_id == current->id();
+ if (current->fp() == frame->fp()) {
+ if (break_frame_found) {
+ isolate->debug()->ScheduleFrameRestart(current);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ if (!break_frame_found) continue;
+ if (current->is_exit() || current->is_builtin_exit()) {
+ return false;
+ }
+ if (!current->is_java_script()) continue;
+ std::vector<Handle<SharedFunctionInfo>> shareds;
+ JavaScriptFrame::cast(current)->GetFunctions(&shareds);
+ for (auto& shared : shareds) {
+ if (IsResumableFunction(shared->kind())) {
+ return false;
+ }
}
}
return false;
}
-// Describes a set of call frames that execute any of listed functions.
-// Finding no such frames does not mean error.
-class MultipleFunctionTarget {
- public:
- MultipleFunctionTarget(Handle<JSArray> old_shared_array,
- Handle<JSArray> new_shared_array,
- Handle<JSArray> result)
- : old_shared_array_(old_shared_array),
- new_shared_array_(new_shared_array),
- result_(result) {}
- bool MatchActivation(StackFrame* frame,
- LiveEdit::FunctionPatchabilityStatus status) {
- return CheckActivation(old_shared_array_, result_, frame, status);
- }
- const char* GetNotFoundMessage() const {
- return NULL;
- }
- bool FrameUsesNewTarget(StackFrame* frame) {
- if (!frame->is_java_script()) return false;
- JavaScriptFrame* jsframe = JavaScriptFrame::cast(frame);
- Handle<SharedFunctionInfo> old_shared(jsframe->function()->shared());
- Isolate* isolate = old_shared->GetIsolate();
- int len = GetArrayLength(old_shared_array_);
- // Find corresponding new shared function info and return whether it
- // references new.target.
- for (int i = 0; i < len; i++) {
- HandleScope scope(isolate);
- Handle<Object> old_element =
- JSReceiver::GetElement(isolate, old_shared_array_, i)
- .ToHandleChecked();
- if (!old_shared.is_identical_to(UnwrapSharedFunctionInfoFromJSValue(
- Handle<JSValue>::cast(old_element)))) {
- continue;
- }
+void LiveEdit::CompareStrings(Isolate* isolate, Handle<String> s1,
+ Handle<String> s2,
+ std::vector<SourceChangeRange>* diffs) {
+ s1 = String::Flatten(isolate, s1);
+ s2 = String::Flatten(isolate, s2);
- Handle<Object> new_element =
- JSReceiver::GetElement(isolate, new_shared_array_, i)
- .ToHandleChecked();
- if (new_element->IsUndefined(isolate)) return false;
- Handle<SharedFunctionInfo> new_shared =
- UnwrapSharedFunctionInfoFromJSValue(
- Handle<JSValue>::cast(new_element));
- if (new_shared->scope_info()->HasNewTarget()) {
- SetElementSloppy(
- result_, i,
- Handle<Smi>(
- Smi::FromInt(
- LiveEdit::FUNCTION_BLOCKED_NO_NEW_TARGET_ON_RESTART),
- isolate));
- return true;
- }
- return false;
- }
- return false;
- }
+ LineEndsWrapper line_ends1(isolate, s1);
+ LineEndsWrapper line_ends2(isolate, s2);
- void set_status(LiveEdit::FunctionPatchabilityStatus status) {
- Isolate* isolate = old_shared_array_->GetIsolate();
- int len = GetArrayLength(old_shared_array_);
- for (int i = 0; i < len; ++i) {
- Handle<Object> old_element =
- JSReceiver::GetElement(isolate, result_, i).ToHandleChecked();
- if (!old_element->IsSmi() ||
- Smi::cast(*old_element)->value() ==
- LiveEdit::FUNCTION_AVAILABLE_FOR_PATCH) {
- SetElementSloppy(result_, i,
- Handle<Smi>(Smi::FromInt(status), isolate));
- }
- }
- }
+ LineArrayCompareInput input(s1, s2, line_ends1, line_ends2);
+ TokenizingLineArrayCompareOutput output(isolate, line_ends1, line_ends2, s1,
+ s2, diffs);
- private:
- Handle<JSArray> old_shared_array_;
- Handle<JSArray> new_shared_array_;
- Handle<JSArray> result_;
-};
+ NarrowDownInput(&input, &output);
-
-// Drops all call frame matched by target and all frames above them.
-template <typename TARGET>
-static const char* DropActivationsInActiveThreadImpl(Isolate* isolate,
- TARGET& target, // NOLINT
- bool do_drop) {
- Debug* debug = isolate->debug();
- Zone zone(isolate->allocator(), ZONE_NAME);
- Vector<StackFrame*> frames = CreateStackMap(isolate, &zone);
-
- int top_frame_index = -1;
- int frame_index = 0;
- for (; frame_index < frames.length(); frame_index++) {
- StackFrame* frame = frames[frame_index];
- if (frame->id() == debug->break_frame_id()) {
- top_frame_index = frame_index;
- break;
- }
- if (target.MatchActivation(
- frame, LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
- // We are still above break_frame. It is not a target frame,
- // it is a problem.
- return "Debugger mark-up on stack is not found";
- }
- }
-
- if (top_frame_index == -1) {
- // We haven't found break frame, but no function is blocking us anyway.
- return target.GetNotFoundMessage();
- }
-
- bool target_frame_found = false;
- int bottom_js_frame_index = top_frame_index;
- bool non_droppable_frame_found = false;
- LiveEdit::FunctionPatchabilityStatus non_droppable_reason;
-
- for (; frame_index < frames.length(); frame_index++) {
- StackFrame* frame = frames[frame_index];
- if (frame->is_exit() || frame->is_builtin_exit()) {
- non_droppable_frame_found = true;
- non_droppable_reason = LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE;
- break;
- }
- if (frame->is_java_script()) {
- SharedFunctionInfo* shared =
- JavaScriptFrame::cast(frame)->function()->shared();
- if (IsResumableFunction(shared->kind())) {
- non_droppable_frame_found = true;
- non_droppable_reason = LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR;
- break;
- }
- }
- if (target.MatchActivation(
- frame, LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
- target_frame_found = true;
- bottom_js_frame_index = frame_index;
- }
- }
-
- if (non_droppable_frame_found) {
- // There is a C or generator frame on stack. We can't drop C frames, and we
- // can't restart generators. Check that there are no target frames below
- // them.
- for (; frame_index < frames.length(); frame_index++) {
- StackFrame* frame = frames[frame_index];
- if (frame->is_java_script()) {
- if (target.MatchActivation(frame, non_droppable_reason)) {
- // Fail.
- return NULL;
- }
- if (non_droppable_reason ==
- LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR &&
- !target_frame_found) {
- // Fail.
- target.set_status(non_droppable_reason);
- return NULL;
- }
- }
- }
- }
-
- // We cannot restart a frame that uses new.target.
- if (target.FrameUsesNewTarget(frames[bottom_js_frame_index])) return NULL;
-
- if (!do_drop) {
- // We are in check-only mode.
- return NULL;
- }
-
- if (!target_frame_found) {
- // Nothing to drop.
- return target.GetNotFoundMessage();
- }
-
- if (!LiveEdit::kFrameDropperSupported) {
- return "Stack manipulations are not supported in this architecture.";
- }
-
- debug->ScheduleFrameRestart(frames[bottom_js_frame_index]);
- return NULL;
+ Comparator::CalculateDifference(&input, &output);
}
-
-// Fills result array with statuses of functions. Modifies the stack
-// removing all listed function if possible and if do_drop is true.
-static const char* DropActivationsInActiveThread(
- Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array,
- Handle<JSArray> result, bool do_drop) {
- MultipleFunctionTarget target(old_shared_array, new_shared_array, result);
- Isolate* isolate = old_shared_array->GetIsolate();
-
- const char* message =
- DropActivationsInActiveThreadImpl(isolate, target, do_drop);
- if (message) {
- return message;
+int LiveEdit::TranslatePosition(const std::vector<SourceChangeRange>& diffs,
+ int position) {
+ auto it = std::lower_bound(diffs.begin(), diffs.end(), position,
+ [](const SourceChangeRange& change, int position) {
+ return change.end_position < position;
+ });
+ if (it != diffs.end() && position == it->end_position) {
+ return it->new_end_position;
}
-
- int array_len = GetArrayLength(old_shared_array);
-
- // Replace "blocked on active" with "replaced on active" status.
- for (int i = 0; i < array_len; i++) {
- Handle<Object> obj =
- JSReceiver::GetElement(isolate, result, i).ToHandleChecked();
- if (*obj == Smi::FromInt(LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
- Handle<Object> replaced(
- Smi::FromInt(LiveEdit::FUNCTION_REPLACED_ON_ACTIVE_STACK), isolate);
- SetElementSloppy(result, i, replaced);
- }
- }
- return NULL;
+ if (it == diffs.begin()) return position;
+ DCHECK(it == diffs.end() || position <= it->start_position);
+ it = std::prev(it);
+ return position + (it->new_end_position - it->end_position);
}
-
-
-bool LiveEdit::FindActiveGenerators(Handle<FixedArray> shared_info_array,
- Handle<FixedArray> result,
- int len) {
- Isolate* isolate = shared_info_array->GetIsolate();
- bool found_suspended_activations = false;
-
- DCHECK_LE(len, result->length());
-
- FunctionPatchabilityStatus active = FUNCTION_BLOCKED_ACTIVE_GENERATOR;
-
- Heap* heap = isolate->heap();
- HeapIterator iterator(heap, HeapIterator::kFilterUnreachable);
- HeapObject* obj = NULL;
- while ((obj = iterator.next()) != NULL) {
- if (!obj->IsJSGeneratorObject()) continue;
-
- JSGeneratorObject* gen = JSGeneratorObject::cast(obj);
- if (gen->is_closed()) continue;
-
- HandleScope scope(isolate);
-
- for (int i = 0; i < len; i++) {
- Handle<JSValue> jsvalue = Handle<JSValue>::cast(
- FixedArray::get(*shared_info_array, i, isolate));
- Handle<SharedFunctionInfo> shared =
- UnwrapSharedFunctionInfoFromJSValue(jsvalue);
-
- if (gen->function()->shared() == *shared) {
- result->set(i, Smi::FromInt(active));
- found_suspended_activations = true;
- }
- }
- }
-
- return found_suspended_activations;
-}
-
-
-class InactiveThreadActivationsChecker : public ThreadVisitor {
- public:
- InactiveThreadActivationsChecker(Handle<JSArray> old_shared_array,
- Handle<JSArray> result)
- : old_shared_array_(old_shared_array),
- result_(result),
- has_blocked_functions_(false) {}
- void VisitThread(Isolate* isolate, ThreadLocalTop* top) {
- for (StackFrameIterator it(isolate, top); !it.done(); it.Advance()) {
- has_blocked_functions_ |=
- CheckActivation(old_shared_array_, result_, it.frame(),
- LiveEdit::FUNCTION_BLOCKED_ON_OTHER_STACK);
- }
- }
- bool HasBlockedFunctions() {
- return has_blocked_functions_;
- }
-
- private:
- Handle<JSArray> old_shared_array_;
- Handle<JSArray> result_;
- bool has_blocked_functions_;
-};
-
-
-Handle<JSArray> LiveEdit::CheckAndDropActivations(
- Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array,
- bool do_drop) {
- Isolate* isolate = old_shared_array->GetIsolate();
- int len = GetArrayLength(old_shared_array);
-
- DCHECK(old_shared_array->HasFastElements());
- Handle<FixedArray> old_shared_array_elements(
- FixedArray::cast(old_shared_array->elements()));
-
- Handle<JSArray> result = isolate->factory()->NewJSArray(len);
- result->set_length(Smi::FromInt(len));
- JSObject::EnsureWritableFastElements(result);
- Handle<FixedArray> result_elements =
- handle(FixedArray::cast(result->elements()), isolate);
-
- // Fill the default values.
- for (int i = 0; i < len; i++) {
- FunctionPatchabilityStatus status = FUNCTION_AVAILABLE_FOR_PATCH;
- result_elements->set(i, Smi::FromInt(status));
- }
-
- // Scan the heap for active generators -- those that are either currently
- // running (as we wouldn't want to restart them, because we don't know where
- // to restart them from) or suspended. Fail if any one corresponds to the set
- // of functions being edited.
- if (FindActiveGenerators(old_shared_array_elements, result_elements, len)) {
- return result;
- }
-
- // Check inactive threads. Fail if some functions are blocked there.
- InactiveThreadActivationsChecker inactive_threads_checker(old_shared_array,
- result);
- isolate->thread_manager()->IterateArchivedThreads(
- &inactive_threads_checker);
- if (inactive_threads_checker.HasBlockedFunctions()) {
- return result;
- }
-
- // Try to drop activations from the current stack.
- const char* error_message = DropActivationsInActiveThread(
- old_shared_array, new_shared_array, result, do_drop);
- if (error_message != NULL) {
- // Add error message as an array extra element.
- Handle<String> str =
- isolate->factory()->NewStringFromAsciiChecked(error_message);
- SetElementSloppy(result, len, str);
- }
- return result;
-}
-
-
-// Describes a single callframe a target. Not finding this frame
-// means an error.
-class SingleFrameTarget {
- public:
- explicit SingleFrameTarget(JavaScriptFrame* frame)
- : m_frame(frame),
- m_saved_status(LiveEdit::FUNCTION_AVAILABLE_FOR_PATCH) {}
-
- bool MatchActivation(StackFrame* frame,
- LiveEdit::FunctionPatchabilityStatus status) {
- if (frame->fp() == m_frame->fp()) {
- m_saved_status = status;
- return true;
- }
- return false;
- }
- const char* GetNotFoundMessage() const {
- return "Failed to found requested frame";
- }
- LiveEdit::FunctionPatchabilityStatus saved_status() {
- return m_saved_status;
- }
- void set_status(LiveEdit::FunctionPatchabilityStatus status) {
- m_saved_status = status;
- }
-
- bool FrameUsesNewTarget(StackFrame* frame) {
- if (!frame->is_java_script()) return false;
- JavaScriptFrame* jsframe = JavaScriptFrame::cast(frame);
- Handle<SharedFunctionInfo> shared(jsframe->function()->shared());
- return shared->scope_info()->HasNewTarget();
- }
-
- private:
- JavaScriptFrame* m_frame;
- LiveEdit::FunctionPatchabilityStatus m_saved_status;
-};
-
-
-// Finds a drops required frame and all frames above.
-// Returns error message or NULL.
-const char* LiveEdit::RestartFrame(JavaScriptFrame* frame) {
- SingleFrameTarget target(frame);
-
- const char* result =
- DropActivationsInActiveThreadImpl(frame->isolate(), target, true);
- if (result != NULL) {
- return result;
- }
- if (target.saved_status() == LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE) {
- return "Function is blocked under native code";
- }
- if (target.saved_status() == LiveEdit::FUNCTION_BLOCKED_UNDER_GENERATOR) {
- return "Function is blocked under a generator activation";
- }
- return NULL;
-}
-
-Handle<JSArray> LiveEditFunctionTracker::Collect(FunctionLiteral* node,
- Handle<Script> script,
- Zone* zone, Isolate* isolate) {
- LiveEditFunctionTracker visitor(script, zone, isolate);
- visitor.VisitFunctionLiteral(node);
- return visitor.result_;
-}
-
-LiveEditFunctionTracker::LiveEditFunctionTracker(Handle<Script> script,
- Zone* zone, Isolate* isolate)
- : AstTraversalVisitor<LiveEditFunctionTracker>(isolate) {
- current_parent_index_ = -1;
- isolate_ = isolate;
- len_ = 0;
- result_ = isolate->factory()->NewJSArray(10);
- script_ = script;
- zone_ = zone;
-}
-
-void LiveEditFunctionTracker::VisitFunctionLiteral(FunctionLiteral* node) {
- // FunctionStarted is called in pre-order.
- FunctionStarted(node);
- // Recurse using the regular traversal.
- AstTraversalVisitor::VisitFunctionLiteral(node);
- // FunctionDone are called in post-order.
- Handle<SharedFunctionInfo> info =
- script_->FindSharedFunctionInfo(isolate_, node).ToHandleChecked();
- FunctionDone(info, node->scope());
-}
-
-void LiveEditFunctionTracker::FunctionStarted(FunctionLiteral* fun) {
- HandleScope handle_scope(isolate_);
- FunctionInfoWrapper info = FunctionInfoWrapper::Create(isolate_);
- info.SetInitialProperties(fun->name(), fun->start_position(),
- fun->end_position(), fun->parameter_count(),
- current_parent_index_, fun->function_literal_id());
- current_parent_index_ = len_;
- SetElementSloppy(result_, len_, info.GetJSArray());
- len_++;
-}
-
-// Saves full information about a function: its code, its scope info
-// and a SharedFunctionInfo object.
-void LiveEditFunctionTracker::FunctionDone(Handle<SharedFunctionInfo> shared,
- Scope* scope) {
- HandleScope handle_scope(isolate_);
- FunctionInfoWrapper info = FunctionInfoWrapper::cast(
- *JSReceiver::GetElement(isolate_, result_, current_parent_index_)
- .ToHandleChecked());
- info.SetSharedFunctionInfo(shared);
-
- Handle<Object> scope_info_list = SerializeFunctionScope(scope);
- info.SetFunctionScopeInfo(scope_info_list);
-
- current_parent_index_ = info.GetParentIndex();
-}
-
-Handle<Object> LiveEditFunctionTracker::SerializeFunctionScope(Scope* scope) {
- Handle<JSArray> scope_info_list = isolate_->factory()->NewJSArray(10);
- int scope_info_length = 0;
-
- // Saves some description of scope. It stores name and indexes of
- // variables in the whole scope chain. Null-named slots delimit
- // scopes of this chain.
- Scope* current_scope = scope;
- while (current_scope != NULL) {
- HandleScope handle_scope(isolate_);
- for (Variable* var : *current_scope->locals()) {
- if (!var->IsContextSlot()) continue;
- int context_index = var->index() - Context::MIN_CONTEXT_SLOTS;
- int location = scope_info_length + context_index * 2;
- SetElementSloppy(scope_info_list, location, var->name());
- SetElementSloppy(scope_info_list, location + 1,
- handle(Smi::FromInt(var->index()), isolate_));
- }
- scope_info_length += current_scope->ContextLocalCount() * 2;
- SetElementSloppy(scope_info_list, scope_info_length,
- isolate_->factory()->null_value());
- scope_info_length++;
-
- current_scope = current_scope->outer_scope();
- }
-
- return scope_info_list;
-}
-
} // namespace internal
} // namespace v8
diff --git a/src/debug/liveedit.h b/src/debug/liveedit.h
index 4ad1bc5..4291efb 100644
--- a/src/debug/liveedit.h
+++ b/src/debug/liveedit.h
@@ -5,317 +5,74 @@
#ifndef V8_DEBUG_LIVEEDIT_H_
#define V8_DEBUG_LIVEEDIT_H_
+#include <vector>
-// Live Edit feature implementation.
-// User should be able to change script on already running VM. This feature
-// matches hot swap features in other frameworks.
-//
-// The basic use-case is when user spots some mistake in function body
-// from debugger and wishes to change the algorithm without restart.
-//
-// A single change always has a form of a simple replacement (in pseudo-code):
-// script.source[positions, positions+length] = new_string;
-// Implementation first determines, which function's body includes this
-// change area. Then both old and new versions of script are fully compiled
-// in order to analyze, whether the function changed its outer scope
-// expectations (or number of parameters). If it didn't, function's code is
-// patched with a newly compiled code. If it did change, enclosing function
-// gets patched. All inner functions are left untouched, whatever happened
-// to them in a new script version. However, new version of code will
-// instantiate newly compiled functions.
-
-
-#include "src/allocation.h"
-#include "src/ast/ast-traversal-visitor.h"
+#include "src/common/globals.h"
+#include "src/handles/handles.h"
namespace v8 {
+namespace debug {
+struct LiveEditResult;
+} // namespace debug
namespace internal {
-// This class collects some specific information on structure of functions
-// in a particular script.
-//
-// The primary interest of the Tracker is to record function scope structures
-// in order to analyze whether function code may be safely patched (with new
-// code successfully reading existing data from function scopes). The Tracker
-// also collects compiled function codes.
-class LiveEditFunctionTracker
- : public AstTraversalVisitor<LiveEditFunctionTracker> {
- public:
- // Traverses the entire AST, and records information about all
- // FunctionLiterals for further use by LiveEdit code patching. The collected
- // information is returned as a serialized array.
- static Handle<JSArray> Collect(FunctionLiteral* node, Handle<Script> script,
- Zone* zone, Isolate* isolate);
+class Script;
+class String;
+class Debug;
+class JavaScriptFrame;
- protected:
- friend AstTraversalVisitor<LiveEditFunctionTracker>;
- void VisitFunctionLiteral(FunctionLiteral* node);
-
- private:
- LiveEditFunctionTracker(Handle<Script> script, Zone* zone, Isolate* isolate);
-
- void FunctionStarted(FunctionLiteral* fun);
- void FunctionDone(Handle<SharedFunctionInfo> shared, Scope* scope);
- Handle<Object> SerializeFunctionScope(Scope* scope);
-
- Handle<Script> script_;
- Zone* zone_;
- Isolate* isolate_;
-
- Handle<JSArray> result_;
- int len_;
- int current_parent_index_;
-
- DISALLOW_COPY_AND_ASSIGN(LiveEditFunctionTracker);
+struct SourceChangeRange {
+ int start_position;
+ int end_position;
+ int new_start_position;
+ int new_end_position;
};
+/**
+ Liveedit step-by-step:
+ 1. calculate diff between old source and new source,
+ 2. map function literals from old source to new source,
+ 3. create new script for new_source,
+ 4. mark literals with changed code as changed, all others as unchanged,
+ 5. check that for changed literals there are no:
+ - running generators in the heap,
+ - non droppable frames (e.g. running generator) above them on stack.
+ 6. mark the bottom most frame with changed function as scheduled for restart
+ if any,
+ 7. for unchanged functions:
+ - deoptimize,
+ - remove from cache,
+ - update source positions,
+ - move to new script,
+ - reset feedback information and preparsed scope information if any,
+ - replace any sfi in constant pool with changed one if any.
+ 8. for changed functions:
+ - deoptimize
+ - remove from cache,
+ - reset feedback information,
+ - update all links from js functions to old shared with new one.
+ 9. swap scripts.
+ */
-class LiveEdit : AllStatic {
+class V8_EXPORT_PRIVATE LiveEdit : AllStatic {
public:
static void InitializeThreadLocal(Debug* debug);
- MUST_USE_RESULT static MaybeHandle<JSArray> GatherCompileInfo(
- Handle<Script> script,
- Handle<String> source);
-
- static void ReplaceFunctionCode(Handle<JSArray> new_compile_info_array,
- Handle<JSArray> shared_info_array);
-
- static void FixupScript(Handle<Script> script, int max_function_literal_id);
-
- static void FunctionSourceUpdated(Handle<JSArray> shared_info_array,
- int new_function_literal_id);
-
- // Updates script field in FunctionSharedInfo.
- static void SetFunctionScript(Handle<JSValue> function_wrapper,
- Handle<Object> script_handle);
-
- static void PatchFunctionPositions(Handle<JSArray> shared_info_array,
- Handle<JSArray> position_change_array);
-
- // For a script updates its source field. If old_script_name is provided
- // (i.e. is a String), also creates a copy of the script with its original
- // source and sends notification to debugger.
- static Handle<Object> ChangeScriptSource(Handle<Script> original_script,
- Handle<String> new_source,
- Handle<Object> old_script_name);
-
- // In a code of a parent function replaces original function as embedded
- // object with a substitution one.
- static void ReplaceRefToNestedFunction(Handle<JSValue> parent_function_shared,
- Handle<JSValue> orig_function_shared,
- Handle<JSValue> subst_function_shared);
-
- // Find open generator activations, and set corresponding "result" elements to
- // FUNCTION_BLOCKED_ACTIVE_GENERATOR.
- static bool FindActiveGenerators(Handle<FixedArray> shared_info_array,
- Handle<FixedArray> result, int len);
-
- // Checks listed functions on stack and return array with corresponding
- // FunctionPatchabilityStatus statuses; extra array element may
- // contain general error message. Modifies the current stack and
- // has restart the lowest found frames and drops all other frames above
- // if possible and if do_drop is true.
- static Handle<JSArray> CheckAndDropActivations(
- Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array,
- bool do_drop);
-
// Restarts the call frame and completely drops all frames above it.
- // Return error message or NULL.
- static const char* RestartFrame(JavaScriptFrame* frame);
+ static bool RestartFrame(JavaScriptFrame* frame);
- // A copy of this is in liveedit.js.
- enum FunctionPatchabilityStatus {
- FUNCTION_AVAILABLE_FOR_PATCH = 1,
- FUNCTION_BLOCKED_ON_ACTIVE_STACK = 2,
- FUNCTION_BLOCKED_ON_OTHER_STACK = 3,
- FUNCTION_BLOCKED_UNDER_NATIVE_CODE = 4,
- FUNCTION_REPLACED_ON_ACTIVE_STACK = 5,
- FUNCTION_BLOCKED_UNDER_GENERATOR = 6,
- FUNCTION_BLOCKED_ACTIVE_GENERATOR = 7,
- FUNCTION_BLOCKED_NO_NEW_TARGET_ON_RESTART = 8
- };
-
- // Compares 2 strings line-by-line, then token-wise and returns diff in form
- // of array of triplets (pos1, pos1_end, pos2_end) describing list
- // of diff chunks.
- static Handle<JSArray> CompareStrings(Handle<String> s1,
- Handle<String> s2);
-
+ static void CompareStrings(Isolate* isolate, Handle<String> a,
+ Handle<String> b,
+ std::vector<SourceChangeRange>* diffs);
+ static int TranslatePosition(const std::vector<SourceChangeRange>& changed,
+ int position);
+ static void PatchScript(Isolate* isolate, Handle<Script> script,
+ Handle<String> source, bool preview,
+ debug::LiveEditResult* result);
// Architecture-specific constant.
static const bool kFrameDropperSupported;
};
-
-
-// A general-purpose comparator between 2 arrays.
-class Comparator {
- public:
- // Holds 2 arrays of some elements allowing to compare any pair of
- // element from the first array and element from the second array.
- class Input {
- public:
- virtual int GetLength1() = 0;
- virtual int GetLength2() = 0;
- virtual bool Equals(int index1, int index2) = 0;
-
- protected:
- virtual ~Input() {}
- };
-
- // Receives compare result as a series of chunks.
- class Output {
- public:
- // Puts another chunk in result list. Note that technically speaking
- // only 3 arguments actually needed with 4th being derivable.
- virtual void AddChunk(int pos1, int pos2, int len1, int len2) = 0;
-
- protected:
- virtual ~Output() {}
- };
-
- // Finds the difference between 2 arrays of elements.
- static void CalculateDifference(Input* input,
- Output* result_writer);
-};
-
-
-
-// Simple helper class that creates more or less typed structures over
-// JSArray object. This is an adhoc method of passing structures from C++
-// to JavaScript.
-template<typename S>
-class JSArrayBasedStruct {
- public:
- static S Create(Isolate* isolate) {
- Factory* factory = isolate->factory();
- Handle<JSArray> array = factory->NewJSArray(S::kSize_);
- return S(array);
- }
-
- static S cast(Object* object) {
- JSArray* array = JSArray::cast(object);
- Handle<JSArray> array_handle(array);
- return S(array_handle);
- }
-
- explicit JSArrayBasedStruct(Handle<JSArray> array) : array_(array) {
- }
-
- Handle<JSArray> GetJSArray() {
- return array_;
- }
-
- Isolate* isolate() const {
- return array_->GetIsolate();
- }
-
- protected:
- void SetField(int field_position, Handle<Object> value) {
- Object::SetElement(isolate(), array_, field_position, value, SLOPPY)
- .Assert();
- }
-
- void SetSmiValueField(int field_position, int value) {
- SetField(field_position, Handle<Smi>(Smi::FromInt(value), isolate()));
- }
-
- Handle<Object> GetField(int field_position) {
- return JSReceiver::GetElement(isolate(), array_, field_position)
- .ToHandleChecked();
- }
-
- int GetSmiValueField(int field_position) {
- Handle<Object> res = GetField(field_position);
- return Handle<Smi>::cast(res)->value();
- }
-
- private:
- Handle<JSArray> array_;
-};
-
-
-// Represents some function compilation details. This structure will be used
-// from JavaScript. It contains Code object, which is kept wrapped
-// into a BlindReference for sanitizing reasons.
-class FunctionInfoWrapper : public JSArrayBasedStruct<FunctionInfoWrapper> {
- public:
- explicit FunctionInfoWrapper(Handle<JSArray> array)
- : JSArrayBasedStruct<FunctionInfoWrapper>(array) {
- }
-
- void SetInitialProperties(Handle<String> name, int start_position,
- int end_position, int param_num, int parent_index,
- int function_literal_id);
-
- void SetFunctionScopeInfo(Handle<Object> scope_info_array) {
- this->SetField(kFunctionScopeInfoOffset_, scope_info_array);
- }
-
- void SetSharedFunctionInfo(Handle<SharedFunctionInfo> info);
-
- Handle<SharedFunctionInfo> GetSharedFunctionInfo();
-
- int GetParentIndex() {
- return this->GetSmiValueField(kParentIndexOffset_);
- }
-
- int GetStartPosition() {
- return this->GetSmiValueField(kStartPositionOffset_);
- }
-
- int GetEndPosition() { return this->GetSmiValueField(kEndPositionOffset_); }
-
- private:
- static const int kFunctionNameOffset_ = 0;
- static const int kStartPositionOffset_ = 1;
- static const int kEndPositionOffset_ = 2;
- static const int kParamNumOffset_ = 3;
- static const int kFunctionScopeInfoOffset_ = 4;
- static const int kParentIndexOffset_ = 5;
- static const int kSharedFunctionInfoOffset_ = 6;
- static const int kFunctionLiteralIdOffset_ = 7;
- static const int kSize_ = 8;
-
- friend class JSArrayBasedStruct<FunctionInfoWrapper>;
-};
-
-
-// Wraps SharedFunctionInfo along with some of its fields for passing it
-// back to JavaScript. SharedFunctionInfo object itself is additionally
-// wrapped into BlindReference for sanitizing reasons.
-class SharedInfoWrapper : public JSArrayBasedStruct<SharedInfoWrapper> {
- public:
- static bool IsInstance(Handle<JSArray> array) {
- if (array->length() != Smi::FromInt(kSize_)) return false;
- Handle<Object> element(
- JSReceiver::GetElement(array->GetIsolate(), array, kSharedInfoOffset_)
- .ToHandleChecked());
- if (!element->IsJSValue()) return false;
- return Handle<JSValue>::cast(element)->value()->IsSharedFunctionInfo();
- }
-
- explicit SharedInfoWrapper(Handle<JSArray> array)
- : JSArrayBasedStruct<SharedInfoWrapper>(array) {
- }
-
- void SetProperties(Handle<String> name,
- int start_position,
- int end_position,
- Handle<SharedFunctionInfo> info);
-
- Handle<SharedFunctionInfo> GetInfo();
-
- private:
- static const int kFunctionNameOffset_ = 0;
- static const int kStartPositionOffset_ = 1;
- static const int kEndPositionOffset_ = 2;
- static const int kSharedInfoOffset_ = 3;
- static const int kSize_ = 4;
-
- friend class JSArrayBasedStruct<SharedInfoWrapper>;
-};
-
} // namespace internal
} // namespace v8
-#endif /* V8_DEBUG_LIVEEDIT_H_ */
+#endif // V8_DEBUG_LIVEEDIT_H_
diff --git a/src/debug/liveedit.js b/src/debug/liveedit.js
deleted file mode 100644
index 8e20654..0000000
--- a/src/debug/liveedit.js
+++ /dev/null
@@ -1,1053 +0,0 @@
-// 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.
-
-// LiveEdit feature implementation. The script should be executed after
-// debug.js.
-
-// A LiveEdit namespace. It contains functions that modifies JavaScript code
-// according to changes of script source (if possible).
-//
-// When new script source is put in, the difference is calculated textually,
-// in form of list of delete/add/change chunks. The functions that include
-// change chunk(s) get recompiled, or their enclosing functions are
-// recompiled instead.
-// If the function may not be recompiled (e.g. it was completely erased in new
-// version of the script) it remains unchanged, but the code that could
-// create a new instance of this function goes away. An old version of script
-// is created to back up this obsolete function.
-// All unchanged functions have their positions updated accordingly.
-//
-// LiveEdit namespace is declared inside a single function constructor.
-
-(function(global, utils) {
- "use strict";
-
- // -------------------------------------------------------------------
- // Imports
-
- var FindScriptSourcePosition = global.Debug.findScriptSourcePosition;
- var GlobalArray = global.Array;
- var MathFloor = global.Math.floor;
- var MathMax = global.Math.max;
- var SyntaxError = global.SyntaxError;
-
- // -------------------------------------------------------------------
-
- // Forward declaration for minifier.
- var FunctionStatus;
-
- // Applies the change to the script.
- // The change is in form of list of chunks encoded in a single array as
- // a series of triplets (pos1_start, pos1_end, pos2_end)
- function ApplyPatchMultiChunk(script, diff_array, new_source, preview_only,
- change_log) {
-
- var old_source = script.source;
-
- // Gather compile information about old version of script.
- var old_compile_info = GatherCompileInfo(old_source, script);
-
- // Build tree structures for old and new versions of the script.
- var root_old_node = BuildCodeInfoTree(old_compile_info);
-
- var pos_translator = new PosTranslator(diff_array);
-
- // Analyze changes.
- MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
-
- // Find all SharedFunctionInfo's that were compiled from this script.
- FindLiveSharedInfos(root_old_node, script);
-
- // Gather compile information about new version of script.
- var new_compile_info;
- try {
- new_compile_info = GatherCompileInfo(new_source, script);
- } catch (e) {
- var failure =
- new Failure("Failed to compile new version of script: " + e);
- if (e instanceof SyntaxError) {
- var details = {
- type: "liveedit_compile_error",
- syntaxErrorMessage: e.message
- };
- CopyErrorPositionToDetails(e, details);
- failure.details = details;
- }
- throw failure;
- }
-
- var max_function_literal_id = new_compile_info.reduce(
- (max, info) => MathMax(max, info.function_literal_id), 0);
-
- var root_new_node = BuildCodeInfoTree(new_compile_info);
-
- // Link recompiled script data with other data.
- FindCorrespondingFunctions(root_old_node, root_new_node);
-
- // Prepare to-do lists.
- var replace_code_list = new GlobalArray();
- var link_to_old_script_list = new GlobalArray();
- var link_to_original_script_list = new GlobalArray();
- var update_positions_list = new GlobalArray();
-
- function HarvestTodo(old_node) {
- function CollectDamaged(node) {
- link_to_old_script_list.push(node);
- for (var i = 0; i < node.children.length; i++) {
- CollectDamaged(node.children[i]);
- }
- }
-
- // Recursively collects all newly compiled functions that are going into
- // business and should have link to the actual script updated.
- function CollectNew(node_list) {
- for (var i = 0; i < node_list.length; i++) {
- link_to_original_script_list.push(node_list[i]);
- CollectNew(node_list[i].children);
- }
- }
-
- if (old_node.status == FunctionStatus.DAMAGED) {
- CollectDamaged(old_node);
- return;
- }
- if (old_node.status == FunctionStatus.UNCHANGED) {
- update_positions_list.push(old_node);
- } else if (old_node.status == FunctionStatus.SOURCE_CHANGED) {
- update_positions_list.push(old_node);
- } else if (old_node.status == FunctionStatus.CHANGED) {
- replace_code_list.push(old_node);
- CollectNew(old_node.unmatched_new_nodes);
- }
- for (var i = 0; i < old_node.children.length; i++) {
- HarvestTodo(old_node.children[i]);
- }
- }
-
- var preview_description = {
- change_tree: DescribeChangeTree(root_old_node),
- textual_diff: {
- old_len: old_source.length,
- new_len: new_source.length,
- chunks: diff_array
- },
- updated: false
- };
-
- if (preview_only) {
- return preview_description;
- }
-
- HarvestTodo(root_old_node);
-
- // Collect shared infos for functions whose code need to be patched.
- var replaced_function_old_infos = new GlobalArray();
- var replaced_function_new_infos = new GlobalArray();
- for (var i = 0; i < replace_code_list.length; i++) {
- var old_infos = replace_code_list[i].live_shared_function_infos;
- var new_info =
- replace_code_list[i].corresponding_node.info.shared_function_info;
-
- if (old_infos) {
- for (var j = 0; j < old_infos.length; j++) {
- replaced_function_old_infos.push(old_infos[j]);
- replaced_function_new_infos.push(new_info);
- }
- }
- }
-
- // We haven't changed anything before this line yet.
- // Committing all changes.
-
- // Check that function being patched is not currently on stack or drop them.
- var dropped_functions_number =
- CheckStackActivations(replaced_function_old_infos,
- replaced_function_new_infos,
- change_log);
-
- // Our current implementation requires client to manually issue "step in"
- // command for correct stack state if the stack was modified.
- preview_description.stack_modified = dropped_functions_number != 0;
-
- var old_script;
-
- // Create an old script only if there are function that should be linked
- // to old version.
- if (link_to_old_script_list.length == 0) {
- %LiveEditReplaceScript(script, new_source, null);
- old_script = UNDEFINED;
- } else {
- var old_script_name = CreateNameForOldScript(script);
-
- // Update the script text and create a new script representing an old
- // version of the script.
- old_script = %LiveEditReplaceScript(script, new_source, old_script_name);
-
- var link_to_old_script_report = new GlobalArray();
- change_log.push( { linked_to_old_script: link_to_old_script_report } );
-
- // We need to link to old script all former nested functions.
- for (var i = 0; i < link_to_old_script_list.length; i++) {
- LinkToOldScript(link_to_old_script_list[i], old_script,
- link_to_old_script_report);
- }
-
- preview_description.created_script_name = old_script_name;
- }
-
- for (var i = 0; i < replace_code_list.length; i++) {
- PatchFunctionCode(replace_code_list[i], change_log);
- }
-
- var position_patch_report = new GlobalArray();
- change_log.push( {position_patched: position_patch_report} );
-
- for (var i = 0; i < update_positions_list.length; i++) {
- // TODO(LiveEdit): take into account whether it's source_changed or
- // unchanged and whether positions changed at all.
- PatchPositions(update_positions_list[i], diff_array,
- position_patch_report);
-
- if (update_positions_list[i].live_shared_function_infos) {
- var new_function_literal_id =
- update_positions_list[i]
- .corresponding_node.info.function_literal_id;
- update_positions_list[i].live_shared_function_infos.forEach(function(
- info) {
- %LiveEditFunctionSourceUpdated(
- info.raw_array, new_function_literal_id);
- });
- }
- }
-
- %LiveEditFixupScript(script, max_function_literal_id);
-
- // Link all the functions we're going to use to an actual script.
- for (var i = 0; i < link_to_original_script_list.length; i++) {
- %LiveEditFunctionSetScript(
- link_to_original_script_list[i].info.shared_function_info, script);
- }
-
- preview_description.updated = true;
- return preview_description;
- }
-
- // Fully compiles source string as a script. Returns Array of
- // FunctionCompileInfo -- a descriptions of all functions of the script.
- // Elements of array are ordered by start positions of functions (from top
- // to bottom) in the source. Fields outer_index and next_sibling_index help
- // to navigate the nesting structure of functions.
- //
- // All functions get compiled linked to script provided as parameter script.
- // TODO(LiveEdit): consider not using actual scripts as script, because
- // we have to manually erase all links right after compile.
- function GatherCompileInfo(source, script) {
- // Get function info, elements are partially sorted (it is a tree of
- // nested functions serialized as parent followed by serialized children.
- var raw_compile_info = %LiveEditGatherCompileInfo(script, source);
-
- // Sort function infos by start position field.
- var compile_info = new GlobalArray();
- var old_index_map = new GlobalArray();
- for (var i = 0; i < raw_compile_info.length; i++) {
- var info = new FunctionCompileInfo(raw_compile_info[i]);
- // Remove all links to the actual script. Breakpoints system and
- // LiveEdit itself believe that any function in heap that points to a
- // particular script is a regular function.
- // For some functions we will restore this link later.
- %LiveEditFunctionSetScript(info.shared_function_info, UNDEFINED);
- compile_info.push(info);
- old_index_map.push(i);
- }
-
- for (var i = 0; i < compile_info.length; i++) {
- var k = i;
- for (var j = i + 1; j < compile_info.length; j++) {
- if (compile_info[k].start_position > compile_info[j].start_position) {
- k = j;
- }
- }
- if (k != i) {
- var temp_info = compile_info[k];
- var temp_index = old_index_map[k];
- compile_info[k] = compile_info[i];
- old_index_map[k] = old_index_map[i];
- compile_info[i] = temp_info;
- old_index_map[i] = temp_index;
- }
- }
-
- // After sorting update outer_index field using old_index_map. Also
- // set next_sibling_index field.
- var current_index = 0;
-
- // The recursive function, that goes over all children of a particular
- // node (i.e. function info).
- function ResetIndexes(new_parent_index, old_parent_index) {
- var previous_sibling = -1;
- while (current_index < compile_info.length &&
- compile_info[current_index].outer_index == old_parent_index) {
- var saved_index = current_index;
- compile_info[saved_index].outer_index = new_parent_index;
- if (previous_sibling != -1) {
- compile_info[previous_sibling].next_sibling_index = saved_index;
- }
- previous_sibling = saved_index;
- current_index++;
- ResetIndexes(saved_index, old_index_map[saved_index]);
- }
- if (previous_sibling != -1) {
- compile_info[previous_sibling].next_sibling_index = -1;
- }
- }
-
- ResetIndexes(-1, -1);
- Assert(current_index == compile_info.length);
-
- return compile_info;
- }
-
-
- // Replaces function's Code.
- function PatchFunctionCode(old_node, change_log) {
- var new_info = old_node.corresponding_node.info;
- if (old_node.live_shared_function_infos) {
- old_node.live_shared_function_infos.forEach(function (old_info) {
- %LiveEditReplaceFunctionCode(new_info.raw_array,
- old_info.raw_array);
-
- // The function got a new code. However, this new code brings all new
- // instances of SharedFunctionInfo for nested functions. However,
- // we want the original instances to be used wherever possible.
- // (This is because old instances and new instances will be both
- // linked to a script and breakpoints subsystem does not really
- // expects this; neither does LiveEdit subsystem on next call).
- for (var i = 0; i < old_node.children.length; i++) {
- if (old_node.children[i].corresponding_node) {
- var corresponding_child_info =
- old_node.children[i].corresponding_node.info.
- shared_function_info;
-
- if (old_node.children[i].live_shared_function_infos) {
- old_node.children[i].live_shared_function_infos.
- forEach(function (old_child_info) {
- %LiveEditReplaceRefToNestedFunction(
- old_info.info,
- corresponding_child_info,
- old_child_info.info);
- });
- }
- }
- }
- });
-
- change_log.push( {function_patched: new_info.function_name} );
- } else {
- change_log.push( {function_patched: new_info.function_name,
- function_info_not_found: true} );
- }
- }
-
-
- // Makes a function associated with another instance of a script (the
- // one representing its old version). This way the function still
- // may access its own text.
- function LinkToOldScript(old_info_node, old_script, report_array) {
- if (old_info_node.live_shared_function_infos) {
- old_info_node.live_shared_function_infos.
- forEach(function (info) {
- %LiveEditFunctionSetScript(info.info, old_script);
- });
-
- report_array.push( { name: old_info_node.info.function_name } );
- } else {
- report_array.push(
- { name: old_info_node.info.function_name, not_found: true } );
- }
- }
-
- function Assert(condition, message) {
- if (!condition) {
- if (message) {
- throw "Assert " + message;
- } else {
- throw "Assert";
- }
- }
- }
-
- function DiffChunk(pos1, pos2, len1, len2) {
- this.pos1 = pos1;
- this.pos2 = pos2;
- this.len1 = len1;
- this.len2 = len2;
- }
-
- function PosTranslator(diff_array) {
- var chunks = new GlobalArray();
- var current_diff = 0;
- for (var i = 0; i < diff_array.length; i += 3) {
- var pos1_begin = diff_array[i];
- var pos2_begin = pos1_begin + current_diff;
- var pos1_end = diff_array[i + 1];
- var pos2_end = diff_array[i + 2];
- chunks.push(new DiffChunk(pos1_begin, pos2_begin, pos1_end - pos1_begin,
- pos2_end - pos2_begin));
- current_diff = pos2_end - pos1_end;
- }
- this.chunks = chunks;
- }
- PosTranslator.prototype.GetChunks = function() {
- return this.chunks;
- };
-
- PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
- var array = this.chunks;
- if (array.length == 0 || pos < array[0].pos1) {
- return pos;
- }
- var chunk_index1 = 0;
- var chunk_index2 = array.length - 1;
-
- while (chunk_index1 < chunk_index2) {
- var middle_index = MathFloor((chunk_index1 + chunk_index2) / 2);
- if (pos < array[middle_index + 1].pos1) {
- chunk_index2 = middle_index;
- } else {
- chunk_index1 = middle_index + 1;
- }
- }
- var chunk = array[chunk_index1];
- if (pos >= chunk.pos1 + chunk.len1) {
- return pos + chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
- }
-
- if (!inside_chunk_handler) {
- inside_chunk_handler = PosTranslator.DefaultInsideChunkHandler;
- }
- return inside_chunk_handler(pos, chunk);
- };
-
- PosTranslator.DefaultInsideChunkHandler = function(pos, diff_chunk) {
- Assert(false, "Cannot translate position in changed area");
- };
-
- PosTranslator.ShiftWithTopInsideChunkHandler =
- function(pos, diff_chunk) {
- // We carelessly do not check whether we stay inside the chunk after
- // translation.
- return pos - diff_chunk.pos1 + diff_chunk.pos2;
- };
-
- var FunctionStatus = {
- // No change to function or its inner functions; however its positions
- // in script may have been shifted.
- UNCHANGED: "unchanged",
- // The code of a function remains unchanged, but something happened inside
- // some inner functions.
- SOURCE_CHANGED: "source changed",
- // The code of a function is changed or some nested function cannot be
- // properly patched so this function must be recompiled.
- CHANGED: "changed",
- // Function is changed but cannot be patched.
- DAMAGED: "damaged"
- };
-
- function CodeInfoTreeNode(code_info, children, array_index) {
- this.info = code_info;
- this.children = children;
- // an index in array of compile_info
- this.array_index = array_index;
- this.parent = UNDEFINED;
-
- this.status = FunctionStatus.UNCHANGED;
- // Status explanation is used for debugging purposes and will be shown
- // in user UI if some explanations are needed.
- this.status_explanation = UNDEFINED;
- this.new_start_pos = UNDEFINED;
- this.new_end_pos = UNDEFINED;
- this.corresponding_node = UNDEFINED;
- this.unmatched_new_nodes = UNDEFINED;
-
- // 'Textual' correspondence/matching is weaker than 'pure'
- // correspondence/matching. We need 'textual' level for visual presentation
- // in UI, we use 'pure' level for actual code manipulation.
- // Sometimes only function body is changed (functions in old and new script
- // textually correspond), but we cannot patch the code, so we see them
- // as an old function deleted and new function created.
- this.textual_corresponding_node = UNDEFINED;
- this.textually_unmatched_new_nodes = UNDEFINED;
-
- this.live_shared_function_infos = UNDEFINED;
- }
-
- // From array of function infos that is implicitly a tree creates
- // an actual tree of functions in script.
- function BuildCodeInfoTree(code_info_array) {
- // Throughtout all function we iterate over input array.
- var index = 0;
-
- // Recursive function that builds a branch of tree.
- function BuildNode() {
- var my_index = index;
- index++;
- var child_array = new GlobalArray();
- while (index < code_info_array.length &&
- code_info_array[index].outer_index == my_index) {
- child_array.push(BuildNode());
- }
- var node = new CodeInfoTreeNode(code_info_array[my_index], child_array,
- my_index);
- for (var i = 0; i < child_array.length; i++) {
- child_array[i].parent = node;
- }
- return node;
- }
-
- var root = BuildNode();
- Assert(index == code_info_array.length);
- return root;
- }
-
- // Applies a list of the textual diff chunks onto the tree of functions.
- // Determines status of each function (from unchanged to damaged). However
- // children of unchanged functions are ignored.
- function MarkChangedFunctions(code_info_tree, chunks) {
-
- // A convenient iterator over diff chunks that also translates
- // positions from old to new in a current non-changed part of script.
- var chunk_it = new function() {
- var chunk_index = 0;
- var pos_diff = 0;
- this.current = function() { return chunks[chunk_index]; };
- this.next = function() {
- var chunk = chunks[chunk_index];
- pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1);
- chunk_index++;
- };
- this.done = function() { return chunk_index >= chunks.length; };
- this.TranslatePos = function(pos) { return pos + pos_diff; };
- };
-
- // A recursive function that processes internals of a function and all its
- // inner functions. Iterator chunk_it initially points to a chunk that is
- // below function start.
- function ProcessInternals(info_node) {
- info_node.new_start_pos = chunk_it.TranslatePos(
- info_node.info.start_position);
- var child_index = 0;
- var code_changed = false;
- var source_changed = false;
- // Simultaneously iterates over child functions and over chunks.
- while (!chunk_it.done() &&
- chunk_it.current().pos1 < info_node.info.end_position) {
- if (child_index < info_node.children.length) {
- var child = info_node.children[child_index];
-
- if (child.info.end_position <= chunk_it.current().pos1) {
- ProcessUnchangedChild(child);
- child_index++;
- continue;
- } else if (child.info.start_position >=
- chunk_it.current().pos1 + chunk_it.current().len1) {
- code_changed = true;
- chunk_it.next();
- continue;
- } else if (child.info.start_position <= chunk_it.current().pos1 &&
- child.info.end_position >= chunk_it.current().pos1 +
- chunk_it.current().len1) {
- ProcessInternals(child);
- source_changed = source_changed ||
- ( child.status != FunctionStatus.UNCHANGED );
- code_changed = code_changed ||
- ( child.status == FunctionStatus.DAMAGED );
- child_index++;
- continue;
- } else {
- code_changed = true;
- child.status = FunctionStatus.DAMAGED;
- child.status_explanation =
- "Text diff overlaps with function boundary";
- child_index++;
- continue;
- }
- } else {
- if (chunk_it.current().pos1 + chunk_it.current().len1 <=
- info_node.info.end_position) {
- info_node.status = FunctionStatus.CHANGED;
- chunk_it.next();
- continue;
- } else {
- info_node.status = FunctionStatus.DAMAGED;
- info_node.status_explanation =
- "Text diff overlaps with function boundary";
- return;
- }
- }
- Assert("Unreachable", false);
- }
- while (child_index < info_node.children.length) {
- var child = info_node.children[child_index];
- ProcessUnchangedChild(child);
- child_index++;
- }
- if (code_changed) {
- info_node.status = FunctionStatus.CHANGED;
- } else if (source_changed) {
- info_node.status = FunctionStatus.SOURCE_CHANGED;
- }
- info_node.new_end_pos =
- chunk_it.TranslatePos(info_node.info.end_position);
- }
-
- function ProcessUnchangedChild(node) {
- node.new_start_pos = chunk_it.TranslatePos(node.info.start_position);
- node.new_end_pos = chunk_it.TranslatePos(node.info.end_position);
- }
-
- ProcessInternals(code_info_tree);
- }
-
- // For each old function (if it is not damaged) tries to find a corresponding
- // function in new script. Typically it should succeed (non-damaged functions
- // by definition may only have changes inside their bodies). However there are
- // reasons for correspondence not to be found; function with unmodified text
- // in new script may become enclosed into other function; the innocent change
- // inside function body may in fact be something like "} function B() {" that
- // splits a function into 2 functions.
- function FindCorrespondingFunctions(old_code_tree, new_code_tree) {
-
- // A recursive function that tries to find a correspondence for all
- // child functions and for their inner functions.
- function ProcessNode(old_node, new_node) {
- var scope_change_description =
- IsFunctionContextLocalsChanged(old_node.info, new_node.info);
- if (scope_change_description) {
- old_node.status = FunctionStatus.CHANGED;
- }
-
- var old_children = old_node.children;
- var new_children = new_node.children;
-
- var unmatched_new_nodes_list = [];
- var textually_unmatched_new_nodes_list = [];
-
- var old_index = 0;
- var new_index = 0;
- while (old_index < old_children.length) {
- if (old_children[old_index].status == FunctionStatus.DAMAGED) {
- old_index++;
- } else if (new_index < new_children.length) {
- if (new_children[new_index].info.start_position <
- old_children[old_index].new_start_pos) {
- unmatched_new_nodes_list.push(new_children[new_index]);
- textually_unmatched_new_nodes_list.push(new_children[new_index]);
- new_index++;
- } else if (new_children[new_index].info.start_position ==
- old_children[old_index].new_start_pos) {
- if (new_children[new_index].info.end_position ==
- old_children[old_index].new_end_pos) {
- old_children[old_index].corresponding_node =
- new_children[new_index];
- old_children[old_index].textual_corresponding_node =
- new_children[new_index];
- if (scope_change_description) {
- old_children[old_index].status = FunctionStatus.DAMAGED;
- old_children[old_index].status_explanation =
- "Enclosing function is now incompatible. " +
- scope_change_description;
- old_children[old_index].corresponding_node = UNDEFINED;
- } else if (old_children[old_index].status !=
- FunctionStatus.UNCHANGED) {
- ProcessNode(old_children[old_index],
- new_children[new_index]);
- if (old_children[old_index].status == FunctionStatus.DAMAGED) {
- unmatched_new_nodes_list.push(
- old_children[old_index].corresponding_node);
- old_children[old_index].corresponding_node = UNDEFINED;
- old_node.status = FunctionStatus.CHANGED;
- }
- } else {
- ProcessNode(old_children[old_index], new_children[new_index]);
- }
- } else {
- old_children[old_index].status = FunctionStatus.DAMAGED;
- old_children[old_index].status_explanation =
- "No corresponding function in new script found";
- old_node.status = FunctionStatus.CHANGED;
- unmatched_new_nodes_list.push(new_children[new_index]);
- textually_unmatched_new_nodes_list.push(new_children[new_index]);
- }
- new_index++;
- old_index++;
- } else {
- old_children[old_index].status = FunctionStatus.DAMAGED;
- old_children[old_index].status_explanation =
- "No corresponding function in new script found";
- old_node.status = FunctionStatus.CHANGED;
- old_index++;
- }
- } else {
- old_children[old_index].status = FunctionStatus.DAMAGED;
- old_children[old_index].status_explanation =
- "No corresponding function in new script found";
- old_node.status = FunctionStatus.CHANGED;
- old_index++;
- }
- }
-
- while (new_index < new_children.length) {
- unmatched_new_nodes_list.push(new_children[new_index]);
- textually_unmatched_new_nodes_list.push(new_children[new_index]);
- new_index++;
- }
-
- if (old_node.status == FunctionStatus.CHANGED) {
- if (old_node.info.param_num != new_node.info.param_num) {
- old_node.status = FunctionStatus.DAMAGED;
- old_node.status_explanation = "Changed parameter number: " +
- old_node.info.param_num + " and " + new_node.info.param_num;
- }
- }
- old_node.unmatched_new_nodes = unmatched_new_nodes_list;
- old_node.textually_unmatched_new_nodes =
- textually_unmatched_new_nodes_list;
- }
-
- ProcessNode(old_code_tree, new_code_tree);
-
- old_code_tree.corresponding_node = new_code_tree;
- old_code_tree.textual_corresponding_node = new_code_tree;
-
- Assert(old_code_tree.status != FunctionStatus.DAMAGED,
- "Script became damaged");
- }
-
- function FindLiveSharedInfos(old_code_tree, script) {
- var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
-
- var shared_infos = new GlobalArray();
-
- for (var i = 0; i < shared_raw_list.length; i++) {
- shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
- }
-
- // Finds all SharedFunctionInfos that corresponds to compile info
- // in old version of the script.
- function FindFunctionInfos(compile_info) {
- var wrappers = [];
-
- for (var i = 0; i < shared_infos.length; i++) {
- var wrapper = shared_infos[i];
- if (wrapper.start_position == compile_info.start_position &&
- wrapper.end_position == compile_info.end_position) {
- wrappers.push(wrapper);
- }
- }
-
- if (wrappers.length > 0) {
- return wrappers;
- }
- }
-
- function TraverseTree(node) {
- node.live_shared_function_infos = FindFunctionInfos(node.info);
-
- for (var i = 0; i < node.children.length; i++) {
- TraverseTree(node.children[i]);
- }
- }
-
- TraverseTree(old_code_tree);
- }
-
-
- // An object describing function compilation details. Its index fields
- // apply to indexes inside array that stores these objects.
- function FunctionCompileInfo(raw_array) {
- this.function_name = raw_array[0];
- this.start_position = raw_array[1];
- this.end_position = raw_array[2];
- this.param_num = raw_array[3];
- this.scope_info = raw_array[4];
- this.outer_index = raw_array[5];
- this.shared_function_info = raw_array[6];
- this.function_literal_id = raw_array[7];
- this.next_sibling_index = null;
- this.raw_array = raw_array;
- }
-
- function SharedInfoWrapper(raw_array) {
- this.function_name = raw_array[0];
- this.start_position = raw_array[1];
- this.end_position = raw_array[2];
- this.info = raw_array[3];
- this.raw_array = raw_array;
- }
-
- // Changes positions (including all statements) in function.
- function PatchPositions(old_info_node, diff_array, report_array) {
- if (old_info_node.live_shared_function_infos) {
- old_info_node.live_shared_function_infos.forEach(function (info) {
- %LiveEditPatchFunctionPositions(info.raw_array,
- diff_array);
- });
-
- report_array.push( { name: old_info_node.info.function_name } );
- } else {
- // TODO(LiveEdit): function is not compiled yet or is already collected.
- report_array.push(
- { name: old_info_node.info.function_name, info_not_found: true } );
- }
- }
-
- // Adds a suffix to script name to mark that it is old version.
- function CreateNameForOldScript(script) {
- // TODO(635): try better than this; support several changes.
- return script.name + " (old)";
- }
-
- // Compares a function scope heap structure, old and new version, whether it
- // changed or not. Returns explanation if they differ.
- function IsFunctionContextLocalsChanged(function_info1, function_info2) {
- var scope_info1 = function_info1.scope_info;
- var scope_info2 = function_info2.scope_info;
-
- var scope_info1_text;
- var scope_info2_text;
-
- if (scope_info1) {
- scope_info1_text = scope_info1.toString();
- } else {
- scope_info1_text = "";
- }
- if (scope_info2) {
- scope_info2_text = scope_info2.toString();
- } else {
- scope_info2_text = "";
- }
-
- if (scope_info1_text != scope_info2_text) {
- return "Variable map changed: [" + scope_info1_text +
- "] => [" + scope_info2_text + "]";
- }
- // No differences. Return undefined.
- return;
- }
-
- // Minifier forward declaration.
- var FunctionPatchabilityStatus;
-
- // For array of wrapped shared function infos checks that none of them
- // have activations on stack (of any thread). Throws a Failure exception
- // if this proves to be false.
- function CheckStackActivations(old_shared_wrapper_list,
- new_shared_list,
- change_log) {
- var old_shared_list = new GlobalArray();
- for (var i = 0; i < old_shared_wrapper_list.length; i++) {
- old_shared_list[i] = old_shared_wrapper_list[i].info;
- }
- var result = %LiveEditCheckAndDropActivations(
- old_shared_list, new_shared_list, true);
- if (result[old_shared_wrapper_list.length]) {
- // Extra array element may contain error message.
- throw new Failure(result[old_shared_wrapper_list.length]);
- }
-
- var problems = new GlobalArray();
- var dropped = new GlobalArray();
- for (var i = 0; i < old_shared_list.length; i++) {
- var shared = old_shared_wrapper_list[i];
- if (result[i] == FunctionPatchabilityStatus.REPLACED_ON_ACTIVE_STACK) {
- dropped.push({ name: shared.function_name } );
- } else if (result[i] != FunctionPatchabilityStatus.AVAILABLE_FOR_PATCH) {
- var description = {
- name: shared.function_name,
- start_pos: shared.start_position,
- end_pos: shared.end_position,
- replace_problem:
- FunctionPatchabilityStatus.SymbolName(result[i])
- };
- problems.push(description);
- }
- }
- if (dropped.length > 0) {
- change_log.push({ dropped_from_stack: dropped });
- }
- if (problems.length > 0) {
- change_log.push( { functions_on_stack: problems } );
- throw new Failure("Blocked by functions on stack");
- }
-
- return dropped.length;
- }
-
- // A copy of the FunctionPatchabilityStatus enum from liveedit.h
- var FunctionPatchabilityStatus = {
- AVAILABLE_FOR_PATCH: 1,
- BLOCKED_ON_ACTIVE_STACK: 2,
- BLOCKED_ON_OTHER_STACK: 3,
- BLOCKED_UNDER_NATIVE_CODE: 4,
- REPLACED_ON_ACTIVE_STACK: 5,
- BLOCKED_UNDER_GENERATOR: 6,
- BLOCKED_ACTIVE_GENERATOR: 7,
- BLOCKED_NO_NEW_TARGET_ON_RESTART: 8
- };
-
- FunctionPatchabilityStatus.SymbolName = function(code) {
- var enumeration = FunctionPatchabilityStatus;
- for (var name in enumeration) {
- if (enumeration[name] == code) {
- return name;
- }
- }
- };
-
-
- // A logical failure in liveedit process. This means that change_log
- // is valid and consistent description of what happened.
- function Failure(message) {
- this.message = message;
- }
-
- Failure.prototype.toString = function() {
- return "LiveEdit Failure: " + this.message;
- };
-
- function CopyErrorPositionToDetails(e, details) {
- function createPositionStruct(script, position) {
- if (position == -1) return;
- var location = script.locationFromPosition(position, true);
- if (location == null) return;
- return {
- line: location.line + 1,
- column: location.column + 1,
- position: position
- };
- }
-
- if (!("scriptObject" in e) || !("startPosition" in e)) {
- return;
- }
-
- var script = e.scriptObject;
-
- var position_struct = {
- start: createPositionStruct(script, e.startPosition),
- end: createPositionStruct(script, e.endPosition)
- };
- details.position = position_struct;
- }
-
- // LiveEdit main entry point: changes a script text to a new string.
- function SetScriptSource(script, new_source, preview_only, change_log) {
- var old_source = script.source;
- var diff = CompareStrings(old_source, new_source);
- return ApplyPatchMultiChunk(script, diff, new_source, preview_only,
- change_log);
- }
-
- function CompareStrings(s1, s2) {
- return %LiveEditCompareStrings(s1, s2);
- }
-
- // Applies the change to the script.
- // The change is always a substring (change_pos, change_pos + change_len)
- // being replaced with a completely different string new_str.
- // This API is a legacy and is obsolete.
- //
- // @param {Script} script that is being changed
- // @param {Array} change_log a list that collects engineer-readable
- // description of what happened.
- function ApplySingleChunkPatch(script, change_pos, change_len, new_str,
- change_log) {
- var old_source = script.source;
-
- // Prepare new source string.
- var new_source = old_source.substring(0, change_pos) +
- new_str + old_source.substring(change_pos + change_len);
-
- return ApplyPatchMultiChunk(script,
- [ change_pos, change_pos + change_len, change_pos + new_str.length],
- new_source, false, change_log);
- }
-
- // Creates JSON description for a change tree.
- function DescribeChangeTree(old_code_tree) {
-
- function ProcessOldNode(node) {
- var child_infos = [];
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- if (child.status != FunctionStatus.UNCHANGED) {
- child_infos.push(ProcessOldNode(child));
- }
- }
- var new_child_infos = [];
- if (node.textually_unmatched_new_nodes) {
- for (var i = 0; i < node.textually_unmatched_new_nodes.length; i++) {
- var child = node.textually_unmatched_new_nodes[i];
- new_child_infos.push(ProcessNewNode(child));
- }
- }
- var res = {
- name: node.info.function_name,
- positions: DescribePositions(node),
- status: node.status,
- children: child_infos,
- new_children: new_child_infos
- };
- if (node.status_explanation) {
- res.status_explanation = node.status_explanation;
- }
- if (node.textual_corresponding_node) {
- res.new_positions = DescribePositions(node.textual_corresponding_node);
- }
- return res;
- }
-
- function ProcessNewNode(node) {
- var child_infos = [];
- // Do not list ancestors.
- if (false) {
- for (var i = 0; i < node.children.length; i++) {
- child_infos.push(ProcessNewNode(node.children[i]));
- }
- }
- var res = {
- name: node.info.function_name,
- positions: DescribePositions(node),
- children: child_infos,
- };
- return res;
- }
-
- function DescribePositions(node) {
- return {
- start_position: node.info.start_position,
- end_position: node.info.end_position
- };
- }
-
- return ProcessOldNode(old_code_tree);
- }
-
- // -------------------------------------------------------------------
- // Exports
-
- var LiveEdit = {};
- LiveEdit.SetScriptSource = SetScriptSource;
- LiveEdit.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
- LiveEdit.Failure = Failure;
-
- LiveEdit.TestApi = {
- PosTranslator: PosTranslator,
- CompareStrings: CompareStrings,
- ApplySingleChunkPatch: ApplySingleChunkPatch
- };
-
- global.Debug.LiveEdit = LiveEdit;
-
-})
diff --git a/src/debug/mips/OWNERS b/src/debug/mips/OWNERS
deleted file mode 100644
index 89455a4..0000000
--- a/src/debug/mips/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-paul.lind@imgtec.com
-gergely.kis@imgtec.com
-akos.palfi@imgtec.com
-balazs.kilvady@imgtec.com
-dusan.milosavljevic@imgtec.com
-ivica.bogosavljevic@imgtec.com
diff --git a/src/debug/mips/debug-mips.cc b/src/debug/mips/debug-mips.cc
index 5b809e6..30bf215 100644
--- a/src/debug/mips/debug-mips.cc
+++ b/src/debug/mips/debug-mips.cc
@@ -6,103 +6,15 @@
#include "src/debug/debug.h"
-#include "src/codegen.h"
+#include "src/codegen/macro-assembler.h"
#include "src/debug/liveedit.h"
+#include "src/execution/frames-inl.h"
namespace v8 {
namespace internal {
#define __ ACCESS_MASM(masm)
-
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_size;
- __ bind(&check_size);
- for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) {
- __ nop(MacroAssembler::DEBUG_BREAK_NOP);
- }
- DCHECK_EQ(Assembler::kDebugBreakSlotInstructions,
- masm->InstructionsGeneratedSince(&check_size));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction. Avoid emitting
- // the trampoline pool in the debug break slot code.
- Assembler::BlockTrampolinePoolScope block_pool(masm);
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- // Patch the code changing the debug break slot code from:
- // nop(DEBUG_BREAK_NOP) - nop(1) is sll(zero_reg, zero_reg, 1)
- // nop(DEBUG_BREAK_NOP)
- // nop(DEBUG_BREAK_NOP)
- // nop(DEBUG_BREAK_NOP)
- // to a call to the debug break slot code.
- // li t9, address (lui t9 / ori t9 instruction pair)
- // call t9 (jalr t9 / nop instruction pair)
- patcher.masm()->li(v8::internal::t9,
- Operand(reinterpret_cast<int32_t>(code->entry())));
- patcher.masm()->Call(v8::internal::t9);
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- Instr current_instr = Assembler::instr_at(pc);
- return !Assembler::IsNop(current_instr, Assembler::DEBUG_BREAK_NOP);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
- {
- FrameScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ push(v0);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ PrepareCEntryArgs(1);
- __ PrepareCEntryFunction(ExternalReference(
- Runtime::FunctionForId(Runtime::kDebugBreak), masm->isolate()));
-
- CEntryStub ceb(masm->isolate(), 1);
- __ CallStub(&ceb);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; i++) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber v0 if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function returned by DebugBreak.
- if (!(reg.is(v0) && (mode == SAVE_RESULT_REGISTER))) {
- __ li(reg, kDebugZapValue);
- }
- }
- }
- // Leave the internal frame.
- }
-
- __ MaybeDropFrames();
-
- // Return to caller.
- __ Ret();
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -121,20 +33,17 @@
// - Leave the frame.
// - Restart the frame by calling the function.
__ mov(fp, a1);
- __ lw(a1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
+ __ lw(a1, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
// Pop return address and frame.
__ LeaveFrame(StackFrame::INTERNAL);
__ lw(a0, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
- __ lw(a0,
- FieldMemOperand(a0, SharedFunctionInfo::kFormalParameterCountOffset));
+ __ lhu(a0,
+ FieldMemOperand(a0, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(a2, a0);
- ParameterCount dummy1(a2);
- ParameterCount dummy2(a0);
- __ InvokeFunction(a1, dummy1, dummy2, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ __ InvokeFunction(a1, a2, a0, JUMP_FUNCTION);
}
diff --git a/src/debug/mips64/OWNERS b/src/debug/mips64/OWNERS
deleted file mode 100644
index 89455a4..0000000
--- a/src/debug/mips64/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-paul.lind@imgtec.com
-gergely.kis@imgtec.com
-akos.palfi@imgtec.com
-balazs.kilvady@imgtec.com
-dusan.milosavljevic@imgtec.com
-ivica.bogosavljevic@imgtec.com
diff --git a/src/debug/mips64/debug-mips64.cc b/src/debug/mips64/debug-mips64.cc
index b8dbbfb..f677a38 100644
--- a/src/debug/mips64/debug-mips64.cc
+++ b/src/debug/mips64/debug-mips64.cc
@@ -6,65 +6,15 @@
#include "src/debug/debug.h"
-#include "src/codegen.h"
+#include "src/codegen/macro-assembler.h"
#include "src/debug/liveedit.h"
+#include "src/execution/frames-inl.h"
namespace v8 {
namespace internal {
#define __ ACCESS_MASM(masm)
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_size;
- __ bind(&check_size);
- for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) {
- __ nop(MacroAssembler::DEBUG_BREAK_NOP);
- }
- DCHECK_EQ(Assembler::kDebugBreakSlotInstructions,
- masm->InstructionsGeneratedSince(&check_size));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction. Avoid emitting
- // the trampoline pool in the debug break slot code.
- Assembler::BlockTrampolinePoolScope block_pool(masm);
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- // Patch the code changing the debug break slot code from:
- // nop(DEBUG_BREAK_NOP) - nop(1) is sll(zero_reg, zero_reg, 1)
- // nop(DEBUG_BREAK_NOP)
- // nop(DEBUG_BREAK_NOP)
- // nop(DEBUG_BREAK_NOP)
- // nop(DEBUG_BREAK_NOP)
- // nop(DEBUG_BREAK_NOP)
- // to a call to the debug break slot code.
- // li t9, address (4-instruction sequence on mips64)
- // call t9 (jalr t9 / nop instruction pair)
- patcher.masm()->li(v8::internal::t9,
- Operand(reinterpret_cast<int64_t>(code->entry())),
- ADDRESS_LOAD);
- patcher.masm()->Call(v8::internal::t9);
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- Instr current_instr = Assembler::instr_at(pc);
- return !Assembler::IsNop(current_instr, Assembler::DEBUG_BREAK_NOP);
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -76,47 +26,6 @@
__ Ret();
}
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
- {
- FrameScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ push(v0);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ PrepareCEntryArgs(1);
- __ PrepareCEntryFunction(ExternalReference(
- Runtime::FunctionForId(Runtime::kDebugBreak), masm->isolate()));
-
- CEntryStub ceb(masm->isolate(), 1);
- __ CallStub(&ceb);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; i++) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber v0 if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function returned by DebugBreak.
- if (!(reg.is(v0) && (mode == SAVE_RESULT_REGISTER))) {
- __ li(reg, kDebugZapValue);
- }
- }
- }
-
- // Leave the internal frame.
- }
-
- __ MaybeDropFrames();
-
- // Return to caller.
- __ Ret();
-}
-
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
// - Drop to the target frame specified by a1.
@@ -124,20 +33,17 @@
// - Leave the frame.
// - Restart the frame by calling the function.
__ mov(fp, a1);
- __ ld(a1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
+ __ Ld(a1, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
// Pop return address and frame.
__ LeaveFrame(StackFrame::INTERNAL);
- __ ld(a0, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
- __ ld(a0,
- FieldMemOperand(a0, SharedFunctionInfo::kFormalParameterCountOffset));
+ __ Ld(a0, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
+ __ Lhu(a0,
+ FieldMemOperand(a0, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(a2, a0);
- ParameterCount dummy1(a2);
- ParameterCount dummy2(a0);
- __ InvokeFunction(a1, dummy1, dummy2, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ __ InvokeFunction(a1, a2, a0, JUMP_FUNCTION);
}
diff --git a/src/debug/mirrors.js b/src/debug/mirrors.js
deleted file mode 100644
index b534fec..0000000
--- a/src/debug/mirrors.js
+++ /dev/null
@@ -1,2410 +0,0 @@
-// Copyright 2006-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.
-
-(function(global, utils) {
-"use strict";
-
-// ----------------------------------------------------------------------------
-// Imports
-
-var GlobalArray = global.Array;
-var IsNaN = global.isNaN;
-var JSONStringify = global.JSON.stringify;
-var MapEntries;
-var MapIteratorNext;
-var SetIteratorNext;
-var SetValues;
-
-utils.Import(function(from) {
- MapEntries = from.MapEntries;
- MapIteratorNext = from.MapIteratorNext;
- SetIteratorNext = from.SetIteratorNext;
- SetValues = from.SetValues;
-});
-
-// ----------------------------------------------------------------------------
-
-// Mirror hierarchy:
-// - Mirror
-// - ValueMirror
-// - UndefinedMirror
-// - NullMirror
-// - BooleanMirror
-// - NumberMirror
-// - StringMirror
-// - SymbolMirror
-// - ObjectMirror
-// - FunctionMirror
-// - UnresolvedFunctionMirror
-// - ArrayMirror
-// - DateMirror
-// - RegExpMirror
-// - ErrorMirror
-// - PromiseMirror
-// - MapMirror
-// - SetMirror
-// - IteratorMirror
-// - GeneratorMirror
-// - PropertyMirror
-// - InternalPropertyMirror
-// - FrameMirror
-// - ScriptMirror
-// - ScopeMirror
-
-// Type names of the different mirrors.
-var MirrorType = {
- UNDEFINED_TYPE : 'undefined',
- NULL_TYPE : 'null',
- BOOLEAN_TYPE : 'boolean',
- NUMBER_TYPE : 'number',
- STRING_TYPE : 'string',
- SYMBOL_TYPE : 'symbol',
- OBJECT_TYPE : 'object',
- FUNCTION_TYPE : 'function',
- REGEXP_TYPE : 'regexp',
- ERROR_TYPE : 'error',
- PROPERTY_TYPE : 'property',
- INTERNAL_PROPERTY_TYPE : 'internalProperty',
- FRAME_TYPE : 'frame',
- SCRIPT_TYPE : 'script',
- CONTEXT_TYPE : 'context',
- SCOPE_TYPE : 'scope',
- PROMISE_TYPE : 'promise',
- MAP_TYPE : 'map',
- SET_TYPE : 'set',
- ITERATOR_TYPE : 'iterator',
- GENERATOR_TYPE : 'generator',
-}
-
-/**
- * Returns the mirror for a specified value or object.
- *
- * @param {value or Object} value the value or object to retrieve the mirror for
- * @returns {Mirror} the mirror reflects the passed value or object
- */
-function MakeMirror(value) {
- var mirror;
-
- if (IS_UNDEFINED(value)) {
- mirror = new UndefinedMirror();
- } else if (IS_NULL(value)) {
- mirror = new NullMirror();
- } else if (IS_BOOLEAN(value)) {
- mirror = new BooleanMirror(value);
- } else if (IS_NUMBER(value)) {
- mirror = new NumberMirror(value);
- } else if (IS_STRING(value)) {
- mirror = new StringMirror(value);
- } else if (IS_SYMBOL(value)) {
- mirror = new SymbolMirror(value);
- } else if (IS_ARRAY(value)) {
- mirror = new ArrayMirror(value);
- } else if (IS_DATE(value)) {
- mirror = new DateMirror(value);
- } else if (IS_FUNCTION(value)) {
- mirror = new FunctionMirror(value);
- } else if (%IsRegExp(value)) {
- mirror = new RegExpMirror(value);
- } else if (IS_ERROR(value)) {
- mirror = new ErrorMirror(value);
- } else if (IS_SCRIPT(value)) {
- mirror = new ScriptMirror(value);
- } else if (IS_MAP(value) || IS_WEAKMAP(value)) {
- mirror = new MapMirror(value);
- } else if (IS_SET(value) || IS_WEAKSET(value)) {
- mirror = new SetMirror(value);
- } else if (IS_MAP_ITERATOR(value) || IS_SET_ITERATOR(value)) {
- mirror = new IteratorMirror(value);
- } else if (%is_promise(value)) {
- mirror = new PromiseMirror(value);
- } else if (IS_GENERATOR(value)) {
- mirror = new GeneratorMirror(value);
- } else {
- mirror = new ObjectMirror(value, MirrorType.OBJECT_TYPE);
- }
-
- return mirror;
-}
-
-
-/**
- * Returns the mirror for the undefined value.
- *
- * @returns {Mirror} the mirror reflects the undefined value
- */
-function GetUndefinedMirror() {
- return MakeMirror(UNDEFINED);
-}
-
-
-/**
- * Inherit the prototype methods from one constructor into another.
- *
- * The Function.prototype.inherits from lang.js rewritten as a standalone
- * function (not on Function.prototype). NOTE: If this file is to be loaded
- * during bootstrapping this function needs to be revritten using some native
- * functions as prototype setup using normal JavaScript does not work as
- * expected during bootstrapping (see mirror.js in r114903).
- *
- * @param {function} ctor Constructor function which needs to inherit the
- * prototype
- * @param {function} superCtor Constructor function to inherit prototype from
- */
-function inherits(ctor, superCtor) {
- var tempCtor = function(){};
- tempCtor.prototype = superCtor.prototype;
- ctor.super_ = superCtor.prototype;
- ctor.prototype = new tempCtor();
- ctor.prototype.constructor = ctor;
-}
-
-// Maximum length when sending strings through the JSON protocol.
-var kMaxProtocolStringLength = 80;
-
-
-// A copy of the PropertyKind enum from property-details.h
-var PropertyType = {};
-PropertyType.Data = 0;
-PropertyType.Accessor = 1;
-
-
-// Different attributes for a property.
-var PropertyAttribute = {};
-PropertyAttribute.None = NONE;
-PropertyAttribute.ReadOnly = READ_ONLY;
-PropertyAttribute.DontEnum = DONT_ENUM;
-PropertyAttribute.DontDelete = DONT_DELETE;
-
-
-// A copy of the scope types from runtime-debug.cc.
-// NOTE: these constants should be backward-compatible, so
-// add new ones to the end of this list.
-var ScopeType = { Global: 0,
- Local: 1,
- With: 2,
- Closure: 3,
- Catch: 4,
- Block: 5,
- Script: 6,
- Eval: 7,
- Module: 8,
- };
-
-/**
- * Base class for all mirror objects.
- * @param {string} type The type of the mirror
- * @constructor
- */
-function Mirror(type) {
- this.type_ = type;
-}
-
-
-Mirror.prototype.type = function() {
- return this.type_;
-};
-
-
-/**
- * Check whether the mirror reflects a value.
- * @returns {boolean} True if the mirror reflects a value.
- */
-Mirror.prototype.isValue = function() {
- return this instanceof ValueMirror;
-};
-
-
-/**
- * Check whether the mirror reflects the undefined value.
- * @returns {boolean} True if the mirror reflects the undefined value.
- */
-Mirror.prototype.isUndefined = function() {
- return this instanceof UndefinedMirror;
-};
-
-
-/**
- * Check whether the mirror reflects the null value.
- * @returns {boolean} True if the mirror reflects the null value
- */
-Mirror.prototype.isNull = function() {
- return this instanceof NullMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a boolean value.
- * @returns {boolean} True if the mirror reflects a boolean value
- */
-Mirror.prototype.isBoolean = function() {
- return this instanceof BooleanMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a number value.
- * @returns {boolean} True if the mirror reflects a number value
- */
-Mirror.prototype.isNumber = function() {
- return this instanceof NumberMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a string value.
- * @returns {boolean} True if the mirror reflects a string value
- */
-Mirror.prototype.isString = function() {
- return this instanceof StringMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a symbol.
- * @returns {boolean} True if the mirror reflects a symbol
- */
-Mirror.prototype.isSymbol = function() {
- return this instanceof SymbolMirror;
-};
-
-
-/**
- * Check whether the mirror reflects an object.
- * @returns {boolean} True if the mirror reflects an object
- */
-Mirror.prototype.isObject = function() {
- return this instanceof ObjectMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a function.
- * @returns {boolean} True if the mirror reflects a function
- */
-Mirror.prototype.isFunction = function() {
- return this instanceof FunctionMirror;
-};
-
-
-/**
- * Check whether the mirror reflects an unresolved function.
- * @returns {boolean} True if the mirror reflects an unresolved function
- */
-Mirror.prototype.isUnresolvedFunction = function() {
- return this instanceof UnresolvedFunctionMirror;
-};
-
-
-/**
- * Check whether the mirror reflects an array.
- * @returns {boolean} True if the mirror reflects an array
- */
-Mirror.prototype.isArray = function() {
- return this instanceof ArrayMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a date.
- * @returns {boolean} True if the mirror reflects a date
- */
-Mirror.prototype.isDate = function() {
- return this instanceof DateMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a regular expression.
- * @returns {boolean} True if the mirror reflects a regular expression
- */
-Mirror.prototype.isRegExp = function() {
- return this instanceof RegExpMirror;
-};
-
-
-/**
- * Check whether the mirror reflects an error.
- * @returns {boolean} True if the mirror reflects an error
- */
-Mirror.prototype.isError = function() {
- return this instanceof ErrorMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a promise.
- * @returns {boolean} True if the mirror reflects a promise
- */
-Mirror.prototype.isPromise = function() {
- return this instanceof PromiseMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a generator object.
- * @returns {boolean} True if the mirror reflects a generator object
- */
-Mirror.prototype.isGenerator = function() {
- return this instanceof GeneratorMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a property.
- * @returns {boolean} True if the mirror reflects a property
- */
-Mirror.prototype.isProperty = function() {
- return this instanceof PropertyMirror;
-};
-
-
-/**
- * Check whether the mirror reflects an internal property.
- * @returns {boolean} True if the mirror reflects an internal property
- */
-Mirror.prototype.isInternalProperty = function() {
- return this instanceof InternalPropertyMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a stack frame.
- * @returns {boolean} True if the mirror reflects a stack frame
- */
-Mirror.prototype.isFrame = function() {
- return this instanceof FrameMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a script.
- * @returns {boolean} True if the mirror reflects a script
- */
-Mirror.prototype.isScript = function() {
- return this instanceof ScriptMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a context.
- * @returns {boolean} True if the mirror reflects a context
- */
-Mirror.prototype.isContext = function() {
- return this instanceof ContextMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a scope.
- * @returns {boolean} True if the mirror reflects a scope
- */
-Mirror.prototype.isScope = function() {
- return this instanceof ScopeMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a map.
- * @returns {boolean} True if the mirror reflects a map
- */
-Mirror.prototype.isMap = function() {
- return this instanceof MapMirror;
-};
-
-
-/**
- * Check whether the mirror reflects a set.
- * @returns {boolean} True if the mirror reflects a set
- */
-Mirror.prototype.isSet = function() {
- return this instanceof SetMirror;
-};
-
-
-/**
- * Check whether the mirror reflects an iterator.
- * @returns {boolean} True if the mirror reflects an iterator
- */
-Mirror.prototype.isIterator = function() {
- return this instanceof IteratorMirror;
-};
-
-
-Mirror.prototype.toText = function() {
- // Simpel to text which is used when on specialization in subclass.
- return "#<" + this.constructor.name + ">";
-};
-
-
-/**
- * Base class for all value mirror objects.
- * @param {string} type The type of the mirror
- * @param {value} value The value reflected by this mirror
- * @constructor
- * @extends Mirror
- */
-function ValueMirror(type, value) {
- %_Call(Mirror, this, type);
- this.value_ = value;
-}
-inherits(ValueMirror, Mirror);
-
-
-/**
- * Check whether this is a primitive value.
- * @return {boolean} True if the mirror reflects a primitive value
- */
-ValueMirror.prototype.isPrimitive = function() {
- var type = this.type();
- return type === 'undefined' ||
- type === 'null' ||
- type === 'boolean' ||
- type === 'number' ||
- type === 'string' ||
- type === 'symbol';
-};
-
-
-/**
- * Get the actual value reflected by this mirror.
- * @return {value} The value reflected by this mirror
- */
-ValueMirror.prototype.value = function() {
- return this.value_;
-};
-
-
-/**
- * Mirror object for Undefined.
- * @constructor
- * @extends ValueMirror
- */
-function UndefinedMirror() {
- %_Call(ValueMirror, this, MirrorType.UNDEFINED_TYPE, UNDEFINED);
-}
-inherits(UndefinedMirror, ValueMirror);
-
-
-UndefinedMirror.prototype.toText = function() {
- return 'undefined';
-};
-
-
-/**
- * Mirror object for null.
- * @constructor
- * @extends ValueMirror
- */
-function NullMirror() {
- %_Call(ValueMirror, this, MirrorType.NULL_TYPE, null);
-}
-inherits(NullMirror, ValueMirror);
-
-
-NullMirror.prototype.toText = function() {
- return 'null';
-};
-
-
-/**
- * Mirror object for boolean values.
- * @param {boolean} value The boolean value reflected by this mirror
- * @constructor
- * @extends ValueMirror
- */
-function BooleanMirror(value) {
- %_Call(ValueMirror, this, MirrorType.BOOLEAN_TYPE, value);
-}
-inherits(BooleanMirror, ValueMirror);
-
-
-BooleanMirror.prototype.toText = function() {
- return this.value_ ? 'true' : 'false';
-};
-
-
-/**
- * Mirror object for number values.
- * @param {number} value The number value reflected by this mirror
- * @constructor
- * @extends ValueMirror
- */
-function NumberMirror(value) {
- %_Call(ValueMirror, this, MirrorType.NUMBER_TYPE, value);
-}
-inherits(NumberMirror, ValueMirror);
-
-
-NumberMirror.prototype.toText = function() {
- return %NumberToString(this.value_);
-};
-
-
-/**
- * Mirror object for string values.
- * @param {string} value The string value reflected by this mirror
- * @constructor
- * @extends ValueMirror
- */
-function StringMirror(value) {
- %_Call(ValueMirror, this, MirrorType.STRING_TYPE, value);
-}
-inherits(StringMirror, ValueMirror);
-
-
-StringMirror.prototype.length = function() {
- return this.value_.length;
-};
-
-StringMirror.prototype.getTruncatedValue = function(maxLength) {
- if (maxLength != -1 && this.length() > maxLength) {
- return this.value_.substring(0, maxLength) +
- '... (length: ' + this.length() + ')';
- }
- return this.value_;
-};
-
-StringMirror.prototype.toText = function() {
- return this.getTruncatedValue(kMaxProtocolStringLength);
-};
-
-
-/**
- * Mirror object for a Symbol
- * @param {Object} value The Symbol
- * @constructor
- * @extends Mirror
- */
-function SymbolMirror(value) {
- %_Call(ValueMirror, this, MirrorType.SYMBOL_TYPE, value);
-}
-inherits(SymbolMirror, ValueMirror);
-
-
-SymbolMirror.prototype.description = function() {
- return %SymbolDescription(%ValueOf(this.value_));
-}
-
-
-SymbolMirror.prototype.toText = function() {
- return %SymbolDescriptiveString(%ValueOf(this.value_));
-}
-
-
-/**
- * Mirror object for objects.
- * @param {object} value The object reflected by this mirror
- * @constructor
- * @extends ValueMirror
- */
-function ObjectMirror(value, type) {
- type = type || MirrorType.OBJECT_TYPE;
- %_Call(ValueMirror, this, type, value);
-}
-inherits(ObjectMirror, ValueMirror);
-
-
-ObjectMirror.prototype.className = function() {
- return %_ClassOf(this.value_);
-};
-
-
-ObjectMirror.prototype.constructorFunction = function() {
- return MakeMirror(%DebugGetProperty(this.value_, 'constructor'));
-};
-
-
-ObjectMirror.prototype.prototypeObject = function() {
- return MakeMirror(%DebugGetProperty(this.value_, 'prototype'));
-};
-
-
-ObjectMirror.prototype.protoObject = function() {
- return MakeMirror(%DebugGetPrototype(this.value_));
-};
-
-
-ObjectMirror.prototype.hasNamedInterceptor = function() {
- // Get information on interceptors for this object.
- var x = %GetInterceptorInfo(this.value_);
- return (x & 2) != 0;
-};
-
-
-ObjectMirror.prototype.hasIndexedInterceptor = function() {
- // Get information on interceptors for this object.
- var x = %GetInterceptorInfo(this.value_);
- return (x & 1) != 0;
-};
-
-
-/**
- * Return the property names for this object.
- * @param {number} kind Indicate whether named, indexed or both kinds of
- * properties are requested
- * @param {number} limit Limit the number of names returend to the specified
- value
- * @return {Array} Property names for this object
- */
-ObjectMirror.prototype.propertyNames = function() {
- return %GetOwnPropertyKeys(this.value_, PROPERTY_FILTER_NONE);
-};
-
-
-/**
- * Return the properties for this object as an array of PropertyMirror objects.
- * @param {number} kind Indicate whether named, indexed or both kinds of
- * properties are requested
- * @param {number} limit Limit the number of properties returned to the
- specified value
- * @return {Array} Property mirrors for this object
- */
-ObjectMirror.prototype.properties = function() {
- var names = this.propertyNames();
- var properties = new GlobalArray(names.length);
- for (var i = 0; i < names.length; i++) {
- properties[i] = this.property(names[i]);
- }
-
- return properties;
-};
-
-
-/**
- * Return the internal properties for this object as an array of
- * InternalPropertyMirror objects.
- * @return {Array} Property mirrors for this object
- */
-ObjectMirror.prototype.internalProperties = function() {
- return ObjectMirror.GetInternalProperties(this.value_);
-}
-
-
-ObjectMirror.prototype.property = function(name) {
- var details = %DebugGetPropertyDetails(this.value_, name);
- if (details) {
- return new PropertyMirror(this, name, details);
- }
-
- // Nothing found.
- return GetUndefinedMirror();
-};
-
-
-
-/**
- * Try to find a property from its value.
- * @param {Mirror} value The property value to look for
- * @return {PropertyMirror} The property with the specified value. If no
- * property was found with the specified value UndefinedMirror is returned
- */
-ObjectMirror.prototype.lookupProperty = function(value) {
- var properties = this.properties();
-
- // Look for property value in properties.
- for (var i = 0; i < properties.length; i++) {
-
- // Skip properties which are defined through accessors.
- var property = properties[i];
- if (property.propertyType() == PropertyType.Data) {
- if (property.value_ === value.value_) {
- return property;
- }
- }
- }
-
- // Nothing found.
- return GetUndefinedMirror();
-};
-
-
-/**
- * Returns objects which has direct references to this object
- * @param {number} opt_max_objects Optional parameter specifying the maximum
- * number of referencing objects to return.
- * @return {Array} The objects which has direct references to this object.
- */
-ObjectMirror.prototype.referencedBy = function(opt_max_objects) {
- // Find all objects with direct references to this object.
- var result = %DebugReferencedBy(this.value_,
- Mirror.prototype, opt_max_objects || 0);
-
- // Make mirrors for all the references found.
- for (var i = 0; i < result.length; i++) {
- result[i] = MakeMirror(result[i]);
- }
-
- return result;
-};
-
-
-ObjectMirror.prototype.toText = function() {
- var name;
- var ctor = this.constructorFunction();
- if (!ctor.isFunction()) {
- name = this.className();
- } else {
- name = ctor.name();
- if (!name) {
- name = this.className();
- }
- }
- return '#<' + name + '>';
-};
-
-
-/**
- * Return the internal properties of the value, such as [[PrimitiveValue]] of
- * scalar wrapper objects, properties of the bound function and properties of
- * the promise.
- * This method is done static to be accessible from Debug API with the bare
- * values without mirrors.
- * @return {Array} array (possibly empty) of InternalProperty instances
- */
-ObjectMirror.GetInternalProperties = function(value) {
- var properties = %DebugGetInternalProperties(value);
- var result = [];
- for (var i = 0; i < properties.length; i += 2) {
- result.push(new InternalPropertyMirror(properties[i], properties[i + 1]));
- }
- return result;
-}
-
-
-/**
- * Mirror object for functions.
- * @param {function} value The function object reflected by this mirror.
- * @constructor
- * @extends ObjectMirror
- */
-function FunctionMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.FUNCTION_TYPE);
- this.resolved_ = true;
-}
-inherits(FunctionMirror, ObjectMirror);
-
-
-/**
- * Returns whether the function is resolved.
- * @return {boolean} True if the function is resolved. Unresolved functions can
- * only originate as functions from stack frames
- */
-FunctionMirror.prototype.resolved = function() {
- return this.resolved_;
-};
-
-
-/**
- * Returns the name of the function.
- * @return {string} Name of the function
- */
-FunctionMirror.prototype.name = function() {
- return %FunctionGetName(this.value_);
-};
-
-
-/**
- * Returns the displayName if it is set, otherwise name, otherwise inferred
- * name.
- * @return {string} Name of the function
- */
-FunctionMirror.prototype.debugName = function() {
- return %FunctionGetDebugName(this.value_);
-}
-
-
-/**
- * Returns the inferred name of the function.
- * @return {string} Name of the function
- */
-FunctionMirror.prototype.inferredName = function() {
- return %FunctionGetInferredName(this.value_);
-};
-
-
-/**
- * Returns the source code for the function.
- * @return {string or undefined} The source code for the function. If the
- * function is not resolved undefined will be returned.
- */
-FunctionMirror.prototype.source = function() {
- // Return source if function is resolved. Otherwise just fall through to
- // return undefined.
- if (this.resolved()) {
- return %FunctionToString(this.value_);
- }
-};
-
-
-/**
- * Returns the script object for the function.
- * @return {ScriptMirror or undefined} Script object for the function or
- * undefined if the function has no script
- */
-FunctionMirror.prototype.script = function() {
- // Return script if function is resolved. Otherwise just fall through
- // to return undefined.
- if (this.resolved()) {
- if (this.script_) {
- return this.script_;
- }
- var script = %FunctionGetScript(this.value_);
- if (script) {
- return this.script_ = MakeMirror(script);
- }
- }
-};
-
-
-/**
- * Returns the script source position for the function. Only makes sense
- * for functions which has a script defined.
- * @return {Number or undefined} in-script position for the function
- */
-FunctionMirror.prototype.sourcePosition_ = function() {
- // Return position if function is resolved. Otherwise just fall
- // through to return undefined.
- if (this.resolved()) {
- return %FunctionGetScriptSourcePosition(this.value_);
- }
-};
-
-
-/**
- * Returns the script source location object for the function. Only makes sense
- * for functions which has a script defined.
- * @return {Location or undefined} in-script location for the function begin
- */
-FunctionMirror.prototype.sourceLocation = function() {
- if (this.resolved()) {
- var script = this.script();
- if (script) {
- return script.locationFromPosition(this.sourcePosition_(), true);
- }
- }
-};
-
-
-/**
- * Returns objects constructed by this function.
- * @param {number} opt_max_instances Optional parameter specifying the maximum
- * number of instances to return.
- * @return {Array or undefined} The objects constructed by this function.
- */
-FunctionMirror.prototype.constructedBy = function(opt_max_instances) {
- if (this.resolved()) {
- // Find all objects constructed from this function.
- var result = %DebugConstructedBy(this.value_, opt_max_instances || 0);
-
- // Make mirrors for all the instances found.
- for (var i = 0; i < result.length; i++) {
- result[i] = MakeMirror(result[i]);
- }
-
- return result;
- } else {
- return [];
- }
-};
-
-
-FunctionMirror.prototype.scopeCount = function() {
- if (this.resolved()) {
- if (IS_UNDEFINED(this.scopeCount_)) {
- this.scopeCount_ = %GetFunctionScopeCount(this.value());
- }
- return this.scopeCount_;
- } else {
- return 0;
- }
-};
-
-
-FunctionMirror.prototype.scope = function(index) {
- if (this.resolved()) {
- return new ScopeMirror(UNDEFINED, this, UNDEFINED, index);
- }
-};
-
-
-FunctionMirror.prototype.toText = function() {
- return this.source();
-};
-
-
-FunctionMirror.prototype.context = function() {
- if (this.resolved()) {
- if (!this._context)
- this._context = new ContextMirror(%FunctionGetContextData(this.value_));
- return this._context;
- }
-};
-
-
-/**
- * Mirror object for unresolved functions.
- * @param {string} value The name for the unresolved function reflected by this
- * mirror.
- * @constructor
- * @extends ObjectMirror
- */
-function UnresolvedFunctionMirror(value) {
- // Construct this using the ValueMirror as an unresolved function is not a
- // real object but just a string.
- %_Call(ValueMirror, this, MirrorType.FUNCTION_TYPE, value);
- this.propertyCount_ = 0;
- this.elementCount_ = 0;
- this.resolved_ = false;
-}
-inherits(UnresolvedFunctionMirror, FunctionMirror);
-
-
-UnresolvedFunctionMirror.prototype.className = function() {
- return 'Function';
-};
-
-
-UnresolvedFunctionMirror.prototype.constructorFunction = function() {
- return GetUndefinedMirror();
-};
-
-
-UnresolvedFunctionMirror.prototype.prototypeObject = function() {
- return GetUndefinedMirror();
-};
-
-
-UnresolvedFunctionMirror.prototype.protoObject = function() {
- return GetUndefinedMirror();
-};
-
-
-UnresolvedFunctionMirror.prototype.name = function() {
- return this.value_;
-};
-
-
-UnresolvedFunctionMirror.prototype.debugName = function() {
- return this.value_;
-};
-
-
-UnresolvedFunctionMirror.prototype.inferredName = function() {
- return UNDEFINED;
-};
-
-
-UnresolvedFunctionMirror.prototype.propertyNames = function(kind, limit) {
- return [];
-};
-
-
-/**
- * Mirror object for arrays.
- * @param {Array} value The Array object reflected by this mirror
- * @constructor
- * @extends ObjectMirror
- */
-function ArrayMirror(value) {
- %_Call(ObjectMirror, this, value);
-}
-inherits(ArrayMirror, ObjectMirror);
-
-
-ArrayMirror.prototype.length = function() {
- return this.value_.length;
-};
-
-
-ArrayMirror.prototype.indexedPropertiesFromRange = function(opt_from_index,
- opt_to_index) {
- var from_index = opt_from_index || 0;
- var to_index = opt_to_index || this.length() - 1;
- if (from_index > to_index) return new GlobalArray();
- var values = new GlobalArray(to_index - from_index + 1);
- for (var i = from_index; i <= to_index; i++) {
- var details = %DebugGetPropertyDetails(this.value_, TO_STRING(i));
- var value;
- if (details) {
- value = new PropertyMirror(this, i, details);
- } else {
- value = GetUndefinedMirror();
- }
- values[i - from_index] = value;
- }
- return values;
-};
-
-
-/**
- * Mirror object for dates.
- * @param {Date} value The Date object reflected by this mirror
- * @constructor
- * @extends ObjectMirror
- */
-function DateMirror(value) {
- %_Call(ObjectMirror, this, value);
-}
-inherits(DateMirror, ObjectMirror);
-
-
-DateMirror.prototype.toText = function() {
- var s = JSONStringify(this.value_);
- return s.substring(1, s.length - 1); // cut quotes
-};
-
-
-/**
- * Mirror object for regular expressions.
- * @param {RegExp} value The RegExp object reflected by this mirror
- * @constructor
- * @extends ObjectMirror
- */
-function RegExpMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.REGEXP_TYPE);
-}
-inherits(RegExpMirror, ObjectMirror);
-
-
-/**
- * Returns the source to the regular expression.
- * @return {string or undefined} The source to the regular expression
- */
-RegExpMirror.prototype.source = function() {
- return this.value_.source;
-};
-
-
-/**
- * Returns whether this regular expression has the global (g) flag set.
- * @return {boolean} Value of the global flag
- */
-RegExpMirror.prototype.global = function() {
- return this.value_.global;
-};
-
-
-/**
- * Returns whether this regular expression has the ignore case (i) flag set.
- * @return {boolean} Value of the ignore case flag
- */
-RegExpMirror.prototype.ignoreCase = function() {
- return this.value_.ignoreCase;
-};
-
-
-/**
- * Returns whether this regular expression has the multiline (m) flag set.
- * @return {boolean} Value of the multiline flag
- */
-RegExpMirror.prototype.multiline = function() {
- return this.value_.multiline;
-};
-
-
-/**
- * Returns whether this regular expression has the sticky (y) flag set.
- * @return {boolean} Value of the sticky flag
- */
-RegExpMirror.prototype.sticky = function() {
- return this.value_.sticky;
-};
-
-
-/**
- * Returns whether this regular expression has the unicode (u) flag set.
- * @return {boolean} Value of the unicode flag
- */
-RegExpMirror.prototype.unicode = function() {
- return this.value_.unicode;
-};
-
-
-RegExpMirror.prototype.toText = function() {
- // Simpel to text which is used when on specialization in subclass.
- return "/" + this.source() + "/";
-};
-
-
-/**
- * Mirror object for error objects.
- * @param {Error} value The error object reflected by this mirror
- * @constructor
- * @extends ObjectMirror
- */
-function ErrorMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.ERROR_TYPE);
-}
-inherits(ErrorMirror, ObjectMirror);
-
-
-/**
- * Returns the message for this eror object.
- * @return {string or undefined} The message for this eror object
- */
-ErrorMirror.prototype.message = function() {
- return this.value_.message;
-};
-
-
-ErrorMirror.prototype.toText = function() {
- // Use the same text representation as in messages.js.
- var text;
- try {
- text = %ErrorToString(this.value_);
- } catch (e) {
- text = '#<Error>';
- }
- return text;
-};
-
-
-/**
- * Mirror object for a Promise object.
- * @param {Object} value The Promise object
- * @constructor
- * @extends ObjectMirror
- */
-function PromiseMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.PROMISE_TYPE);
-}
-inherits(PromiseMirror, ObjectMirror);
-
-
-function PromiseGetStatus_(value) {
- var status = %PromiseStatus(value);
- if (status == 0) return "pending";
- if (status == 1) return "resolved";
- return "rejected";
-}
-
-
-function PromiseGetValue_(value) {
- return %PromiseResult(value);
-}
-
-
-PromiseMirror.prototype.status = function() {
- return PromiseGetStatus_(this.value_);
-};
-
-
-PromiseMirror.prototype.promiseValue = function() {
- return MakeMirror(PromiseGetValue_(this.value_));
-};
-
-
-function MapMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.MAP_TYPE);
-}
-inherits(MapMirror, ObjectMirror);
-
-
-/**
- * Returns an array of key/value pairs of a map.
- * This will keep keys alive for WeakMaps.
- *
- * @param {number=} opt_limit Max elements to return.
- * @returns {Array.<Object>} Array of key/value pairs of a map.
- */
-MapMirror.prototype.entries = function(opt_limit) {
- var result = [];
-
- if (IS_WEAKMAP(this.value_)) {
- var entries = %GetWeakMapEntries(this.value_, opt_limit || 0);
- for (var i = 0; i < entries.length; i += 2) {
- result.push({
- key: entries[i],
- value: entries[i + 1]
- });
- }
- return result;
- }
-
- var iter = %_Call(MapEntries, this.value_);
- var next;
- while ((!opt_limit || result.length < opt_limit) &&
- !(next = iter.next()).done) {
- result.push({
- key: next.value[0],
- value: next.value[1]
- });
- }
- return result;
-};
-
-
-function SetMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.SET_TYPE);
-}
-inherits(SetMirror, ObjectMirror);
-
-
-function IteratorGetValues_(iter, next_function, opt_limit) {
- var result = [];
- var next;
- while ((!opt_limit || result.length < opt_limit) &&
- !(next = %_Call(next_function, iter)).done) {
- result.push(next.value);
- }
- return result;
-}
-
-
-/**
- * Returns an array of elements of a set.
- * This will keep elements alive for WeakSets.
- *
- * @param {number=} opt_limit Max elements to return.
- * @returns {Array.<Object>} Array of elements of a set.
- */
-SetMirror.prototype.values = function(opt_limit) {
- if (IS_WEAKSET(this.value_)) {
- return %GetWeakSetValues(this.value_, opt_limit || 0);
- }
-
- var iter = %_Call(SetValues, this.value_);
- return IteratorGetValues_(iter, SetIteratorNext, opt_limit);
-};
-
-
-function IteratorMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.ITERATOR_TYPE);
-}
-inherits(IteratorMirror, ObjectMirror);
-
-
-/**
- * Returns a preview of elements of an iterator.
- * Does not change the backing iterator state.
- *
- * @param {number=} opt_limit Max elements to return.
- * @returns {Array.<Object>} Array of elements of an iterator.
- */
-IteratorMirror.prototype.preview = function(opt_limit) {
- if (IS_MAP_ITERATOR(this.value_)) {
- return IteratorGetValues_(%MapIteratorClone(this.value_),
- MapIteratorNext,
- opt_limit);
- } else if (IS_SET_ITERATOR(this.value_)) {
- return IteratorGetValues_(%SetIteratorClone(this.value_),
- SetIteratorNext,
- opt_limit);
- }
-};
-
-
-/**
- * Mirror object for a Generator object.
- * @param {Object} data The Generator object
- * @constructor
- * @extends Mirror
- */
-function GeneratorMirror(value) {
- %_Call(ObjectMirror, this, value, MirrorType.GENERATOR_TYPE);
-}
-inherits(GeneratorMirror, ObjectMirror);
-
-
-function GeneratorGetStatus_(value) {
- var continuation = %GeneratorGetContinuation(value);
- if (continuation < -1) return "running";
- if (continuation == -1) return "closed";
- return "suspended";
-}
-
-
-GeneratorMirror.prototype.status = function() {
- return GeneratorGetStatus_(this.value_);
-};
-
-
-GeneratorMirror.prototype.sourcePosition_ = function() {
- return %GeneratorGetSourcePosition(this.value_);
-};
-
-
-GeneratorMirror.prototype.sourceLocation = function() {
- var pos = this.sourcePosition_();
- if (!IS_UNDEFINED(pos)) {
- var script = this.func().script();
- if (script) {
- return script.locationFromPosition(pos, true);
- }
- }
-};
-
-
-GeneratorMirror.prototype.func = function() {
- if (!this.func_) {
- this.func_ = MakeMirror(%GeneratorGetFunction(this.value_));
- }
- return this.func_;
-};
-
-
-GeneratorMirror.prototype.receiver = function() {
- if (!this.receiver_) {
- this.receiver_ = MakeMirror(%GeneratorGetReceiver(this.value_));
- }
- return this.receiver_;
-};
-
-
-GeneratorMirror.prototype.scopeCount = function() {
- // This value can change over time as the underlying generator is suspended
- // at different locations.
- return %GetGeneratorScopeCount(this.value());
-};
-
-
-GeneratorMirror.prototype.scope = function(index) {
- return new ScopeMirror(UNDEFINED, UNDEFINED, this, index);
-};
-
-
-GeneratorMirror.prototype.allScopes = function() {
- var scopes = [];
- for (let i = 0; i < this.scopeCount(); i++) {
- scopes.push(this.scope(i));
- }
- return scopes;
-};
-
-
-/**
- * Base mirror object for properties.
- * @param {ObjectMirror} mirror The mirror object having this property
- * @param {string} name The name of the property
- * @param {Array} details Details about the property
- * @constructor
- * @extends Mirror
- */
-function PropertyMirror(mirror, name, details) {
- %_Call(Mirror, this, MirrorType.PROPERTY_TYPE);
- this.mirror_ = mirror;
- this.name_ = name;
- this.value_ = details[0];
- this.details_ = details[1];
- this.is_interceptor_ = details[2];
- if (details.length > 3) {
- this.exception_ = details[3];
- this.getter_ = details[4];
- this.setter_ = details[5];
- }
-}
-inherits(PropertyMirror, Mirror);
-
-
-PropertyMirror.prototype.isReadOnly = function() {
- return (this.attributes() & PropertyAttribute.ReadOnly) != 0;
-};
-
-
-PropertyMirror.prototype.isEnum = function() {
- return (this.attributes() & PropertyAttribute.DontEnum) == 0;
-};
-
-
-PropertyMirror.prototype.canDelete = function() {
- return (this.attributes() & PropertyAttribute.DontDelete) == 0;
-};
-
-
-PropertyMirror.prototype.name = function() {
- return this.name_;
-};
-
-
-PropertyMirror.prototype.toText = function() {
- if (IS_SYMBOL(this.name_)) return %SymbolDescriptiveString(this.name_);
- return this.name_;
-};
-
-
-PropertyMirror.prototype.isIndexed = function() {
- for (var i = 0; i < this.name_.length; i++) {
- if (this.name_[i] < '0' || '9' < this.name_[i]) {
- return false;
- }
- }
- return true;
-};
-
-
-PropertyMirror.prototype.value = function() {
- return MakeMirror(this.value_, false);
-};
-
-
-/**
- * Returns whether this property value is an exception.
- * @return {boolean} True if this property value is an exception
- */
-PropertyMirror.prototype.isException = function() {
- return this.exception_ ? true : false;
-};
-
-
-PropertyMirror.prototype.attributes = function() {
- return %DebugPropertyAttributesFromDetails(this.details_);
-};
-
-
-PropertyMirror.prototype.propertyType = function() {
- return %DebugPropertyKindFromDetails(this.details_);
-};
-
-
-/**
- * Returns whether this property has a getter defined through __defineGetter__.
- * @return {boolean} True if this property has a getter
- */
-PropertyMirror.prototype.hasGetter = function() {
- return this.getter_ ? true : false;
-};
-
-
-/**
- * Returns whether this property has a setter defined through __defineSetter__.
- * @return {boolean} True if this property has a setter
- */
-PropertyMirror.prototype.hasSetter = function() {
- return this.setter_ ? true : false;
-};
-
-
-/**
- * Returns the getter for this property defined through __defineGetter__.
- * @return {Mirror} FunctionMirror reflecting the getter function or
- * UndefinedMirror if there is no getter for this property
- */
-PropertyMirror.prototype.getter = function() {
- if (this.hasGetter()) {
- return MakeMirror(this.getter_);
- } else {
- return GetUndefinedMirror();
- }
-};
-
-
-/**
- * Returns the setter for this property defined through __defineSetter__.
- * @return {Mirror} FunctionMirror reflecting the setter function or
- * UndefinedMirror if there is no setter for this property
- */
-PropertyMirror.prototype.setter = function() {
- if (this.hasSetter()) {
- return MakeMirror(this.setter_);
- } else {
- return GetUndefinedMirror();
- }
-};
-
-
-/**
- * Returns whether this property is natively implemented by the host or a set
- * through JavaScript code.
- * @return {boolean} True if the property is
- * UndefinedMirror if there is no setter for this property
- */
-PropertyMirror.prototype.isNative = function() {
- return this.is_interceptor_ ||
- ((this.propertyType() == PropertyType.Accessor) &&
- !this.hasGetter() && !this.hasSetter());
-};
-
-
-/**
- * Mirror object for internal properties. Internal property reflects properties
- * not accessible from user code such as [[BoundThis]] in bound function.
- * Their names are merely symbolic.
- * @param {string} name The name of the property
- * @param {value} property value
- * @constructor
- * @extends Mirror
- */
-function InternalPropertyMirror(name, value) {
- %_Call(Mirror, this, MirrorType.INTERNAL_PROPERTY_TYPE);
- this.name_ = name;
- this.value_ = value;
-}
-inherits(InternalPropertyMirror, Mirror);
-
-
-InternalPropertyMirror.prototype.name = function() {
- return this.name_;
-};
-
-
-InternalPropertyMirror.prototype.value = function() {
- return MakeMirror(this.value_, false);
-};
-
-
-var kFrameDetailsFrameIdIndex = 0;
-var kFrameDetailsReceiverIndex = 1;
-var kFrameDetailsFunctionIndex = 2;
-var kFrameDetailsScriptIndex = 3;
-var kFrameDetailsArgumentCountIndex = 4;
-var kFrameDetailsLocalCountIndex = 5;
-var kFrameDetailsSourcePositionIndex = 6;
-var kFrameDetailsConstructCallIndex = 7;
-var kFrameDetailsAtReturnIndex = 8;
-var kFrameDetailsFlagsIndex = 9;
-var kFrameDetailsFirstDynamicIndex = 10;
-
-var kFrameDetailsNameIndex = 0;
-var kFrameDetailsValueIndex = 1;
-var kFrameDetailsNameValueSize = 2;
-
-var kFrameDetailsFlagDebuggerFrameMask = 1 << 0;
-var kFrameDetailsFlagOptimizedFrameMask = 1 << 1;
-var kFrameDetailsFlagInlinedFrameIndexMask = 7 << 2;
-
-/**
- * Wrapper for the frame details information retreived from the VM. The frame
- * details from the VM is an array with the following content. See runtime.cc
- * Runtime_GetFrameDetails.
- * 0: Id
- * 1: Receiver
- * 2: Function
- * 3: Script
- * 4: Argument count
- * 5: Local count
- * 6: Source position
- * 7: Construct call
- * 8: Is at return
- * 9: Flags (debugger frame, optimized frame, inlined frame index)
- * Arguments name, value
- * Locals name, value
- * Return value if any
- * @param {number} break_id Current break id
- * @param {number} index Frame number
- * @constructor
- */
-function FrameDetails(break_id, index) {
- this.break_id_ = break_id;
- this.details_ = %GetFrameDetails(break_id, index);
-}
-
-
-FrameDetails.prototype.frameId = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsFrameIdIndex];
-};
-
-
-FrameDetails.prototype.receiver = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsReceiverIndex];
-};
-
-
-FrameDetails.prototype.func = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsFunctionIndex];
-};
-
-
-FrameDetails.prototype.script = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsScriptIndex];
-};
-
-
-FrameDetails.prototype.isConstructCall = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsConstructCallIndex];
-};
-
-
-FrameDetails.prototype.isAtReturn = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsAtReturnIndex];
-};
-
-
-FrameDetails.prototype.isDebuggerFrame = function() {
- %CheckExecutionState(this.break_id_);
- var f = kFrameDetailsFlagDebuggerFrameMask;
- return (this.details_[kFrameDetailsFlagsIndex] & f) == f;
-};
-
-
-FrameDetails.prototype.isOptimizedFrame = function() {
- %CheckExecutionState(this.break_id_);
- var f = kFrameDetailsFlagOptimizedFrameMask;
- return (this.details_[kFrameDetailsFlagsIndex] & f) == f;
-};
-
-
-FrameDetails.prototype.isInlinedFrame = function() {
- return this.inlinedFrameIndex() > 0;
-};
-
-
-FrameDetails.prototype.inlinedFrameIndex = function() {
- %CheckExecutionState(this.break_id_);
- var f = kFrameDetailsFlagInlinedFrameIndexMask;
- return (this.details_[kFrameDetailsFlagsIndex] & f) >> 2;
-};
-
-
-FrameDetails.prototype.argumentCount = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsArgumentCountIndex];
-};
-
-
-FrameDetails.prototype.argumentName = function(index) {
- %CheckExecutionState(this.break_id_);
- if (index >= 0 && index < this.argumentCount()) {
- return this.details_[kFrameDetailsFirstDynamicIndex +
- index * kFrameDetailsNameValueSize +
- kFrameDetailsNameIndex];
- }
-};
-
-
-FrameDetails.prototype.argumentValue = function(index) {
- %CheckExecutionState(this.break_id_);
- if (index >= 0 && index < this.argumentCount()) {
- return this.details_[kFrameDetailsFirstDynamicIndex +
- index * kFrameDetailsNameValueSize +
- kFrameDetailsValueIndex];
- }
-};
-
-
-FrameDetails.prototype.localCount = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsLocalCountIndex];
-};
-
-
-FrameDetails.prototype.sourcePosition = function() {
- %CheckExecutionState(this.break_id_);
- return this.details_[kFrameDetailsSourcePositionIndex];
-};
-
-
-FrameDetails.prototype.localName = function(index) {
- %CheckExecutionState(this.break_id_);
- if (index >= 0 && index < this.localCount()) {
- var locals_offset = kFrameDetailsFirstDynamicIndex +
- this.argumentCount() * kFrameDetailsNameValueSize;
- return this.details_[locals_offset +
- index * kFrameDetailsNameValueSize +
- kFrameDetailsNameIndex];
- }
-};
-
-
-FrameDetails.prototype.localValue = function(index) {
- %CheckExecutionState(this.break_id_);
- if (index >= 0 && index < this.localCount()) {
- var locals_offset = kFrameDetailsFirstDynamicIndex +
- this.argumentCount() * kFrameDetailsNameValueSize;
- return this.details_[locals_offset +
- index * kFrameDetailsNameValueSize +
- kFrameDetailsValueIndex];
- }
-};
-
-
-FrameDetails.prototype.returnValue = function() {
- %CheckExecutionState(this.break_id_);
- var return_value_offset =
- kFrameDetailsFirstDynamicIndex +
- (this.argumentCount() + this.localCount()) * kFrameDetailsNameValueSize;
- if (this.details_[kFrameDetailsAtReturnIndex]) {
- return this.details_[return_value_offset];
- }
-};
-
-
-FrameDetails.prototype.scopeCount = function() {
- if (IS_UNDEFINED(this.scopeCount_)) {
- this.scopeCount_ = %GetScopeCount(this.break_id_, this.frameId());
- }
- return this.scopeCount_;
-};
-
-
-/**
- * Mirror object for stack frames.
- * @param {number} break_id The break id in the VM for which this frame is
- valid
- * @param {number} index The frame index (top frame is index 0)
- * @constructor
- * @extends Mirror
- */
-function FrameMirror(break_id, index) {
- %_Call(Mirror, this, MirrorType.FRAME_TYPE);
- this.break_id_ = break_id;
- this.index_ = index;
- this.details_ = new FrameDetails(break_id, index);
-}
-inherits(FrameMirror, Mirror);
-
-
-FrameMirror.prototype.details = function() {
- return this.details_;
-};
-
-
-FrameMirror.prototype.index = function() {
- return this.index_;
-};
-
-
-FrameMirror.prototype.func = function() {
- if (this.func_) {
- return this.func_;
- }
-
- // Get the function for this frame from the VM.
- var f = this.details_.func();
-
- // Create a function mirror. NOTE: MakeMirror cannot be used here as the
- // value returned from the VM might be a string if the function for the
- // frame is unresolved.
- if (IS_FUNCTION(f)) {
- return this.func_ = MakeMirror(f);
- } else {
- return new UnresolvedFunctionMirror(f);
- }
-};
-
-
-FrameMirror.prototype.script = function() {
- if (!this.script_) {
- this.script_ = MakeMirror(this.details_.script());
- }
-
- return this.script_;
-}
-
-
-FrameMirror.prototype.receiver = function() {
- return MakeMirror(this.details_.receiver());
-};
-
-
-FrameMirror.prototype.isConstructCall = function() {
- return this.details_.isConstructCall();
-};
-
-
-FrameMirror.prototype.isAtReturn = function() {
- return this.details_.isAtReturn();
-};
-
-
-FrameMirror.prototype.isDebuggerFrame = function() {
- return this.details_.isDebuggerFrame();
-};
-
-
-FrameMirror.prototype.isOptimizedFrame = function() {
- return this.details_.isOptimizedFrame();
-};
-
-
-FrameMirror.prototype.isInlinedFrame = function() {
- return this.details_.isInlinedFrame();
-};
-
-
-FrameMirror.prototype.inlinedFrameIndex = function() {
- return this.details_.inlinedFrameIndex();
-};
-
-
-FrameMirror.prototype.argumentCount = function() {
- return this.details_.argumentCount();
-};
-
-
-FrameMirror.prototype.argumentName = function(index) {
- return this.details_.argumentName(index);
-};
-
-
-FrameMirror.prototype.argumentValue = function(index) {
- return MakeMirror(this.details_.argumentValue(index));
-};
-
-
-FrameMirror.prototype.localCount = function() {
- return this.details_.localCount();
-};
-
-
-FrameMirror.prototype.localName = function(index) {
- return this.details_.localName(index);
-};
-
-
-FrameMirror.prototype.localValue = function(index) {
- return MakeMirror(this.details_.localValue(index));
-};
-
-
-FrameMirror.prototype.returnValue = function() {
- return MakeMirror(this.details_.returnValue());
-};
-
-
-FrameMirror.prototype.sourcePosition = function() {
- return this.details_.sourcePosition();
-};
-
-
-FrameMirror.prototype.sourceLocation = function() {
- var script = this.script();
- if (script) {
- return script.locationFromPosition(this.sourcePosition(), true);
- }
-};
-
-
-FrameMirror.prototype.sourceLine = function() {
- var location = this.sourceLocation();
- if (location) {
- return location.line;
- }
-};
-
-
-FrameMirror.prototype.sourceColumn = function() {
- var location = this.sourceLocation();
- if (location) {
- return location.column;
- }
-};
-
-
-FrameMirror.prototype.sourceLineText = function() {
- var location = this.sourceLocation();
- if (location) {
- return location.sourceText;
- }
-};
-
-
-FrameMirror.prototype.scopeCount = function() {
- return this.details_.scopeCount();
-};
-
-
-FrameMirror.prototype.scope = function(index) {
- return new ScopeMirror(this, UNDEFINED, UNDEFINED, index);
-};
-
-
-FrameMirror.prototype.allScopes = function(opt_ignore_nested_scopes) {
- var scopeDetails = %GetAllScopesDetails(this.break_id_,
- this.details_.frameId(),
- this.details_.inlinedFrameIndex(),
- !!opt_ignore_nested_scopes);
- var result = [];
- for (var i = 0; i < scopeDetails.length; ++i) {
- result.push(new ScopeMirror(this, UNDEFINED, UNDEFINED, i,
- scopeDetails[i]));
- }
- return result;
-};
-
-
-FrameMirror.prototype.evaluate = function(source, throw_on_side_effect = false) {
- return MakeMirror(%DebugEvaluate(this.break_id_,
- this.details_.frameId(),
- this.details_.inlinedFrameIndex(),
- source,
- throw_on_side_effect));
-};
-
-
-FrameMirror.prototype.invocationText = function() {
- // Format frame invoaction (receiver, function and arguments).
- var result = '';
- var func = this.func();
- var receiver = this.receiver();
- if (this.isConstructCall()) {
- // For constructor frames display new followed by the function name.
- result += 'new ';
- result += func.name() ? func.name() : '[anonymous]';
- } else if (this.isDebuggerFrame()) {
- result += '[debugger]';
- } else {
- // If the receiver has a className which is 'global' don't display it.
- var display_receiver =
- !receiver.className || (receiver.className() != 'global');
- if (display_receiver) {
- result += receiver.toText();
- }
- // Try to find the function as a property in the receiver. Include the
- // prototype chain in the lookup.
- var property = GetUndefinedMirror();
- if (receiver.isObject()) {
- for (var r = receiver;
- !r.isNull() && property.isUndefined();
- r = r.protoObject()) {
- property = r.lookupProperty(func);
- }
- }
- if (!property.isUndefined()) {
- // The function invoked was found on the receiver. Use the property name
- // for the backtrace.
- if (!property.isIndexed()) {
- if (display_receiver) {
- result += '.';
- }
- result += property.toText();
- } else {
- result += '[';
- result += property.toText();
- result += ']';
- }
- // Also known as - if the name in the function doesn't match the name
- // under which it was looked up.
- if (func.name() && func.name() != property.name()) {
- result += '(aka ' + func.name() + ')';
- }
- } else {
- // The function invoked was not found on the receiver. Use the function
- // name if available for the backtrace.
- if (display_receiver) {
- result += '.';
- }
- result += func.name() ? func.name() : '[anonymous]';
- }
- }
-
- // Render arguments for normal frames.
- if (!this.isDebuggerFrame()) {
- result += '(';
- for (var i = 0; i < this.argumentCount(); i++) {
- if (i != 0) result += ', ';
- if (this.argumentName(i)) {
- result += this.argumentName(i);
- result += '=';
- }
- result += this.argumentValue(i).toText();
- }
- result += ')';
- }
-
- if (this.isAtReturn()) {
- result += ' returning ';
- result += this.returnValue().toText();
- }
-
- return result;
-};
-
-
-FrameMirror.prototype.sourceAndPositionText = function() {
- // Format source and position.
- var result = '';
- var func = this.func();
- if (func.resolved()) {
- var script = func.script();
- if (script) {
- if (script.name()) {
- result += script.name();
- } else {
- result += '[unnamed]';
- }
- if (!this.isDebuggerFrame()) {
- var location = this.sourceLocation();
- result += ' line ';
- result += !IS_UNDEFINED(location) ? (location.line + 1) : '?';
- result += ' column ';
- result += !IS_UNDEFINED(location) ? (location.column + 1) : '?';
- if (!IS_UNDEFINED(this.sourcePosition())) {
- result += ' (position ' + (this.sourcePosition() + 1) + ')';
- }
- }
- } else {
- result += '[no source]';
- }
- } else {
- result += '[unresolved]';
- }
-
- return result;
-};
-
-
-FrameMirror.prototype.localsText = function() {
- // Format local variables.
- var result = '';
- var locals_count = this.localCount();
- if (locals_count > 0) {
- for (var i = 0; i < locals_count; ++i) {
- result += ' var ';
- result += this.localName(i);
- result += ' = ';
- result += this.localValue(i).toText();
- if (i < locals_count - 1) result += '\n';
- }
- }
-
- return result;
-};
-
-
-FrameMirror.prototype.restart = function() {
- var result = %LiveEditRestartFrame(this.break_id_, this.index_);
- if (IS_UNDEFINED(result)) {
- result = "Failed to find requested frame";
- }
- return result;
-};
-
-
-FrameMirror.prototype.toText = function(opt_locals) {
- var result = '';
- result += '#' + (this.index() <= 9 ? '0' : '') + this.index();
- result += ' ';
- result += this.invocationText();
- result += ' ';
- result += this.sourceAndPositionText();
- if (opt_locals) {
- result += '\n';
- result += this.localsText();
- }
- return result;
-};
-
-
-// This indexes correspond definitions in debug-scopes.h.
-var kScopeDetailsTypeIndex = 0;
-var kScopeDetailsObjectIndex = 1;
-var kScopeDetailsNameIndex = 2;
-var kScopeDetailsStartPositionIndex = 3;
-var kScopeDetailsEndPositionIndex = 4;
-var kScopeDetailsFunctionIndex = 5;
-
-function ScopeDetails(frame, fun, gen, index, opt_details) {
- if (frame) {
- this.break_id_ = frame.break_id_;
- this.details_ = opt_details ||
- %GetScopeDetails(frame.break_id_,
- frame.details_.frameId(),
- frame.details_.inlinedFrameIndex(),
- index);
- this.frame_id_ = frame.details_.frameId();
- this.inlined_frame_id_ = frame.details_.inlinedFrameIndex();
- } else if (fun) {
- this.details_ = opt_details || %GetFunctionScopeDetails(fun.value(), index);
- this.fun_value_ = fun.value();
- this.break_id_ = UNDEFINED;
- } else {
- this.details_ =
- opt_details || %GetGeneratorScopeDetails(gen.value(), index);
- this.gen_value_ = gen.value();
- this.break_id_ = UNDEFINED;
- }
- this.index_ = index;
-}
-
-
-ScopeDetails.prototype.type = function() {
- if (!IS_UNDEFINED(this.break_id_)) {
- %CheckExecutionState(this.break_id_);
- }
- return this.details_[kScopeDetailsTypeIndex];
-};
-
-
-ScopeDetails.prototype.object = function() {
- if (!IS_UNDEFINED(this.break_id_)) {
- %CheckExecutionState(this.break_id_);
- }
- return this.details_[kScopeDetailsObjectIndex];
-};
-
-
-ScopeDetails.prototype.name = function() {
- if (!IS_UNDEFINED(this.break_id_)) {
- %CheckExecutionState(this.break_id_);
- }
- return this.details_[kScopeDetailsNameIndex];
-};
-
-
-ScopeDetails.prototype.startPosition = function() {
- if (!IS_UNDEFINED(this.break_id_)) {
- %CheckExecutionState(this.break_id_);
- }
- return this.details_[kScopeDetailsStartPositionIndex];
-}
-
-
-ScopeDetails.prototype.endPosition = function() {
- if (!IS_UNDEFINED(this.break_id_)) {
- %CheckExecutionState(this.break_id_);
- }
- return this.details_[kScopeDetailsEndPositionIndex];
-}
-
-ScopeDetails.prototype.func = function() {
- if (!IS_UNDEFINED(this.break_id_)) {
- %CheckExecutionState(this.break_id_);
- }
- return this.details_[kScopeDetailsFunctionIndex];
-}
-
-
-ScopeDetails.prototype.setVariableValueImpl = function(name, new_value) {
- var raw_res;
- if (!IS_UNDEFINED(this.break_id_)) {
- %CheckExecutionState(this.break_id_);
- raw_res = %SetScopeVariableValue(this.break_id_, this.frame_id_,
- this.inlined_frame_id_, this.index_, name, new_value);
- } else if (!IS_UNDEFINED(this.fun_value_)) {
- raw_res = %SetScopeVariableValue(this.fun_value_, null, null, this.index_,
- name, new_value);
- } else {
- raw_res = %SetScopeVariableValue(this.gen_value_, null, null, this.index_,
- name, new_value);
- }
- if (!raw_res) throw %make_error(kDebugger, "Failed to set variable value");
-};
-
-
-/**
- * Mirror object for scope of frame or function. Either frame or function must
- * be specified.
- * @param {FrameMirror} frame The frame this scope is a part of
- * @param {FunctionMirror} function The function this scope is a part of
- * @param {GeneratorMirror} gen The generator this scope is a part of
- * @param {number} index The scope index in the frame
- * @param {Array=} opt_details Raw scope details data
- * @constructor
- * @extends Mirror
- */
-function ScopeMirror(frame, fun, gen, index, opt_details) {
- %_Call(Mirror, this, MirrorType.SCOPE_TYPE);
- if (frame) {
- this.frame_index_ = frame.index_;
- } else {
- this.frame_index_ = UNDEFINED;
- }
- this.scope_index_ = index;
- this.details_ = new ScopeDetails(frame, fun, gen, index, opt_details);
-}
-inherits(ScopeMirror, Mirror);
-
-
-ScopeMirror.prototype.details = function() {
- return this.details_;
-};
-
-
-ScopeMirror.prototype.frameIndex = function() {
- return this.frame_index_;
-};
-
-
-ScopeMirror.prototype.scopeIndex = function() {
- return this.scope_index_;
-};
-
-
-ScopeMirror.prototype.scopeType = function() {
- return this.details_.type();
-};
-
-
-ScopeMirror.prototype.scopeObject = function() {
- // For local, closure and script scopes create a mirror
- // as these objects are created on the fly materializing the local
- // or closure scopes and therefore will not preserve identity.
- return MakeMirror(this.details_.object());
-};
-
-
-ScopeMirror.prototype.setVariableValue = function(name, new_value) {
- this.details_.setVariableValueImpl(name, new_value);
-};
-
-
-/**
- * Mirror object for script source.
- * @param {Script} script The script object
- * @constructor
- * @extends Mirror
- */
-function ScriptMirror(script) {
- %_Call(Mirror, this, MirrorType.SCRIPT_TYPE);
- this.script_ = script;
- this.context_ = new ContextMirror(script.context_data);
-}
-inherits(ScriptMirror, Mirror);
-
-
-ScriptMirror.prototype.value = function() {
- return this.script_;
-};
-
-
-ScriptMirror.prototype.name = function() {
- return this.script_.name || this.script_.nameOrSourceURL();
-};
-
-
-ScriptMirror.prototype.id = function() {
- return this.script_.id;
-};
-
-
-ScriptMirror.prototype.source = function() {
- return this.script_.source;
-};
-
-
-ScriptMirror.prototype.setSource = function(source) {
- if (!IS_STRING(source)) throw %make_error(kDebugger, "Source is not a string");
- %DebugSetScriptSource(this.script_, source);
-};
-
-
-ScriptMirror.prototype.lineOffset = function() {
- return this.script_.line_offset;
-};
-
-
-ScriptMirror.prototype.columnOffset = function() {
- return this.script_.column_offset;
-};
-
-
-ScriptMirror.prototype.data = function() {
- return this.script_.data;
-};
-
-
-ScriptMirror.prototype.scriptType = function() {
- return this.script_.type;
-};
-
-
-ScriptMirror.prototype.compilationType = function() {
- return this.script_.compilation_type;
-};
-
-
-ScriptMirror.prototype.lineCount = function() {
- return %ScriptLineCount(this.script_);
-};
-
-
-ScriptMirror.prototype.locationFromPosition = function(
- position, include_resource_offset) {
- return this.script_.locationFromPosition(position, include_resource_offset);
-};
-
-
-ScriptMirror.prototype.context = function() {
- return this.context_;
-};
-
-
-ScriptMirror.prototype.evalFromScript = function() {
- return MakeMirror(this.script_.eval_from_script);
-};
-
-
-ScriptMirror.prototype.evalFromFunctionName = function() {
- return MakeMirror(this.script_.eval_from_function_name);
-};
-
-
-ScriptMirror.prototype.evalFromLocation = function() {
- var eval_from_script = this.evalFromScript();
- if (!eval_from_script.isUndefined()) {
- var position = this.script_.eval_from_script_position;
- return eval_from_script.locationFromPosition(position, true);
- }
-};
-
-
-ScriptMirror.prototype.toText = function() {
- var result = '';
- result += this.name();
- result += ' (lines: ';
- if (this.lineOffset() > 0) {
- result += this.lineOffset();
- result += '-';
- result += this.lineOffset() + this.lineCount() - 1;
- } else {
- result += this.lineCount();
- }
- result += ')';
- return result;
-};
-
-
-/**
- * Mirror object for context.
- * @param {Object} data The context data
- * @constructor
- * @extends Mirror
- */
-function ContextMirror(data) {
- %_Call(Mirror, this, MirrorType.CONTEXT_TYPE);
- this.data_ = data;
-}
-inherits(ContextMirror, Mirror);
-
-
-ContextMirror.prototype.data = function() {
- return this.data_;
-};
-
-// ----------------------------------------------------------------------------
-// Exports
-
-utils.InstallFunctions(global, DONT_ENUM, [
- "MakeMirror", MakeMirror,
-]);
-
-utils.InstallConstants(global, [
- "ScopeType", ScopeType,
- "PropertyType", PropertyType,
- "PropertyAttribute", PropertyAttribute,
- "Mirror", Mirror,
- "ValueMirror", ValueMirror,
- "UndefinedMirror", UndefinedMirror,
- "NullMirror", NullMirror,
- "BooleanMirror", BooleanMirror,
- "NumberMirror", NumberMirror,
- "StringMirror", StringMirror,
- "SymbolMirror", SymbolMirror,
- "ObjectMirror", ObjectMirror,
- "FunctionMirror", FunctionMirror,
- "UnresolvedFunctionMirror", UnresolvedFunctionMirror,
- "ArrayMirror", ArrayMirror,
- "DateMirror", DateMirror,
- "RegExpMirror", RegExpMirror,
- "ErrorMirror", ErrorMirror,
- "PromiseMirror", PromiseMirror,
- "MapMirror", MapMirror,
- "SetMirror", SetMirror,
- "IteratorMirror", IteratorMirror,
- "GeneratorMirror", GeneratorMirror,
- "PropertyMirror", PropertyMirror,
- "InternalPropertyMirror", InternalPropertyMirror,
- "FrameMirror", FrameMirror,
- "ScriptMirror", ScriptMirror,
- "ScopeMirror", ScopeMirror,
- "FrameDetails", FrameDetails,
-]);
-
-})
diff --git a/src/debug/ppc/OWNERS b/src/debug/ppc/OWNERS
index 752e8e3..02c2cd7 100644
--- a/src/debug/ppc/OWNERS
+++ b/src/debug/ppc/OWNERS
@@ -1,6 +1,5 @@
-jyan@ca.ibm.com
-dstence@us.ibm.com
+junyan@redhat.com
joransiu@ca.ibm.com
-mbrandy@us.ibm.com
-michael_dawson@ca.ibm.com
-bjaideep@ca.ibm.com
+midawson@redhat.com
+mfarazma@redhat.com
+vasili.skurydzin@ibm.com
diff --git a/src/debug/ppc/debug-ppc.cc b/src/debug/ppc/debug-ppc.cc
index 42be185..eeed2d8 100644
--- a/src/debug/ppc/debug-ppc.cc
+++ b/src/debug/ppc/debug-ppc.cc
@@ -2,116 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#if V8_TARGET_ARCH_PPC
+#if V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
#include "src/debug/debug.h"
-#include "src/codegen.h"
+#include "src/codegen/macro-assembler.h"
#include "src/debug/liveedit.h"
+#include "src/execution/frames-inl.h"
namespace v8 {
namespace internal {
#define __ ACCESS_MASM(masm)
-
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_size;
- __ bind(&check_size);
- for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) {
- __ nop(MacroAssembler::DEBUG_BREAK_NOP);
- }
- DCHECK_EQ(Assembler::kDebugBreakSlotInstructions,
- masm->InstructionsGeneratedSince(&check_size));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction. Avoid emitting
- // the trampoline pool in the debug break slot code.
- Assembler::BlockTrampolinePoolScope block_trampoline_pool(masm);
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotInstructions);
- // Patch the code changing the debug break slot code from
- //
- // ori r3, r3, 0
- // ori r3, r3, 0
- // ori r3, r3, 0
- // ori r3, r3, 0
- // ori r3, r3, 0
- //
- // to a call to the debug break code, using a FIXED_SEQUENCE.
- //
- // mov r0, <address>
- // mtlr r0
- // blrl
- //
- Assembler::BlockTrampolinePoolScope block_trampoline_pool(patcher.masm());
- patcher.masm()->mov(v8::internal::r0,
- Operand(reinterpret_cast<intptr_t>(code->entry())));
- patcher.masm()->mtctr(v8::internal::r0);
- patcher.masm()->bctrl();
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- Instr current_instr = Assembler::instr_at(pc);
- return !Assembler::IsNop(current_instr, Assembler::DEBUG_BREAK_NOP);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
- {
- FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ push(r3);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ mov(r3, Operand(1));
- __ mov(r4,
- Operand(ExternalReference(
- Runtime::FunctionForId(Runtime::kDebugBreak), masm->isolate())));
-
- CEntryStub ceb(masm->isolate(), 1);
- __ CallStub(&ceb);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; i++) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber r3 if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function.
- if (!(reg.is(r3) && (mode == SAVE_RESULT_REGISTER))) {
- __ mov(reg, Operand(kDebugZapValue));
- }
- }
- }
- // Leave the internal frame.
- }
-
- __ MaybeDropFrames();
-
- // Return to caller.
- __ Ret();
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -131,17 +34,15 @@
// - Restart the frame by calling the function.
__ mr(fp, r4);
- __ LoadP(r4, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
+ __ LoadP(r4, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
__ LeaveFrame(StackFrame::INTERNAL);
- __ LoadP(r3, FieldMemOperand(r4, JSFunction::kSharedFunctionInfoOffset));
- __ LoadP(
- r3, FieldMemOperand(r3, SharedFunctionInfo::kFormalParameterCountOffset));
+ __ LoadTaggedPointerField(
+ r3, FieldMemOperand(r4, JSFunction::kSharedFunctionInfoOffset));
+ __ lhz(r3,
+ FieldMemOperand(r3, SharedFunctionInfo::kFormalParameterCountOffset));
__ mr(r5, r3);
- ParameterCount dummy1(r5);
- ParameterCount dummy2(r3);
- __ InvokeFunction(r4, dummy1, dummy2, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ __ InvokeFunction(r4, r5, r3, JUMP_FUNCTION);
}
const bool LiveEdit::kFrameDropperSupported = true;
@@ -150,4 +51,4 @@
} // namespace internal
} // namespace v8
-#endif // V8_TARGET_ARCH_PPC
+#endif // V8_TARGET_ARCH_PPC || V8_TARGET_ARCH_PPC64
diff --git a/src/debug/s390/OWNERS b/src/debug/s390/OWNERS
deleted file mode 100644
index 752e8e3..0000000
--- a/src/debug/s390/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-jyan@ca.ibm.com
-dstence@us.ibm.com
-joransiu@ca.ibm.com
-mbrandy@us.ibm.com
-michael_dawson@ca.ibm.com
-bjaideep@ca.ibm.com
diff --git a/src/debug/s390/debug-s390.cc b/src/debug/s390/debug-s390.cc
index 5ef6a60..7d08e13 100644
--- a/src/debug/s390/debug-s390.cc
+++ b/src/debug/s390/debug-s390.cc
@@ -2,120 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "src/v8.h"
+#include "src/init/v8.h"
#if V8_TARGET_ARCH_S390
#include "src/debug/debug.h"
-#include "src/codegen.h"
+#include "src/codegen/macro-assembler.h"
#include "src/debug/liveedit.h"
+#include "src/execution/frames-inl.h"
namespace v8 {
namespace internal {
#define __ ACCESS_MASM(masm)
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_size;
- __ bind(&check_size);
- // oill r3, 0
- // oill r3, 0
- __ nop(Assembler::DEBUG_BREAK_NOP);
- __ nop(Assembler::DEBUG_BREAK_NOP);
-
- // lr r0, r0 64-bit only
- // lr r0, r0 64-bit only
- // lr r0, r0 64-bit only
- for (int i = 8; i < Assembler::kDebugBreakSlotLength; i += 2) {
- __ nop();
- }
- DCHECK_EQ(Assembler::kDebugBreakSlotLength,
- masm->SizeOfCodeGeneratedSince(&check_size));
-}
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction.
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotLength);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotLength);
- // Patch the code changing the debug break slot code from
- //
- // oill r3, 0
- // oill r3, 0
- // oill r3, 0 64-bit only
- // lr r0, r0 64-bit only
- //
- // to a call to the debug break code, using a FIXED_SEQUENCE.
- //
- // iilf r14, <address> 6-bytes
- // basr r14, r14A 2-bytes
- //
- // The 64bit sequence has an extra iihf.
- //
- // iihf r14, <high 32-bits address> 6-bytes
- // iilf r14, <lower 32-bits address> 6-bytes
- // basr r14, r14 2-bytes
- patcher.masm()->mov(v8::internal::r14,
- Operand(reinterpret_cast<intptr_t>(code->entry())));
- patcher.masm()->basr(v8::internal::r14, v8::internal::r14);
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- Instr current_instr = Assembler::instr_at(pc);
- return !Assembler::IsNop(current_instr, Assembler::DEBUG_BREAK_NOP);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
- {
- FrameScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ push(r2);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ mov(r2, Operand(1));
- __ mov(r3,
- Operand(ExternalReference(
- Runtime::FunctionForId(Runtime::kDebugBreak), masm->isolate())));
-
- CEntryStub ceb(masm->isolate(), 1);
- __ CallStub(&ceb);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; i++) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber r2 if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function.
- if (!(reg.is(r2) && (mode == SAVE_RESULT_REGISTER))) {
- __ mov(reg, Operand(kDebugZapValue));
- }
- }
- }
- // Leave the internal frame.
- }
- __ MaybeDropFrames();
-
- // Return to caller.
- __ Ret();
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -135,17 +36,15 @@
// - Restart the frame by calling the function.
__ LoadRR(fp, r3);
- __ LoadP(r3, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
+ __ LoadP(r3, MemOperand(fp, StandardFrameConstants::kFunctionOffset));
__ LeaveFrame(StackFrame::INTERNAL);
- __ LoadP(r2, FieldMemOperand(r3, JSFunction::kSharedFunctionInfoOffset));
- __ LoadP(
+ __ LoadTaggedPointerField(
+ r2, FieldMemOperand(r3, JSFunction::kSharedFunctionInfoOffset));
+ __ LoadLogicalHalfWordP(
r2, FieldMemOperand(r2, SharedFunctionInfo::kFormalParameterCountOffset));
__ LoadRR(r4, r2);
- ParameterCount dummy1(r4);
- ParameterCount dummy2(r2);
- __ InvokeFunction(r3, dummy1, dummy2, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ __ InvokeFunction(r3, r4, r2, JUMP_FUNCTION);
}
const bool LiveEdit::kFrameDropperSupported = true;
diff --git a/src/debug/wasm/gdb-server/DIR_METADATA b/src/debug/wasm/gdb-server/DIR_METADATA
new file mode 100644
index 0000000..3b428d9
--- /dev/null
+++ b/src/debug/wasm/gdb-server/DIR_METADATA
@@ -0,0 +1,11 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail {
+ component: "Blink>JavaScript>WebAssembly"
+}
\ No newline at end of file
diff --git a/src/debug/wasm/gdb-server/OWNERS b/src/debug/wasm/gdb-server/OWNERS
new file mode 100644
index 0000000..e2c94e8
--- /dev/null
+++ b/src/debug/wasm/gdb-server/OWNERS
@@ -0,0 +1 @@
+paolosev@microsoft.com
diff --git a/src/debug/wasm/gdb-server/gdb-remote-util.cc b/src/debug/wasm/gdb-server/gdb-remote-util.cc
new file mode 100644
index 0000000..b4880ed
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-remote-util.cc
@@ -0,0 +1,104 @@
+// Copyright 2020 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/wasm/gdb-server/gdb-remote-util.h"
+using std::string;
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+// GDB expects lower case values.
+static const char kHexChars[] = "0123456789abcdef";
+
+void UInt8ToHex(uint8_t byte, char chars[2]) {
+ DCHECK(chars);
+ chars[0] = kHexChars[byte >> 4];
+ chars[1] = kHexChars[byte & 0xF];
+}
+
+bool HexToUInt8(const char chars[2], uint8_t* byte) {
+ uint8_t o1, o2;
+ if (NibbleToUInt8(chars[0], &o1) && NibbleToUInt8(chars[1], &o2)) {
+ *byte = (o1 << 4) + o2;
+ return true;
+ }
+
+ return false;
+}
+
+bool NibbleToUInt8(char ch, uint8_t* byte) {
+ DCHECK(byte);
+
+ // Check for nibble of a-f
+ if ((ch >= 'a') && (ch <= 'f')) {
+ *byte = (ch - 'a' + 10);
+ return true;
+ }
+
+ // Check for nibble of A-F
+ if ((ch >= 'A') && (ch <= 'F')) {
+ *byte = (ch - 'A' + 10);
+ return true;
+ }
+
+ // Check for nibble of 0-9
+ if ((ch >= '0') && (ch <= '9')) {
+ *byte = (ch - '0');
+ return true;
+ }
+
+ // Not a valid nibble representation
+ return false;
+}
+
+std::vector<std::string> StringSplit(const string& instr, const char* delim) {
+ std::vector<std::string> result;
+
+ const char* in = instr.data();
+ if (nullptr == in) return result;
+
+ // Check if we have nothing to do
+ if (nullptr == delim) {
+ result.push_back(string(in));
+ return result;
+ }
+
+ while (*in) {
+ // Toss all preceeding delimiters
+ while (*in && strchr(delim, *in)) in++;
+
+ // If we still have something to process
+ if (*in) {
+ const char* start = in;
+ size_t len = 0;
+ // Keep moving forward for all valid chars
+ while (*in && (strchr(delim, *in) == nullptr)) {
+ len++;
+ in++;
+ }
+
+ // Build this token and add it to the array.
+ result.push_back(string{start, len});
+ }
+ }
+ return result;
+}
+
+std::string Mem2Hex(const uint8_t* mem, size_t count) {
+ std::vector<char> result(count * 2 + 1);
+ for (size_t i = 0; i < count; i++) UInt8ToHex(*mem++, &result[i * 2]);
+ result[count * 2] = '\0';
+ return result.data();
+}
+
+std::string Mem2Hex(const std::string& str) {
+ return Mem2Hex(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-remote-util.h b/src/debug/wasm/gdb-server/gdb-remote-util.h
new file mode 100644
index 0000000..88a2715
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-remote-util.h
@@ -0,0 +1,72 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
+
+#include <string>
+#include <vector>
+#include "src/flags/flags.h"
+#include "src/utils/utils.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+#define TRACE_GDB_REMOTE(...) \
+ do { \
+ if (FLAG_trace_wasm_gdb_remote) PrintF("[gdb-remote] " __VA_ARGS__); \
+ } while (false)
+
+// Convert from 0-255 to a pair of ASCII chars (0-9,a-f).
+void UInt8ToHex(uint8_t byte, char chars[2]);
+
+// Convert a pair of hex chars into a value 0-255 or return false if either
+// input character is not a valid nibble.
+bool HexToUInt8(const char chars[2], uint8_t* byte);
+
+// Convert from ASCII (0-9,a-f,A-F) to 4b unsigned or return false if the
+// input char is unexpected.
+bool NibbleToUInt8(char ch, uint8_t* byte);
+
+std::vector<std::string> V8_EXPORT_PRIVATE StringSplit(const std::string& instr,
+ const char* delim);
+
+// Convert the memory pointed to by {mem} into a hex string in GDB-remote
+// format.
+std::string Mem2Hex(const uint8_t* mem, size_t count);
+std::string Mem2Hex(const std::string& str);
+
+// For LLDB debugging, an address in a Wasm module code space is represented
+// with 64 bits, where the first 32 bits identify the module id:
+// +--------------------+--------------------+
+// | module_id | offset |
+// +--------------------+--------------------+
+// <----- 32 bit -----> <----- 32 bit ----->
+class wasm_addr_t {
+ public:
+ wasm_addr_t(uint32_t module_id, uint32_t offset)
+ : module_id_(module_id), offset_(offset) {}
+ explicit wasm_addr_t(uint64_t address)
+ : module_id_(address >> 32), offset_(address & 0xffffffff) {}
+
+ inline uint32_t ModuleId() const { return module_id_; }
+ inline uint32_t Offset() const { return offset_; }
+
+ inline operator uint64_t() const {
+ return static_cast<uint64_t>(module_id_) << 32 | offset_;
+ }
+
+ private:
+ uint32_t module_id_;
+ uint32_t offset_;
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_REMOTE_UTIL_H_
diff --git a/src/debug/wasm/gdb-server/gdb-server-thread.cc b/src/debug/wasm/gdb-server/gdb-server-thread.cc
new file mode 100644
index 0000000..03b9b8f
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server-thread.cc
@@ -0,0 +1,118 @@
+// Copyright 2020 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/wasm/gdb-server/gdb-server-thread.h"
+#include "src/debug/wasm/gdb-server/gdb-server.h"
+#include "src/debug/wasm/gdb-server/session.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+GdbServerThread::GdbServerThread(GdbServer* gdb_server)
+ : Thread(v8::base::Thread::Options("GdbServerThread")),
+ gdb_server_(gdb_server),
+ start_semaphore_(0) {}
+
+bool GdbServerThread::StartAndInitialize() {
+ // Executed in the Isolate thread.
+ if (!Start()) {
+ return false;
+ }
+
+ // We need to make sure that {Stop} is never called before the thread has
+ // completely initialized {transport_} and {target_}. Otherwise there could be
+ // a race condition where in the main thread {Stop} might get called before
+ // the transport is created, and then in the GDBServer thread we may have time
+ // to setup the transport and block on accept() before the main thread blocks
+ // on joining the thread.
+ // The small performance hit caused by this Wait should be negligeable because
+ // this operation happensat most once per process and only when the
+ // --wasm-gdb-remote flag is set.
+ start_semaphore_.Wait();
+ return !!target_;
+}
+
+void GdbServerThread::CleanupThread() {
+ // Executed in the GdbServer thread.
+ v8::base::MutexGuard guard(&mutex_);
+
+ target_ = nullptr;
+ transport_ = nullptr;
+
+#if _WIN32
+ ::WSACleanup();
+#endif
+}
+
+void GdbServerThread::Run() {
+ // Executed in the GdbServer thread.
+#ifdef _WIN32
+ // Initialize Winsock
+ WSADATA wsaData;
+ int iResult = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
+ if (iResult != 0) {
+ TRACE_GDB_REMOTE("GdbServerThread::Run: WSAStartup failed\n");
+ return;
+ }
+#endif
+
+ // If the default port is not available, try any port.
+ SocketBinding socket_binding = SocketBinding::Bind(FLAG_wasm_gdb_remote_port);
+ if (!socket_binding.IsValid()) {
+ socket_binding = SocketBinding::Bind(0);
+ }
+ if (!socket_binding.IsValid()) {
+ TRACE_GDB_REMOTE("GdbServerThread::Run: Failed to bind any TCP port\n");
+ return;
+ }
+ TRACE_GDB_REMOTE("gdb-remote(%d) : Connect GDB with 'target remote :%d\n",
+ __LINE__, socket_binding.GetBoundPort());
+
+ transport_ = socket_binding.CreateTransport();
+ target_ = std::make_unique<Target>(gdb_server_);
+
+ // Here we have completed the initialization, and the thread that called
+ // {StartAndInitialize} may resume execution.
+ start_semaphore_.Signal();
+
+ while (!target_->IsTerminated()) {
+ // Wait for incoming connections.
+ if (!transport_->AcceptConnection()) {
+ continue;
+ }
+
+ // Create a new session for this connection
+ Session session(transport_.get());
+ TRACE_GDB_REMOTE("GdbServerThread: Connected\n");
+
+ // Run this session for as long as it lasts
+ target_->Run(&session);
+ }
+ CleanupThread();
+}
+
+void GdbServerThread::Stop() {
+ // Executed in the Isolate thread.
+
+ // Synchronized, becauses {Stop} might be called while {Run} is still
+ // initializing {transport_} and {target_}. If this happens and the thread is
+ // blocked waiting for an incoming connection or GdbServer for incoming
+ // packets, it will unblocked when {transport_} is closed.
+ v8::base::MutexGuard guard(&mutex_);
+
+ if (target_) {
+ target_->Terminate();
+ }
+
+ if (transport_) {
+ transport_->Close();
+ }
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-server-thread.h b/src/debug/wasm/gdb-server/gdb-server-thread.h
new file mode 100644
index 0000000..cca1e4a
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server-thread.h
@@ -0,0 +1,63 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
+
+#include "src/base/platform/platform.h"
+#include "src/base/platform/semaphore.h"
+#include "src/debug/wasm/gdb-server/target.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class GdbServer;
+
+// class GdbServerThread spawns a thread where all communication with a debugger
+// happens.
+class GdbServerThread : public v8::base::Thread {
+ public:
+ explicit GdbServerThread(GdbServer* gdb_server);
+
+ // base::Thread
+ void Run() override;
+
+ // Starts the GDB-server thread and waits Run() method is called on the new
+ // thread and the initialization completes.
+ bool StartAndInitialize();
+
+ // Stops the GDB-server thread when the V8 process shuts down; gracefully
+ // closes any active debugging session.
+ void Stop();
+
+ Target& GetTarget() { return *target_; }
+
+ private:
+ void CleanupThread();
+
+ GdbServer* gdb_server_;
+
+ // Used to block the caller on StartAndInitialize() waiting for the new thread
+ // to have completed its initialization.
+ // (Note that Thread::StartSynchronously() wouldn't work in this case because
+ // it returns as soon as the new thread starts, but before Run() is called).
+ base::Semaphore start_semaphore_;
+
+ base::Mutex mutex_;
+ // Protected by {mutex_}:
+ std::unique_ptr<TransportBase> transport_;
+ std::unique_ptr<Target> target_;
+
+ DISALLOW_COPY_AND_ASSIGN(GdbServerThread);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_THREAD_H_
diff --git a/src/debug/wasm/gdb-server/gdb-server.cc b/src/debug/wasm/gdb-server/gdb-server.cc
new file mode 100644
index 0000000..96e2308
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server.cc
@@ -0,0 +1,424 @@
+// Copyright 2020 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/wasm/gdb-server/gdb-server.h"
+
+#include <inttypes.h>
+#include <functional>
+#include "src/api/api-inl.h"
+#include "src/api/api.h"
+#include "src/debug/debug.h"
+#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
+#include "src/utils/locked-queue-inl.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+static const uint32_t kMaxWasmCallStack = 20;
+
+// A TaskRunner is an object that runs posted tasks (in the form of closure
+// objects). Tasks are queued and run, in order, in the thread where the
+// TaskRunner::RunMessageLoop() is called.
+class TaskRunner {
+ public:
+ // Class Task wraps a std::function with a semaphore to signal its completion.
+ // This logic would be neatly implemented with std::packaged_tasks but we
+ // cannot use <future> in V8.
+ class Task {
+ public:
+ Task(base::Semaphore* ready_semaphore, std::function<void()> func)
+ : ready_semaphore_(ready_semaphore), func_(func) {}
+
+ void Run() {
+ func_();
+ ready_semaphore_->Signal();
+ }
+
+ // A semaphore object passed by the thread that posts a task.
+ // The sender can Wait on this semaphore to block until the task has
+ // completed execution in the TaskRunner thread.
+ base::Semaphore* ready_semaphore_;
+
+ // The function to run.
+ std::function<void()> func_;
+ };
+
+ TaskRunner()
+ : process_queue_semaphore_(0),
+ nested_loop_count_(0),
+ is_terminated_(false) {}
+
+ // Starts the task runner. All tasks posted are run, in order, in the thread
+ // that calls this function.
+ void Run() {
+ is_terminated_ = false;
+ int loop_number = ++nested_loop_count_;
+ while (nested_loop_count_ == loop_number && !is_terminated_) {
+ std::shared_ptr<Task> task = GetNext();
+ if (task) {
+ task->Run();
+ }
+ }
+ }
+
+ // Terminates the task runner. Tasks that are still pending in the queue are
+ // not discarded and will be executed when the task runner is restarted.
+ void Terminate() {
+ DCHECK_LT(0, nested_loop_count_);
+ --nested_loop_count_;
+
+ is_terminated_ = true;
+ process_queue_semaphore_.Signal();
+ }
+
+ // Posts a task to the task runner, to be executed in the task runner thread.
+ template <typename Functor>
+ auto Append(base::Semaphore* ready_semaphore, Functor&& task) {
+ queue_.Enqueue(std::make_shared<Task>(ready_semaphore, task));
+ process_queue_semaphore_.Signal();
+ }
+
+ private:
+ std::shared_ptr<Task> GetNext() {
+ while (!is_terminated_) {
+ if (queue_.IsEmpty()) {
+ process_queue_semaphore_.Wait();
+ }
+
+ std::shared_ptr<Task> task;
+ if (queue_.Dequeue(&task)) {
+ return task;
+ }
+ }
+ return nullptr;
+ }
+
+ LockedQueue<std::shared_ptr<Task>> queue_;
+ v8::base::Semaphore process_queue_semaphore_;
+ int nested_loop_count_;
+ std::atomic<bool> is_terminated_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskRunner);
+};
+
+GdbServer::GdbServer() { task_runner_ = std::make_unique<TaskRunner>(); }
+
+template <typename Functor>
+auto GdbServer::RunSyncTask(Functor&& callback) const {
+ // Executed in the GDBServerThread.
+ v8::base::Semaphore ready_semaphore(0);
+ task_runner_->Append(&ready_semaphore, callback);
+ ready_semaphore.Wait();
+}
+
+// static
+std::unique_ptr<GdbServer> GdbServer::Create() {
+ DCHECK(FLAG_wasm_gdb_remote);
+
+ std::unique_ptr<GdbServer> gdb_server(new GdbServer());
+
+ // Spawns the GDB-stub thread where all the communication with the debugger
+ // happens.
+ gdb_server->thread_ = std::make_unique<GdbServerThread>(gdb_server.get());
+ if (!gdb_server->thread_->StartAndInitialize()) {
+ TRACE_GDB_REMOTE(
+ "Cannot initialize thread, GDB-remote debugging will be disabled.\n");
+ return nullptr;
+ }
+ return gdb_server;
+}
+
+GdbServer::~GdbServer() {
+ // All Isolates have been deregistered.
+ DCHECK(isolate_delegates_.empty());
+
+ if (thread_) {
+ // Waits for the GDB-stub thread to terminate.
+ thread_->Stop();
+ thread_->Join();
+ }
+}
+
+void GdbServer::RunMessageLoopOnPause() { task_runner_->Run(); }
+
+void GdbServer::QuitMessageLoopOnPause() { task_runner_->Terminate(); }
+
+std::vector<GdbServer::WasmModuleInfo> GdbServer::GetLoadedModules() {
+ // Executed in the GDBServerThread.
+ std::vector<GdbServer::WasmModuleInfo> modules;
+
+ RunSyncTask([this, &modules]() {
+ // Executed in the isolate thread.
+ for (const auto& pair : scripts_) {
+ uint32_t module_id = pair.first;
+ const WasmModuleDebug& module_debug = pair.second;
+ modules.push_back({module_id, module_debug.GetModuleName()});
+ }
+ });
+ return modules;
+}
+
+bool GdbServer::GetModuleDebugHandler(uint32_t module_id,
+ WasmModuleDebug** wasm_module_debug) {
+ // Always executed in the isolate thread.
+ ScriptsMap::iterator scriptIterator = scripts_.find(module_id);
+ if (scriptIterator != scripts_.end()) {
+ *wasm_module_debug = &scriptIterator->second;
+ return true;
+ }
+ wasm_module_debug = nullptr;
+ return false;
+}
+
+bool GdbServer::GetWasmGlobal(uint32_t frame_index, uint32_t index,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
+ // Executed in the isolate thread.
+ result = WasmModuleDebug::GetWasmGlobal(GetTarget().GetCurrentIsolate(),
+ frame_index, index, buffer,
+ buffer_size, size);
+ });
+ return result;
+}
+
+bool GdbServer::GetWasmLocal(uint32_t frame_index, uint32_t index,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
+ // Executed in the isolate thread.
+ result = WasmModuleDebug::GetWasmLocal(GetTarget().GetCurrentIsolate(),
+ frame_index, index, buffer,
+ buffer_size, size);
+ });
+ return result;
+}
+
+bool GdbServer::GetWasmStackValue(uint32_t frame_index, uint32_t index,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, frame_index, index, buffer, buffer_size, size]() {
+ // Executed in the isolate thread.
+ result = WasmModuleDebug::GetWasmStackValue(GetTarget().GetCurrentIsolate(),
+ frame_index, index, buffer,
+ buffer_size, size);
+ });
+ return result;
+}
+
+uint32_t GdbServer::GetWasmMemory(uint32_t frame_index, uint32_t offset,
+ uint8_t* buffer, uint32_t size) {
+ // Executed in the GDBServerThread.
+ uint32_t bytes_read = 0;
+ RunSyncTask([this, &bytes_read, frame_index, offset, buffer, size]() {
+ // Executed in the isolate thread.
+ bytes_read = WasmModuleDebug::GetWasmMemory(
+ GetTarget().GetCurrentIsolate(), frame_index, offset, buffer, size);
+ });
+ return bytes_read;
+}
+
+uint32_t GdbServer::GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
+ uint32_t size) {
+ // Executed in the GDBServerThread.
+ uint32_t bytes_read = 0;
+ RunSyncTask([this, &bytes_read, wasm_addr, buffer, size]() {
+ // Executed in the isolate thread.
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(wasm_addr.ModuleId(), &module_debug)) {
+ bytes_read = module_debug->GetWasmModuleBytes(wasm_addr, buffer, size);
+ }
+ });
+ return bytes_read;
+}
+
+bool GdbServer::AddBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, wasm_module_id, offset]() {
+ // Executed in the isolate thread.
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
+ int breakpoint_id = 0;
+ if (module_debug->AddBreakpoint(offset, &breakpoint_id)) {
+ breakpoints_[wasm_addr_t(wasm_module_id, offset)] = breakpoint_id;
+ result = true;
+ }
+ }
+ });
+ return result;
+}
+
+bool GdbServer::RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset) {
+ // Executed in the GDBServerThread.
+ bool result = false;
+ RunSyncTask([this, &result, wasm_module_id, offset]() {
+ // Executed in the isolate thread.
+ BreakpointsMap::iterator it =
+ breakpoints_.find(wasm_addr_t(wasm_module_id, offset));
+ if (it != breakpoints_.end()) {
+ int breakpoint_id = it->second;
+ breakpoints_.erase(it);
+
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(wasm_module_id, &module_debug)) {
+ module_debug->RemoveBreakpoint(offset, breakpoint_id);
+ result = true;
+ }
+ }
+ });
+ return result;
+}
+
+std::vector<wasm_addr_t> GdbServer::GetWasmCallStack() const {
+ // Executed in the GDBServerThread.
+ std::vector<wasm_addr_t> result;
+ RunSyncTask([this, &result]() {
+ // Executed in the isolate thread.
+ result = GetTarget().GetCallStack();
+ });
+ return result;
+}
+
+void GdbServer::AddIsolate(Isolate* isolate) {
+ // Executed in the isolate thread.
+ if (isolate_delegates_.find(isolate) == isolate_delegates_.end()) {
+ isolate_delegates_[isolate] =
+ std::make_unique<DebugDelegate>(isolate, this);
+ }
+}
+
+void GdbServer::RemoveIsolate(Isolate* isolate) {
+ // Executed in the isolate thread.
+ auto it = isolate_delegates_.find(isolate);
+ if (it != isolate_delegates_.end()) {
+ for (auto it = scripts_.begin(); it != scripts_.end();) {
+ if (it->second.GetIsolate() == isolate) {
+ it = scripts_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ isolate_delegates_.erase(it);
+ }
+}
+
+void GdbServer::Suspend() {
+ // Executed in the GDBServerThread.
+ auto it = isolate_delegates_.begin();
+ if (it != isolate_delegates_.end()) {
+ Isolate* isolate = it->first;
+ v8::Isolate* v8Isolate = (v8::Isolate*)isolate;
+ v8Isolate->RequestInterrupt(
+ // Executed in the isolate thread.
+ [](v8::Isolate* isolate, void*) {
+ if (v8::debug::AllFramesOnStackAreBlackboxed(isolate)) {
+ v8::debug::SetBreakOnNextFunctionCall(isolate);
+ } else {
+ v8::debug::BreakRightNow(isolate);
+ }
+ },
+ this);
+ }
+}
+
+void GdbServer::PrepareStep() {
+ // Executed in the GDBServerThread.
+ wasm_addr_t pc = GetTarget().GetCurrentPc();
+ RunSyncTask([this, pc]() {
+ // Executed in the isolate thread.
+ WasmModuleDebug* module_debug;
+ if (GetModuleDebugHandler(pc.ModuleId(), &module_debug)) {
+ module_debug->PrepareStep();
+ }
+ });
+}
+
+void GdbServer::AddWasmModule(uint32_t module_id,
+ Local<debug::WasmScript> wasm_script) {
+ // Executed in the isolate thread.
+ DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type());
+ v8::Isolate* isolate = wasm_script->GetIsolate();
+ scripts_.insert(
+ std::make_pair(module_id, WasmModuleDebug(isolate, wasm_script)));
+
+ if (FLAG_wasm_pause_waiting_for_debugger && scripts_.size() == 1) {
+ TRACE_GDB_REMOTE("Paused, waiting for a debugger to attach...\n");
+ Suspend();
+ }
+}
+
+Target& GdbServer::GetTarget() const { return thread_->GetTarget(); }
+
+// static
+std::atomic<uint32_t> GdbServer::DebugDelegate::id_s;
+
+GdbServer::DebugDelegate::DebugDelegate(Isolate* isolate, GdbServer* gdb_server)
+ : isolate_(isolate), id_(id_s++), gdb_server_(gdb_server) {
+ isolate_->SetCaptureStackTraceForUncaughtExceptions(
+ true, kMaxWasmCallStack, v8::StackTrace::kOverview);
+
+ // Register the delegate
+ isolate_->debug()->SetDebugDelegate(this);
+ v8::debug::TierDownAllModulesPerIsolate((v8::Isolate*)isolate_);
+ v8::debug::ChangeBreakOnException((v8::Isolate*)isolate_,
+ v8::debug::BreakOnUncaughtException);
+}
+
+GdbServer::DebugDelegate::~DebugDelegate() {
+ // Deregister the delegate
+ isolate_->debug()->SetDebugDelegate(nullptr);
+}
+
+void GdbServer::DebugDelegate::ScriptCompiled(Local<debug::Script> script,
+ bool is_live_edited,
+ bool has_compile_error) {
+ // Executed in the isolate thread.
+ if (script->IsWasm()) {
+ DCHECK_EQ(reinterpret_cast<v8::Isolate*>(isolate_), script->GetIsolate());
+ gdb_server_->AddWasmModule(GetModuleId(script->Id()),
+ script.As<debug::WasmScript>());
+ }
+}
+
+void GdbServer::DebugDelegate::BreakProgramRequested(
+ // Executed in the isolate thread.
+ Local<v8::Context> paused_context,
+ const std::vector<debug::BreakpointId>& inspector_break_points_hit) {
+ gdb_server_->GetTarget().OnProgramBreak(
+ isolate_, WasmModuleDebug::GetCallStack(id_, isolate_));
+ gdb_server_->RunMessageLoopOnPause();
+}
+
+void GdbServer::DebugDelegate::ExceptionThrown(
+ // Executed in the isolate thread.
+ Local<v8::Context> paused_context, Local<Value> exception,
+ Local<Value> promise, bool is_uncaught,
+ debug::ExceptionType exception_type) {
+ if (exception_type == v8::debug::kException && is_uncaught) {
+ gdb_server_->GetTarget().OnException(
+ isolate_, WasmModuleDebug::GetCallStack(id_, isolate_));
+ gdb_server_->RunMessageLoopOnPause();
+ }
+}
+
+bool GdbServer::DebugDelegate::IsFunctionBlackboxed(
+ // Executed in the isolate thread.
+ Local<debug::Script> script, const debug::Location& start,
+ const debug::Location& end) {
+ return false;
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/gdb-server.h b/src/debug/wasm/gdb-server/gdb-server.h
new file mode 100644
index 0000000..50939af
--- /dev/null
+++ b/src/debug/wasm/gdb-server/gdb-server.h
@@ -0,0 +1,201 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
+#define V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
+
+#include <map>
+#include <memory>
+#include "src/debug/wasm/gdb-server/gdb-server-thread.h"
+#include "src/debug/wasm/gdb-server/wasm-module-debug.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class TaskRunner;
+
+// class GdbServer acts as a manager for the GDB-remote stub. It is instantiated
+// as soon as the first Wasm module is loaded in the Wasm engine and spawns a
+// separate thread to accept connections and exchange messages with a debugger.
+// It will contain the logic to serve debugger queries and access the state of
+// the Wasm engine.
+class GdbServer {
+ public:
+ // Factory method: creates and returns a GdbServer. Spawns a "GDB-remote"
+ // thread that will be used to communicate with the debugger.
+ // May return null on failure.
+ // This should be called once, the first time a Wasm module is loaded in the
+ // Wasm engine.
+ static std::unique_ptr<GdbServer> Create();
+
+ // Stops the "GDB-remote" thread and waits for it to complete. This should be
+ // called once, when the Wasm engine shuts down.
+ ~GdbServer();
+
+ // Queries the set of the Wasm modules currently loaded. Each module is
+ // identified by a unique integer module id.
+ struct WasmModuleInfo {
+ uint32_t module_id;
+ std::string module_name;
+ };
+ std::vector<WasmModuleInfo> GetLoadedModules();
+
+ // Queries the value of the {index} global value in the Wasm module identified
+ // by {frame_index}.
+ //
+ bool GetWasmGlobal(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Queries the value of the {index} local value in the {frame_index}th stack
+ // frame in the Wasm module identified by {frame_index}.
+ //
+ bool GetWasmLocal(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Queries the value of the {index} value in the operand stack.
+ //
+ bool GetWasmStackValue(uint32_t frame_index, uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Reads {size} bytes, starting from {offset}, from the Memory instance
+ // associated to the Wasm module identified by {frame_index}.
+ // Returns the number of bytes copied to {buffer}, or 0 is case of error.
+ // Note: only one Memory for Module is currently supported.
+ //
+ uint32_t GetWasmMemory(uint32_t frame_index, uint32_t offset, uint8_t* buffer,
+ uint32_t size);
+
+ // Reads {size} bytes, starting from the low dword of {address}, from the Code
+ // space of th Wasm module identified by high dword of {address}.
+ // Returns the number of bytes copied to {buffer}, or 0 is case of error.
+ uint32_t GetWasmModuleBytes(wasm_addr_t address, uint8_t* buffer,
+ uint32_t size);
+
+ // Inserts a breakpoint at the offset {offset} of the Wasm module identified
+ // by {wasm_module_id}.
+ // Returns true if the breakpoint was successfully added.
+ bool AddBreakpoint(uint32_t wasm_module_id, uint32_t offset);
+
+ // Removes a breakpoint at the offset {offset} of the Wasm module identified
+ // by {wasm_module_id}.
+ // Returns true if the breakpoint was successfully removed.
+ bool RemoveBreakpoint(uint32_t wasm_module_id, uint32_t offset);
+
+ // Returns the current call stack as a vector of program counters.
+ std::vector<wasm_addr_t> GetWasmCallStack() const;
+
+ // Manage the set of Isolates for this GdbServer.
+ void AddIsolate(Isolate* isolate);
+ void RemoveIsolate(Isolate* isolate);
+
+ // Requests that the thread suspend execution at the next Wasm instruction.
+ void Suspend();
+
+ // Handle stepping in wasm functions via the wasm interpreter.
+ void PrepareStep();
+
+ // Called when the target debuggee can resume execution (for example after
+ // having been suspended on a breakpoint). Terminates the task runner leaving
+ // all pending tasks in the queue.
+ void QuitMessageLoopOnPause();
+
+ private:
+ GdbServer();
+
+ // When the target debuggee is suspended for a breakpoint or exception, blocks
+ // the main (isolate) thread and enters in a message loop. Here it waits on a
+ // queue of Task objects that are posted by the GDB-stub thread and that
+ // represent queries received from the debugger via the GDB-remote protocol.
+ void RunMessageLoopOnPause();
+
+ // Post a task to run a callback in the isolate thread.
+ template <typename Callback>
+ auto RunSyncTask(Callback&& callback) const;
+
+ void AddWasmModule(uint32_t module_id, Local<debug::WasmScript> wasm_script);
+
+ // Given a Wasm module id, retrieves the corresponding debugging WasmScript
+ // object.
+ bool GetModuleDebugHandler(uint32_t module_id,
+ WasmModuleDebug** wasm_module_debug);
+
+ // Returns the debugging target.
+ Target& GetTarget() const;
+
+ // Class DebugDelegate implements the debug::DebugDelegate interface to
+ // receive notifications when debug events happen in a given isolate, like a
+ // script being loaded, a breakpoint being hit, an exception being thrown.
+ class DebugDelegate : public debug::DebugDelegate {
+ public:
+ DebugDelegate(Isolate* isolate, GdbServer* gdb_server);
+ ~DebugDelegate();
+
+ // debug::DebugDelegate
+ void ScriptCompiled(Local<debug::Script> script, bool is_live_edited,
+ bool has_compile_error) override;
+ void BreakProgramRequested(Local<v8::Context> paused_context,
+ const std::vector<debug::BreakpointId>&
+ inspector_break_points_hit) override;
+ void ExceptionThrown(Local<v8::Context> paused_context,
+ Local<Value> exception, Local<Value> promise,
+ bool is_uncaught,
+ debug::ExceptionType exception_type) override;
+ bool IsFunctionBlackboxed(Local<debug::Script> script,
+ const debug::Location& start,
+ const debug::Location& end) override;
+
+ private:
+ // Calculates module_id as:
+ // +--------------------+------------------- +
+ // | DebugDelegate::id_ | Script::Id() |
+ // +--------------------+------------------- +
+ // <----- 16 bit -----> <----- 16 bit ----->
+ uint32_t GetModuleId(uint32_t script_id) const {
+ DCHECK_LT(script_id, 0x10000);
+ DCHECK_LT(id_, 0x10000);
+ return id_ << 16 | script_id;
+ }
+
+ Isolate* isolate_;
+ uint32_t id_;
+ GdbServer* gdb_server_;
+
+ static std::atomic<uint32_t> id_s;
+ };
+
+ // The GDB-stub thread where all the communication with the debugger happens.
+ std::unique_ptr<GdbServerThread> thread_;
+
+ // Used to transform the queries that arrive in the GDB-stub thread into
+ // tasks executed in the main (isolate) thread.
+ std::unique_ptr<TaskRunner> task_runner_;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Always accessed in the isolate thread.
+
+ // Set of breakpoints currently defines in Wasm code.
+ typedef std::map<uint64_t, int> BreakpointsMap;
+ BreakpointsMap breakpoints_;
+
+ typedef std::map<uint32_t, WasmModuleDebug> ScriptsMap;
+ ScriptsMap scripts_;
+
+ typedef std::map<Isolate*, std::unique_ptr<DebugDelegate>>
+ IsolateDebugDelegateMap;
+ IsolateDebugDelegateMap isolate_delegates_;
+
+ // End of fields always accessed in the isolate thread.
+ //////////////////////////////////////////////////////////////////////////////
+
+ DISALLOW_COPY_AND_ASSIGN(GdbServer);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_GDB_SERVER_H_
diff --git a/src/debug/wasm/gdb-server/packet.cc b/src/debug/wasm/gdb-server/packet.cc
new file mode 100644
index 0000000..f8306c4
--- /dev/null
+++ b/src/debug/wasm/gdb-server/packet.cc
@@ -0,0 +1,364 @@
+// Copyright 2020 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/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+Packet::Packet() {
+ seq_ = -1;
+ Clear();
+}
+
+void Packet::Clear() {
+ data_.clear();
+ read_index_ = 0;
+}
+
+void Packet::Rewind() { read_index_ = 0; }
+
+bool Packet::EndOfPacket() const { return (read_index_ >= GetPayloadSize()); }
+
+void Packet::AddRawChar(char ch) { data_.push_back(ch); }
+
+void Packet::AddWord8(uint8_t byte) {
+ char seq[2];
+ UInt8ToHex(byte, seq);
+ AddRawChar(seq[0]);
+ AddRawChar(seq[1]);
+}
+
+void Packet::AddBlock(const void* ptr, uint32_t len) {
+ DCHECK(ptr);
+
+ const char* p = (const char*)ptr;
+
+ for (uint32_t offs = 0; offs < len; offs++) {
+ AddWord8(p[offs]);
+ }
+}
+
+void Packet::AddString(const char* str) {
+ DCHECK(str);
+
+ while (*str) {
+ AddRawChar(*str);
+ str++;
+ }
+}
+
+void Packet::AddHexString(const char* str) {
+ DCHECK(str);
+
+ while (*str) {
+ AddWord8(*str);
+ str++;
+ }
+}
+
+void Packet::AddNumberSep(uint64_t val, char sep) {
+ char out[sizeof(val) * 2];
+ char temp[2];
+
+ // Check for -1 optimization
+ if (val == static_cast<uint64_t>(-1)) {
+ AddRawChar('-');
+ AddRawChar('1');
+ } else {
+ int nibbles = 0;
+
+ // In the GDB remote protocol numbers are formatted as big-endian hex
+ // strings. Leading zeros can be skipped.
+ // For example the value 0x00001234 is formatted as "1234".
+ for (size_t a = 0; a < sizeof(val); a++) {
+ uint8_t byte = static_cast<uint8_t>(val & 0xFF);
+
+ // Stream in with bytes reversed, starting with the least significant.
+ // So if we have the value 0x00001234, we store 4, then 3, 2, 1.
+ // Note that the characters are later reversed to be in big-endian order.
+ UInt8ToHex(byte, temp);
+ out[nibbles++] = temp[1];
+ out[nibbles++] = temp[0];
+
+ // Get the next 8 bits;
+ val >>= 8;
+
+ // Suppress leading zeros, so we are done when val hits zero
+ if (val == 0) {
+ break;
+ }
+ }
+
+ // Strip the high zero for this byte if present.
+ if ((nibbles > 1) && (out[nibbles - 1] == '0')) nibbles--;
+
+ // Now write it out reverse to correct the order
+ while (nibbles) {
+ nibbles--;
+ AddRawChar(out[nibbles]);
+ }
+ }
+
+ // If we asked for a separator, insert it
+ if (sep) AddRawChar(sep);
+}
+
+bool Packet::GetNumberSep(uint64_t* val, char* sep) {
+ uint64_t out = 0;
+ char ch;
+ if (!GetRawChar(&ch)) {
+ return false;
+ }
+
+ // Numbers are formatted as a big-endian hex strings.
+ // The literals "0" and "-1" as special cases.
+
+ // Check for -1
+ if (ch == '-') {
+ if (!GetRawChar(&ch)) {
+ return false;
+ }
+
+ if (ch == '1') {
+ *val = (uint64_t)-1;
+
+ ch = 0;
+ GetRawChar(&ch);
+ if (sep) {
+ *sep = ch;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ do {
+ uint8_t nib;
+
+ // Check for separator
+ if (!NibbleToUInt8(ch, &nib)) {
+ break;
+ }
+
+ // Add this nibble.
+ out = (out << 4) + nib;
+
+ // Get the next character (if availible)
+ ch = 0;
+ if (!GetRawChar(&ch)) {
+ break;
+ }
+ } while (1);
+
+ // Set the value;
+ *val = out;
+
+ // Add the separator if the user wants it...
+ if (sep != nullptr) *sep = ch;
+
+ return true;
+}
+
+bool Packet::GetRawChar(char* ch) {
+ DCHECK(ch != nullptr);
+
+ if (read_index_ >= GetPayloadSize()) return false;
+
+ *ch = data_[read_index_++];
+
+ // Check for RLE X*N, where X is the value, N is the reps.
+ if (*ch == '*') {
+ if (read_index_ < 2) {
+ TRACE_GDB_REMOTE("Unexpected RLE at start of packet.\n");
+ return false;
+ }
+
+ if (read_index_ >= GetPayloadSize()) {
+ TRACE_GDB_REMOTE("Unexpected EoP during RLE.\n");
+ return false;
+ }
+
+ // GDB does not use "CTRL" characters in the stream, so the
+ // number of reps is encoded as the ASCII value beyond 28
+ // (which when you add a min rep size of 4, forces the rep
+ // character to be ' ' (32) or greater).
+ int32_t cnt = (data_[read_index_] - 28);
+ if (cnt < 3) {
+ TRACE_GDB_REMOTE("Unexpected RLE length.\n");
+ return false;
+ }
+
+ // We have just read '*' and incremented the read pointer,
+ // so here is the old state, and expected new state.
+ //
+ // Assume N = 5, we grow by N - size of encoding (3).
+ //
+ // OldP: R W
+ // OldD: 012X*N89 = 8 chars
+ // Size: 012X*N89__ = 10 chars
+ // Move: 012X*__N89 = 10 chars
+ // Fill: 012XXXXX89 = 10 chars
+ // NewP: R W (shifted 5 - 3)
+
+ // First, store the remaining characters to the right into a temp string.
+ std::string right = data_.substr(read_index_ + 1);
+ // Discard the '*' we just read
+ data_.erase(read_index_ - 1);
+ // Append (N-1) 'X' chars
+ *ch = data_[read_index_ - 2];
+ data_.append(cnt - 1, *ch);
+ // Finally, append the remaining characters
+ data_.append(right);
+ }
+ return true;
+}
+
+bool Packet::GetWord8(uint8_t* value) {
+ DCHECK(value);
+
+ // Get two ASCII hex values and convert them to ints
+ char seq[2];
+ if (!GetRawChar(&seq[0]) || !GetRawChar(&seq[1])) {
+ return false;
+ }
+ return HexToUInt8(seq, value);
+}
+
+bool Packet::GetBlock(void* ptr, uint32_t len) {
+ DCHECK(ptr);
+
+ uint8_t* p = reinterpret_cast<uint8_t*>(ptr);
+ bool res = true;
+
+ for (uint32_t offs = 0; offs < len; offs++) {
+ res = GetWord8(&p[offs]);
+ if (false == res) {
+ break;
+ }
+ }
+
+ return res;
+}
+
+bool Packet::GetString(std::string* str) {
+ if (EndOfPacket()) {
+ return false;
+ }
+
+ *str = data_.substr(read_index_);
+ read_index_ = GetPayloadSize();
+ return true;
+}
+
+bool Packet::GetHexString(std::string* str) {
+ // Decode a string encoded as a series of 2-hex digit pairs.
+
+ if (EndOfPacket()) {
+ return false;
+ }
+
+ // Pull values until we hit a separator
+ str->clear();
+ char ch1;
+ while (GetRawChar(&ch1)) {
+ uint8_t nib1;
+ if (!NibbleToUInt8(ch1, &nib1)) {
+ read_index_--;
+ break;
+ }
+ char ch2;
+ uint8_t nib2;
+ if (!GetRawChar(&ch2) || !NibbleToUInt8(ch2, &nib2)) {
+ return false;
+ }
+ *str += static_cast<char>((nib1 << 4) + nib2);
+ }
+ return true;
+}
+
+const char* Packet::GetPayload() const { return data_.c_str(); }
+
+size_t Packet::GetPayloadSize() const { return data_.size(); }
+
+bool Packet::GetSequence(int32_t* ch) const {
+ DCHECK(ch);
+
+ if (seq_ != -1) {
+ *ch = seq_;
+ return true;
+ }
+
+ return false;
+}
+
+void Packet::ParseSequence() {
+ size_t saved_read_index = read_index_;
+ unsigned char seq;
+ char ch;
+ if (GetWord8(&seq) && GetRawChar(&ch)) {
+ if (ch == ':') {
+ SetSequence(seq);
+ return;
+ }
+ }
+ // No sequence number present, so reset to original position.
+ read_index_ = saved_read_index;
+}
+
+void Packet::SetSequence(int32_t val) { seq_ = val; }
+
+void Packet::SetError(ErrDef error) {
+ Clear();
+ AddRawChar('E');
+ AddWord8(static_cast<uint8_t>(error));
+}
+
+std::string Packet::GetPacketData() const {
+ char chars[2];
+ const char* ptr = GetPayload();
+ size_t size = GetPayloadSize();
+
+ std::stringstream outstr;
+
+ // Signal start of response
+ outstr << '$';
+
+ char run_xsum = 0;
+
+ // If there is a sequence, send as two nibble 8bit value + ':'
+ int32_t seq;
+ if (GetSequence(&seq)) {
+ UInt8ToHex(seq, chars);
+ outstr << chars[0];
+ run_xsum += chars[0];
+ outstr << chars[1];
+ run_xsum += chars[1];
+
+ outstr << ':';
+ run_xsum += ':';
+ }
+
+ // Send the main payload
+ for (size_t offs = 0; offs < size; ++offs) {
+ outstr << ptr[offs];
+ run_xsum += ptr[offs];
+ }
+
+ // Send XSUM as two nibble 8bit value preceeded by '#'
+ outstr << '#';
+ UInt8ToHex(run_xsum, chars);
+ outstr << chars[0];
+ outstr << chars[1];
+
+ return outstr.str();
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/packet.h b/src/debug/wasm/gdb-server/packet.h
new file mode 100644
index 0000000..4308081
--- /dev/null
+++ b/src/debug/wasm/gdb-server/packet.h
@@ -0,0 +1,105 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
+#define V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
+
+#include <string>
+#include <vector>
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class V8_EXPORT_PRIVATE Packet {
+ public:
+ Packet();
+
+ // Empty the vector and reset the read/write pointers.
+ void Clear();
+
+ // Reset the read pointer, allowing the packet to be re-read.
+ void Rewind();
+
+ // Return true of the read pointer has reached the write pointer.
+ bool EndOfPacket() const;
+
+ // Store a single raw 8 bit value
+ void AddRawChar(char ch);
+
+ // Store a block of data as hex pairs per byte
+ void AddBlock(const void* ptr, uint32_t len);
+
+ // Store a byte as a 2 chars block.
+ void AddWord8(uint8_t val);
+
+ // Store a number up to 64 bits, formatted as a big-endian hex string with
+ // preceeding zeros removed. Since zeros can be removed, the width of this
+ // number is unknown, and the number is always followed by a NULL or a
+ // separator (non hex digit).
+ void AddNumberSep(uint64_t val, char sep);
+
+ // Add a raw string.
+ void AddString(const char* str);
+
+ // Add a string stored as a stream of ASCII hex digit pairs. It is safe
+ // to use any non-null character in this stream. If this does not terminate
+ // the packet, there should be a separator (non hex digit) immediately
+ // following.
+ void AddHexString(const char* str);
+
+ // Retrieve a single character if available
+ bool GetRawChar(char* ch);
+
+ // Retrieve "len" ASCII character pairs.
+ bool GetBlock(void* ptr, uint32_t len);
+
+ // Retrieve a 8, 16, 32, or 64 bit word as pairs of hex digits. These
+ // functions will always consume bits/4 characters from the stream.
+ bool GetWord8(uint8_t* val);
+
+ // Retrieve a number (formatted as a big-endian hex string) and a separator.
+ // If 'sep' is null, the separator is consumed but thrown away.
+ bool GetNumberSep(uint64_t* val, char* sep);
+
+ // Get a string from the stream
+ bool GetString(std::string* str);
+ bool GetHexString(std::string* str);
+
+ // Return a pointer to the entire packet payload
+ const char* GetPayload() const;
+ size_t GetPayloadSize() const;
+
+ // Returns true and the sequence number, or false if it is unset.
+ bool GetSequence(int32_t* seq) const;
+
+ // Parses sequence number in package data and moves read pointer past it.
+ void ParseSequence();
+
+ // Set the sequence number.
+ void SetSequence(int32_t seq);
+
+ enum class ErrDef { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
+ void SetError(ErrDef);
+
+ // Returns the full content of a GDB-remote packet, in the format:
+ // $payload#checksum
+ // where the two-digit checksum is computed as the modulo 256 sum of all
+ // characters between the leading ‘$’ and the trailing ‘#’.
+ std::string GetPacketData() const;
+
+ private:
+ int32_t seq_;
+ std::string data_;
+ size_t read_index_;
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_PACKET_H_
diff --git a/src/debug/wasm/gdb-server/session.cc b/src/debug/wasm/gdb-server/session.cc
new file mode 100644
index 0000000..b052934
--- /dev/null
+++ b/src/debug/wasm/gdb-server/session.cc
@@ -0,0 +1,148 @@
+// Copyright 2020 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/wasm/gdb-server/session.h"
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+Session::Session(TransportBase* transport)
+ : io_(transport), connected_(true), ack_enabled_(true) {}
+
+void Session::WaitForDebugStubEvent() { io_->WaitForDebugStubEvent(); }
+
+bool Session::SignalThreadEvent() { return io_->SignalThreadEvent(); }
+
+bool Session::IsDataAvailable() const { return io_->IsDataAvailable(); }
+
+bool Session::IsConnected() const { return connected_; }
+
+void Session::Disconnect() {
+ io_->Disconnect();
+ connected_ = false;
+}
+
+bool Session::GetChar(char* ch) {
+ if (!io_->Read(ch, 1)) {
+ Disconnect();
+ return false;
+ }
+
+ return true;
+}
+
+bool Session::SendPacket(Packet* pkt, bool expect_ack) {
+ char ch;
+ do {
+ std::string data = pkt->GetPacketData();
+
+ TRACE_GDB_REMOTE("TX %s\n", data.size() < 160
+ ? data.c_str()
+ : (data.substr(0, 160) + "...").c_str());
+ if (!io_->Write(data.data(), static_cast<int32_t>(data.length()))) {
+ return false;
+ }
+
+ // If ACKs are off, we are done.
+ if (!expect_ack || !ack_enabled_) {
+ break;
+ }
+
+ // Otherwise, poll for '+'
+ if (!GetChar(&ch)) {
+ return false;
+ }
+
+ // Retry if we didn't get a '+'
+ } while (ch != '+');
+
+ return true;
+}
+
+bool Session::GetPayload(Packet* pkt, uint8_t* checksum) {
+ pkt->Clear();
+ *checksum = 0;
+
+ // Stream in the characters
+ char ch;
+ while (GetChar(&ch)) {
+ if (ch == '#') {
+ // If we see a '#' we must be done with the data.
+ return true;
+ } else if (ch == '$') {
+ // If we see a '$' we must have missed the last cmd, let's retry.
+ TRACE_GDB_REMOTE("RX Missing $, retry.\n");
+ *checksum = 0;
+ pkt->Clear();
+ } else {
+ // Keep a running XSUM.
+ *checksum += ch;
+ pkt->AddRawChar(ch);
+ }
+ }
+ return false;
+}
+
+bool Session::GetPacket(Packet* pkt) {
+ while (true) {
+ // Toss characters until we see a start of command
+ char ch;
+ do {
+ if (!GetChar(&ch)) {
+ return false;
+ }
+ } while (ch != '$');
+
+ uint8_t running_checksum = 0;
+ if (!GetPayload(pkt, &running_checksum)) {
+ return false;
+ }
+
+ // Get two nibble checksum
+ uint8_t trailing_checksum = 0;
+ char chars[2];
+ if (!GetChar(&chars[0]) || !GetChar(&chars[1]) ||
+ !HexToUInt8(chars, &trailing_checksum)) {
+ return false;
+ }
+
+ TRACE_GDB_REMOTE("RX $%s#%c%c\n", pkt->GetPayload(), chars[0], chars[1]);
+
+ pkt->ParseSequence();
+
+ // If ACKs are off, we are done.
+ if (!ack_enabled_) {
+ return true;
+ }
+
+ // If the XSUMs don't match, signal bad packet
+ if (trailing_checksum == running_checksum) {
+ char out[3] = {'+', 0, 0};
+
+ // If we have a sequence number
+ int32_t seq;
+ if (pkt->GetSequence(&seq)) {
+ // Respond with sequence number
+ UInt8ToHex(seq, &out[1]);
+ return io_->Write(out, 3);
+ } else {
+ return io_->Write(out, 1);
+ }
+ } else {
+ // Resend a bad XSUM and look for retransmit
+ TRACE_GDB_REMOTE("RX Bad XSUM, retry\n");
+ io_->Write("-", 1);
+ // retry...
+ }
+ }
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/session.h b/src/debug/wasm/gdb-server/session.h
new file mode 100644
index 0000000..d7c2263
--- /dev/null
+++ b/src/debug/wasm/gdb-server/session.h
@@ -0,0 +1,73 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
+#define V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
+
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class Packet;
+class TransportBase;
+
+// Represents a gdb-remote debugging session.
+class V8_EXPORT_PRIVATE Session {
+ public:
+ explicit Session(TransportBase* transport);
+
+ // Attempt to send a packet and optionally wait for an ACK from the receiver.
+ bool SendPacket(Packet* packet, bool expect_ack = true);
+
+ // Attempt to receive a packet.
+ bool GetPacket(Packet* packet);
+
+ // Return true if there is data to read.
+ bool IsDataAvailable() const;
+
+ // Return true if the connection is still valid.
+ bool IsConnected() const;
+
+ // Shutdown the connection.
+ void Disconnect();
+
+ // When a debugging session is active, the GDB-remote thread can block waiting
+ // for events and it will resume execution when one of these two events arise:
+ // - A network event (a new packet arrives, or the connection is dropped)
+ // - A thread event (the execution stopped because of a trap or breakpoint).
+ void WaitForDebugStubEvent();
+
+ // Signal that the debuggee execution stopped because of a trap or breakpoint.
+ bool SignalThreadEvent();
+
+ // By default, when either the debugger or the GDB-stub sends a packet,
+ // the first response expected is an acknowledgment: either '+' (to indicate
+ // the packet was received correctly) or '-' (to request retransmission).
+ // When a transport is reliable, the debugger may request that acknowledgement
+ // be disabled by means of the 'QStartNoAckMode' packet.
+ void EnableAck(bool ack_enabled) { ack_enabled_ = ack_enabled; }
+
+ private:
+ // Read a single character from the transport.
+ bool GetChar(char* ch);
+
+ // Read the content of a packet, from a leading '$' to a trailing '#'.
+ bool GetPayload(Packet* pkt, uint8_t* checksum);
+
+ TransportBase* io_; // Transport object not owned by the Session.
+ bool connected_; // Is the connection still valid.
+ bool ack_enabled_; // If true, emit or wait for '+' from RSP stream.
+
+ DISALLOW_COPY_AND_ASSIGN(Session);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_SESSION_H_
diff --git a/src/debug/wasm/gdb-server/target.cc b/src/debug/wasm/gdb-server/target.cc
new file mode 100644
index 0000000..6992fd1
--- /dev/null
+++ b/src/debug/wasm/gdb-server/target.cc
@@ -0,0 +1,679 @@
+// Copyright 2020 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/wasm/gdb-server/target.h"
+
+#include <inttypes.h>
+#include "src/base/platform/time.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+#include "src/debug/wasm/gdb-server/gdb-server.h"
+#include "src/debug/wasm/gdb-server/packet.h"
+#include "src/debug/wasm/gdb-server/session.h"
+#include "src/debug/wasm/gdb-server/transport.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+static const int kThreadId = 1;
+
+// Signals.
+static const int kSigTrace = 5;
+static const int kSigSegv = 11;
+
+Target::Target(GdbServer* gdb_server)
+ : gdb_server_(gdb_server),
+ status_(Status::Running),
+ cur_signal_(0),
+ session_(nullptr),
+ debugger_initial_suspension_(true),
+ semaphore_(0),
+ current_isolate_(nullptr) {
+ InitQueryPropertyMap();
+}
+
+void Target::InitQueryPropertyMap() {
+ // Request LLDB to send packets up to 4000 bytes for bulk transfers.
+ query_properties_["Supported"] =
+ "PacketSize=1000;vContSupported-;qXfer:libraries:read+;";
+
+ query_properties_["Attached"] = "1";
+
+ // There is only one register, named 'pc', in this architecture
+ query_properties_["RegisterInfo0"] =
+ "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:"
+ "General Purpose Registers;gcc:16;dwarf:16;generic:pc;";
+ query_properties_["RegisterInfo1"] = "E45";
+
+ // ProcessInfo for wasm32
+ query_properties_["ProcessInfo"] =
+ "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:6c6c6462;triple:" +
+ Mem2Hex("wasm32-unknown-unknown-wasm") + ";ptrsize:4;";
+ query_properties_["Symbol"] = "OK";
+
+ // Current thread info
+ char buff[16];
+ snprintf(buff, sizeof(buff), "QC%x", kThreadId);
+ query_properties_["C"] = buff;
+}
+
+void Target::Terminate() {
+ // Executed in the Isolate thread, when the process shuts down.
+ SetStatus(Status::Terminated);
+}
+
+void Target::OnProgramBreak(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames) {
+ OnSuspended(isolate, kSigTrace, call_frames);
+}
+void Target::OnException(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames) {
+ OnSuspended(isolate, kSigSegv, call_frames);
+}
+void Target::OnSuspended(Isolate* isolate, int signal,
+ const std::vector<wasm_addr_t>& call_frames) {
+ // This function will be called in the isolate thread, when the wasm
+ // interpreter gets suspended.
+
+ bool isWaitingForSuspension = (status_ == Status::WaitingForSuspension);
+ SetStatus(Status::Suspended, signal, call_frames, isolate);
+ if (isWaitingForSuspension) {
+ // Wake the GdbServer thread that was blocked waiting for the Target
+ // to suspend.
+ semaphore_.Signal();
+ } else if (session_) {
+ session_->SignalThreadEvent();
+ }
+}
+
+void Target::Run(Session* session) {
+ // Executed in the GdbServer thread.
+ session_ = session;
+ do {
+ WaitForDebugEvent();
+ ProcessDebugEvent();
+ ProcessCommands();
+ } while (!IsTerminated() && session_->IsConnected());
+ session_ = nullptr;
+}
+
+void Target::WaitForDebugEvent() {
+ // Executed in the GdbServer thread.
+
+ if (status_ == Status::Running) {
+ // Wait for either:
+ // * the thread to fault (or single-step)
+ // * an interrupt from LLDB
+ session_->WaitForDebugStubEvent();
+ }
+}
+
+void Target::ProcessDebugEvent() {
+ // Executed in the GdbServer thread
+
+ if (status_ == Status::Running) {
+ // Blocks, waiting for the engine to suspend.
+ Suspend();
+ }
+
+ // Here, the wasm interpreter has suspended and we have updated the current
+ // thread info.
+
+ if (debugger_initial_suspension_) {
+ // First time on a connection, we don't send the signal.
+ // All other times, send the signal that triggered us.
+ debugger_initial_suspension_ = false;
+ } else {
+ Packet pktOut;
+ SetStopReply(&pktOut);
+ session_->SendPacket(&pktOut, false);
+ }
+}
+
+void Target::Suspend() {
+ // Executed in the GdbServer thread
+ if (status_ == Status::Running) {
+ // TODO(paolosev) - this only suspends the wasm interpreter.
+ gdb_server_->Suspend();
+
+ status_ = Status::WaitingForSuspension;
+ }
+
+ while (status_ == Status::WaitingForSuspension) {
+ if (semaphore_.WaitFor(base::TimeDelta::FromMilliseconds(500))) {
+ // Here the wasm interpreter is suspended.
+ return;
+ }
+ }
+}
+
+void Target::ProcessCommands() {
+ // GDB-remote messages are processed in the GDBServer thread.
+
+ if (IsTerminated()) {
+ return;
+ } else if (status_ != Status::Suspended) {
+ // Don't process commands if we haven't stopped.
+ return;
+ }
+
+ // Now we are ready to process commands.
+ // Loop through packets until we process a continue packet or a detach.
+ Packet recv, reply;
+ while (session_->IsConnected()) {
+ if (!session_->GetPacket(&recv)) {
+ continue;
+ }
+
+ reply.Clear();
+ ProcessPacketResult result = ProcessPacket(&recv, &reply);
+ switch (result) {
+ case ProcessPacketResult::Paused:
+ session_->SendPacket(&reply);
+ break;
+
+ case ProcessPacketResult::Continue:
+ DCHECK_EQ(status_, Status::Running);
+ // If this is a continue type command, break out of this loop.
+ gdb_server_->QuitMessageLoopOnPause();
+ return;
+
+ case ProcessPacketResult::Detach:
+ SetStatus(Status::Running);
+ session_->SendPacket(&reply);
+ session_->Disconnect();
+ gdb_server_->QuitMessageLoopOnPause();
+ return;
+
+ case ProcessPacketResult::Kill:
+ session_->SendPacket(&reply);
+ exit(-9);
+
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ if (!session_->IsConnected()) {
+ debugger_initial_suspension_ = true;
+ }
+}
+
+Target::ProcessPacketResult Target::ProcessPacket(Packet* pkt_in,
+ Packet* pkt_out) {
+ ErrorCode err = ErrorCode::None;
+
+ // Clear the outbound message.
+ pkt_out->Clear();
+
+ // Set the sequence number, if present.
+ int32_t seq = -1;
+ if (pkt_in->GetSequence(&seq)) {
+ pkt_out->SetSequence(seq);
+ }
+
+ // A GDB-remote packet begins with an upper- or lower-case letter, which
+ // generally represents a single command.
+ // The letters 'q' and 'Q' introduce a "General query packets" and are used
+ // to extend the protocol with custom commands.
+ // The format of GDB-remote commands is documented here:
+ // https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview.
+ char cmd;
+ pkt_in->GetRawChar(&cmd);
+
+ switch (cmd) {
+ // Queries the reason the target halted.
+ // IN : $?
+ // OUT: A Stop-reply packet
+ case '?':
+ SetStopReply(pkt_out);
+ break;
+
+ // Resumes execution
+ // IN : $c
+ // OUT: A Stop-reply packet is sent later, when the execution halts.
+ case 'c':
+ SetStatus(Status::Running);
+ return ProcessPacketResult::Continue;
+
+ // Detaches the debugger from this target
+ // IN : $D
+ // OUT: $OK
+ case 'D':
+ TRACE_GDB_REMOTE("Requested Detach.\n");
+ pkt_out->AddString("OK");
+ return ProcessPacketResult::Detach;
+
+ // Read general registers (We only support register 'pc' that contains
+ // the current instruction pointer).
+ // IN : $g
+ // OUT: $xx...xx
+ case 'g': {
+ uint64_t pc = GetCurrentPc();
+ pkt_out->AddBlock(&pc, sizeof(pc));
+ break;
+ }
+
+ // Write general registers - NOT SUPPORTED
+ // IN : $Gxx..xx
+ // OUT: $ (empty string)
+ case 'G': {
+ break;
+ }
+
+ // Set thread for subsequent operations. For Wasm targets, we currently
+ // assume that there is only one thread with id = kThreadId (= 1).
+ // IN : $H(c/g)(-1,0,xxxx)
+ // OUT: $OK
+ case 'H': {
+ // Type of the operation (‘m’, ‘M’, ‘g’, ‘G’, ...)
+ char operation;
+ if (!pkt_in->GetRawChar(&operation)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ uint64_t thread_id;
+ if (!pkt_in->GetNumberSep(&thread_id, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ // Ignore, only one thread supported for now.
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // Kills the debuggee.
+ // IN : $k
+ // OUT: $OK
+ case 'k':
+ TRACE_GDB_REMOTE("Requested Kill.\n");
+ pkt_out->AddString("OK");
+ return ProcessPacketResult::Kill;
+
+ // Reads {llll} addressable memory units starting at address {aaaa}.
+ // IN : $maaaa,llll
+ // OUT: $xx..xx
+ case 'm': {
+ uint64_t address;
+ if (!pkt_in->GetNumberSep(&address, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+ wasm_addr_t wasm_addr(address);
+
+ uint64_t len;
+ if (!pkt_in->GetNumberSep(&len, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ if (len > Transport::kBufSize / 2) {
+ err = ErrorCode::BadArgs;
+ break;
+ }
+
+ uint32_t length = static_cast<uint32_t>(len);
+ uint8_t buff[Transport::kBufSize];
+ if (wasm_addr.ModuleId() > 0) {
+ uint32_t read =
+ gdb_server_->GetWasmModuleBytes(wasm_addr, buff, length);
+ if (read > 0) {
+ pkt_out->AddBlock(buff, read);
+ } else {
+ err = ErrorCode::Failed;
+ }
+ } else {
+ err = ErrorCode::BadArgs;
+ }
+ break;
+ }
+
+ // Writes {llll} addressable memory units starting at address {aaaa}.
+ // IN : $Maaaa,llll:xx..xx
+ // OUT: $OK
+ case 'M': {
+ // Writing to memory not supported for Wasm.
+ err = ErrorCode::Failed;
+ break;
+ }
+
+ // pN: Reads the value of register N.
+ // IN : $pxx
+ // OUT: $xx..xx
+ case 'p': {
+ uint64_t pc = GetCurrentPc();
+ pkt_out->AddBlock(&pc, sizeof(pc));
+ } break;
+
+ case 'q': {
+ err = ProcessQueryPacket(pkt_in, pkt_out);
+ break;
+ }
+
+ // Single step
+ // IN : $s
+ // OUT: A Stop-reply packet is sent later, when the execution halts.
+ case 's': {
+ if (status_ == Status::Suspended) {
+ gdb_server_->PrepareStep();
+ SetStatus(Status::Running);
+ }
+ return ProcessPacketResult::Continue;
+ }
+
+ // Find out if the thread 'id' is alive.
+ // IN : $T
+ // OUT: $OK if alive, $Enn if thread is dead.
+ case 'T': {
+ uint64_t id;
+ if (!pkt_in->GetNumberSep(&id, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+ if (id != kThreadId) {
+ err = ErrorCode::BadArgs;
+ break;
+ }
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // Z: Adds a breakpoint
+ // IN : $Z<type>,<addr>,<kind>
+ // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
+ // OUT: $OK (success) or $Enn (error)
+ case 'Z': {
+ uint64_t breakpoint_type;
+ uint64_t breakpoint_address;
+ uint64_t breakpoint_kind;
+ // Only software breakpoints are supported.
+ if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
+ !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
+ !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
+ if (!gdb_server_->AddBreakpoint(wasm_breakpoint_addr.ModuleId(),
+ wasm_breakpoint_addr.Offset())) {
+ err = ErrorCode::Failed;
+ break;
+ }
+
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // z: Removes a breakpoint
+ // IN : $z<type>,<addr>,<kind>
+ // <type>: 0: sw breakpoint, 1: hw breakpoint, 2: watchpoint
+ // OUT: $OK (success) or $Enn (error)
+ case 'z': {
+ uint64_t breakpoint_type;
+ uint64_t breakpoint_address;
+ uint64_t breakpoint_kind;
+ if (!pkt_in->GetNumberSep(&breakpoint_type, 0) || breakpoint_type != 0 ||
+ !pkt_in->GetNumberSep(&breakpoint_address, 0) ||
+ !pkt_in->GetNumberSep(&breakpoint_kind, 0)) {
+ err = ErrorCode::BadFormat;
+ break;
+ }
+
+ wasm_addr_t wasm_breakpoint_addr(breakpoint_address);
+ if (!gdb_server_->RemoveBreakpoint(wasm_breakpoint_addr.ModuleId(),
+ wasm_breakpoint_addr.Offset())) {
+ err = ErrorCode::Failed;
+ break;
+ }
+
+ pkt_out->AddString("OK");
+ break;
+ }
+
+ // If the command is not recognized, ignore it by sending an empty reply.
+ default: {
+ TRACE_GDB_REMOTE("Unknown command: %s\n", pkt_in->GetPayload());
+ }
+ }
+
+ // If there is an error, return the error code instead of a payload
+ if (err != ErrorCode::None) {
+ pkt_out->Clear();
+ pkt_out->AddRawChar('E');
+ pkt_out->AddWord8(static_cast<uint8_t>(err));
+ }
+ return ProcessPacketResult::Paused;
+}
+
+Target::ErrorCode Target::ProcessQueryPacket(const Packet* pkt_in,
+ Packet* pkt_out) {
+ const char* str = &pkt_in->GetPayload()[1];
+
+ // Get first thread query
+ // IN : $qfThreadInfo
+ // OUT: $m<tid>
+ //
+ // Get next thread query
+ // IN : $qsThreadInfo
+ // OUT: $m<tid> or l to denote end of list.
+ if (!strcmp(str, "fThreadInfo") || !strcmp(str, "sThreadInfo")) {
+ if (str[0] == 'f') {
+ pkt_out->AddString("m");
+ pkt_out->AddNumberSep(kThreadId, 0);
+ } else {
+ pkt_out->AddString("l");
+ }
+ return ErrorCode::None;
+ }
+
+ // Get a list of loaded libraries
+ // IN : $qXfer:libraries:read
+ // OUT: an XML document which lists loaded libraries, with this format:
+ // <library-list>
+ // <library name="foo.wasm">
+ // <section address="0x100000000"/>
+ // </library>
+ // <library name="bar.wasm">
+ // <section address="0x200000000"/>
+ // </library>
+ // </library-list>
+ // Note that LLDB must be compiled with libxml2 support to handle this packet.
+ std::string tmp = "Xfer:libraries:read";
+ if (!strncmp(str, tmp.data(), tmp.length())) {
+ std::vector<GdbServer::WasmModuleInfo> modules =
+ gdb_server_->GetLoadedModules();
+ std::string result("l<library-list>");
+ for (const auto& module : modules) {
+ wasm_addr_t address(module.module_id, 0);
+ char address_string[32];
+ snprintf(address_string, sizeof(address_string), "%" PRIu64,
+ static_cast<uint64_t>(address));
+ result += "<library name=\"";
+ result += module.module_name;
+ result += "\"><section address=\"";
+ result += address_string;
+ result += "\"/></library>";
+ }
+ result += "</library-list>";
+ pkt_out->AddString(result.c_str());
+ return ErrorCode::None;
+ }
+
+ // Get the current call stack.
+ // IN : $qWasmCallStack
+ // OUT: $xx..xxyy..yyzz..zz (A sequence of uint64_t values represented as
+ // consecutive 8-bytes blocks).
+ std::vector<std::string> toks = StringSplit(str, ":;");
+ if (toks[0] == "WasmCallStack") {
+ std::vector<wasm_addr_t> call_stack_pcs = gdb_server_->GetWasmCallStack();
+ std::vector<uint64_t> buffer;
+ for (wasm_addr_t pc : call_stack_pcs) {
+ buffer.push_back(pc);
+ }
+ pkt_out->AddBlock(buffer.data(),
+ static_cast<uint32_t>(sizeof(uint64_t) * buffer.size()));
+ return ErrorCode::None;
+ }
+
+ // Get a Wasm global value in the Wasm module specified.
+ // IN : $qWasmGlobal:frame_index;index
+ // OUT: $xx..xx
+ if (toks[0] == "WasmGlobal") {
+ if (toks.size() == 3) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t index =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+ uint8_t buff[16];
+ uint32_t size = 0;
+ if (gdb_server_->GetWasmGlobal(frame_index, index, buff, 16, &size)) {
+ pkt_out->AddBlock(buff, size);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // Get a Wasm local value in the stack frame specified.
+ // IN : $qWasmLocal:frame_index;index
+ // OUT: $xx..xx
+ if (toks[0] == "WasmLocal") {
+ if (toks.size() == 3) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t index =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+ uint8_t buff[16];
+ uint32_t size = 0;
+ if (gdb_server_->GetWasmLocal(frame_index, index, buff, 16, &size)) {
+ pkt_out->AddBlock(buff, size);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // Get a Wasm local from the operand stack at the index specified.
+ // IN : qWasmStackValue:frame_index;index
+ // OUT: $xx..xx
+ if (toks[0] == "WasmStackValue") {
+ if (toks.size() == 3) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t index =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 10));
+ uint8_t buff[16];
+ uint32_t size = 0;
+ if (gdb_server_->GetWasmStackValue(frame_index, index, buff, 16, &size)) {
+ pkt_out->AddBlock(buff, size);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // Read Wasm memory.
+ // IN : $qWasmMem:frame_index;addr;len
+ // OUT: $xx..xx
+ if (toks[0] == "WasmMem") {
+ if (toks.size() == 4) {
+ uint32_t frame_index =
+ static_cast<uint32_t>(strtol(toks[1].data(), nullptr, 10));
+ uint32_t address =
+ static_cast<uint32_t>(strtol(toks[2].data(), nullptr, 16));
+ uint32_t length =
+ static_cast<uint32_t>(strtol(toks[3].data(), nullptr, 16));
+ if (length > Transport::kBufSize / 2) {
+ return ErrorCode::BadArgs;
+ }
+ uint8_t buff[Transport::kBufSize];
+ uint32_t read =
+ gdb_server_->GetWasmMemory(frame_index, address, buff, length);
+ if (read > 0) {
+ pkt_out->AddBlock(buff, read);
+ return ErrorCode::None;
+ } else {
+ return ErrorCode::Failed;
+ }
+ }
+ return ErrorCode::BadFormat;
+ }
+
+ // No match so far, check the property cache.
+ QueryPropertyMap::const_iterator it = query_properties_.find(toks[0]);
+ if (it != query_properties_.end()) {
+ pkt_out->AddString(it->second.data());
+ }
+ // If not found, just send an empty response.
+ return ErrorCode::None;
+}
+
+// A Stop-reply packet has the format:
+// Sxx
+// or:
+// Txx<name1>:<value1>;...;<nameN>:<valueN>
+// where 'xx' is a two-digit hex number that represents the stop signal
+// and the <name>:<value> pairs are used to report additional information,
+// like the thread id.
+void Target::SetStopReply(Packet* pkt_out) const {
+ pkt_out->AddRawChar('T');
+ pkt_out->AddWord8(cur_signal_);
+
+ // Adds 'thread-pcs:<pc1>,...,<pcN>;' A list of pc values for all threads that
+ // currently exist in the process.
+ char buff[64];
+ snprintf(buff, sizeof(buff), "thread-pcs:%" PRIx64 ";",
+ static_cast<uint64_t>(GetCurrentPc()));
+ pkt_out->AddString(buff);
+
+ // Adds 'thread:<tid>;' pair. Note that a terminating ';' is required.
+ pkt_out->AddString("thread:");
+ pkt_out->AddNumberSep(kThreadId, ';');
+}
+
+void Target::SetStatus(Status status, int8_t signal,
+ std::vector<wasm_addr_t> call_frames, Isolate* isolate) {
+ v8::base::MutexGuard guard(&mutex_);
+
+ DCHECK((status == Status::Suspended && signal != 0 &&
+ call_frames.size() > 0 && isolate != nullptr) ||
+ (status != Status::Suspended && signal == 0 &&
+ call_frames.size() == 0 && isolate == nullptr));
+
+ current_isolate_ = isolate;
+ status_ = status;
+ cur_signal_ = signal;
+ call_frames_ = call_frames;
+}
+
+const std::vector<wasm_addr_t> Target::GetCallStack() const {
+ v8::base::MutexGuard guard(&mutex_);
+
+ return call_frames_;
+}
+
+wasm_addr_t Target::GetCurrentPc() const {
+ v8::base::MutexGuard guard(&mutex_);
+
+ wasm_addr_t pc{0};
+ if (call_frames_.size() > 0) {
+ pc = call_frames_[0];
+ }
+ return pc;
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/target.h b/src/debug/wasm/gdb-server/target.h
new file mode 100644
index 0000000..1af81d3
--- /dev/null
+++ b/src/debug/wasm/gdb-server/target.h
@@ -0,0 +1,140 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
+#define V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
+
+#include <atomic>
+#include <map>
+#include "src/base/macros.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class GdbServer;
+class Packet;
+class Session;
+
+// Class Target represents a debugging target. It contains the logic to decode
+// incoming GDB-remote packets, execute them forwarding the debugger commands
+// and queries to the Wasm engine, and send back GDB-remote packets.
+class Target {
+ public:
+ // Contruct a Target object.
+ explicit Target(GdbServer* gdb_server);
+
+ // This function spin on a debugging session, until it closes.
+ void Run(Session* ses);
+
+ void Terminate();
+ bool IsTerminated() const { return status_ == Status::Terminated; }
+
+ // Notifies that the debuggee thread suspended at a breakpoint.
+ void OnProgramBreak(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames);
+ // Notifies that the debuggee thread suspended because of an unhandled
+ // exception.
+ void OnException(Isolate* isolate,
+ const std::vector<wasm_addr_t>& call_frames);
+
+ // Returns the state at the moment of the thread suspension.
+ const std::vector<wasm_addr_t> GetCallStack() const;
+ wasm_addr_t GetCurrentPc() const;
+ Isolate* GetCurrentIsolate() const { return current_isolate_; }
+
+ private:
+ void OnSuspended(Isolate* isolate, int signal,
+ const std::vector<wasm_addr_t>& call_frames);
+
+ // Initializes a map used to make fast lookups when handling query packets
+ // that have a constant response.
+ void InitQueryPropertyMap();
+
+ // Blocks waiting for one of these two events to occur:
+ // - A network packet arrives from the debugger, or the debugger connection is
+ // closed;
+ // - The debuggee suspends execution because of a trap or breakpoint.
+ void WaitForDebugEvent();
+ void ProcessDebugEvent();
+
+ // Processes GDB-remote packets that arrive from the debugger.
+ // This method should be called when the debuggee has suspended its execution.
+ void ProcessCommands();
+
+ // Requests that the thread suspends execution at the next Wasm instruction.
+ void Suspend();
+
+ enum class ErrorCode { None = 0, BadFormat = 1, BadArgs = 2, Failed = 3 };
+
+ enum class ProcessPacketResult {
+ Paused, // The command was processed, debuggee still paused.
+ Continue, // The debuggee should resume execution.
+ Detach, // Request to detach from the debugger.
+ Kill // Request to terminate the debuggee process.
+ };
+ // This function always succeedes, since all errors are reported as an error
+ // string "Exx" where xx is a two digit number.
+ // The return value indicates if the target can resume execution or it is
+ // still paused.
+ ProcessPacketResult ProcessPacket(Packet* pkt_in, Packet* pkt_out);
+
+ // Processes a general query packet
+ ErrorCode ProcessQueryPacket(const Packet* pkt_in, Packet* pkt_out);
+
+ // Formats a 'Stop-reply' packet, which is sent in response of a 'c'
+ // (continue), 's' (step) and '?' (query halt reason) commands.
+ void SetStopReply(Packet* pkt_out) const;
+
+ enum class Status { Running, WaitingForSuspension, Suspended, Terminated };
+
+ void SetStatus(Status status, int8_t signal = 0,
+ std::vector<wasm_addr_t> call_frames_ = {},
+ Isolate* isolate = nullptr);
+
+ GdbServer* gdb_server_;
+
+ std::atomic<Status> status_;
+
+ // Signal being processed.
+ std::atomic<int8_t> cur_signal_;
+
+ // Session object not owned by the Target.
+ Session* session_;
+
+ // Map used to make fast lookups when handling query packets.
+ typedef std::map<std::string, std::string> QueryPropertyMap;
+ QueryPropertyMap query_properties_;
+
+ bool debugger_initial_suspension_;
+
+ // Used to block waiting for suspension
+ v8::base::Semaphore semaphore_;
+
+ mutable v8::base::Mutex mutex_;
+ //////////////////////////////////////////////////////////////////////////////
+ // Protected by {mutex_}:
+
+ // Current isolate. This is not null only when the target is in a Suspended
+ // state and it is the isolate associated to the current call stack and used
+ // for all debugging activities.
+ Isolate* current_isolate_;
+
+ // Call stack when the execution is suspended.
+ std::vector<wasm_addr_t> call_frames_;
+
+ // End of fields protected by {mutex_}.
+ //////////////////////////////////////////////////////////////////////////////
+
+ DISALLOW_COPY_AND_ASSIGN(Target);
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_TARGET_H_
diff --git a/src/debug/wasm/gdb-server/transport.cc b/src/debug/wasm/gdb-server/transport.cc
new file mode 100644
index 0000000..f1aed96
--- /dev/null
+++ b/src/debug/wasm/gdb-server/transport.cc
@@ -0,0 +1,457 @@
+// Copyright 2020 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/wasm/gdb-server/transport.h"
+#include <fcntl.h>
+
+#ifndef SD_BOTH
+#define SD_BOTH 2
+#endif
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+SocketBinding::SocketBinding(SocketHandle socket_handle)
+ : socket_handle_(socket_handle) {}
+
+// static
+SocketBinding SocketBinding::Bind(uint16_t tcp_port) {
+ SocketHandle socket_handle = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (socket_handle == InvalidSocket) {
+ TRACE_GDB_REMOTE("Failed to create socket.\n");
+ return SocketBinding(InvalidSocket);
+ }
+ struct sockaddr_in sockaddr;
+ // Clearing sockaddr_in first appears to be necessary on Mac OS X.
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ socklen_t addrlen = static_cast<socklen_t>(sizeof(sockaddr));
+ sockaddr.sin_family = AF_INET;
+ sockaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sockaddr.sin_port = htons(tcp_port);
+
+#if _WIN32
+ // On Windows, SO_REUSEADDR has a different meaning than on POSIX systems.
+ // SO_REUSEADDR allows hijacking of an open socket by another process.
+ // The SO_EXCLUSIVEADDRUSE flag prevents this behavior.
+ // See:
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx
+ //
+ // Additionally, unlike POSIX, TCP server sockets can be bound to
+ // ports in the TIME_WAIT state, without setting SO_REUSEADDR.
+ int exclusive_address = 1;
+ if (setsockopt(socket_handle, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+ reinterpret_cast<char*>(&exclusive_address),
+ sizeof(exclusive_address))) {
+ TRACE_GDB_REMOTE("Failed to set SO_EXCLUSIVEADDRUSE option.\n");
+ }
+#else
+ // On POSIX, this is necessary to ensure that the TCP port is released
+ // promptly when sel_ldr exits. Without this, the TCP port might
+ // only be released after a timeout, and later processes can fail
+ // to bind it.
+ int reuse_address = 1;
+ if (setsockopt(socket_handle, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<char*>(&reuse_address),
+ sizeof(reuse_address))) {
+ TRACE_GDB_REMOTE("Failed to set SO_REUSEADDR option.\n");
+ }
+#endif
+
+ if (bind(socket_handle, reinterpret_cast<struct sockaddr*>(&sockaddr),
+ addrlen)) {
+ TRACE_GDB_REMOTE("Failed to bind server.\n");
+ return SocketBinding(InvalidSocket);
+ }
+
+ if (listen(socket_handle, 1)) {
+ TRACE_GDB_REMOTE("Failed to listen.\n");
+ return SocketBinding(InvalidSocket);
+ }
+ return SocketBinding(socket_handle);
+}
+
+std::unique_ptr<SocketTransport> SocketBinding::CreateTransport() {
+ return std::make_unique<SocketTransport>(socket_handle_);
+}
+
+uint16_t SocketBinding::GetBoundPort() {
+ struct sockaddr_in saddr;
+ struct sockaddr* psaddr = reinterpret_cast<struct sockaddr*>(&saddr);
+ // Clearing sockaddr_in first appears to be necessary on Mac OS X.
+ memset(&saddr, 0, sizeof(saddr));
+ socklen_t addrlen = static_cast<socklen_t>(sizeof(saddr));
+ if (::getsockname(socket_handle_, psaddr, &addrlen)) {
+ TRACE_GDB_REMOTE("Failed to retrieve bound address.\n");
+ return 0;
+ }
+ return ntohs(saddr.sin_port);
+}
+
+// Do not delay sending small packets. This significantly speeds up
+// remote debugging. Debug stub uses buffering to send outgoing packets
+// so they are not split into more TCP packets than necessary.
+void DisableNagleAlgorithm(SocketHandle socket) {
+ int nodelay = 1;
+ if (::setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
+ reinterpret_cast<char*>(&nodelay), sizeof(nodelay))) {
+ TRACE_GDB_REMOTE("Failed to set TCP_NODELAY option.\n");
+ }
+}
+
+Transport::Transport(SocketHandle s)
+ : buf_(new char[kBufSize]),
+ pos_(0),
+ size_(0),
+ handle_bind_(s),
+ handle_accept_(InvalidSocket) {}
+
+Transport::~Transport() {
+ if (handle_accept_ != InvalidSocket) {
+ CloseSocket(handle_accept_);
+ }
+}
+
+void Transport::CopyFromBuffer(char** dst, int32_t* len) {
+ int32_t copy_bytes = std::min(*len, size_ - pos_);
+ memcpy(*dst, buf_.get() + pos_, copy_bytes);
+ pos_ += copy_bytes;
+ *len -= copy_bytes;
+ *dst += copy_bytes;
+}
+
+bool Transport::Read(char* dst, int32_t len) {
+ if (pos_ < size_) {
+ CopyFromBuffer(&dst, &len);
+ }
+ while (len > 0) {
+ pos_ = 0;
+ size_ = 0;
+ if (!ReadSomeData()) {
+ return false;
+ }
+ CopyFromBuffer(&dst, &len);
+ }
+ return true;
+}
+
+bool Transport::Write(const char* src, int32_t len) {
+ while (len > 0) {
+ ssize_t result = ::send(handle_accept_, src, len, 0);
+ if (result > 0) {
+ src += result;
+ len -= result;
+ continue;
+ }
+ if (result == 0) {
+ return false;
+ }
+ if (SocketGetLastError() != kErrInterrupt) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Return true if there is data to read.
+bool Transport::IsDataAvailable() const {
+ if (pos_ < size_) {
+ return true;
+ }
+ fd_set fds;
+
+ FD_ZERO(&fds);
+ FD_SET(handle_accept_, &fds);
+
+ // We want a "non-blocking" check
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+
+ // Check if this file handle can select on read
+ int cnt = select(static_cast<int>(handle_accept_) + 1, &fds, 0, 0, &timeout);
+
+ // If we are ready, or if there is an error. We return true
+ // on error, to let the next IO request fail.
+ if (cnt != 0) return true;
+
+ return false;
+}
+
+void Transport::Close() {
+ ::shutdown(handle_bind_, SD_BOTH);
+ CloseSocket(handle_bind_);
+ Disconnect();
+}
+
+void Transport::Disconnect() {
+ if (handle_accept_ != InvalidSocket) {
+ // Shutdown the connection in both directions. This should
+ // always succeed, and nothing we can do if this fails.
+ ::shutdown(handle_accept_, SD_BOTH);
+ CloseSocket(handle_accept_);
+ handle_accept_ = InvalidSocket;
+ }
+}
+
+#if _WIN32
+
+SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
+ socket_event_ = WSA_INVALID_EVENT;
+ faulted_thread_event_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (faulted_thread_event_ == NULL) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::SocketTransport: Failed to create event object for "
+ "faulted thread\n");
+ }
+}
+
+SocketTransport::~SocketTransport() {
+ if (!CloseHandle(faulted_thread_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "event\n");
+ }
+
+ if (socket_event_) {
+ if (!::WSACloseEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "socket event\n");
+ }
+ }
+}
+
+bool SocketTransport::AcceptConnection() {
+ CHECK(handle_accept_ == InvalidSocket);
+ handle_accept_ = ::accept(handle_bind_, NULL, 0);
+ if (handle_accept_ != InvalidSocket) {
+ DisableNagleAlgorithm(handle_accept_);
+
+ // Create socket event
+ socket_event_ = ::WSACreateEvent();
+ if (socket_event_ == WSA_INVALID_EVENT) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::AcceptConnection: Failed to create socket event\n");
+ }
+
+ // Listen for close events in order to handle them correctly.
+ // Additionally listen for read readiness as WSAEventSelect sets the socket
+ // to non-blocking mode.
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547(v=vs.85).aspx
+ if (::WSAEventSelect(handle_accept_, socket_event_, FD_CLOSE | FD_READ) ==
+ SOCKET_ERROR) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::AcceptConnection: Failed to bind event to "
+ "socket\n");
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SocketTransport::ReadSomeData() {
+ while (true) {
+ ssize_t result =
+ ::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
+ if (result > 0) {
+ size_ += result;
+ return true;
+ }
+ if (result == 0) {
+ return false; // The connection was gracefully closed.
+ }
+ // WSAEventSelect sets socket to non-blocking mode. This is essential
+ // for socket event notification to work, there is no workaround.
+ // See remarks section at the page
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
+ if (SocketGetLastError() == WSAEWOULDBLOCK) {
+ if (::WaitForSingleObject(socket_event_, INFINITE) == WAIT_FAILED) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::ReadSomeData: Failed to wait on socket event\n");
+ }
+ if (!::ResetEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::ReadSomeData: Failed to reset socket event\n");
+ }
+ continue;
+ }
+
+ if (SocketGetLastError() != kErrInterrupt) {
+ return false;
+ }
+ }
+}
+
+void SocketTransport::WaitForDebugStubEvent() {
+ // Don't wait if we already have data to read.
+ bool wait = !(pos_ < size_);
+
+ HANDLE handles[2];
+ handles[0] = faulted_thread_event_;
+ handles[1] = socket_event_;
+ int count = size_ < kBufSize ? 2 : 1;
+ int result =
+ WaitForMultipleObjects(count, handles, FALSE, wait ? INFINITE : 0);
+ if (result == WAIT_OBJECT_0 + 1) {
+ if (!ResetEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to reset socket "
+ "event\n");
+ }
+ return;
+ } else if (result == WAIT_OBJECT_0) {
+ if (!ResetEvent(faulted_thread_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to reset event\n");
+ }
+ return;
+ } else if (result == WAIT_TIMEOUT) {
+ return;
+ }
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Wait for events failed\n");
+}
+
+bool SocketTransport::SignalThreadEvent() {
+ if (!SetEvent(faulted_thread_event_)) {
+ return false;
+ }
+ return true;
+}
+
+void SocketTransport::Disconnect() {
+ Transport::Disconnect();
+
+ if (socket_event_ != WSA_INVALID_EVENT && !::WSACloseEvent(socket_event_)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "socket event\n");
+ }
+ socket_event_ = WSA_INVALID_EVENT;
+ SignalThreadEvent();
+}
+
+#else // _WIN32
+
+SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
+ int fds[2];
+#if defined(__linux__)
+ int ret = pipe2(fds, O_CLOEXEC);
+#else
+ int ret = pipe(fds);
+#endif
+ if (ret < 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::SocketTransport: Failed to allocate pipe for faulted "
+ "thread\n");
+ }
+ faulted_thread_fd_read_ = fds[0];
+ faulted_thread_fd_write_ = fds[1];
+}
+
+SocketTransport::~SocketTransport() {
+ if (close(faulted_thread_fd_read_) != 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "event\n");
+ }
+ if (close(faulted_thread_fd_write_) != 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::~SocketTransport: Failed to close "
+ "event\n");
+ }
+}
+
+bool SocketTransport::AcceptConnection() {
+ CHECK(handle_accept_ == InvalidSocket);
+ handle_accept_ = ::accept(handle_bind_, NULL, 0);
+ if (handle_accept_ != InvalidSocket) {
+ DisableNagleAlgorithm(handle_accept_);
+ return true;
+ }
+ return false;
+}
+
+bool SocketTransport::ReadSomeData() {
+ while (true) {
+ ssize_t result =
+ ::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
+ if (result > 0) {
+ size_ += result;
+ return true;
+ }
+ if (result == 0) {
+ return false; // The connection was gracefully closed.
+ }
+ if (SocketGetLastError() != kErrInterrupt) {
+ return false;
+ }
+ }
+}
+
+void SocketTransport::WaitForDebugStubEvent() {
+ // Don't wait if we already have data to read.
+ bool wait = !(pos_ < size_);
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(faulted_thread_fd_read_, &fds);
+ int max_fd = faulted_thread_fd_read_;
+ if (size_ < kBufSize) {
+ FD_SET(handle_accept_, &fds);
+ max_fd = std::max(max_fd, handle_accept_);
+ }
+
+ int ret;
+ // We don't need sleep-polling on Linux now, so we set either zero or infinite
+ // timeout.
+ if (wait) {
+ ret = select(max_fd + 1, &fds, NULL, NULL, NULL);
+ } else {
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ ret = select(max_fd + 1, &fds, NULL, NULL, &timeout);
+ }
+ if (ret < 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to wait for "
+ "debug stub event\n");
+ }
+
+ if (ret > 0) {
+ if (FD_ISSET(faulted_thread_fd_read_, &fds)) {
+ char buf[16];
+ if (read(faulted_thread_fd_read_, &buf, sizeof(buf)) < 0) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport::WaitForDebugStubEvent: Failed to read from "
+ "debug stub event pipe fd\n");
+ }
+ }
+ if (FD_ISSET(handle_accept_, &fds)) ReadSomeData();
+ }
+}
+
+bool SocketTransport::SignalThreadEvent() {
+ // Notify the debug stub by marking the thread as faulted.
+ char buf = 0;
+ if (write(faulted_thread_fd_write_, &buf, sizeof(buf)) != sizeof(buf)) {
+ TRACE_GDB_REMOTE(
+ "SocketTransport:SignalThreadEvent: Can't send debug stub "
+ "event\n");
+ return false;
+ }
+ return true;
+}
+
+#endif // _WIN32
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#undef SD_BOTH
diff --git a/src/debug/wasm/gdb-server/transport.h b/src/debug/wasm/gdb-server/transport.h
new file mode 100644
index 0000000..42bf438
--- /dev/null
+++ b/src/debug/wasm/gdb-server/transport.h
@@ -0,0 +1,193 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
+#define V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
+
+#include <sstream>
+#include <vector>
+#include "src/base/macros.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+
+#if _WIN32
+#include <windows.h>
+#include <winsock2.h>
+
+typedef SOCKET SocketHandle;
+
+#define CloseSocket closesocket
+#define InvalidSocket INVALID_SOCKET
+#define SocketGetLastError() WSAGetLastError()
+static const int kErrInterrupt = WSAEINTR;
+typedef int ssize_t;
+typedef int socklen_t;
+
+#else // _WIN32
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string>
+
+typedef int SocketHandle;
+
+#define CloseSocket close
+#define InvalidSocket (-1)
+#define SocketGetLastError() errno
+static const int kErrInterrupt = EINTR;
+
+#endif // _WIN32
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+class SocketTransport;
+
+// Acts as a factory for Transport objects bound to a specified TCP port.
+class SocketBinding {
+ public:
+ // Wrap existing socket handle.
+ explicit SocketBinding(SocketHandle socket_handle);
+
+ // Bind to the specified TCP port.
+ static SocketBinding Bind(uint16_t tcp_port);
+
+ bool IsValid() const { return socket_handle_ != InvalidSocket; }
+
+ // Create a transport object from this socket binding
+ std::unique_ptr<SocketTransport> CreateTransport();
+
+ // Get port the socket is bound to.
+ uint16_t GetBoundPort();
+
+ private:
+ SocketHandle socket_handle_;
+};
+
+class V8_EXPORT_PRIVATE TransportBase {
+ public:
+ virtual ~TransportBase() {}
+
+ // Waits for an incoming connection on the bound port.
+ virtual bool AcceptConnection() = 0;
+
+ // Read {len} bytes from this transport, possibly blocking until enough data
+ // is available.
+ // {dst} must point to a buffer large enough to contain {len} bytes.
+ // Returns true on success.
+ // Returns false if the connection is closed; in that case the {dst} may have
+ // been partially overwritten.
+ virtual bool Read(char* dst, int32_t len) = 0;
+
+ // Write {len} bytes to this transport.
+ // Return true on success, false if the connection is closed.
+ virtual bool Write(const char* src, int32_t len) = 0;
+
+ // Return true if there is data to read.
+ virtual bool IsDataAvailable() const = 0;
+
+ // If we are connected to a debugger, gracefully closes the connection.
+ // This should be called when a debugging session gets closed.
+ virtual void Disconnect() = 0;
+
+ // Shuts down this transport, gracefully closing the existing connection and
+ // also closing the listening socket. This should be called when the GDB stub
+ // shuts down, when the program terminates.
+ virtual void Close() = 0;
+
+ // Blocks waiting for one of these two events to occur:
+ // - A network event (a new packet arrives, or the connection is dropped),
+ // - A thread event is signaled (the execution stopped because of a trap or
+ // breakpoint).
+ virtual void WaitForDebugStubEvent() = 0;
+
+ // Signal that this transport should leave an alertable wait state because
+ // the execution of the debuggee was stopped because of a trap or breakpoint.
+ virtual bool SignalThreadEvent() = 0;
+};
+
+class Transport : public TransportBase {
+ public:
+ explicit Transport(SocketHandle s);
+ ~Transport() override;
+
+ // TransportBase
+ bool Read(char* dst, int32_t len) override;
+ bool Write(const char* src, int32_t len) override;
+ bool IsDataAvailable() const override;
+ void Disconnect() override;
+ void Close() override;
+
+ static const int kBufSize = 4096;
+
+ protected:
+ // Copy buffered data to *dst up to len bytes and update dst and len.
+ void CopyFromBuffer(char** dst, int32_t* len);
+
+ // Read available data from the socket. Return false on EOF or error.
+ virtual bool ReadSomeData() = 0;
+
+ std::unique_ptr<char[]> buf_;
+ int32_t pos_;
+ int32_t size_;
+ SocketHandle handle_bind_;
+ SocketHandle handle_accept_;
+};
+
+#if _WIN32
+
+class SocketTransport : public Transport {
+ public:
+ explicit SocketTransport(SocketHandle s);
+ ~SocketTransport() override;
+
+ // TransportBase
+ bool AcceptConnection() override;
+ void Disconnect() override;
+ void WaitForDebugStubEvent() override;
+ bool SignalThreadEvent() override;
+
+ private:
+ bool ReadSomeData() override;
+
+ HANDLE socket_event_;
+ HANDLE faulted_thread_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketTransport);
+};
+
+#else // _WIN32
+
+class SocketTransport : public Transport {
+ public:
+ explicit SocketTransport(SocketHandle s);
+ ~SocketTransport() override;
+
+ // TransportBase
+ bool AcceptConnection() override;
+ void WaitForDebugStubEvent() override;
+ bool SignalThreadEvent() override;
+
+ private:
+ bool ReadSomeData() override;
+
+ int faulted_thread_fd_read_;
+ int faulted_thread_fd_write_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketTransport);
+};
+
+#endif // _WIN32
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
diff --git a/src/debug/wasm/gdb-server/wasm-module-debug.cc b/src/debug/wasm/gdb-server/wasm-module-debug.cc
new file mode 100644
index 0000000..f0b77bc
--- /dev/null
+++ b/src/debug/wasm/gdb-server/wasm-module-debug.cc
@@ -0,0 +1,387 @@
+// Copyright 2020 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/wasm/gdb-server/wasm-module-debug.h"
+
+#include "src/api/api-inl.h"
+#include "src/api/api.h"
+#include "src/execution/frames-inl.h"
+#include "src/execution/frames.h"
+#include "src/objects/script.h"
+#include "src/wasm/wasm-debug.h"
+#include "src/wasm/wasm-value.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+namespace gdb_server {
+
+WasmModuleDebug::WasmModuleDebug(v8::Isolate* isolate,
+ Local<debug::WasmScript> wasm_script) {
+ DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type());
+
+ isolate_ = isolate;
+ wasm_script_ = Global<debug::WasmScript>(isolate, wasm_script);
+}
+
+std::string WasmModuleDebug::GetModuleName() const {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ v8::Local<v8::String> name;
+ std::string module_name;
+ if (wasm_script->Name().ToLocal(&name)) {
+ module_name = *(v8::String::Utf8Value(isolate_, name));
+ }
+ return module_name;
+}
+
+Handle<WasmInstanceObject> WasmModuleDebug::GetFirstWasmInstance() {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ Handle<Script> script = Utils::OpenHandle(*wasm_script);
+
+ Handle<WeakArrayList> weak_instance_list(script->wasm_weak_instance_list(),
+ GetIsolate());
+ if (weak_instance_list->length() > 0) {
+ MaybeObject maybe_instance = weak_instance_list->Get(0);
+ if (maybe_instance->IsWeak()) {
+ Handle<WasmInstanceObject> instance(
+ WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()),
+ GetIsolate());
+ return instance;
+ }
+ }
+ return Handle<WasmInstanceObject>::null();
+}
+
+int GetLEB128Size(Vector<const uint8_t> module_bytes, int offset) {
+ int index = offset;
+ while (module_bytes[index] & 0x80) index++;
+ return index + 1 - offset;
+}
+
+int ReturnPc(const NativeModule* native_module, int pc) {
+ Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
+ uint8_t opcode = wire_bytes[pc];
+ switch (opcode) {
+ case kExprCallFunction: {
+ // skip opcode
+ pc++;
+ // skip function index
+ return pc + GetLEB128Size(wire_bytes, pc);
+ }
+ case kExprCallIndirect: {
+ // skip opcode
+ pc++;
+ // skip signature index
+ pc += GetLEB128Size(wire_bytes, pc);
+ // skip table index
+ return pc + GetLEB128Size(wire_bytes, pc);
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+// static
+std::vector<wasm_addr_t> WasmModuleDebug::GetCallStack(
+ uint32_t debug_context_id, Isolate* isolate) {
+ std::vector<wasm_addr_t> call_stack;
+ for (StackFrameIterator frame_it(isolate); !frame_it.done();
+ frame_it.Advance()) {
+ StackFrame* const frame = frame_it.frame();
+ switch (frame->type()) {
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
+ case StackFrame::OPTIMIZED:
+ case StackFrame::INTERPRETED:
+ case StackFrame::BUILTIN:
+ case StackFrame::WASM: {
+ // A standard frame may include many summarized frames, due to inlining.
+ std::vector<FrameSummary> frames;
+ CommonFrame::cast(frame)->Summarize(&frames);
+ for (size_t i = frames.size(); i-- != 0;) {
+ int offset = 0;
+ Handle<Script> script;
+
+ auto& summary = frames[i];
+ if (summary.IsJavaScript()) {
+ FrameSummary::JavaScriptFrameSummary const& java_script =
+ summary.AsJavaScript();
+ offset = java_script.code_offset();
+ script = Handle<Script>::cast(java_script.script());
+ } else if (summary.IsWasm()) {
+ FrameSummary::WasmFrameSummary const& wasm = summary.AsWasm();
+ offset = GetWasmFunctionOffset(wasm.wasm_instance()->module(),
+ wasm.function_index()) +
+ wasm.byte_offset();
+ script = wasm.script();
+
+ bool zeroth_frame = call_stack.empty();
+ if (!zeroth_frame) {
+ const NativeModule* native_module =
+ wasm.wasm_instance()->module_object().native_module();
+ offset = ReturnPc(native_module, offset);
+ }
+ }
+
+ if (offset > 0) {
+ call_stack.push_back(
+ {debug_context_id << 16 | script->id(), uint32_t(offset)});
+ }
+ }
+ break;
+ }
+
+ case StackFrame::BUILTIN_EXIT:
+ default:
+ // ignore the frame.
+ break;
+ }
+ }
+ if (call_stack.empty()) call_stack.push_back({1, 0});
+ return call_stack;
+}
+
+// static
+std::vector<FrameSummary> WasmModuleDebug::FindWasmFrame(
+ StackTraceFrameIterator* frame_it, uint32_t* frame_index) {
+ while (!frame_it->done()) {
+ StackFrame* const frame = frame_it->frame();
+ switch (frame->type()) {
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
+ case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
+ case StackFrame::OPTIMIZED:
+ case StackFrame::INTERPRETED:
+ case StackFrame::BUILTIN:
+ case StackFrame::WASM: {
+ // A standard frame may include many summarized frames, due to inlining.
+ std::vector<FrameSummary> frames;
+ CommonFrame::cast(frame)->Summarize(&frames);
+ const size_t frame_count = frames.size();
+ DCHECK_GT(frame_count, 0);
+
+ if (frame_count > *frame_index) {
+ if (frame_it->is_wasm())
+ return frames;
+ else
+ return {};
+ } else {
+ *frame_index -= frame_count;
+ frame_it->Advance();
+ }
+ break;
+ }
+
+ case StackFrame::BUILTIN_EXIT:
+ default:
+ // ignore the frame.
+ break;
+ }
+ }
+ return {};
+}
+
+// static
+Handle<WasmInstanceObject> WasmModuleDebug::GetWasmInstance(
+ Isolate* isolate, uint32_t frame_index) {
+ StackTraceFrameIterator frame_it(isolate);
+ std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+ if (frames.empty()) {
+ return Handle<WasmInstanceObject>::null();
+ }
+
+ int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+ const FrameSummary::WasmFrameSummary& summary =
+ frames[reversed_index].AsWasm();
+ return summary.wasm_instance();
+}
+
+// static
+bool WasmModuleDebug::GetWasmGlobal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size) {
+ HandleScope handles(isolate);
+
+ Handle<WasmInstanceObject> instance = GetWasmInstance(isolate, frame_index);
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
+ const wasm::WasmModule* module = module_object->module();
+ if (index < module->globals.size()) {
+ wasm::WasmValue wasm_value =
+ WasmInstanceObject::GetGlobalValue(instance, module->globals[index]);
+ return GetWasmValue(wasm_value, buffer, buffer_size, size);
+ }
+ }
+ return false;
+}
+
+// static
+bool WasmModuleDebug::GetWasmLocal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size) {
+ HandleScope handles(isolate);
+
+ StackTraceFrameIterator frame_it(isolate);
+ std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+ if (frames.empty()) {
+ return false;
+ }
+
+ int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+ const FrameSummary& summary = frames[reversed_index];
+ if (summary.IsWasm()) {
+ Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(),
+ isolate);
+ wasm::NativeModule* native_module = module_object->native_module();
+ DebugInfo* debug_info = native_module->GetDebugInfo();
+ if (static_cast<uint32_t>(debug_info->GetNumLocals(
+ isolate, frame_it.frame()->pc())) > index) {
+ wasm::WasmValue wasm_value = debug_info->GetLocalValue(
+ index, isolate, frame_it.frame()->pc(), frame_it.frame()->fp(),
+ frame_it.frame()->callee_fp());
+ return GetWasmValue(wasm_value, buffer, buffer_size, size);
+ }
+ }
+ }
+ return false;
+}
+
+// static
+bool WasmModuleDebug::GetWasmStackValue(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size) {
+ HandleScope handles(isolate);
+
+ StackTraceFrameIterator frame_it(isolate);
+ std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
+ if (frames.empty()) {
+ return false;
+ }
+
+ int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
+ const FrameSummary& summary = frames[reversed_index];
+ if (summary.IsWasm()) {
+ Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(),
+ isolate);
+ wasm::NativeModule* native_module = module_object->native_module();
+ DebugInfo* debug_info = native_module->GetDebugInfo();
+ if (static_cast<uint32_t>(debug_info->GetStackDepth(
+ isolate, frame_it.frame()->pc())) > index) {
+ WasmValue wasm_value = debug_info->GetStackValue(
+ index, isolate, frame_it.frame()->pc(), frame_it.frame()->fp(),
+ frame_it.frame()->callee_fp());
+ return GetWasmValue(wasm_value, buffer, buffer_size, size);
+ }
+ }
+ }
+ return false;
+}
+
+// static
+uint32_t WasmModuleDebug::GetWasmMemory(Isolate* isolate, uint32_t frame_index,
+ uint32_t offset, uint8_t* buffer,
+ uint32_t size) {
+ HandleScope handles(isolate);
+
+ uint32_t bytes_read = 0;
+ Handle<WasmInstanceObject> instance = GetWasmInstance(isolate, frame_index);
+ if (!instance.is_null()) {
+ uint8_t* mem_start = instance->memory_start();
+ size_t mem_size = instance->memory_size();
+ if (static_cast<uint64_t>(offset) + size <= mem_size) {
+ memcpy(buffer, mem_start + offset, size);
+ bytes_read = size;
+ } else if (offset < mem_size) {
+ bytes_read = static_cast<uint32_t>(mem_size) - offset;
+ memcpy(buffer, mem_start + offset, bytes_read);
+ }
+ }
+ return bytes_read;
+}
+
+uint32_t WasmModuleDebug::GetWasmModuleBytes(wasm_addr_t wasm_addr,
+ uint8_t* buffer, uint32_t size) {
+ uint32_t bytes_read = 0;
+ // Any instance will work.
+ Handle<WasmInstanceObject> instance = GetFirstWasmInstance();
+ if (!instance.is_null()) {
+ Handle<WasmModuleObject> module_object(instance->module_object(),
+ GetIsolate());
+ wasm::NativeModule* native_module = module_object->native_module();
+ const wasm::ModuleWireBytes wire_bytes(native_module->wire_bytes());
+ uint32_t offset = wasm_addr.Offset();
+ if (offset < wire_bytes.length()) {
+ uint32_t module_size = static_cast<uint32_t>(wire_bytes.length());
+ bytes_read = module_size - offset >= size ? size : module_size - offset;
+ memcpy(buffer, wire_bytes.start() + offset, bytes_read);
+ }
+ }
+ return bytes_read;
+}
+
+bool WasmModuleDebug::AddBreakpoint(uint32_t offset, int* breakpoint_id) {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ Handle<Script> script = Utils::OpenHandle(*wasm_script);
+ Handle<String> condition = GetIsolate()->factory()->empty_string();
+ int breakpoint_address = static_cast<int>(offset);
+ return GetIsolate()->debug()->SetBreakPointForScript(
+ script, condition, &breakpoint_address, breakpoint_id);
+}
+
+void WasmModuleDebug::RemoveBreakpoint(uint32_t offset, int breakpoint_id) {
+ v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
+ Handle<Script> script = Utils::OpenHandle(*wasm_script);
+ GetIsolate()->debug()->RemoveBreakpointForWasmScript(script, breakpoint_id);
+}
+
+void WasmModuleDebug::PrepareStep() {
+ i::Isolate* isolate = GetIsolate();
+ DebugScope debug_scope(isolate->debug());
+ debug::PrepareStep(reinterpret_cast<v8::Isolate*>(isolate),
+ debug::StepAction::StepIn);
+}
+
+template <typename T>
+bool StoreValue(const T& value, uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ *size = sizeof(value);
+ if (*size > buffer_size) return false;
+ memcpy(buffer, &value, *size);
+ return true;
+}
+
+// static
+bool WasmModuleDebug::GetWasmValue(const wasm::WasmValue& wasm_value,
+ uint8_t* buffer, uint32_t buffer_size,
+ uint32_t* size) {
+ switch (wasm_value.type().kind()) {
+ case wasm::kWasmI32.kind():
+ return StoreValue(wasm_value.to_i32(), buffer, buffer_size, size);
+ case wasm::kWasmI64.kind():
+ return StoreValue(wasm_value.to_i64(), buffer, buffer_size, size);
+ case wasm::kWasmF32.kind():
+ return StoreValue(wasm_value.to_f32(), buffer, buffer_size, size);
+ case wasm::kWasmF64.kind():
+ return StoreValue(wasm_value.to_f64(), buffer, buffer_size, size);
+ case wasm::kWasmS128.kind():
+ return StoreValue(wasm_value.to_s128(), buffer, buffer_size, size);
+
+ case wasm::kWasmStmt.kind():
+ case wasm::kWasmExternRef.kind():
+ case wasm::kWasmFuncRef.kind():
+ case wasm::kWasmExnRef.kind():
+ case wasm::kWasmBottom.kind():
+ default:
+ // Not supported
+ return false;
+ }
+}
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/wasm/gdb-server/wasm-module-debug.h b/src/debug/wasm/gdb-server/wasm-module-debug.h
new file mode 100644
index 0000000..10e6a5d
--- /dev/null
+++ b/src/debug/wasm/gdb-server/wasm-module-debug.h
@@ -0,0 +1,105 @@
+// Copyright 2020 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.
+
+#ifndef V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_
+#define V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_
+
+#include "src/debug/debug.h"
+#include "src/debug/wasm/gdb-server/gdb-remote-util.h"
+#include "src/execution/frames.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+
+class WasmValue;
+
+namespace gdb_server {
+
+// Represents the interface to access the Wasm engine state for a given module.
+// For the moment it only works with interpreted functions, in the future it
+// could be extended to also support Liftoff.
+class WasmModuleDebug {
+ public:
+ WasmModuleDebug(v8::Isolate* isolate, Local<debug::WasmScript> script);
+
+ std::string GetModuleName() const;
+ i::Isolate* GetIsolate() const {
+ return reinterpret_cast<i::Isolate*>(isolate_);
+ }
+
+ // Gets the value of the {index}th global value.
+ static bool GetWasmGlobal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Gets the value of the {index}th local value in the {frame_index}th stack
+ // frame.
+ static bool GetWasmLocal(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Gets the value of the {index}th value in the operand stack.
+ static bool GetWasmStackValue(Isolate* isolate, uint32_t frame_index,
+ uint32_t index, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ // Reads {size} bytes, starting from {offset}, from the Memory instance
+ // associated to this module.
+ // Returns the number of byte copied to {buffer}, or 0 is case of error.
+ // Note: only one Memory for Module is currently supported.
+ static uint32_t GetWasmMemory(Isolate* isolate, uint32_t frame_index,
+ uint32_t offset, uint8_t* buffer,
+ uint32_t size);
+
+ // Gets {size} bytes, starting from {offset}, from the Code space of this
+ // module.
+ // Returns the number of byte copied to {buffer}, or 0 is case of error.
+ uint32_t GetWasmModuleBytes(wasm_addr_t wasm_addr, uint8_t* buffer,
+ uint32_t size);
+
+ // Inserts a breakpoint at the offset {offset} of this module.
+ // Returns {true} if the breakpoint was successfully added.
+ bool AddBreakpoint(uint32_t offset, int* breakpoint_id);
+
+ // Removes a breakpoint at the offset {offset} of the this module.
+ void RemoveBreakpoint(uint32_t offset, int breakpoint_id);
+
+ // Handle stepping in wasm functions via the wasm interpreter.
+ void PrepareStep();
+
+ // Returns the current stack trace as a vector of instruction pointers.
+ static std::vector<wasm_addr_t> GetCallStack(uint32_t debug_context_id,
+ Isolate* isolate);
+
+ private:
+ // Returns the module WasmInstance associated to the {frame_index}th frame
+ // in the call stack.
+ static Handle<WasmInstanceObject> GetWasmInstance(Isolate* isolate,
+ uint32_t frame_index);
+
+ // Returns its first WasmInstance for this Wasm module.
+ Handle<WasmInstanceObject> GetFirstWasmInstance();
+
+ // Iterates on current stack frames and return frame information for the
+ // {frame_index} specified.
+ // Returns an empty array if the frame specified does not correspond to a Wasm
+ // stack frame.
+ static std::vector<FrameSummary> FindWasmFrame(
+ StackTraceFrameIterator* frame_it, uint32_t* frame_index);
+
+ // Converts a WasmValue into an array of bytes.
+ static bool GetWasmValue(const wasm::WasmValue& wasm_value, uint8_t* buffer,
+ uint32_t buffer_size, uint32_t* size);
+
+ v8::Isolate* isolate_;
+ Global<debug::WasmScript> wasm_script_;
+};
+
+} // namespace gdb_server
+} // namespace wasm
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_WASM_GDB_SERVER_WASM_MODULE_DEBUG_H_
diff --git a/src/debug/x64/debug-x64.cc b/src/debug/x64/debug-x64.cc
index 63689de..3d25c5f 100644
--- a/src/debug/x64/debug-x64.cc
+++ b/src/debug/x64/debug-x64.cc
@@ -6,95 +6,17 @@
#include "src/debug/debug.h"
-#include "src/assembler.h"
-#include "src/codegen.h"
+#include "src/codegen/assembler.h"
+#include "src/codegen/macro-assembler.h"
#include "src/debug/liveedit.h"
-#include "src/objects-inl.h"
+#include "src/execution/frames-inl.h"
+#include "src/objects/objects-inl.h"
namespace v8 {
namespace internal {
#define __ ACCESS_MASM(masm)
-
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_codesize;
- __ bind(&check_codesize);
- __ Nop(Assembler::kDebugBreakSlotLength);
- DCHECK_EQ(Assembler::kDebugBreakSlotLength,
- masm->SizeOfCodeGeneratedSince(&check_codesize));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction.
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotLength);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- static const int kSize = Assembler::kDebugBreakSlotLength;
- CodePatcher patcher(isolate, pc, kSize);
- Label check_codesize;
- patcher.masm()->bind(&check_codesize);
- patcher.masm()->movp(kScratchRegister, reinterpret_cast<void*>(code->entry()),
- Assembler::RelocInfoNone());
- patcher.masm()->call(kScratchRegister);
- // Check that the size of the code generated is as expected.
- DCHECK_EQ(kSize, patcher.masm()->SizeOfCodeGeneratedSince(&check_codesize));
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- return !Assembler::IsNop(pc);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
-
- // Enter an internal frame.
- {
- FrameScope scope(masm, StackFrame::INTERNAL);
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ Push(rax);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
-
- __ CallRuntime(Runtime::kDebugBreak, 1, kDontSaveFPRegs);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; ++i) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber rax if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function.
- if (!(reg.is(rax) && (mode == SAVE_RESULT_REGISTER))) {
- __ Set(reg, kDebugZapValue);
- }
- }
- }
- // Get rid of the internal frame.
- }
-
- __ MaybeDropFrames();
-
- // Return to caller.
- __ ret(0);
-}
-
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
@@ -112,17 +34,17 @@
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
- __ movp(rbp, rbx);
- __ movp(rdi, Operand(rbp, JavaScriptFrameConstants::kFunctionOffset));
+
+ __ movq(rbp, rbx);
+ __ movq(rdi, Operand(rbp, StandardFrameConstants::kFunctionOffset));
__ leave();
- __ movp(rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset));
- __ LoadSharedFunctionInfoSpecialField(
- rbx, rbx, SharedFunctionInfo::kFormalParameterCountOffset);
+ __ LoadTaggedPointerField(
+ rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset));
+ __ movzxwq(
+ rbx, FieldOperand(rbx, SharedFunctionInfo::kFormalParameterCountOffset));
- ParameterCount dummy(rbx);
- __ InvokeFunction(rdi, no_reg, dummy, dummy, JUMP_FUNCTION,
- CheckDebugStepCallWrapper());
+ __ InvokeFunction(rdi, no_reg, rbx, rbx, JUMP_FUNCTION);
}
const bool LiveEdit::kFrameDropperSupported = true;
diff --git a/src/debug/x87/OWNERS b/src/debug/x87/OWNERS
deleted file mode 100644
index 61245ae..0000000
--- a/src/debug/x87/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-weiliang.lin@intel.com
-chunyang.dai@intel.com
diff --git a/src/debug/x87/debug-x87.cc b/src/debug/x87/debug-x87.cc
deleted file mode 100644
index 8810f01..0000000
--- a/src/debug/x87/debug-x87.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-// 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.
-
-#if V8_TARGET_ARCH_X87
-
-#include "src/debug/debug.h"
-
-#include "src/codegen.h"
-#include "src/debug/liveedit.h"
-#include "src/x87/frames-x87.h"
-
-namespace v8 {
-namespace internal {
-
-#define __ ACCESS_MASM(masm)
-
-
-void EmitDebugBreakSlot(MacroAssembler* masm) {
- Label check_codesize;
- __ bind(&check_codesize);
- __ Nop(Assembler::kDebugBreakSlotLength);
- DCHECK_EQ(Assembler::kDebugBreakSlotLength,
- masm->SizeOfCodeGeneratedSince(&check_codesize));
-}
-
-
-void DebugCodegen::GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode) {
- // Generate enough nop's to make space for a call instruction.
- masm->RecordDebugBreakSlot(mode);
- EmitDebugBreakSlot(masm);
-}
-
-
-void DebugCodegen::ClearDebugBreakSlot(Isolate* isolate, Address pc) {
- CodePatcher patcher(isolate, pc, Assembler::kDebugBreakSlotLength);
- EmitDebugBreakSlot(patcher.masm());
-}
-
-
-void DebugCodegen::PatchDebugBreakSlot(Isolate* isolate, Address pc,
- Handle<Code> code) {
- DCHECK(code->is_debug_stub());
- static const int kSize = Assembler::kDebugBreakSlotLength;
- CodePatcher patcher(isolate, pc, kSize);
-
- // Add a label for checking the size of the code used for returning.
- Label check_codesize;
- patcher.masm()->bind(&check_codesize);
- patcher.masm()->call(code->entry(), RelocInfo::NONE32);
- // Check that the size of the code generated is as expected.
- DCHECK_EQ(kSize, patcher.masm()->SizeOfCodeGeneratedSince(&check_codesize));
-}
-
-bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
- return !Assembler::IsNop(pc);
-}
-
-void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
- DebugBreakCallHelperMode mode) {
- __ RecordComment("Debug break");
-
- // Enter an internal frame.
- {
- FrameScope scope(masm, StackFrame::INTERNAL);
-
- // Load padding words on stack.
- for (int i = 0; i < LiveEdit::kFramePaddingInitialSize; i++) {
- __ push(Immediate(Smi::FromInt(LiveEdit::kFramePaddingValue)));
- }
- __ push(Immediate(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
-
- // Push arguments for DebugBreak call.
- if (mode == SAVE_RESULT_REGISTER) {
- // Break on return.
- __ push(eax);
- } else {
- // Non-return breaks.
- __ Push(masm->isolate()->factory()->the_hole_value());
- }
- __ Move(eax, Immediate(1));
- __ mov(ebx,
- Immediate(ExternalReference(
- Runtime::FunctionForId(Runtime::kDebugBreak), masm->isolate())));
-
- CEntryStub ceb(masm->isolate(), 1);
- __ CallStub(&ceb);
-
- if (FLAG_debug_code) {
- for (int i = 0; i < kNumJSCallerSaved; ++i) {
- Register reg = {JSCallerSavedCode(i)};
- // Do not clobber eax if mode is SAVE_RESULT_REGISTER. It will
- // contain return value of the function.
- if (!(reg.is(eax) && (mode == SAVE_RESULT_REGISTER))) {
- __ Move(reg, Immediate(kDebugZapValue));
- }
- }
- }
-
- __ pop(ebx);
- // We divide stored value by 2 (untagging) and multiply it by word's size.
- STATIC_ASSERT(kSmiTagSize == 1 && kSmiShiftSize == 0);
- __ lea(esp, Operand(esp, ebx, times_half_pointer_size, 0));
-
- // Get rid of the internal frame.
- }
-
- // This call did not replace a call , so there will be an unwanted
- // return address left on the stack. Here we get rid of that.
- __ add(esp, Immediate(kPointerSize));
-
- // Now that the break point has been handled, resume normal execution by
- // jumping to the target address intended by the caller and that was
- // overwritten by the address of DebugBreakXXX.
- ExternalReference after_break_target =
- ExternalReference::debug_after_break_target_address(masm->isolate());
- __ jmp(Operand::StaticVariable(after_break_target));
-}
-
-
-void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
- // We do not know our frame height, but set esp based on ebp.
- __ lea(esp, Operand(ebp, FrameDropperFrameConstants::kFunctionOffset));
- __ pop(edi); // Function.
- __ add(esp, Immediate(-FrameDropperFrameConstants::kCodeOffset)); // INTERNAL
- // frame
- // marker
- // and code
- __ pop(ebp);
-
- ParameterCount dummy(0);
- __ CheckDebugHook(edi, no_reg, dummy, dummy);
-
- // Load context from the function.
- __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
-
- // Clear new.target register as a safety measure.
- __ mov(edx, masm->isolate()->factory()->undefined_value());
-
- // Get function code.
- __ mov(ebx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
- __ mov(ebx, FieldOperand(ebx, SharedFunctionInfo::kCodeOffset));
- __ lea(ebx, FieldOperand(ebx, Code::kHeaderSize));
-
- // Re-run JSFunction, edi is function, esi is context.
- __ jmp(ebx);
-}
-
-
-const bool LiveEdit::kFrameDropperSupported = true;
-
-#undef __
-
-} // namespace internal
-} // namespace v8
-
-#endif // V8_TARGET_ARCH_X87