Upgrade V8 to version 4.9.385.28
https://chromium.googlesource.com/v8/v8/+/4.9.385.28
FPIIM-449
Change-Id: I4b2e74289d4bf3667f2f3dc8aa2e541f63e26eb4
diff --git a/src/debug/OWNERS b/src/debug/OWNERS
new file mode 100644
index 0000000..cf18bd8
--- /dev/null
+++ b/src/debug/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+bmeurer@chromium.org
+mvstanton@chromium.org
+ulan@chromium.org
+verwaest@chromium.org
+yangguo@chromium.org
diff --git a/src/debug/arm/OWNERS b/src/debug/arm/OWNERS
new file mode 100644
index 0000000..906a5ce
--- /dev/null
+++ b/src/debug/arm/OWNERS
@@ -0,0 +1 @@
+rmcilroy@chromium.org
diff --git a/src/debug/arm/debug-arm.cc b/src/debug/arm/debug-arm.cc
new file mode 100644
index 0000000..2d4cbf1
--- /dev/null
+++ b/src/debug/arm/debug-arm.cc
@@ -0,0 +1,153 @@
+// 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_ARM
+
+#include "src/codegen.h"
+#include "src/debug/debug.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 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_EQ(Code::BUILTIN, code->kind());
+ 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);
+}
+
+
+void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
+ DebugBreakCallHelperMode mode) {
+ __ RecordComment("Debug break");
+ {
+ FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
+
+ // Load padding words on stack.
+ __ mov(ip, Operand(Smi::FromInt(LiveEdit::kFramePaddingValue)));
+ for (int i = 0; i < LiveEdit::kFramePaddingInitialSize; i++) {
+ __ push(ip);
+ }
+ __ mov(ip, Operand(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
+ __ push(ip);
+
+ if (mode == SAVE_RESULT_REGISTER) __ push(r0);
+
+ __ mov(r0, Operand::Zero()); // no arguments
+ __ 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)};
+ __ mov(reg, Operand(kDebugZapValue));
+ }
+ }
+
+ if (mode == SAVE_RESULT_REGISTER) __ pop(r0);
+
+ // Don't bother removing padding bytes pushed on the stack
+ // as the frame is going to be restored right away.
+
+ // Leave the internal frame.
+ }
+
+ // 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());
+ __ mov(ip, Operand(after_break_target));
+ __ ldr(ip, MemOperand(ip));
+ __ Jump(ip);
+}
+
+
+void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ // Load the function pointer off of our current stack frame.
+ __ ldr(r1, MemOperand(fp,
+ StandardFrameConstants::kConstantPoolOffset - kPointerSize));
+
+ // Pop return address, frame and constant pool pointer (if
+ // FLAG_enable_embedded_constant_pool).
+ __ LeaveFrame(StackFrame::INTERNAL);
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(r1, no_reg, dummy, dummy);
+
+ { ConstantPoolUnavailableScope constant_pool_unavailable(masm);
+ // Load context from the function.
+ __ ldr(cp, FieldMemOperand(r1, JSFunction::kContextOffset));
+
+ // Clear new.target as a safety measure.
+ __ LoadRoot(r3, Heap::kUndefinedValueRootIndex);
+
+ // Get function code.
+ __ ldr(ip, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
+ __ ldr(ip, FieldMemOperand(ip, SharedFunctionInfo::kCodeOffset));
+ __ add(ip, ip, Operand(Code::kHeaderSize - kHeapObjectTag));
+
+ // Re-run JSFunction, r1 is function, cp is context.
+ __ Jump(ip);
+ }
+}
+
+
+const bool LiveEdit::kFrameDropperSupported = true;
+
+#undef __
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_TARGET_ARCH_ARM
diff --git a/src/debug/arm64/OWNERS b/src/debug/arm64/OWNERS
new file mode 100644
index 0000000..906a5ce
--- /dev/null
+++ b/src/debug/arm64/OWNERS
@@ -0,0 +1 @@
+rmcilroy@chromium.org
diff --git a/src/debug/arm64/debug-arm64.cc b/src/debug/arm64/debug-arm64.cc
new file mode 100644
index 0000000..c2b60a9
--- /dev/null
+++ b/src/debug/arm64/debug-arm64.cc
@@ -0,0 +1,161 @@
+// Copyright 2013 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_ARM64
+
+#include "src/arm64/frames-arm64.h"
+#include "src/codegen.h"
+#include "src/debug/debug.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_EQ(Code::BUILTIN, code->kind());
+ 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);
+}
+
+
+void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
+ DebugBreakCallHelperMode mode) {
+ __ RecordComment("Debug break");
+ Register scratch = x10;
+ {
+ FrameScope scope(masm, StackFrame::INTERNAL);
+
+ // Load padding words on stack.
+ __ Mov(scratch, Smi::FromInt(LiveEdit::kFramePaddingValue));
+ __ PushMultipleTimes(scratch, LiveEdit::kFramePaddingInitialSize);
+ __ Mov(scratch, Smi::FromInt(LiveEdit::kFramePaddingInitialSize));
+ __ Push(scratch);
+
+ if (mode == SAVE_RESULT_REGISTER) __ Push(x0);
+
+ __ Mov(x0, 0); // No arguments.
+ __ 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));
+ __ Mov(reg, Operand(kDebugZapValue));
+ }
+ }
+
+ // Restore the register values from the expression stack.
+ if (mode == SAVE_RESULT_REGISTER) __ Pop(x0);
+
+ // Don't bother removing padding bytes pushed on the stack
+ // as the frame is going to be restored right away.
+
+ // Leave the internal frame.
+ }
+
+ // 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());
+ __ Mov(scratch, after_break_target);
+ __ Ldr(scratch, MemOperand(scratch));
+ __ Br(scratch);
+}
+
+
+void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ // We do not know our frame height, but set sp based on fp.
+ __ Sub(masm->StackPointer(), fp, kPointerSize);
+ __ AssertStackConsistency();
+
+ __ Pop(x1, fp, lr); // Function, Frame, Return address.
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(x1, no_reg, dummy, dummy);
+
+ UseScratchRegisterScope temps(masm);
+ Register scratch = temps.AcquireX();
+
+ // Load context from the function.
+ __ Ldr(cp, FieldMemOperand(x1, JSFunction::kContextOffset));
+
+ // Clear new.target as a safety measure.
+ __ LoadRoot(x3, Heap::kUndefinedValueRootIndex);
+
+ // Get function code.
+ __ Ldr(scratch, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
+ __ Ldr(scratch, FieldMemOperand(scratch, SharedFunctionInfo::kCodeOffset));
+ __ Add(scratch, scratch, Code::kHeaderSize - kHeapObjectTag);
+
+ // Re-run JSFunction, x1 is function, cp is context.
+ __ Br(scratch);
+}
+
+
+const bool LiveEdit::kFrameDropperSupported = true;
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_TARGET_ARCH_ARM64
diff --git a/src/debug/debug-evaluate.cc b/src/debug/debug-evaluate.cc
new file mode 100644
index 0000000..e19b93e
--- /dev/null
+++ b/src/debug/debug-evaluate.cc
@@ -0,0 +1,407 @@
+// Copyright 2015 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-evaluate.h"
+
+#include "src/accessors.h"
+#include "src/contexts.h"
+#include "src/debug/debug.h"
+#include "src/debug/debug-frames.h"
+#include "src/debug/debug-scopes.h"
+#include "src/frames-inl.h"
+#include "src/isolate-inl.h"
+
+namespace v8 {
+namespace internal {
+
+static inline bool IsDebugContext(Isolate* isolate, Context* context) {
+ return context->native_context() == *isolate->debug()->debug_context();
+}
+
+
+MaybeHandle<Object> DebugEvaluate::Global(
+ Isolate* isolate, Handle<String> source, bool disable_break,
+ Handle<HeapObject> context_extension) {
+ // Handle the processing of break.
+ DisableBreak disable_break_scope(isolate->debug(), disable_break);
+
+ // 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();
+ }
+ 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, context_extension, receiver,
+ source);
+}
+
+
+MaybeHandle<Object> DebugEvaluate::Local(Isolate* isolate,
+ StackFrame::Id frame_id,
+ int inlined_jsframe_index,
+ Handle<String> source,
+ bool disable_break,
+ Handle<HeapObject> context_extension) {
+ // Handle the processing of break.
+ DisableBreak disable_break_scope(isolate->debug(), disable_break);
+
+ // Get the frame where the debugging is performed.
+ JavaScriptFrameIterator it(isolate, frame_id);
+ JavaScriptFrame* frame = it.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
+ // the materialized object are written back afterwards.
+ // Note that the native context is taken from the original context chain,
+ // which may not be the current native context of the isolate.
+ ContextBuilder context_builder(isolate, frame, inlined_jsframe_index);
+ if (isolate->has_pending_exception()) return MaybeHandle<Object>();
+
+ Handle<Context> context = context_builder.native_context();
+ Handle<JSObject> receiver(context->global_proxy());
+ MaybeHandle<Object> maybe_result = Evaluate(
+ isolate, context_builder.outer_info(),
+ context_builder.innermost_context(), context_extension, receiver, source);
+ if (!maybe_result.is_null() && !FLAG_debug_eval_readonly_locals) {
+ context_builder.UpdateValues();
+ }
+ return maybe_result;
+}
+
+
+// Compile and evaluate source for the given context.
+MaybeHandle<Object> DebugEvaluate::Evaluate(
+ Isolate* isolate, Handle<SharedFunctionInfo> outer_info,
+ Handle<Context> context, Handle<HeapObject> context_extension,
+ Handle<Object> receiver, Handle<String> source) {
+ if (context_extension->IsJSObject()) {
+ Handle<JSObject> extension = Handle<JSObject>::cast(context_extension);
+ Handle<JSFunction> closure(context->closure(), isolate);
+ context = isolate->factory()->NewWithContext(closure, context, extension);
+ }
+
+ Handle<JSFunction> eval_fun;
+ ASSIGN_RETURN_ON_EXCEPTION(isolate, eval_fun,
+ Compiler::GetFunctionFromEval(
+ source, outer_info, context, SLOPPY,
+ NO_PARSE_RESTRICTION, RelocInfo::kNoPosition),
+ Object);
+
+ Handle<Object> result;
+ 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, result);
+ // TODO(verwaest): This will crash when the global proxy is detached.
+ result = PrototypeIterator::GetCurrent<JSObject>(iter);
+ }
+
+ return result;
+}
+
+
+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 =
+ handle(JSFunction::cast(frame_inspector.GetFunction()));
+ Handle<Context> outer_context(local_function->context());
+ native_context_ = Handle<Context>(outer_context->native_context());
+ Handle<JSFunction> global_function(native_context_->closure());
+ outer_info_ = handle(global_function->shared());
+ Handle<Context> inner_context;
+
+ bool stop = false;
+
+ // Iterate the original context chain to create a context chain that reflects
+ // our needs. The original context chain may look like this:
+ // <native context> <outer contexts> <function context> <inner contexts>
+ // In the resulting context chain, we want to materialize the receiver,
+ // the parameters of the current function, the stack locals. We only
+ // materialize context variables that the function already references,
+ // because only for those variables we can be sure that they will be resolved
+ // correctly. Variables that are not referenced by the function may be
+ // context-allocated and thus accessible, but may be shadowed by stack-
+ // allocated variables and the resolution would be incorrect.
+ // The result will look like this:
+ // <native context> <receiver context>
+ // <materialized stack and accessible context vars> <inner contexts>
+ // All contexts use the closure of the native context, since there is no
+ // function context in the chain. Variables that cannot be resolved are
+ // bound to toplevel (script contexts or global object).
+ // Once debug-evaluate has been executed, the changes to the materialized
+ // objects are written back to the original context chain. Any changes to
+ // the original context chain will therefore be overwritten.
+ const ScopeIterator::Option option = ScopeIterator::COLLECT_NON_LOCALS;
+ for (ScopeIterator it(isolate, &frame_inspector, option);
+ !it.Failed() && !it.Done() && !stop; it.Next()) {
+ ScopeIterator::ScopeType scope_type = it.Type();
+ if (scope_type == ScopeIterator::ScopeTypeLocal) {
+ DCHECK_EQ(FUNCTION_SCOPE, it.CurrentScopeInfo()->scope_type());
+ it.GetNonLocals(&non_locals_);
+ Handle<Context> local_context =
+ it.HasContext() ? it.CurrentContext() : outer_context;
+
+ // The "this" binding, if any, can't be bound via "with". If we need
+ // to, add another node onto the outer context to bind "this".
+ Handle<Context> receiver_context =
+ MaterializeReceiver(native_context_, local_context, local_function,
+ global_function, it.ThisIsNonLocal());
+
+ Handle<JSObject> materialized_function = NewJSObjectWithNullProto();
+ frame_inspector.MaterializeStackLocals(materialized_function,
+ local_function);
+ MaterializeArgumentsObject(materialized_function, local_function);
+ MaterializeContextChain(materialized_function, local_context);
+
+ Handle<Context> with_context = isolate->factory()->NewWithContext(
+ global_function, receiver_context, materialized_function);
+
+ ContextChainElement context_chain_element;
+ context_chain_element.original_context = local_context;
+ context_chain_element.materialized_object = materialized_function;
+ context_chain_element.scope_info = it.CurrentScopeInfo();
+ context_chain_.Add(context_chain_element);
+
+ stop = true;
+ RecordContextsInChain(&inner_context, receiver_context, with_context);
+ } else if (scope_type == ScopeIterator::ScopeTypeCatch ||
+ scope_type == ScopeIterator::ScopeTypeWith) {
+ Handle<Context> cloned_context = Handle<Context>::cast(
+ isolate->factory()->CopyFixedArray(it.CurrentContext()));
+
+ ContextChainElement context_chain_element;
+ context_chain_element.original_context = it.CurrentContext();
+ context_chain_element.cloned_context = cloned_context;
+ context_chain_.Add(context_chain_element);
+
+ RecordContextsInChain(&inner_context, cloned_context, cloned_context);
+ } else if (scope_type == ScopeIterator::ScopeTypeBlock) {
+ Handle<JSObject> materialized_object = NewJSObjectWithNullProto();
+ frame_inspector.MaterializeStackLocals(materialized_object,
+ it.CurrentScopeInfo());
+ if (it.HasContext()) {
+ Handle<Context> cloned_context = Handle<Context>::cast(
+ isolate->factory()->CopyFixedArray(it.CurrentContext()));
+ Handle<Context> with_context = isolate->factory()->NewWithContext(
+ global_function, cloned_context, materialized_object);
+
+ ContextChainElement context_chain_element;
+ context_chain_element.original_context = it.CurrentContext();
+ context_chain_element.cloned_context = cloned_context;
+ context_chain_element.materialized_object = materialized_object;
+ context_chain_element.scope_info = it.CurrentScopeInfo();
+ context_chain_.Add(context_chain_element);
+
+ RecordContextsInChain(&inner_context, cloned_context, with_context);
+ } else {
+ Handle<Context> with_context = isolate->factory()->NewWithContext(
+ global_function, outer_context, materialized_object);
+
+ ContextChainElement context_chain_element;
+ context_chain_element.materialized_object = materialized_object;
+ context_chain_element.scope_info = it.CurrentScopeInfo();
+ context_chain_.Add(context_chain_element);
+
+ RecordContextsInChain(&inner_context, with_context, with_context);
+ }
+ } else {
+ stop = true;
+ }
+ }
+ if (innermost_context_.is_null()) {
+ innermost_context_ = outer_context;
+ }
+ DCHECK(!innermost_context_.is_null());
+}
+
+
+void DebugEvaluate::ContextBuilder::UpdateValues() {
+ // TODO(yangguo): remove updating values.
+ for (int i = 0; i < context_chain_.length(); i++) {
+ ContextChainElement element = context_chain_[i];
+ if (!element.original_context.is_null() &&
+ !element.cloned_context.is_null()) {
+ Handle<Context> cloned_context = element.cloned_context;
+ cloned_context->CopyTo(
+ Context::MIN_CONTEXT_SLOTS, *element.original_context,
+ Context::MIN_CONTEXT_SLOTS,
+ cloned_context->length() - Context::MIN_CONTEXT_SLOTS);
+ }
+ 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);
+ if (element.scope_info->scope_type() == FUNCTION_SCOPE) {
+ DCHECK_EQ(context_chain_.length() - 1, i);
+ UpdateContextChainFromMaterializedObject(element.materialized_object,
+ element.original_context);
+ }
+ }
+ }
+}
+
+
+Handle<JSObject> DebugEvaluate::ContextBuilder::NewJSObjectWithNullProto() {
+ Handle<JSObject> result =
+ isolate_->factory()->NewJSObject(isolate_->object_function());
+ Handle<Map> new_map =
+ Map::Copy(Handle<Map>(result->map()), "ObjectWithNullProto");
+ Map::SetPrototype(new_map, isolate_->factory()->null_value());
+ JSObject::MigrateToMap(result, new_map);
+ return result;
+}
+
+
+void DebugEvaluate::ContextBuilder::RecordContextsInChain(
+ Handle<Context>* inner_context, Handle<Context> first,
+ Handle<Context> last) {
+ if (!inner_context->is_null()) {
+ (*inner_context)->set_previous(*last);
+ } else {
+ innermost_context_ = last;
+ }
+ *inner_context = first;
+}
+
+
+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_function()) 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 =
+ Handle<JSObject>::cast(Accessors::FunctionGetArguments(function));
+ Handle<String> arguments_str = isolate_->factory()->arguments_string();
+ JSObject::SetOwnPropertyIgnoreAttributes(target, arguments_str, arguments,
+ NONE)
+ .Check();
+}
+
+
+MaybeHandle<Object> DebugEvaluate::ContextBuilder::LoadFromContext(
+ Handle<Context> context, Handle<String> name, bool* global) {
+ static const ContextLookupFlags flags = FOLLOW_CONTEXT_CHAIN;
+ int index;
+ PropertyAttributes attributes;
+ BindingFlags binding;
+ Handle<Object> holder =
+ context->Lookup(name, flags, &index, &attributes, &binding);
+ if (holder.is_null()) return MaybeHandle<Object>();
+ Handle<Object> value;
+ if (index != Context::kNotFound) { // Found on context.
+ Handle<Context> context = Handle<Context>::cast(holder);
+ // Do not shadow variables on the script context.
+ *global = context->IsScriptContext();
+ return Handle<Object>(context->get(index), isolate_);
+ } else { // Found on object.
+ Handle<JSReceiver> object = Handle<JSReceiver>::cast(holder);
+ // Do not shadow properties on the global object.
+ *global = object->IsJSGlobalObject();
+ return JSReceiver::GetDataProperty(object, name);
+ }
+}
+
+
+void DebugEvaluate::ContextBuilder::MaterializeContextChain(
+ Handle<JSObject> target, Handle<Context> context) {
+ for (const Handle<String>& name : non_locals_) {
+ HandleScope scope(isolate_);
+ Handle<Object> value;
+ bool global;
+ if (!LoadFromContext(context, name, &global).ToHandle(&value) || global) {
+ // If resolving the variable fails, skip it. If it resolves to a global
+ // variable, skip it as well since it's not read-only and can be resolved
+ // within debug-evaluate.
+ continue;
+ }
+ JSObject::SetOwnPropertyIgnoreAttributes(target, name, value, NONE).Check();
+ }
+}
+
+
+void DebugEvaluate::ContextBuilder::StoreToContext(Handle<Context> context,
+ Handle<String> name,
+ Handle<Object> value) {
+ static const ContextLookupFlags flags = FOLLOW_CONTEXT_CHAIN;
+ int index;
+ PropertyAttributes attributes;
+ BindingFlags binding;
+ Handle<Object> holder =
+ context->Lookup(name, flags, &index, &attributes, &binding);
+ if (holder.is_null()) return;
+ if (attributes & READ_ONLY) return;
+ if (index != Context::kNotFound) { // Found on context.
+ Handle<Context> context = Handle<Context>::cast(holder);
+ context->set(index, *value);
+ } else { // Found on object.
+ Handle<JSReceiver> object = Handle<JSReceiver>::cast(holder);
+ LookupIterator lookup(object, name);
+ if (lookup.state() != LookupIterator::DATA) return;
+ CHECK(JSReceiver::SetDataProperty(&lookup, value).FromJust());
+ }
+}
+
+
+void DebugEvaluate::ContextBuilder::UpdateContextChainFromMaterializedObject(
+ Handle<JSObject> source, Handle<Context> context) {
+ // TODO(yangguo): check whether overwriting context fields is actually safe
+ // wrt fields we consider constant.
+ for (const Handle<String>& name : non_locals_) {
+ HandleScope scope(isolate_);
+ Handle<Object> value = JSReceiver::GetDataProperty(source, name);
+ StoreToContext(context, name, value);
+ }
+}
+
+
+Handle<Context> DebugEvaluate::ContextBuilder::MaterializeReceiver(
+ Handle<Context> parent_context, Handle<Context> lookup_context,
+ Handle<JSFunction> local_function, Handle<JSFunction> global_function,
+ bool this_is_non_local) {
+ Handle<Object> receiver = isolate_->factory()->undefined_value();
+ Handle<String> this_string = isolate_->factory()->this_string();
+ if (this_is_non_local) {
+ bool global;
+ LoadFromContext(lookup_context, this_string, &global).ToHandle(&receiver);
+ } else if (local_function->shared()->scope_info()->HasReceiver()) {
+ receiver = handle(frame_->receiver(), isolate_);
+ }
+ return isolate_->factory()->NewCatchContext(global_function, parent_context,
+ this_string, receiver);
+}
+
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug-evaluate.h b/src/debug/debug-evaluate.h
new file mode 100644
index 0000000..c0b1f02
--- /dev/null
+++ b/src/debug/debug-evaluate.h
@@ -0,0 +1,118 @@
+// Copyright 2015 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_EVALUATE_H_
+#define V8_DEBUG_DEBUG_EVALUATE_H_
+
+#include "src/frames.h"
+#include "src/objects.h"
+
+namespace v8 {
+namespace internal {
+
+class DebugEvaluate : public AllStatic {
+ public:
+ static MaybeHandle<Object> Global(Isolate* isolate, Handle<String> source,
+ bool disable_break,
+ Handle<HeapObject> context_extension);
+
+ // 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,
+ int inlined_jsframe_index,
+ Handle<String> source, bool disable_break,
+ Handle<HeapObject> context_extension);
+
+ private:
+ // This class builds a context chain for evaluation of expressions
+ // in debugger.
+ // The scope chain leading up to a breakpoint where evaluation occurs
+ // looks like:
+ // - [a mix of with, catch and block scopes]
+ // - [function stack + context]
+ // - [outer context]
+ // The builder materializes all stack variables into properties of objects;
+ // the expression is then evaluated as if it is inside a series of 'with'
+ // statements using those objects. To this end, the builder builds a new
+ // context chain, based on a scope chain:
+ // - every With and Catch scope begets a cloned context
+ // - Block scope begets one or two contexts:
+ // - if a block has context-allocated varaibles, its context is cloned
+ // - stack locals are materizalized as a With context
+ // - Local scope begets a With context for materizalized locals, chained to
+ // original function context. Original function context is the end of
+ // the chain.
+ class ContextBuilder {
+ public:
+ ContextBuilder(Isolate* isolate, JavaScriptFrame* frame,
+ int inlined_jsframe_index);
+
+ void UpdateValues();
+
+ Handle<Context> innermost_context() const { return innermost_context_; }
+ Handle<Context> native_context() const { return native_context_; }
+ Handle<SharedFunctionInfo> outer_info() const { return outer_info_; }
+
+ private:
+ struct ContextChainElement {
+ Handle<Context> original_context;
+ Handle<Context> cloned_context;
+ Handle<JSObject> materialized_object;
+ Handle<ScopeInfo> scope_info;
+ };
+
+ void RecordContextsInChain(Handle<Context>* inner_context,
+ Handle<Context> first, Handle<Context> last);
+
+ Handle<JSObject> NewJSObjectWithNullProto();
+
+ // Helper function to find or create the arguments object for
+ // Runtime_DebugEvaluate.
+ void MaterializeArgumentsObject(Handle<JSObject> target,
+ Handle<JSFunction> function);
+
+ void MaterializeContextChain(Handle<JSObject> target,
+ Handle<Context> context);
+
+ void UpdateContextChainFromMaterializedObject(Handle<JSObject> source,
+ Handle<Context> context);
+
+ Handle<Context> MaterializeReceiver(Handle<Context> parent_context,
+ Handle<Context> lookup_context,
+ Handle<JSFunction> local_function,
+ Handle<JSFunction> global_function,
+ bool this_is_non_local);
+
+ MaybeHandle<Object> LoadFromContext(Handle<Context> context,
+ Handle<String> name, bool* global);
+
+ void StoreToContext(Handle<Context> context, Handle<String> name,
+ Handle<Object> value);
+
+ Handle<SharedFunctionInfo> outer_info_;
+ Handle<Context> innermost_context_;
+ Handle<Context> native_context_;
+ List<ContextChainElement> context_chain_;
+ List<Handle<String> > non_locals_;
+ Isolate* isolate_;
+ JavaScriptFrame* frame_;
+ int inlined_jsframe_index_;
+ };
+
+ static MaybeHandle<Object> Evaluate(Isolate* isolate,
+ Handle<SharedFunctionInfo> outer_info,
+ Handle<Context> context,
+ Handle<HeapObject> context_extension,
+ Handle<Object> receiver,
+ Handle<String> source);
+};
+
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_EVALUATE_H_
diff --git a/src/debug/debug-frames.cc b/src/debug/debug-frames.cc
new file mode 100644
index 0000000..012d291
--- /dev/null
+++ b/src/debug/debug-frames.cc
@@ -0,0 +1,215 @@
+// Copyright 2015 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-frames.h"
+
+#include "src/frames-inl.h"
+
+namespace v8 {
+namespace internal {
+
+FrameInspector::FrameInspector(JavaScriptFrame* frame,
+ int inlined_jsframe_index, Isolate* isolate)
+ : frame_(frame), deoptimized_frame_(NULL), isolate_(isolate) {
+ has_adapted_arguments_ = frame_->has_adapted_arguments();
+ is_bottommost_ = inlined_jsframe_index == 0;
+ is_optimized_ = frame_->is_optimized();
+ // Calculate the deoptimized frame.
+ if (frame->is_optimized()) {
+ // TODO(turbofan): Revisit once we support deoptimization.
+ if (frame->LookupCode()->is_turbofanned() &&
+ frame->function()->shared()->asm_function() &&
+ !FLAG_turbo_asm_deoptimization) {
+ is_optimized_ = false;
+ return;
+ }
+
+ deoptimized_frame_ = Deoptimizer::DebuggerInspectableFrame(
+ frame, inlined_jsframe_index, isolate);
+ }
+}
+
+
+FrameInspector::~FrameInspector() {
+ // Get rid of the calculated deoptimized frame if any.
+ if (deoptimized_frame_ != NULL) {
+ Deoptimizer::DeleteDebuggerInspectableFrame(deoptimized_frame_, isolate_);
+ }
+}
+
+
+int FrameInspector::GetParametersCount() {
+ return is_optimized_ ? deoptimized_frame_->parameters_count()
+ : frame_->ComputeParametersCount();
+}
+
+
+Object* FrameInspector::GetFunction() {
+ return is_optimized_ ? deoptimized_frame_->GetFunction() : frame_->function();
+}
+
+
+Object* FrameInspector::GetParameter(int index) {
+ return is_optimized_ ? deoptimized_frame_->GetParameter(index)
+ : frame_->GetParameter(index);
+}
+
+
+Object* FrameInspector::GetExpression(int index) {
+ // TODO(turbofan): Revisit once we support deoptimization.
+ if (frame_->LookupCode()->is_turbofanned() &&
+ frame_->function()->shared()->asm_function() &&
+ !FLAG_turbo_asm_deoptimization) {
+ return isolate_->heap()->undefined_value();
+ }
+ return is_optimized_ ? deoptimized_frame_->GetExpression(index)
+ : frame_->GetExpression(index);
+}
+
+
+int FrameInspector::GetSourcePosition() {
+ return is_optimized_ ? deoptimized_frame_->GetSourcePosition()
+ : frame_->LookupCode()->SourcePosition(frame_->pc());
+}
+
+
+bool FrameInspector::IsConstructor() {
+ return is_optimized_ && !is_bottommost_
+ ? deoptimized_frame_->HasConstructStub()
+ : frame_->IsConstructor();
+}
+
+
+Object* FrameInspector::GetContext() {
+ return is_optimized_ ? deoptimized_frame_->GetContext() : frame_->context();
+}
+
+
+// To inspect all the provided arguments the frame might need to be
+// replaced with the arguments frame.
+void FrameInspector::SetArgumentsFrame(JavaScriptFrame* frame) {
+ DCHECK(has_adapted_arguments_);
+ frame_ = frame;
+ is_optimized_ = frame_->is_optimized();
+ DCHECK(!is_optimized_);
+}
+
+
+// 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 (ParameterIsShadowedByContextLocal(scope_info, name)) continue;
+
+ Handle<Object> value(i < GetParametersCount()
+ ? GetParameter(i)
+ : isolate_->heap()->undefined_value(),
+ isolate_);
+ DCHECK(!value->IsTheHole());
+
+ JSObject::SetOwnPropertyIgnoreAttributes(target, name, value, NONE).Check();
+ }
+
+ // Second fill all stack locals.
+ for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
+ if (scope_info->LocalIsSynthetic(i)) continue;
+ Handle<String> name(scope_info->StackLocalName(i));
+ Handle<Object> value(GetExpression(scope_info->StackLocalIndex(i)),
+ isolate_);
+ if (value->IsTheHole()) 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) {
+ if (is_optimized_) {
+ // Optimized frames are not supported. Simply give up.
+ 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 (ParameterIsShadowedByContextLocal(scope_info, name)) continue;
+
+ DCHECK(!frame_->GetParameter(i)->IsTheHole());
+ Handle<Object> value =
+ Object::GetPropertyOrElement(target, name).ToHandleChecked();
+ frame_->SetParameterValue(i, *value);
+ }
+
+ // Stack locals.
+ for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
+ if (scope_info->LocalIsSynthetic(i)) continue;
+ int index = scope_info->StackLocalIndex(i);
+ if (frame_->GetExpression(index)->IsTheHole()) continue;
+ Handle<Object> value =
+ Object::GetPropertyOrElement(
+ target, handle(scope_info->StackLocalName(i), isolate_))
+ .ToHandleChecked();
+ frame_->SetExpression(index, *value);
+ }
+}
+
+
+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;
+}
+
+
+SaveContext* DebugFrameHelper::FindSavedContextForFrame(
+ Isolate* isolate, JavaScriptFrame* frame) {
+ SaveContext* save = isolate->save_context();
+ while (save != NULL && !save->IsBelowFrame(frame)) {
+ save = save->prev();
+ }
+ DCHECK(save != NULL);
+ return save;
+}
+
+
+int DebugFrameHelper::FindIndexedNonNativeFrame(JavaScriptFrameIterator* 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].function()->shared()->IsSubjectToDebugging()) continue;
+ if (++count == index) return i;
+ }
+ }
+ return -1;
+}
+
+
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug-frames.h b/src/debug/debug-frames.h
new file mode 100644
index 0000000..c0d20bb
--- /dev/null
+++ b/src/debug/debug-frames.h
@@ -0,0 +1,80 @@
+// Copyright 2015 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_FRAMES_H_
+#define V8_DEBUG_DEBUG_FRAMES_H_
+
+#include "src/deoptimizer.h"
+#include "src/frames.h"
+#include "src/isolate.h"
+#include "src/objects.h"
+
+namespace v8 {
+namespace internal {
+
+class FrameInspector {
+ public:
+ FrameInspector(JavaScriptFrame* frame, int inlined_jsframe_index,
+ Isolate* isolate);
+
+ ~FrameInspector();
+
+ int GetParametersCount();
+ Object* GetFunction();
+ Object* GetParameter(int index);
+ Object* GetExpression(int index);
+ int GetSourcePosition();
+ bool IsConstructor();
+ Object* GetContext();
+
+ JavaScriptFrame* GetArgumentsFrame() { return frame_; }
+ void SetArgumentsFrame(JavaScriptFrame* frame);
+
+ void MaterializeStackLocals(Handle<JSObject> target,
+ Handle<ScopeInfo> scope_info);
+
+ void MaterializeStackLocals(Handle<JSObject> target,
+ Handle<JSFunction> function);
+
+ void UpdateStackLocalsFromMaterializedObject(Handle<JSObject> object,
+ Handle<ScopeInfo> scope_info);
+
+ private:
+ bool ParameterIsShadowedByContextLocal(Handle<ScopeInfo> info,
+ Handle<String> parameter_name);
+
+ JavaScriptFrame* frame_;
+ DeoptimizedFrameInfo* deoptimized_frame_;
+ Isolate* isolate_;
+ bool is_optimized_;
+ bool is_bottommost_;
+ bool has_adapted_arguments_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameInspector);
+};
+
+
+class DebugFrameHelper : public AllStatic {
+ public:
+ static SaveContext* FindSavedContextForFrame(Isolate* isolate,
+ JavaScriptFrame* 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(JavaScriptFrameIterator* it, int index);
+
+ // 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);
+ }
+
+ static StackFrame::Id UnwrapFrameId(int wrapped) {
+ return static_cast<StackFrame::Id>(wrapped << 2);
+ }
+};
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_FRAMES_H_
diff --git a/src/debug/debug-scopes.cc b/src/debug/debug-scopes.cc
new file mode 100644
index 0000000..15a0594
--- /dev/null
+++ b/src/debug/debug-scopes.cc
@@ -0,0 +1,825 @@
+// Copyright 2015 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-scopes.h"
+
+#include "src/ast/scopes.h"
+#include "src/debug/debug.h"
+#include "src/frames-inl.h"
+#include "src/globals.h"
+#include "src/isolate-inl.h"
+#include "src/parsing/parser.h"
+
+namespace v8 {
+namespace internal {
+
+ScopeIterator::ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector,
+ ScopeIterator::Option option)
+ : isolate_(isolate),
+ frame_inspector_(frame_inspector),
+ nested_scope_chain_(4),
+ non_locals_(nullptr),
+ seen_script_scope_(false),
+ failed_(false) {
+ if (!frame_inspector->GetContext()->IsContext() ||
+ !frame_inspector->GetFunction()->IsJSFunction()) {
+ // Optimized frame, context or function cannot be materialized. Give up.
+ return;
+ }
+
+ context_ = Handle<Context>(Context::cast(frame_inspector->GetContext()));
+
+ // 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() == isolate->heap()->undefined_value()) {
+ while (context_->closure() == *function) {
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ }
+ 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()) {
+ // 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());
+
+ // PC points to the instruction after the current one, possibly a break
+ // location as well. So the "- 1" to exclude it from the search.
+ Address call_pc = GetFrame()->pc() - 1;
+
+ // Find the break point where execution has stopped.
+ BreakLocation location = BreakLocation::FromAddress(debug_info, call_pc);
+
+ 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_);
+ }
+ }
+ if (scope_info->scope_type() == FUNCTION_SCOPE) {
+ nested_scope_chain_.Add(scope_info);
+ }
+ if (!collect_non_locals) return;
+ }
+
+ // Reparse the code and analyze the scopes.
+ Scope* scope = NULL;
+ // Check whether we are in global, eval or function code.
+ Zone zone;
+ if (scope_info->scope_type() != FUNCTION_SCOPE) {
+ // Global or eval code.
+ Handle<Script> script(Script::cast(shared_info->script()));
+ ParseInfo info(&zone, script);
+ if (scope_info->scope_type() == SCRIPT_SCOPE) {
+ info.set_global();
+ } else {
+ DCHECK(scope_info->scope_type() == EVAL_SCOPE);
+ info.set_eval();
+ info.set_context(Handle<Context>(function->context()));
+ }
+ if (Parser::ParseStatic(&info) && Scope::Analyze(&info)) {
+ scope = info.literal()->scope();
+ }
+ if (!ignore_nested_scopes) RetrieveScopeChain(scope);
+ if (collect_non_locals) CollectNonLocals(scope);
+ } else {
+ // Function code
+ ParseInfo info(&zone, function);
+ if (Parser::ParseStatic(&info) && Scope::Analyze(&info)) {
+ scope = info.literal()->scope();
+ }
+ if (!ignore_nested_scopes) RetrieveScopeChain(scope);
+ if (collect_non_locals) CollectNonLocals(scope);
+ }
+}
+
+
+ScopeIterator::ScopeIterator(Isolate* isolate, Handle<JSFunction> function)
+ : isolate_(isolate),
+ frame_inspector_(NULL),
+ context_(function->context()),
+ non_locals_(nullptr),
+ seen_script_scope_(false),
+ failed_(false) {
+ if (!function->shared()->IsSubjectToDebugging()) context_ = Handle<Context>();
+}
+
+
+MUST_USE_RESULT MaybeHandle<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);
+ details->set(kScopeDetailsObjectIndex, *scope_object);
+ if (HasContext() && CurrentContext()->closure() != NULL) {
+ Handle<String> closure_name = JSFunction::GetDebugName(
+ Handle<JSFunction>(CurrentContext()->closure()));
+ if (!closure_name.is_null() && (closure_name->length() != 0))
+ details->set(kScopeDetailsNameIndex, *closure_name);
+ }
+ return isolate_->factory()->NewJSArrayWithElements(details);
+}
+
+
+void ScopeIterator::Next() {
+ DCHECK(!failed_);
+ ScopeType scope_type = Type();
+ if (scope_type == ScopeTypeGlobal) {
+ // The global scope is always the last in the chain.
+ DCHECK(context_->IsNativeContext());
+ context_ = Handle<Context>();
+ return;
+ }
+ if (scope_type == ScopeTypeScript) {
+ seen_script_scope_ = true;
+ if (context_->IsScriptContext()) {
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ }
+ if (!nested_scope_chain_.is_empty()) {
+ DCHECK_EQ(nested_scope_chain_.last()->scope_type(), SCRIPT_SCOPE);
+ nested_scope_chain_.RemoveLast();
+ DCHECK(nested_scope_chain_.is_empty());
+ }
+ CHECK(context_->IsNativeContext());
+ return;
+ }
+ if (nested_scope_chain_.is_empty()) {
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ } else {
+ if (nested_scope_chain_.last()->HasContext()) {
+ DCHECK(context_->previous() != NULL);
+ context_ = Handle<Context>(context_->previous(), isolate_);
+ }
+ nested_scope_chain_.RemoveLast();
+ }
+}
+
+
+// Return the type of the current scope.
+ScopeIterator::ScopeType ScopeIterator::Type() {
+ DCHECK(!failed_);
+ if (!nested_scope_chain_.is_empty()) {
+ Handle<ScopeInfo> scope_info = nested_scope_chain_.last();
+ switch (scope_info->scope_type()) {
+ case FUNCTION_SCOPE:
+ DCHECK(context_->IsFunctionContext() || !scope_info->HasContext());
+ return ScopeTypeLocal;
+ case MODULE_SCOPE:
+ DCHECK(context_->IsModuleContext());
+ return ScopeTypeModule;
+ case SCRIPT_SCOPE:
+ DCHECK(context_->IsScriptContext() || context_->IsNativeContext());
+ return ScopeTypeScript;
+ case WITH_SCOPE:
+ DCHECK(context_->IsWithContext());
+ return ScopeTypeWith;
+ case CATCH_SCOPE:
+ DCHECK(context_->IsCatchContext());
+ return ScopeTypeCatch;
+ case BLOCK_SCOPE:
+ DCHECK(!scope_info->HasContext() || context_->IsBlockContext());
+ return ScopeTypeBlock;
+ case EVAL_SCOPE:
+ UNREACHABLE();
+ }
+ }
+ if (context_->IsNativeContext()) {
+ 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()) {
+ return ScopeTypeClosure;
+ }
+ if (context_->IsCatchContext()) {
+ return ScopeTypeCatch;
+ }
+ if (context_->IsBlockContext()) {
+ return ScopeTypeBlock;
+ }
+ if (context_->IsModuleContext()) {
+ return ScopeTypeModule;
+ }
+ if (context_->IsScriptContext()) {
+ return ScopeTypeScript;
+ }
+ DCHECK(context_->IsWithContext());
+ return ScopeTypeWith;
+}
+
+
+MaybeHandle<JSObject> ScopeIterator::ScopeObject() {
+ DCHECK(!failed_);
+ 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 the with object.
+ // TODO(neis): This breaks for proxies.
+ return handle(JSObject::cast(CurrentContext()->extension_receiver()));
+ case ScopeIterator::ScopeTypeCatch:
+ return MaterializeCatchScope();
+ case ScopeIterator::ScopeTypeClosure:
+ // Materialize the content of the closure scope into a JSObject.
+ return MaterializeClosure();
+ case ScopeIterator::ScopeTypeBlock:
+ return MaterializeBlockScope();
+ case ScopeIterator::ScopeTypeModule:
+ return MaterializeModuleScope();
+ }
+ UNREACHABLE();
+ return Handle<JSObject>();
+}
+
+
+bool ScopeIterator::HasContext() {
+ ScopeType type = Type();
+ if (type == ScopeTypeBlock || type == ScopeTypeLocal) {
+ if (!nested_scope_chain_.is_empty()) {
+ return nested_scope_chain_.last()->HasContext();
+ }
+ }
+ return true;
+}
+
+
+bool ScopeIterator::SetVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value) {
+ DCHECK(!failed_);
+ switch (Type()) {
+ case ScopeIterator::ScopeTypeGlobal:
+ 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:
+ return SetBlockVariableValue(variable_name, new_value);
+ case ScopeIterator::ScopeTypeModule:
+ // TODO(2399): should we implement it?
+ break;
+ }
+ return false;
+}
+
+
+Handle<ScopeInfo> ScopeIterator::CurrentScopeInfo() {
+ DCHECK(!failed_);
+ if (!nested_scope_chain_.is_empty()) {
+ return nested_scope_chain_.last();
+ } else if (context_->IsBlockContext()) {
+ return Handle<ScopeInfo>(context_->scope_info());
+ } else if (context_->IsFunctionContext()) {
+ return Handle<ScopeInfo>(context_->closure()->shared()->scope_info());
+ }
+ return Handle<ScopeInfo>::null();
+}
+
+
+Handle<Context> ScopeIterator::CurrentContext() {
+ DCHECK(!failed_);
+ if (Type() == ScopeTypeGlobal || Type() == ScopeTypeScript ||
+ nested_scope_chain_.is_empty()) {
+ return context_;
+ } else if (nested_scope_chain_.last()->HasContext()) {
+ return context_;
+ } else {
+ return Handle<Context>();
+ }
+}
+
+
+void ScopeIterator::GetNonLocals(List<Handle<String> >* list_out) {
+ Handle<String> this_string = isolate_->factory()->this_string();
+ for (HashMap::Entry* entry = non_locals_->Start(); entry != nullptr;
+ entry = non_locals_->Next(entry)) {
+ Handle<String> name(reinterpret_cast<String**>(entry->key));
+ // We need to treat "this" differently.
+ if (name.is_identical_to(this_string)) continue;
+ list_out->Add(Handle<String>(reinterpret_cast<String**>(entry->key)));
+ }
+}
+
+
+bool ScopeIterator::ThisIsNonLocal() {
+ Handle<String> this_string = isolate_->factory()->this_string();
+ void* key = reinterpret_cast<void*>(this_string.location());
+ HashMap::Entry* entry = non_locals_->Lookup(key, this_string->Hash());
+ return entry != nullptr;
+}
+
+
+#ifdef DEBUG
+// Debug print of the content of the current scope.
+void ScopeIterator::DebugPrint() {
+ OFStream os(stdout);
+ DCHECK(!failed_);
+ switch (Type()) {
+ case ScopeIterator::ScopeTypeGlobal:
+ os << "Global:\n";
+ CurrentContext()->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);
+ }
+ }
+ }
+ break;
+ }
+
+ case ScopeIterator::ScopeTypeWith:
+ os << "With:\n";
+ CurrentContext()->extension()->Print(os);
+ break;
+
+ case ScopeIterator::ScopeTypeCatch:
+ os << "Catch:\n";
+ CurrentContext()->extension()->Print(os);
+ CurrentContext()->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);
+ }
+ }
+ break;
+
+ case ScopeIterator::ScopeTypeScript:
+ os << "Script:\n";
+ CurrentContext()
+ ->global_object()
+ ->native_context()
+ ->script_context_table()
+ ->Print(os);
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+ PrintF("\n");
+}
+#endif
+
+
+void ScopeIterator::RetrieveScopeChain(Scope* scope) {
+ if (scope != NULL) {
+ int source_position = frame_inspector_->GetSourcePosition();
+ scope->GetNestedScopeChain(isolate_, &nested_scope_chain_, source_position);
+ } 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.
+ DCHECK(isolate_->has_pending_exception());
+ failed_ = true;
+ }
+}
+
+
+void ScopeIterator::CollectNonLocals(Scope* scope) {
+ if (scope != NULL) {
+ DCHECK_NULL(non_locals_);
+ non_locals_ = new HashMap(InternalizedStringMatch);
+ scope->CollectNonLocals(non_locals_);
+ }
+}
+
+
+MaybeHandle<JSObject> ScopeIterator::MaterializeScriptScope() {
+ Handle<JSGlobalObject> global(CurrentContext()->global_object());
+ Handle<ScriptContextTable> script_contexts(
+ global->native_context()->script_context_table());
+
+ Handle<JSObject> script_scope =
+ isolate_->factory()->NewJSObject(isolate_->object_function());
+
+ 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);
+ }
+ return script_scope;
+}
+
+
+MaybeHandle<JSObject> ScopeIterator::MaterializeLocalScope() {
+ Handle<JSFunction> function = GetFunction();
+
+ Handle<JSObject> local_scope =
+ isolate_->factory()->NewJSObject(isolate_->object_function());
+ frame_inspector_->MaterializeStackLocals(local_scope, function);
+
+ Handle<Context> frame_context(Context::cast(frame_inspector_->GetContext()));
+
+ HandleScope scope(isolate_);
+ Handle<SharedFunctionInfo> shared(function->shared());
+ Handle<ScopeInfo> scope_info(shared->scope_info());
+
+ if (!scope_info->HasContext()) return local_scope;
+
+ // Third fill all context locals.
+ Handle<Context> function_context(frame_context->declaration_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->has_extension() &&
+ !function_context->IsNativeContext()) {
+ bool success = CopyContextExtensionToScopeObject(
+ handle(function_context->extension_object(), isolate_),
+ local_scope, JSReceiver::INCLUDE_PROTOS);
+ if (!success) return MaybeHandle<JSObject>();
+ }
+
+ 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());
+
+ 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()->NewJSObject(isolate_->object_function());
+
+ // 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.
+ if (context->has_extension()) {
+ bool success = CopyContextExtensionToScopeObject(
+ handle(context->extension_object(), isolate_), closure_scope,
+ JSReceiver::OWN_ONLY);
+ DCHECK(success);
+ USE(success);
+ }
+
+ return closure_scope;
+}
+
+
+// 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()->NewJSObject(isolate_->object_function());
+ JSObject::SetOwnPropertyIgnoreAttributes(catch_scope, name, thrown_object,
+ NONE)
+ .Check();
+ return catch_scope;
+}
+
+
+// Create a plain JSObject which materializes the block scope for the specified
+// block context.
+Handle<JSObject> ScopeIterator::MaterializeBlockScope() {
+ Handle<JSObject> block_scope =
+ isolate_->factory()->NewJSObject(isolate_->object_function());
+
+ Handle<Context> context = Handle<Context>::null();
+ if (!nested_scope_chain_.is_empty()) {
+ Handle<ScopeInfo> scope_info = nested_scope_chain_.last();
+ frame_inspector_->MaterializeStackLocals(block_scope, scope_info);
+ if (scope_info->HasContext()) context = CurrentContext();
+ } else {
+ context = CurrentContext();
+ }
+
+ if (!context.is_null()) {
+ // Fill all context locals.
+ CopyContextLocalsToScopeObject(handle(context->scope_info()),
+ context, block_scope);
+ // Fill all extension variables.
+ if (context->extension_object() != nullptr) {
+ bool success = CopyContextExtensionToScopeObject(
+ handle(context->extension_object()), block_scope,
+ JSReceiver::OWN_ONLY);
+ DCHECK(success);
+ USE(success);
+ }
+ }
+ return block_scope;
+}
+
+
+// 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());
+
+ // Allocate and initialize a JSObject with all the members of the debugged
+ // module.
+ Handle<JSObject> module_scope =
+ isolate_->factory()->NewJSObject(isolate_->object_function());
+
+ // Fill all context locals.
+ CopyContextLocalsToScopeObject(scope_info, context, module_scope);
+
+ return module_scope;
+}
+
+
+// Set the context local variable value.
+bool ScopeIterator::SetContextLocalValue(Handle<ScopeInfo> scope_info,
+ Handle<Context> context,
+ Handle<String> variable_name,
+ Handle<Object> new_value) {
+ 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;
+ }
+ }
+
+ return false;
+}
+
+
+bool ScopeIterator::SetLocalVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value) {
+ JavaScriptFrame* frame = GetFrame();
+ // Optimized frames are not supported.
+ if (frame->is_optimized()) return false;
+
+ Handle<JSFunction> function(frame->function());
+ Handle<SharedFunctionInfo> shared(function->shared());
+ Handle<ScopeInfo> scope_info(shared->scope_info());
+
+ bool default_result = false;
+
+ // Parameters.
+ for (int i = 0; i < scope_info->ParameterCount(); ++i) {
+ HandleScope scope(isolate_);
+ if (String::Equals(handle(scope_info->ParameterName(i)), variable_name)) {
+ frame->SetParameterValue(i, *new_value);
+ // Argument might be shadowed in heap context, don't stop here.
+ default_result = true;
+ }
+ }
+
+ // Stack locals.
+ for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
+ HandleScope scope(isolate_);
+ if (String::Equals(handle(scope_info->StackLocalName(i)), variable_name)) {
+ frame->SetExpression(scope_info->StackLocalIndex(i), *new_value);
+ return true;
+ }
+ }
+
+ if (scope_info->HasContext()) {
+ // Context locals.
+ Handle<Context> frame_context(Context::cast(frame->context()));
+ Handle<Context> function_context(frame_context->declaration_context());
+ if (SetContextLocalValue(scope_info, function_context, variable_name,
+ new_value)) {
+ return true;
+ }
+
+ // Function context extension. These are variables introduced by eval.
+ if (function_context->closure() == *function) {
+ if (function_context->has_extension() &&
+ !function_context->IsNativeContext()) {
+ Handle<JSObject> ext(function_context->extension_object());
+
+ Maybe<bool> maybe = JSReceiver::HasProperty(ext, variable_name);
+ DCHECK(maybe.IsJust());
+ if (maybe.FromJust()) {
+ // We don't expect this to do anything except replacing
+ // property value.
+ Runtime::SetObjectProperty(isolate_, ext, variable_name, new_value,
+ SLOPPY)
+ .Assert();
+ return true;
+ }
+ }
+ }
+ }
+
+ return default_result;
+}
+
+
+bool ScopeIterator::SetBlockVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value) {
+ Handle<ScopeInfo> scope_info = CurrentScopeInfo();
+ JavaScriptFrame* frame = GetFrame();
+
+ for (int i = 0; i < scope_info->StackLocalCount(); ++i) {
+ HandleScope scope(isolate_);
+ if (String::Equals(handle(scope_info->StackLocalName(i)), variable_name)) {
+ frame->SetExpression(scope_info->StackLocalIndex(i), *new_value);
+ return true;
+ }
+ }
+
+ if (HasContext()) {
+ Handle<Context> context = CurrentContext();
+ if (SetContextLocalValue(scope_info, context, variable_name, new_value)) {
+ return true;
+ }
+
+ Handle<JSObject> ext(context->extension_object(), isolate_);
+ if (!ext.is_null()) {
+ 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;
+}
+
+
+// This method copies structure of MaterializeClosure method above.
+bool ScopeIterator::SetClosureVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value) {
+ Handle<Context> context = CurrentContext();
+ DCHECK(context->IsFunctionContext());
+
+ // Context locals to the context extension.
+ Handle<SharedFunctionInfo> shared(context->closure()->shared());
+ Handle<ScopeInfo> scope_info(shared->scope_info());
+ if (SetContextLocalValue(scope_info, context, variable_name, new_value)) {
+ return true;
+ }
+
+ // Properties from the function context extension. This will
+ // be variables introduced by eval.
+ if (context->has_extension()) {
+ Handle<JSObject> ext(JSObject::cast(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::SetScriptVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value) {
+ Handle<Context> context = CurrentContext();
+ Handle<ScriptContextTable> script_contexts(
+ context->global_object()->native_context()->script_context_table());
+ ScriptContextTable::LookupResult lookup_result;
+ if (ScriptContextTable::Lookup(script_contexts, variable_name,
+ &lookup_result)) {
+ Handle<Context> script_context = ScriptContextTable::GetContext(
+ script_contexts, lookup_result.context_index);
+ script_context->set(lookup_result.slot_index, *new_value);
+ return true;
+ }
+
+ 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.
+ int first_context_var = scope_info->StackLocalCount();
+ int start = scope_info->ContextLocalNameEntriesIndex();
+ for (int i = 0; i < local_count; ++i) {
+ if (scope_info->LocalIsSynthetic(first_context_var + i)) 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()) continue;
+ // This should always succeed.
+ // TODO(verwaest): Use AddDataProperty instead.
+ JSObject::SetOwnPropertyIgnoreAttributes(
+ scope_object, handle(String::cast(scope_info->get(i + start))), value,
+ NONE)
+ .Check();
+ }
+}
+
+
+bool ScopeIterator::CopyContextExtensionToScopeObject(
+ Handle<JSObject> extension, Handle<JSObject> scope_object,
+ JSReceiver::KeyCollectionType type) {
+ Handle<FixedArray> keys;
+ ASSIGN_RETURN_ON_EXCEPTION_VALUE(
+ isolate_, keys, JSReceiver::GetKeys(extension, type, ENUMERABLE_STRINGS),
+ false);
+
+ 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;
+ ASSIGN_RETURN_ON_EXCEPTION_VALUE(
+ isolate_, value, Object::GetPropertyOrElement(extension, key), false);
+ RETURN_ON_EXCEPTION_VALUE(
+ isolate_, JSObject::SetOwnPropertyIgnoreAttributes(
+ scope_object, key, value, NONE), false);
+ }
+ return true;
+}
+
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug-scopes.h b/src/debug/debug-scopes.h
new file mode 100644
index 0000000..d4e335a
--- /dev/null
+++ b/src/debug/debug-scopes.h
@@ -0,0 +1,150 @@
+// Copyright 2015 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_SCOPES_H_
+#define V8_DEBUG_DEBUG_SCOPES_H_
+
+#include "src/debug/debug-frames.h"
+#include "src/frames.h"
+
+namespace v8 {
+namespace internal {
+
+// Iterate over the actual scopes visible from a stack frame or from a closure.
+// The iteration proceeds from the innermost visible nested scope outwards.
+// All scopes are backed by an actual context except the local scope,
+// which is inserted "artificially" in the context chain.
+class ScopeIterator {
+ public:
+ enum ScopeType {
+ ScopeTypeGlobal = 0,
+ ScopeTypeLocal,
+ ScopeTypeWith,
+ ScopeTypeClosure,
+ ScopeTypeCatch,
+ ScopeTypeBlock,
+ ScopeTypeScript,
+ ScopeTypeModule
+ };
+
+ static const int kScopeDetailsTypeIndex = 0;
+ static const int kScopeDetailsObjectIndex = 1;
+ static const int kScopeDetailsNameIndex = 2;
+ static const int kScopeDetailsSize = 3;
+
+ enum Option { DEFAULT, IGNORE_NESTED_SCOPES, COLLECT_NON_LOCALS };
+
+ ScopeIterator(Isolate* isolate, FrameInspector* frame_inspector,
+ Option options = DEFAULT);
+
+ ScopeIterator(Isolate* isolate, Handle<JSFunction> function);
+
+ ~ScopeIterator() { delete non_locals_; }
+
+ MUST_USE_RESULT MaybeHandle<JSObject> MaterializeScopeDetails();
+
+ // More scopes?
+ bool Done() {
+ DCHECK(!failed_);
+ return context_.is_null();
+ }
+
+ bool Failed() { return failed_; }
+
+ // Move to the next scope.
+ void Next();
+
+ // Return the type of the current scope.
+ ScopeType Type();
+
+ // Return the JavaScript object with the content of the current scope.
+ MaybeHandle<JSObject> ScopeObject();
+
+ bool HasContext();
+
+ // 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();
+
+ // Populate the list with collected non-local variable names.
+ void GetNonLocals(List<Handle<String> >* list_out);
+
+ bool ThisIsNonLocal();
+
+#ifdef DEBUG
+ // Debug print of the content of the current scope.
+ void DebugPrint();
+#endif
+
+ private:
+ Isolate* isolate_;
+ FrameInspector* const frame_inspector_;
+ Handle<Context> context_;
+ List<Handle<ScopeInfo> > nested_scope_chain_;
+ HashMap* non_locals_;
+ bool seen_script_scope_;
+ bool failed_;
+
+ inline JavaScriptFrame* GetFrame() {
+ return frame_inspector_->GetArgumentsFrame();
+ }
+
+ inline Handle<JSFunction> GetFunction() {
+ return Handle<JSFunction>(
+ JSFunction::cast(frame_inspector_->GetFunction()));
+ }
+
+ static bool InternalizedStringMatch(void* key1, void* key2) {
+ Handle<String> s1(reinterpret_cast<String**>(key1));
+ Handle<String> s2(reinterpret_cast<String**>(key2));
+ DCHECK(s1->IsInternalizedString());
+ DCHECK(s2->IsInternalizedString());
+ return s1.is_identical_to(s2);
+ }
+
+ void RetrieveScopeChain(Scope* scope);
+
+ void CollectNonLocals(Scope* scope);
+
+ 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> MaterializeBlockScope();
+
+ bool SetLocalVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value);
+ bool SetBlockVariableValue(Handle<String> variable_name,
+ Handle<Object> new_value);
+ bool SetClosureVariableValue(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 SetContextLocalValue(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);
+ bool CopyContextExtensionToScopeObject(Handle<JSObject> extension,
+ Handle<JSObject> scope_object,
+ JSReceiver::KeyCollectionType type);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ScopeIterator);
+};
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_SCOPES_H_
diff --git a/src/debug/debug.cc b/src/debug/debug.cc
new file mode 100644
index 0000000..bd45b71
--- /dev/null
+++ b/src/debug/debug.cc
@@ -0,0 +1,2414 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/debug.h"
+
+#include "src/api.h"
+#include "src/arguments.h"
+#include "src/bootstrapper.h"
+#include "src/code-stubs.h"
+#include "src/codegen.h"
+#include "src/compilation-cache.h"
+#include "src/compiler.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/isolate-inl.h"
+#include "src/list.h"
+#include "src/log.h"
+#include "src/messages.h"
+#include "src/snapshot/natives.h"
+
+#include "include/v8-debug.h"
+
+namespace v8 {
+namespace internal {
+
+Debug::Debug(Isolate* isolate)
+ : debug_context_(Handle<Context>()),
+ event_listener_(Handle<Object>()),
+ event_listener_data_(Handle<Object>()),
+ message_handler_(NULL),
+ command_received_(0),
+ command_queue_(isolate->logger(), kQueueInitialSize),
+ is_active_(false),
+ is_suppressed_(false),
+ live_edit_enabled_(true), // TODO(yangguo): set to false by default.
+ break_disabled_(false),
+ break_points_active_(true),
+ in_debug_event_listener_(false),
+ break_on_exception_(false),
+ break_on_uncaught_exception_(false),
+ debug_info_list_(NULL),
+ feature_tracker_(isolate),
+ isolate_(isolate) {
+ ThreadInit();
+}
+
+
+static 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);
+}
+
+
+BreakLocation::BreakLocation(Handle<DebugInfo> debug_info, RelocInfo* rinfo,
+ int position, int statement_position)
+ : debug_info_(debug_info),
+ pc_offset_(static_cast<int>(rinfo->pc() - debug_info->code()->entry())),
+ rmode_(rinfo->rmode()),
+ data_(rinfo->data()),
+ position_(position),
+ statement_position_(statement_position) {}
+
+
+BreakLocation::Iterator::Iterator(Handle<DebugInfo> debug_info,
+ BreakLocatorType type)
+ : debug_info_(debug_info),
+ reloc_iterator_(debug_info->code(), GetModeMask(type)),
+ break_index_(-1),
+ position_(1),
+ statement_position_(1) {
+ if (!Done()) Next();
+}
+
+
+int BreakLocation::Iterator::GetModeMask(BreakLocatorType type) {
+ int mask = 0;
+ mask |= RelocInfo::ModeMask(RelocInfo::POSITION);
+ mask |= RelocInfo::ModeMask(RelocInfo::STATEMENT_POSITION);
+ mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_RETURN);
+ mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_CALL);
+ if (type == ALL_BREAK_LOCATIONS) {
+ mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_POSITION);
+ mask |= RelocInfo::ModeMask(RelocInfo::DEBUGGER_STATEMENT);
+ }
+ return mask;
+}
+
+
+void BreakLocation::Iterator::Next() {
+ DisallowHeapAllocation no_gc;
+ DCHECK(!Done());
+
+ // Iterate through reloc info for code and original code stopping at each
+ // breakable code target.
+ bool first = break_index_ == -1;
+ while (!Done()) {
+ if (!first) reloc_iterator_.next();
+ first = false;
+ if (Done()) return;
+
+ // Whenever a statement position or (plain) position is passed update the
+ // current value of these.
+ if (RelocInfo::IsPosition(rmode())) {
+ if (RelocInfo::IsStatementPosition(rmode())) {
+ statement_position_ = static_cast<int>(
+ rinfo()->data() - debug_info_->shared()->start_position());
+ }
+ // Always update the position as we don't want that to be before the
+ // statement position.
+ position_ = static_cast<int>(rinfo()->data() -
+ debug_info_->shared()->start_position());
+ DCHECK(position_ >= 0);
+ DCHECK(statement_position_ >= 0);
+ continue;
+ }
+
+ DCHECK(RelocInfo::IsDebugBreakSlot(rmode()) ||
+ RelocInfo::IsDebuggerStatement(rmode()));
+
+ if (RelocInfo::IsDebugBreakSlotAtReturn(rmode())) {
+ // Set the positions to the end of the function.
+ if (debug_info_->shared()->HasSourceCode()) {
+ position_ = debug_info_->shared()->end_position() -
+ debug_info_->shared()->start_position() - 1;
+ } else {
+ position_ = 0;
+ }
+ statement_position_ = position_;
+ }
+
+ break;
+ }
+ break_index_++;
+}
+
+
+// Find the break point at the supplied address, or the closest one before
+// the address.
+BreakLocation BreakLocation::FromAddress(Handle<DebugInfo> debug_info,
+ Address pc) {
+ Iterator it(debug_info, ALL_BREAK_LOCATIONS);
+ it.SkipTo(BreakIndexFromAddress(debug_info, pc));
+ return it.GetBreakLocation();
+}
+
+
+// Find the break point at the supplied address, or the closest one before
+// the address.
+void BreakLocation::FromAddressSameStatement(Handle<DebugInfo> debug_info,
+ Address pc,
+ List<BreakLocation>* result_out) {
+ int break_index = BreakIndexFromAddress(debug_info, pc);
+ Iterator it(debug_info, ALL_BREAK_LOCATIONS);
+ it.SkipTo(break_index);
+ int statement_position = it.statement_position();
+ while (!it.Done() && it.statement_position() == statement_position) {
+ result_out->Add(it.GetBreakLocation());
+ it.Next();
+ }
+}
+
+
+int BreakLocation::BreakIndexFromAddress(Handle<DebugInfo> debug_info,
+ Address pc) {
+ // Run through all break points to locate the one closest to the address.
+ int closest_break = 0;
+ int distance = kMaxInt;
+ for (Iterator it(debug_info, ALL_BREAK_LOCATIONS); !it.Done(); it.Next()) {
+ // Check if this break point is closer that what was previously found.
+ if (it.pc() <= pc && pc - it.pc() < distance) {
+ closest_break = it.break_index();
+ distance = static_cast<int>(pc - it.pc());
+ // Check whether we can't get any closer.
+ if (distance == 0) break;
+ }
+ }
+ return closest_break;
+}
+
+
+BreakLocation BreakLocation::FromPosition(Handle<DebugInfo> debug_info,
+ int position,
+ BreakPositionAlignment alignment) {
+ // Run through all break points to locate the one closest to the source
+ // position.
+ int closest_break = 0;
+ int distance = kMaxInt;
+
+ for (Iterator it(debug_info, ALL_BREAK_LOCATIONS); !it.Done(); it.Next()) {
+ int next_position;
+ if (alignment == STATEMENT_ALIGNED) {
+ next_position = it.statement_position();
+ } else {
+ DCHECK(alignment == BREAK_POSITION_ALIGNED);
+ next_position = it.position();
+ }
+ if (position <= next_position && next_position - position < distance) {
+ closest_break = it.break_index();
+ distance = next_position - position;
+ // Check whether we can't get any closer.
+ if (distance == 0) break;
+ }
+ }
+
+ Iterator it(debug_info, ALL_BREAK_LOCATIONS);
+ it.SkipTo(closest_break);
+ return it.GetBreakLocation();
+}
+
+
+void BreakLocation::SetBreakPoint(Handle<Object> break_point_object) {
+ // If there is not already a real break point here patch code with debug
+ // break.
+ if (!HasBreakPoint()) SetDebugBreak();
+ DCHECK(IsDebugBreak() || IsDebuggerStatement());
+ // Set the break point information.
+ DebugInfo::SetBreakPoint(debug_info_, pc_offset_, position_,
+ statement_position_, break_point_object);
+}
+
+
+void BreakLocation::ClearBreakPoint(Handle<Object> break_point_object) {
+ // Clear the break point information.
+ DebugInfo::ClearBreakPoint(debug_info_, pc_offset_, break_point_object);
+ // If there are no more break points here remove the debug break.
+ if (!HasBreakPoint()) {
+ ClearDebugBreak();
+ DCHECK(!IsDebugBreak());
+ }
+}
+
+
+void BreakLocation::SetOneShot() {
+ // Debugger statement always calls debugger. No need to modify it.
+ if (IsDebuggerStatement()) return;
+
+ // If there is a real break point here no more to do.
+ if (HasBreakPoint()) {
+ DCHECK(IsDebugBreak());
+ return;
+ }
+
+ // Patch code with debug break.
+ SetDebugBreak();
+}
+
+
+void BreakLocation::ClearOneShot() {
+ // Debugger statement always calls debugger. No need to modify it.
+ if (IsDebuggerStatement()) return;
+
+ // If there is a real break point here no more to do.
+ if (HasBreakPoint()) {
+ DCHECK(IsDebugBreak());
+ return;
+ }
+
+ // Patch code removing debug break.
+ ClearDebugBreak();
+ DCHECK(!IsDebugBreak());
+}
+
+
+void BreakLocation::SetDebugBreak() {
+ // Debugger statement always calls debugger. No need to modify it.
+ if (IsDebuggerStatement()) return;
+
+ // If there is already a break point here just return. This might happen if
+ // the same code is flooded with break points twice. Flooding the same
+ // function twice might happen when stepping in a function with an exception
+ // handler as the handler and the function is the same.
+ if (IsDebugBreak()) return;
+
+ DCHECK(IsDebugBreakSlot());
+ Isolate* isolate = debug_info_->GetIsolate();
+ Builtins* builtins = isolate->builtins();
+ Handle<Code> target =
+ IsReturn() ? builtins->Return_DebugBreak() : builtins->Slot_DebugBreak();
+ DebugCodegen::PatchDebugBreakSlot(isolate, pc(), target);
+ DCHECK(IsDebugBreak());
+}
+
+
+void BreakLocation::ClearDebugBreak() {
+ // Debugger statement always calls debugger. No need to modify it.
+ if (IsDebuggerStatement()) return;
+
+ DCHECK(IsDebugBreakSlot());
+ DebugCodegen::ClearDebugBreakSlot(debug_info_->GetIsolate(), pc());
+ DCHECK(!IsDebugBreak());
+}
+
+
+bool BreakLocation::IsDebugBreak() const {
+ if (IsDebuggerStatement()) return false;
+ DCHECK(IsDebugBreakSlot());
+ return rinfo().IsPatchedDebugBreakSlotSequence();
+}
+
+
+Handle<Object> BreakLocation::BreakPointObjects() const {
+ return debug_info_->GetBreakPointObjects(pc_offset_);
+}
+
+
+void DebugFeatureTracker::Track(DebugFeatureTracker::Feature feature) {
+ uint32_t mask = 1 << feature;
+ // Only count one sample per feature and isolate.
+ if (bitfield_ & mask) return;
+ isolate_->counters()->debug_feature_usage()->AddSample(feature);
+ bitfield_ |= mask;
+}
+
+
+// Threading support.
+void Debug::ThreadInit() {
+ thread_local_.break_count_ = 0;
+ thread_local_.break_id_ = 0;
+ thread_local_.break_frame_id_ = StackFrame::NO_ID;
+ thread_local_.last_step_action_ = StepNone;
+ thread_local_.last_statement_position_ = RelocInfo::kNoPosition;
+ thread_local_.last_fp_ = 0;
+ thread_local_.target_fp_ = 0;
+ thread_local_.step_in_enabled_ = false;
+ // TODO(isolates): frames_are_dropped_?
+ base::NoBarrier_Store(&thread_local_.current_debug_scope_,
+ static_cast<base::AtomicWord>(0));
+}
+
+
+char* Debug::ArchiveDebug(char* storage) {
+ char* to = storage;
+ MemCopy(to, reinterpret_cast<char*>(&thread_local_), sizeof(ThreadLocal));
+ ThreadInit();
+ return storage + ArchiveSpacePerThread();
+}
+
+
+char* Debug::RestoreDebug(char* storage) {
+ char* from = storage;
+ MemCopy(reinterpret_cast<char*>(&thread_local_), from, sizeof(ThreadLocal));
+ return storage + ArchiveSpacePerThread();
+}
+
+
+int Debug::ArchiveSpacePerThread() {
+ return sizeof(ThreadLocal);
+}
+
+
+DebugInfoListNode::DebugInfoListNode(DebugInfo* debug_info): next_(NULL) {
+ // 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();
+}
+
+
+DebugInfoListNode::~DebugInfoListNode() {
+ if (debug_info_ == nullptr) return;
+ GlobalHandles::Destroy(reinterpret_cast<Object**>(debug_info_));
+ debug_info_ = nullptr;
+}
+
+
+bool Debug::Load() {
+ // Return if debugger is already loaded.
+ if (is_loaded()) return true;
+
+ // Bail out if we're already in the process of compiling the native
+ // JavaScript source code for the debugger.
+ if (is_suppressed_) return false;
+ SuppressDebug while_loading(this);
+
+ // Disable breakpoints and interrupts while compiling and running the
+ // debugger scripts including the context creation code.
+ DisableBreak disable(this, true);
+ PostponeInterruptsScope postpone(isolate_);
+
+ // Create the debugger context.
+ HandleScope scope(isolate_);
+ ExtensionConfiguration no_extensions;
+ Handle<Context> context = isolate_->bootstrapper()->CreateEnvironment(
+ MaybeHandle<JSGlobalProxy>(), v8::Local<ObjectTemplate>(), &no_extensions,
+ 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();
+
+ // Return debugger is not loaded.
+ if (!is_loaded()) return;
+
+ // Clear debugger context global handle.
+ GlobalHandles::Destroy(Handle<Object>::cast(debug_context_).location());
+ debug_context_ = Handle<Context>();
+}
+
+
+void Debug::Break(Arguments args, JavaScriptFrame* frame) {
+ HandleScope scope(isolate_);
+ DCHECK(args.length() == 0);
+
+ // Initialize LiveEdit.
+ LiveEdit::InitializeThreadLocal(this);
+
+ // Just continue if breaks are disabled or debugger cannot be loaded.
+ if (break_disabled()) return;
+
+ // Enter the debugger.
+ DebugScope debug_scope(this);
+ if (debug_scope.failed()) return;
+
+ // Postpone interrupt during breakpoint processing.
+ PostponeInterruptsScope postpone(isolate_);
+
+ // Get the debug info (create it if it does not exist).
+ Handle<JSFunction> function(frame->function());
+ Handle<SharedFunctionInfo> shared(function->shared());
+ if (!EnsureDebugInfo(shared, function)) {
+ // Return if we failed to retrieve the debug info.
+ return;
+ }
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+
+ // Find the break location where execution has stopped.
+ // PC points to the instruction after the current one, possibly a break
+ // location as well. So the "- 1" to exclude it from the search.
+ Address call_pc = frame->pc() - 1;
+ BreakLocation location = BreakLocation::FromAddress(debug_info, call_pc);
+
+ // Find actual break points, if any, and trigger debug break event.
+ if (break_points_active_ && location.HasBreakPoint()) {
+ Handle<Object> break_point_objects = location.BreakPointObjects();
+ Handle<Object> break_points_hit = CheckBreakPoints(break_point_objects);
+ if (!break_points_hit->IsUndefined()) {
+ // Clear all current stepping setup.
+ ClearStepping();
+ // Notify the debug event listeners.
+ OnDebugBreak(break_points_hit, false);
+ return;
+ }
+ }
+
+ // No break point. Check for stepping.
+ StepAction step_action = last_step_action();
+ Address current_fp = frame->UnpaddedFP();
+ Address target_fp = thread_local_.target_fp_;
+ Address last_fp = thread_local_.last_fp_;
+
+ bool step_break = true;
+ switch (step_action) {
+ case StepNone:
+ return;
+ case StepOut:
+ // Step out has not reached the target frame yet.
+ if (current_fp < target_fp) return;
+ break;
+ case StepNext:
+ // Step next should not break in a deeper frame.
+ if (current_fp < target_fp) return;
+ // Fall through.
+ case StepIn:
+ step_break = location.IsReturn() || (current_fp != last_fp) ||
+ (thread_local_.last_statement_position_ !=
+ location.code()->SourceStatementPosition(frame->pc()));
+ break;
+ case StepFrame:
+ step_break = current_fp != last_fp;
+ break;
+ }
+
+ // Clear all current stepping setup.
+ ClearStepping();
+
+ if (step_break) {
+ // Notify the debug event listeners.
+ OnDebugBreak(isolate_->factory()->undefined_value(), false);
+ } else {
+ // Re-prepare to continue.
+ PrepareStep(step_action);
+ }
+}
+
+
+// Check the break point objects for whether one or more are actually
+// triggered. This function returns a JSArray with the break point objects
+// which is triggered.
+Handle<Object> Debug::CheckBreakPoints(Handle<Object> break_point_objects) {
+ Factory* factory = isolate_->factory();
+
+ // Count the number of break points hit. If there are multiple break points
+ // they are in a FixedArray.
+ Handle<FixedArray> break_points_hit;
+ int break_points_hit_count = 0;
+ DCHECK(!break_point_objects->IsUndefined());
+ if (break_point_objects->IsFixedArray()) {
+ Handle<FixedArray> array(FixedArray::cast(*break_point_objects));
+ break_points_hit = factory->NewFixedArray(array->length());
+ for (int i = 0; i < array->length(); i++) {
+ Handle<Object> o(array->get(i), isolate_);
+ if (CheckBreakPoint(o)) {
+ break_points_hit->set(break_points_hit_count++, *o);
+ }
+ }
+ } else {
+ break_points_hit = factory->NewFixedArray(1);
+ if (CheckBreakPoint(break_point_objects)) {
+ break_points_hit->set(break_points_hit_count++, *break_point_objects);
+ }
+ }
+
+ // Return undefined if no break points were triggered.
+ if (break_points_hit_count == 0) {
+ return factory->undefined_value();
+ }
+ // Return break points hit as a JSArray.
+ Handle<JSArray> result = factory->NewJSArrayWithElements(break_points_hit);
+ result->set_length(Smi::FromInt(break_points_hit_count));
+ return result;
+}
+
+
+MaybeHandle<Object> Debug::CallFunction(const char* name, int argc,
+ Handle<Object> args[]) {
+ PostponeInterruptsScope no_interrupts(isolate_);
+ AssertDebugContext();
+ Handle<Object> holder = isolate_->natives_utils_object();
+ Handle<JSFunction> fun = Handle<JSFunction>::cast(
+ Object::GetProperty(isolate_, holder, name, STRICT).ToHandleChecked());
+ Handle<Object> undefined = isolate_->factory()->undefined_value();
+ return Execution::TryCall(isolate_, fun, undefined, argc, args);
+}
+
+
+// Check whether a single break point object is triggered.
+bool Debug::CheckBreakPoint(Handle<Object> break_point_object) {
+ Factory* factory = isolate_->factory();
+ HandleScope scope(isolate_);
+
+ // Ignore check if break point object is not a JSObject.
+ if (!break_point_object->IsJSObject()) return true;
+
+ // Get the break id as an object.
+ Handle<Object> break_id = factory->NewNumberFromInt(Debug::break_id());
+
+ // Call IsBreakPointTriggered.
+ Handle<Object> argv[] = { break_id, break_point_object };
+ Handle<Object> result;
+ if (!CallFunction("IsBreakPointTriggered", arraysize(argv), argv)
+ .ToHandle(&result)) {
+ return false;
+ }
+
+ // Return whether the break point is triggered.
+ return result->IsTrue();
+}
+
+
+bool Debug::SetBreakPoint(Handle<JSFunction> function,
+ Handle<Object> break_point_object,
+ int* source_position) {
+ HandleScope scope(isolate_);
+
+ // Make sure the function is compiled and has set up the debug info.
+ Handle<SharedFunctionInfo> shared(function->shared());
+ if (!EnsureDebugInfo(shared, function)) {
+ // Return if retrieving debug info failed.
+ return true;
+ }
+
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ // Source positions starts with zero.
+ DCHECK(*source_position >= 0);
+
+ // Find the break point and change it.
+ BreakLocation location = BreakLocation::FromPosition(
+ debug_info, *source_position, STATEMENT_ALIGNED);
+ *source_position = location.statement_position();
+ location.SetBreakPoint(break_point_object);
+
+ feature_tracker()->Track(DebugFeatureTracker::kBreakPoint);
+
+ // At least one active break point now.
+ return debug_info->GetBreakPointCount() > 0;
+}
+
+
+bool Debug::SetBreakPointForScript(Handle<Script> script,
+ Handle<Object> break_point_object,
+ int* source_position,
+ BreakPositionAlignment alignment) {
+ HandleScope scope(isolate_);
+
+ // Obtain shared function info for the function.
+ Handle<Object> result =
+ FindSharedFunctionInfoInScript(script, *source_position);
+ if (result->IsUndefined()) return false;
+
+ // Make sure the function has set up the debug info.
+ Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>::cast(result);
+ if (!EnsureDebugInfo(shared, Handle<JSFunction>::null())) {
+ // Return if retrieving debug info failed.
+ return false;
+ }
+
+ // Find position within function. The script position might be before the
+ // source position of the first function.
+ int position;
+ if (shared->start_position() > *source_position) {
+ position = 0;
+ } else {
+ position = *source_position - shared->start_position();
+ }
+
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ // Source positions starts with zero.
+ DCHECK(position >= 0);
+
+ // Find the break point and change it.
+ BreakLocation location =
+ BreakLocation::FromPosition(debug_info, position, alignment);
+ location.SetBreakPoint(break_point_object);
+
+ feature_tracker()->Track(DebugFeatureTracker::kBreakPoint);
+
+ position = (alignment == STATEMENT_ALIGNED) ? location.statement_position()
+ : location.position();
+
+ *source_position = position + shared->start_position();
+
+ // At least one active break point now.
+ DCHECK(debug_info->GetBreakPointCount() > 0);
+ return true;
+}
+
+
+void Debug::ClearBreakPoint(Handle<Object> break_point_object) {
+ HandleScope scope(isolate_);
+
+ DebugInfoListNode* node = debug_info_list_;
+ while (node != NULL) {
+ Handle<Object> result =
+ DebugInfo::FindBreakPointInfo(node->debug_info(), break_point_object);
+ if (!result->IsUndefined()) {
+ // Get information in the break point.
+ Handle<BreakPointInfo> break_point_info =
+ Handle<BreakPointInfo>::cast(result);
+ Handle<DebugInfo> debug_info = node->debug_info();
+
+ // Find the break point and clear it.
+ Address pc =
+ debug_info->code()->entry() + break_point_info->code_position();
+
+ BreakLocation location = BreakLocation::FromAddress(debug_info, pc);
+ location.ClearBreakPoint(break_point_object);
+
+ // If there are no more break points left remove the debug info for this
+ // function.
+ if (debug_info->GetBreakPointCount() == 0) {
+ RemoveDebugInfoAndClearFromShared(debug_info);
+ }
+
+ return;
+ }
+ node = node->next();
+ }
+}
+
+
+// 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()) {
+ for (BreakLocation::Iterator it(node->debug_info(), ALL_BREAK_LOCATIONS);
+ !it.Done(); it.Next()) {
+ it.GetBreakLocation().ClearDebugBreak();
+ }
+ }
+ // Remove all debug info.
+ while (debug_info_list_ != NULL) {
+ RemoveDebugInfoAndClearFromShared(debug_info_list_->debug_info());
+ }
+}
+
+
+void Debug::FloodWithOneShot(Handle<JSFunction> function,
+ BreakLocatorType type) {
+ // Debug utility functions are not subject to debugging.
+ if (function->native_context() == *debug_context()) return;
+
+ if (!function->shared()->IsSubjectToDebugging()) {
+ // Builtin functions are not subject to stepping, but need to be
+ // deoptimized, because optimized code does not check for debug
+ // step in at call sites.
+ Deoptimizer::DeoptimizeFunction(*function);
+ return;
+ }
+ // Make sure the function is compiled and has set up the debug info.
+ Handle<SharedFunctionInfo> shared(function->shared());
+ if (!EnsureDebugInfo(shared, function)) {
+ // Return if we failed to retrieve the debug info.
+ return;
+ }
+
+ // Flood the function with break points.
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ for (BreakLocation::Iterator it(debug_info, type); !it.Done(); it.Next()) {
+ it.GetBreakLocation().SetOneShot();
+ }
+}
+
+
+void Debug::ChangeBreakOnException(ExceptionBreakType type, bool enable) {
+ if (type == BreakUncaughtException) {
+ break_on_uncaught_exception_ = enable;
+ } else {
+ break_on_exception_ = enable;
+ }
+}
+
+
+bool Debug::IsBreakOnException(ExceptionBreakType type) {
+ if (type == BreakUncaughtException) {
+ return break_on_uncaught_exception_;
+ } else {
+ return break_on_exception_;
+ }
+}
+
+
+FrameSummary GetFirstFrameSummary(JavaScriptFrame* frame) {
+ List<FrameSummary> frames(FLAG_max_inlining_levels + 1);
+ frame->Summarize(&frames);
+ return frames.first();
+}
+
+
+void Debug::PrepareStepIn(Handle<JSFunction> function) {
+ if (!is_active()) return;
+ if (last_step_action() < StepIn) return;
+ if (in_debug_scope()) return;
+ if (thread_local_.step_in_enabled_) {
+ FloodWithOneShot(function);
+ }
+}
+
+
+void Debug::PrepareStepOnThrow() {
+ if (!is_active()) return;
+ if (last_step_action() == StepNone) return;
+ if (in_debug_scope()) return;
+
+ ClearOneShot();
+
+ // Iterate through the JavaScript stack looking for handlers.
+ JavaScriptFrameIterator it(isolate_);
+ while (!it.done()) {
+ JavaScriptFrame* frame = it.frame();
+ int stack_slots = 0; // The computed stack slot count is not used.
+ if (frame->LookupExceptionHandlerInTable(&stack_slots, NULL) > 0) break;
+ it.Advance();
+ }
+
+ // Find the closest Javascript frame we can flood with one-shots.
+ while (!it.done() &&
+ !it.frame()->function()->shared()->IsSubjectToDebugging()) {
+ it.Advance();
+ }
+
+ if (it.done()) return; // No suitable Javascript catch handler.
+
+ FloodWithOneShot(Handle<JSFunction>(it.frame()->function()));
+}
+
+
+void Debug::PrepareStep(StepAction step_action) {
+ HandleScope scope(isolate_);
+
+ DCHECK(in_debug_scope());
+
+ // Get the frame where the execution has stopped and skip the debug frame if
+ // any. The debug frame will only be present if execution was stopped due to
+ // hitting a break point. In other situations (e.g. unhandled exception) the
+ // debug frame is not present.
+ StackFrame::Id frame_id = break_frame_id();
+ // If there is no JavaScript stack don't do anything.
+ if (frame_id == StackFrame::NO_ID) return;
+
+ JavaScriptFrameIterator frames_it(isolate_, frame_id);
+ JavaScriptFrame* frame = frames_it.frame();
+
+ feature_tracker()->Track(DebugFeatureTracker::kStepping);
+
+ // Remember this step action and count.
+ thread_local_.last_step_action_ = step_action;
+ STATIC_ASSERT(StepFrame > StepIn);
+ thread_local_.step_in_enabled_ = (step_action >= StepIn);
+
+ // If the function on the top frame is unresolved perform step out. This will
+ // be the case when calling unknown function and having the debugger stopped
+ // in an unhandled exception.
+ if (!frame->function()->IsJSFunction()) {
+ // Step out: Find the calling JavaScript frame and flood it with
+ // breakpoints.
+ frames_it.Advance();
+ // Fill the function to return to with one-shot break points.
+ JSFunction* function = frames_it.frame()->function();
+ FloodWithOneShot(Handle<JSFunction>(function));
+ return;
+ }
+
+ // Get the debug info (create it if it does not exist).
+ FrameSummary summary = GetFirstFrameSummary(frame);
+ Handle<JSFunction> function(summary.function());
+ Handle<SharedFunctionInfo> shared(function->shared());
+ if (!EnsureDebugInfo(shared, function)) {
+ // Return if ensuring debug info failed.
+ return;
+ }
+
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ // Refresh frame summary if the code has been recompiled for debugging.
+ if (shared->code() != *summary.code()) summary = GetFirstFrameSummary(frame);
+
+ // PC points to the instruction after the current one, possibly a break
+ // location as well. So the "- 1" to exclude it from the search.
+ Address call_pc = summary.pc() - 1;
+ BreakLocation location = BreakLocation::FromAddress(debug_info, call_pc);
+
+ // At a return statement we will step out either way.
+ if (location.IsReturn()) step_action = StepOut;
+
+ thread_local_.last_statement_position_ =
+ debug_info->code()->SourceStatementPosition(summary.pc());
+ thread_local_.last_fp_ = frame->UnpaddedFP();
+
+ switch (step_action) {
+ case StepNone:
+ UNREACHABLE();
+ break;
+ case StepOut:
+ // Advance to caller frame.
+ frames_it.Advance();
+ // Skip native and extension functions on the stack.
+ while (!frames_it.done() &&
+ !frames_it.frame()->function()->shared()->IsSubjectToDebugging()) {
+ // Builtin functions are not subject to stepping, but need to be
+ // deoptimized to include checks for step-in at call sites.
+ Deoptimizer::DeoptimizeFunction(frames_it.frame()->function());
+ frames_it.Advance();
+ }
+ if (frames_it.done()) {
+ // Stepping out to the embedder. Disable step-in to avoid stepping into
+ // the next (unrelated) call that the embedder makes.
+ thread_local_.step_in_enabled_ = false;
+ } else {
+ // Fill the caller function to return to with one-shot break points.
+ Handle<JSFunction> caller_function(frames_it.frame()->function());
+ FloodWithOneShot(caller_function);
+ thread_local_.target_fp_ = frames_it.frame()->UnpaddedFP();
+ }
+ // Clear last position info. For stepping out it does not matter.
+ thread_local_.last_statement_position_ = RelocInfo::kNoPosition;
+ thread_local_.last_fp_ = 0;
+ break;
+ case StepNext:
+ thread_local_.target_fp_ = frame->UnpaddedFP();
+ FloodWithOneShot(function);
+ break;
+ case StepIn:
+ FloodWithOneShot(function);
+ break;
+ case StepFrame:
+ // No point in setting one-shot breaks at places where we are not about
+ // to leave the current frame.
+ FloodWithOneShot(function, CALLS_AND_RETURNS);
+ break;
+ }
+}
+
+
+// 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();
+ Heap* heap = isolate->heap();
+ if (!shared->HasDebugInfo()) {
+ return Handle<Object>(heap->undefined_value(), isolate);
+ }
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ if (debug_info->GetBreakPointCount() == 0) {
+ return Handle<Object>(heap->undefined_value(), isolate);
+ }
+ Handle<FixedArray> locations =
+ isolate->factory()->NewFixedArray(debug_info->GetBreakPointCount());
+ int count = 0;
+ for (int i = 0; i < debug_info->break_points()->length(); ++i) {
+ if (!debug_info->break_points()->get(i)->IsUndefined()) {
+ BreakPointInfo* break_point_info =
+ BreakPointInfo::cast(debug_info->break_points()->get(i));
+ int break_points = break_point_info->GetBreakPointCount();
+ if (break_points == 0) continue;
+ Smi* position = NULL;
+ switch (position_alignment) {
+ case STATEMENT_ALIGNED:
+ position = Smi::FromInt(break_point_info->statement_position());
+ break;
+ case BREAK_POSITION_ALIGNED:
+ position = Smi::FromInt(break_point_info->source_position());
+ break;
+ }
+ for (int j = 0; j < break_points; ++j) locations->set(count++, position);
+ }
+ }
+ return locations;
+}
+
+
+void Debug::ClearStepping() {
+ // Clear the various stepping setup.
+ ClearOneShot();
+
+ thread_local_.last_step_action_ = StepNone;
+ thread_local_.step_in_enabled_ = false;
+ thread_local_.last_statement_position_ = RelocInfo::kNoPosition;
+ thread_local_.last_fp_ = 0;
+ thread_local_.target_fp_ = 0;
+}
+
+
+// Clears all the one-shot break points that are currently set. Normally this
+// function is called each time a break point is hit as one shot break points
+// are used to support stepping.
+void Debug::ClearOneShot() {
+ // The current implementation just runs through all the breakpoints. When the
+ // last break point for a function is removed that function is automatically
+ // removed from the list.
+ for (DebugInfoListNode* node = debug_info_list_; node != NULL;
+ node = node->next()) {
+ for (BreakLocation::Iterator it(node->debug_info(), ALL_BREAK_LOCATIONS);
+ !it.Done(); it.Next()) {
+ it.GetBreakLocation().ClearOneShot();
+ }
+ }
+}
+
+
+void Debug::EnableStepIn() {
+ STATIC_ASSERT(StepFrame > StepIn);
+ thread_local_.step_in_enabled_ = (last_step_action() >= StepIn);
+}
+
+
+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();
+}
+
+
+// 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());
+ }
+
+ // 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++;
+ }
+
+ 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;
+}
+
+
+// Count the number of continuations at which the current pc offset is at.
+static int ComputeContinuationIndexFromPcOffset(Code* code, int pc_offset) {
+ DCHECK_EQ(code->kind(), Code::FUNCTION);
+ Address pc = code->instruction_start() + pc_offset;
+ int mask = RelocInfo::ModeMask(RelocInfo::GENERATOR_CONTINUATION);
+ int index = 0;
+ for (RelocIterator it(code, mask); !it.done(); it.next()) {
+ index++;
+ RelocInfo* rinfo = it.rinfo();
+ Address current_pc = rinfo->pc();
+ if (current_pc == pc) break;
+ DCHECK(current_pc < pc);
+ }
+ return index;
+}
+
+
+// Find the pc offset for the given continuation index.
+static int ComputePcOffsetFromContinuationIndex(Code* code, int index) {
+ DCHECK_EQ(code->kind(), Code::FUNCTION);
+ DCHECK(code->has_debug_break_slots());
+ int mask = RelocInfo::ModeMask(RelocInfo::GENERATOR_CONTINUATION);
+ RelocIterator it(code, mask);
+ for (int i = 1; i < index; i++) it.next();
+ return static_cast<int>(it.rinfo()->pc() - code->instruction_start());
+}
+
+
+class RedirectActiveFunctions : public ThreadVisitor {
+ public:
+ explicit RedirectActiveFunctions(SharedFunctionInfo* shared)
+ : shared_(shared) {
+ DCHECK(shared->HasDebugCode());
+ }
+
+ 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;
+
+ 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();
+ }
+
+ List<Handle<JSFunction> > functions;
+ List<Handle<JSGeneratorObject> > suspended_generators;
+
+ // 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::Iterator iterator(isolate_);
+ while (SharedFunctionInfo* shared = iterator.Next()) {
+ if (!shared->OptimizedCodeMapIsCleared()) {
+ shared->ClearOptimizedCodeMap();
+ }
+ }
+ }
+
+ // Make sure we abort incremental marking.
+ isolate_->heap()->CollectAllGarbage(Heap::kMakeHeapIterableMask,
+ "prepare for break points");
+
+ {
+ HeapIterator iterator(isolate_->heap());
+ HeapObject* obj;
+ bool include_generators = shared->is_generator();
+
+ 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);
+ }
+ if (function->shared() == *shared) functions.Add(handle(function));
+ } else if (include_generators && obj->IsJSGeneratorObject()) {
+ JSGeneratorObject* generator_obj = JSGeneratorObject::cast(obj);
+ if (!generator_obj->is_suspended()) continue;
+ JSFunction* function = generator_obj->function();
+ if (!function->Inlines(*shared)) continue;
+ int pc_offset = generator_obj->continuation();
+ int index =
+ ComputeContinuationIndexFromPcOffset(function->code(), pc_offset);
+ generator_obj->set_continuation(index);
+ suspended_generators.Add(handle(generator_obj));
+ }
+ }
+ }
+
+ if (!shared->HasDebugCode()) {
+ DCHECK(functions.length() > 0);
+ if (!Compiler::CompileDebugCode(functions.first())) return false;
+ }
+
+ for (Handle<JSFunction> const function : functions) {
+ function->ReplaceCode(shared->code());
+ }
+
+ for (Handle<JSGeneratorObject> const generator_obj : suspended_generators) {
+ int index = generator_obj->continuation();
+ int pc_offset = ComputePcOffsetFromContinuationIndex(shared->code(), index);
+ generator_obj->set_continuation(pc_offset);
+ }
+
+ // 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;
+}
+
+
+class SharedFunctionInfoFinder {
+ public:
+ explicit SharedFunctionInfoFinder(int target_position)
+ : current_candidate_(NULL),
+ current_candidate_closure_(NULL),
+ current_start_position_(RelocInfo::kNoPosition),
+ target_position_(target_position) {}
+
+ void NewCandidate(SharedFunctionInfo* shared, JSFunction* closure = NULL) {
+ if (!shared->IsSubjectToDebugging()) return;
+ int start_position = shared->function_token_position();
+ if (start_position == RelocInfo::kNoPosition) {
+ start_position = shared->start_position();
+ }
+
+ if (start_position > target_position_) return;
+ if (target_position_ > shared->end_position()) return;
+
+ if (current_candidate_ != NULL) {
+ if (current_start_position_ == start_position &&
+ shared->end_position() == current_candidate_->end_position()) {
+ // If we already have a matching closure, do not throw it away.
+ if (current_candidate_closure_ != NULL && closure == NULL) return;
+ // If a top-level function contains only one function
+ // declaration the source for the top-level and the function
+ // is the same. In that case prefer the non top-level function.
+ if (!current_candidate_->is_toplevel() && shared->is_toplevel()) return;
+ } else if (start_position < current_start_position_ ||
+ current_candidate_->end_position() < shared->end_position()) {
+ return;
+ }
+ }
+
+ current_start_position_ = start_position;
+ current_candidate_ = shared;
+ current_candidate_closure_ = closure;
+ }
+
+ SharedFunctionInfo* Result() { return current_candidate_; }
+
+ JSFunction* ResultClosure() { return current_candidate_closure_; }
+
+ private:
+ SharedFunctionInfo* current_candidate_;
+ JSFunction* current_candidate_closure_;
+ int current_start_position_;
+ int target_position_;
+ DisallowHeapAllocation no_gc_;
+};
+
+
+// We need to find a SFI for a literal that may not yet have been compiled yet,
+// and there may not be a JSFunction referencing it. Find the SFI closest to
+// the given position, compile it to reveal possible inner SFIs and repeat.
+// While we are at this, also ensure code with debug break slots so that we do
+// not have to compile a SFI without JSFunction, which is paifu for those that
+// cannot be compiled without context (need to find outer compilable SFI etc.)
+Handle<Object> Debug::FindSharedFunctionInfoInScript(Handle<Script> script,
+ int position) {
+ for (int iteration = 0;; iteration++) {
+ // Go through all shared function infos associated with this script to
+ // find the inner most function containing this position.
+ // If there is no shared function info for this script at all, there is
+ // no point in looking for it by walking the heap.
+ if (!script->shared_function_infos()->IsWeakFixedArray()) break;
+
+ SharedFunctionInfo* shared;
+ {
+ SharedFunctionInfoFinder finder(position);
+ WeakFixedArray::Iterator iterator(script->shared_function_infos());
+ SharedFunctionInfo* candidate;
+ while ((candidate = iterator.Next<SharedFunctionInfo>())) {
+ finder.NewCandidate(candidate);
+ }
+ 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 the iteration count is larger than 1, we had to compile the outer
+ // function in order to create this shared function info. So there can
+ // be no JSFunction referencing it. We can anticipate creating a debug
+ // info while bypassing PrepareFunctionForBreakpoints.
+ if (iteration > 1) {
+ AllowHeapAllocation allow_before_return;
+ CreateDebugInfo(shared_handle);
+ }
+ return shared_handle;
+ }
+ }
+ // If not, compile to reveal inner functions, if possible.
+ if (shared->allows_lazy_compilation_without_context()) {
+ HandleScope scope(isolate_);
+ if (!Compiler::CompileDebugCode(handle(shared))) break;
+ continue;
+ }
+
+ // If not possible, comb the heap for the best suitable compile target.
+ JSFunction* closure;
+ {
+ HeapIterator it(isolate_->heap());
+ SharedFunctionInfoFinder finder(position);
+ while (HeapObject* object = it.next()) {
+ JSFunction* candidate_closure = NULL;
+ SharedFunctionInfo* candidate = NULL;
+ if (object->IsJSFunction()) {
+ candidate_closure = JSFunction::cast(object);
+ candidate = candidate_closure->shared();
+ } else if (object->IsSharedFunctionInfo()) {
+ candidate = SharedFunctionInfo::cast(object);
+ if (!candidate->allows_lazy_compilation_without_context()) continue;
+ } else {
+ continue;
+ }
+ if (candidate->script() == *script) {
+ finder.NewCandidate(candidate, candidate_closure);
+ }
+ }
+ closure = finder.ResultClosure();
+ shared = finder.Result();
+ }
+ if (shared == NULL) break;
+ HandleScope scope(isolate_);
+ if (closure == NULL) {
+ if (!Compiler::CompileDebugCode(handle(shared))) break;
+ } else {
+ if (!Compiler::CompileDebugCode(handle(closure))) break;
+ }
+ }
+ return isolate_->factory()->undefined_value();
+}
+
+
+// Ensures the debug information is present for shared.
+bool Debug::EnsureDebugInfo(Handle<SharedFunctionInfo> shared,
+ Handle<JSFunction> function) {
+ if (!shared->IsSubjectToDebugging()) return false;
+
+ // Return if we already have the debug info for shared.
+ if (shared->HasDebugInfo()) return true;
+
+ if (function.is_null()) {
+ DCHECK(shared->HasDebugCode());
+ } else if (!Compiler::Compile(function, CLEAR_EXCEPTION)) {
+ return false;
+ }
+
+ if (!PrepareFunctionForBreakPoints(shared)) return false;
+
+ CreateDebugInfo(shared);
+
+ return true;
+}
+
+
+void Debug::CreateDebugInfo(Handle<SharedFunctionInfo> shared) {
+ // Create the debug info object.
+ DCHECK(shared->HasDebugCode());
+ Handle<DebugInfo> debug_info = isolate_->factory()->NewDebugInfo(shared);
+
+ // Add debug info to the list.
+ DebugInfoListNode* node = new DebugInfoListNode(*debug_info);
+ node->set_next(debug_info_list_);
+ debug_info_list_ = node;
+}
+
+
+void Debug::RemoveDebugInfoAndClearFromShared(Handle<DebugInfo> debug_info) {
+ 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());
+ }
+ delete current;
+ shared->set_debug_info(isolate_->heap()->undefined_value());
+ return;
+ }
+ // Move to next in list.
+ prev = current;
+ current = current->next();
+ }
+
+ UNREACHABLE();
+}
+
+
+void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
+ after_break_target_ = NULL;
+
+ if (LiveEdit::SetAfterBreakTarget(this)) return; // LiveEdit did the job.
+
+ // Continue just after the slot.
+ after_break_target_ = frame->pc();
+}
+
+
+bool Debug::IsBreakAtReturn(JavaScriptFrame* frame) {
+ HandleScope scope(isolate_);
+
+ // Get the executing function in which the debug break occurred.
+ Handle<JSFunction> function(JSFunction::cast(frame->function()));
+ Handle<SharedFunctionInfo> shared(function->shared());
+
+ // With no debug info there are no break points, so we can't be at a return.
+ if (!shared->HasDebugInfo()) return false;
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ Handle<Code> code(debug_info->code());
+#ifdef DEBUG
+ // Get the code which is actually executing.
+ Handle<Code> frame_code(frame->LookupCode());
+ DCHECK(frame_code.is_identical_to(code));
+#endif
+
+ // Find the reloc info matching the start of the debug break slot.
+ Address slot_pc = frame->pc() - Assembler::kDebugBreakSlotLength;
+ int mask = RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_RETURN);
+ for (RelocIterator it(*code, mask); !it.done(); it.next()) {
+ if (it.rinfo()->pc() == slot_pc) return true;
+ }
+ return false;
+}
+
+
+void Debug::FramesHaveBeenDropped(StackFrame::Id new_break_frame_id,
+ LiveEdit::FrameDropMode mode) {
+ if (mode != LiveEdit::CURRENTLY_SET_MODE) {
+ thread_local_.frame_drop_mode_ = mode;
+ }
+ thread_local_.break_frame_id_ = new_break_frame_id;
+}
+
+
+bool Debug::IsDebugGlobal(JSGlobalObject* global) {
+ return is_loaded() && global == debug_context()->global_object();
+}
+
+
+void Debug::ClearMirrorCache() {
+ PostponeInterruptsScope postpone(isolate_);
+ HandleScope scope(isolate_);
+ CallFunction("ClearMirrorCache", 0, NULL);
+}
+
+
+Handle<FixedArray> Debug::GetLoadedScripts() {
+ isolate_->heap()->CollectAllGarbage();
+ Factory* factory = isolate_->factory();
+ if (!factory->script_list()->IsWeakFixedArray()) {
+ return factory->empty_fixed_array();
+ }
+ Handle<WeakFixedArray> array =
+ Handle<WeakFixedArray>::cast(factory->script_list());
+ Handle<FixedArray> results = factory->NewFixedArray(array->Length());
+ int length = 0;
+ {
+ Script::Iterator iterator(isolate_);
+ Script* script;
+ while ((script = iterator.Next())) {
+ if (script->HasValidSource()) results->set(length++, script);
+ }
+ }
+ results->Shrink(length);
+ return results;
+}
+
+
+void Debug::GetStepinPositions(JavaScriptFrame* frame, StackFrame::Id frame_id,
+ List<int>* results_out) {
+ FrameSummary summary = GetFirstFrameSummary(frame);
+
+ Handle<JSFunction> fun = Handle<JSFunction>(summary.function());
+ Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>(fun->shared());
+
+ if (!EnsureDebugInfo(shared, fun)) return;
+
+ Handle<DebugInfo> debug_info(shared->GetDebugInfo());
+ // Refresh frame summary if the code has been recompiled for debugging.
+ if (shared->code() != *summary.code()) summary = GetFirstFrameSummary(frame);
+
+ // Find range of break points starting from the break point where execution
+ // has stopped.
+ Address call_pc = summary.pc() - 1;
+ List<BreakLocation> locations;
+ BreakLocation::FromAddressSameStatement(debug_info, call_pc, &locations);
+
+ for (BreakLocation location : locations) {
+ if (location.pc() <= summary.pc()) {
+ // The break point is near our pc. Could be a step-in possibility,
+ // that is currently taken by active debugger call.
+ if (break_frame_id() == StackFrame::NO_ID) {
+ continue; // We are not stepping.
+ } else {
+ JavaScriptFrameIterator frame_it(isolate_, break_frame_id());
+ // If our frame is a top frame and we are stepping, we can do step-in
+ // at this place.
+ if (frame_it.frame()->id() != frame_id) continue;
+ }
+ }
+ if (location.IsCall()) results_out->Add(location.position());
+ }
+}
+
+
+void Debug::RecordEvalCaller(Handle<Script> script) {
+ script->set_compilation_type(Script::COMPILATION_TYPE_EVAL);
+ // For eval scripts add information on the function from which eval was
+ // called.
+ StackTraceFrameIterator it(script->GetIsolate());
+ if (!it.done()) {
+ script->set_eval_from_shared(it.frame()->function()->shared());
+ Code* code = it.frame()->LookupCode();
+ int offset = static_cast<int>(
+ it.frame()->pc() - code->instruction_start());
+ script->set_eval_from_instructions_offset(offset);
+ }
+}
+
+
+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::MakePromiseEvent(Handle<JSObject> event_data) {
+ // Create the promise event object.
+ Handle<Object> argv[] = { event_data };
+ return CallFunction("MakePromiseEvent", arraysize(argv), argv);
+}
+
+
+MaybeHandle<Object> Debug::MakeAsyncTaskEvent(Handle<JSObject> task_event) {
+ // Create the async task event object.
+ Handle<Object> argv[] = { task_event };
+ return CallFunction("MakeAsyncTaskEvent", arraysize(argv), argv);
+}
+
+
+void Debug::OnThrow(Handle<Object> exception) {
+ if (in_debug_scope() || ignore_events()) return;
+ PrepareStepOnThrow();
+ // Temporarily clear any scheduled_exception to allow evaluating
+ // JavaScript from the debug event handler.
+ HandleScope scope(isolate_);
+ Handle<Object> scheduled_exception;
+ if (isolate_->has_scheduled_exception()) {
+ scheduled_exception = handle(isolate_->scheduled_exception(), isolate_);
+ isolate_->clear_scheduled_exception();
+ }
+ OnException(exception, isolate_->GetPromiseOnStackOnThrow());
+ if (!scheduled_exception.is_null()) {
+ isolate_->thread_local_top()->scheduled_exception_ = *scheduled_exception;
+ }
+}
+
+
+void Debug::OnPromiseReject(Handle<JSObject> promise, Handle<Object> value) {
+ if (in_debug_scope() || ignore_events()) return;
+ HandleScope scope(isolate_);
+ // Check whether the promise has been marked as having triggered a message.
+ Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
+ if (JSReceiver::GetDataProperty(promise, key)->IsUndefined()) {
+ OnException(value, promise);
+ }
+}
+
+
+MaybeHandle<Object> Debug::PromiseHasUserDefinedRejectHandler(
+ Handle<JSObject> promise) {
+ Handle<JSFunction> fun = isolate_->promise_has_user_defined_reject_handler();
+ return Execution::Call(isolate_, fun, promise, 0, NULL);
+}
+
+
+void Debug::OnException(Handle<Object> exception, Handle<Object> promise) {
+ // In our prediction, try-finally is not considered to catch.
+ Isolate::CatchType catch_type = isolate_->PredictExceptionCatcher();
+ bool uncaught = (catch_type == Isolate::NOT_CAUGHT);
+ if (promise->IsJSObject()) {
+ Handle<JSObject> jspromise = Handle<JSObject>::cast(promise);
+ // Mark the promise as already having triggered a message.
+ Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
+ JSObject::SetProperty(jspromise, key, key, STRICT).Assert();
+ // Check whether the promise reject is considered an uncaught exception.
+ Handle<Object> has_reject_handler;
+ ASSIGN_RETURN_ON_EXCEPTION_VALUE(
+ isolate_, has_reject_handler,
+ PromiseHasUserDefinedRejectHandler(jspromise), /* void */);
+ uncaught = has_reject_handler->IsFalse();
+ }
+ // Bail out if exception breaks are not active
+ if (uncaught) {
+ // Uncaught exceptions are reported by either flags.
+ if (!(break_on_uncaught_exception_ || break_on_exception_)) return;
+ } else {
+ // Caught exceptions are reported is activated.
+ if (!break_on_exception_) return;
+ }
+
+ DebugScope debug_scope(this);
+ if (debug_scope.failed()) return;
+
+ // Create the event data object.
+ Handle<Object> event_data;
+ // Bail out and don't call debugger if exception.
+ if (!MakeExceptionEvent(
+ exception, uncaught, promise).ToHandle(&event_data)) {
+ return;
+ }
+
+ // Process debug event.
+ ProcessDebugEvent(v8::Exception, Handle<JSObject>::cast(event_data), false);
+ // Return to continue execution from where the exception was thrown.
+}
+
+
+void Debug::OnDebugBreak(Handle<Object> break_points_hit,
+ bool auto_continue) {
+ // The caller provided for DebugScope.
+ AssertDebugContext();
+ // Bail out if there is no listener for this event
+ if (ignore_events()) return;
+
+ HandleScope scope(isolate_);
+ // Create the event data object.
+ Handle<Object> event_data;
+ // Bail out and don't call debugger if exception.
+ if (!MakeBreakEvent(break_points_hit).ToHandle(&event_data)) return;
+
+ // Process debug event.
+ ProcessDebugEvent(v8::Break,
+ Handle<JSObject>::cast(event_data),
+ auto_continue);
+}
+
+
+void Debug::OnCompileError(Handle<Script> script) {
+ ProcessCompileEvent(v8::CompileError, script);
+}
+
+
+void Debug::OnBeforeCompile(Handle<Script> script) {
+ ProcessCompileEvent(v8::BeforeCompile, script);
+}
+
+
+// Handle debugger actions when a new script is compiled.
+void Debug::OnAfterCompile(Handle<Script> script) {
+ ProcessCompileEvent(v8::AfterCompile, script);
+}
+
+
+void Debug::OnPromiseEvent(Handle<JSObject> data) {
+ if (in_debug_scope() || ignore_events()) return;
+
+ HandleScope scope(isolate_);
+ DebugScope debug_scope(this);
+ if (debug_scope.failed()) return;
+
+ // Create the script collected state object.
+ Handle<Object> event_data;
+ // Bail out and don't call debugger if exception.
+ if (!MakePromiseEvent(data).ToHandle(&event_data)) return;
+
+ // Process debug event.
+ ProcessDebugEvent(v8::PromiseEvent,
+ Handle<JSObject>::cast(event_data),
+ true);
+}
+
+
+void Debug::OnAsyncTaskEvent(Handle<JSObject> data) {
+ if (in_debug_scope() || ignore_events()) return;
+
+ HandleScope scope(isolate_);
+ DebugScope debug_scope(this);
+ if (debug_scope.failed()) return;
+
+ // Create the script collected state object.
+ Handle<Object> event_data;
+ // Bail out and don't call debugger if exception.
+ if (!MakeAsyncTaskEvent(data).ToHandle(&event_data)) return;
+
+ // Process debug event.
+ ProcessDebugEvent(v8::AsyncTaskEvent,
+ Handle<JSObject>::cast(event_data),
+ true);
+}
+
+
+void Debug::ProcessDebugEvent(v8::DebugEvent event,
+ Handle<JSObject> event_data,
+ bool auto_continue) {
+ HandleScope scope(isolate_);
+
+ // Create the execution state.
+ Handle<Object> exec_state;
+ // Bail out and don't call debugger if exception.
+ if (!MakeExecutionState().ToHandle(&exec_state)) return;
+
+ // First notify the message handler if any.
+ if (message_handler_ != NULL) {
+ NotifyMessageHandler(event,
+ Handle<JSObject>::cast(exec_state),
+ event_data,
+ auto_continue);
+ }
+ // Notify registered debug event listener. This can be either a C or
+ // a JavaScript function. Don't call event listener for v8::Break
+ // here, if it's only a debug command -- they will be processed later.
+ if ((event != v8::Break || !auto_continue) && !event_listener_.is_null()) {
+ CallEventCallback(event, exec_state, event_data, NULL);
+ }
+}
+
+
+void Debug::CallEventCallback(v8::DebugEvent event,
+ Handle<Object> exec_state,
+ Handle<Object> event_data,
+ v8::Debug::ClientData* client_data) {
+ // Prevent other interrupts from triggering, for example API callbacks,
+ // while dispatching event listners.
+ PostponeInterruptsScope postpone(isolate_);
+ bool previous = in_debug_event_listener_;
+ in_debug_event_listener_ = true;
+ if (event_listener_->IsForeign()) {
+ // Invoke the C debug event listener.
+ v8::Debug::EventCallback callback =
+ FUNCTION_CAST<v8::Debug::EventCallback>(
+ Handle<Foreign>::cast(event_listener_)->foreign_address());
+ EventDetailsImpl event_details(event,
+ Handle<JSObject>::cast(exec_state),
+ Handle<JSObject>::cast(event_data),
+ event_listener_data_,
+ client_data);
+ callback(event_details);
+ DCHECK(!isolate_->has_scheduled_exception());
+ } else {
+ // Invoke the JavaScript debug event listener.
+ DCHECK(event_listener_->IsJSFunction());
+ Handle<Object> argv[] = { Handle<Object>(Smi::FromInt(event), isolate_),
+ exec_state,
+ event_data,
+ event_listener_data_ };
+ Handle<JSReceiver> global(isolate_->global_proxy());
+ Execution::TryCall(isolate_, Handle<JSFunction>::cast(event_listener_),
+ global, arraysize(argv), argv);
+ }
+ in_debug_event_listener_ = previous;
+}
+
+
+void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) {
+ if (ignore_events()) return;
+ SuppressDebug while_processing(this);
+
+ bool in_nested_debug_scope = in_debug_scope();
+ HandleScope scope(isolate_);
+ DebugScope debug_scope(this);
+ if (debug_scope.failed()) return;
+
+ if (event == v8::AfterCompile) {
+ // If debugging there might be script break points registered for this
+ // script. Make sure that these break points are set.
+ Handle<Object> argv[] = {Script::GetWrapper(script)};
+ if (CallFunction("UpdateScriptBreakPoints", arraysize(argv), argv)
+ .is_null()) {
+ return;
+ }
+ }
+
+ // Create the compile state object.
+ Handle<Object> event_data;
+ // Bail out and don't call debugger if exception.
+ if (!MakeCompileEvent(script, event).ToHandle(&event_data)) return;
+
+ // Don't call NotifyMessageHandler if already in debug scope to avoid running
+ // nested command loop.
+ if (in_nested_debug_scope) {
+ if (event_listener_.is_null()) return;
+ // Create the execution state.
+ Handle<Object> exec_state;
+ // Bail out and don't call debugger if exception.
+ if (!MakeExecutionState().ToHandle(&exec_state)) return;
+
+ CallEventCallback(event, exec_state, event_data, NULL);
+ } else {
+ // Process debug event.
+ ProcessDebugEvent(event, Handle<JSObject>::cast(event_data), true);
+ }
+}
+
+
+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_);
+}
+
+
+void Debug::NotifyMessageHandler(v8::DebugEvent event,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ bool auto_continue) {
+ // Prevent other interrupts from triggering, for example API callbacks,
+ // while dispatching message handler callbacks.
+ PostponeInterruptsScope no_interrupts(isolate_);
+ DCHECK(is_active_);
+ HandleScope scope(isolate_);
+ // Process the individual events.
+ bool sendEventMessage = false;
+ switch (event) {
+ case v8::Break:
+ sendEventMessage = !auto_continue;
+ break;
+ case v8::NewFunction:
+ case v8::BeforeCompile:
+ case v8::CompileError:
+ case v8::PromiseEvent:
+ case v8::AsyncTaskEvent:
+ break;
+ case v8::Exception:
+ case v8::AfterCompile:
+ sendEventMessage = true;
+ break;
+ }
+
+ // The debug command interrupt flag might have been set when the command was
+ // added. It should be enough to clear the flag only once while we are in the
+ // debugger.
+ DCHECK(in_debug_scope());
+ isolate_->stack_guard()->ClearDebugCommand();
+
+ // Notify the debugger that a debug event has occurred unless auto continue is
+ // active in which case no event is send.
+ if (sendEventMessage) {
+ MessageImpl message = MessageImpl::NewEvent(
+ event,
+ auto_continue,
+ Handle<JSObject>::cast(exec_state),
+ Handle<JSObject>::cast(event_data));
+ InvokeMessageHandler(message);
+ }
+
+ // If auto continue don't make the event cause a break, but process messages
+ // in the queue if any. For script collected events don't even process
+ // messages in the queue as the execution state might not be what is expected
+ // by the client.
+ if (auto_continue && !has_commands()) return;
+
+ // DebugCommandProcessor goes here.
+ bool running = auto_continue;
+
+ Handle<Object> cmd_processor_ctor = Object::GetProperty(
+ isolate_, exec_state, "debugCommandProcessor").ToHandleChecked();
+ Handle<Object> ctor_args[] = { isolate_->factory()->ToBoolean(running) };
+ Handle<Object> cmd_processor = Execution::Call(
+ isolate_, cmd_processor_ctor, exec_state, 1, ctor_args).ToHandleChecked();
+ Handle<JSFunction> process_debug_request = Handle<JSFunction>::cast(
+ Object::GetProperty(
+ isolate_, cmd_processor, "processDebugRequest").ToHandleChecked());
+ Handle<Object> is_running = Object::GetProperty(
+ isolate_, cmd_processor, "isRunning").ToHandleChecked();
+
+ // Process requests from the debugger.
+ do {
+ // Wait for new command in the queue.
+ command_received_.Wait();
+
+ // Get the command from the queue.
+ CommandMessage command = command_queue_.Get();
+ isolate_->logger()->DebugTag(
+ "Got request from command queue, in interactive loop.");
+ if (!is_active()) {
+ // Delete command text and user data.
+ command.Dispose();
+ return;
+ }
+
+ Vector<const uc16> command_text(
+ const_cast<const uc16*>(command.text().start()),
+ command.text().length());
+ Handle<String> request_text = isolate_->factory()->NewStringFromTwoByte(
+ command_text).ToHandleChecked();
+ Handle<Object> request_args[] = { request_text };
+ Handle<Object> answer_value;
+ Handle<String> answer;
+ MaybeHandle<Object> maybe_exception;
+ MaybeHandle<Object> maybe_result =
+ Execution::TryCall(isolate_, process_debug_request, cmd_processor, 1,
+ request_args, &maybe_exception);
+
+ if (maybe_result.ToHandle(&answer_value)) {
+ if (answer_value->IsUndefined()) {
+ answer = isolate_->factory()->empty_string();
+ } else {
+ answer = Handle<String>::cast(answer_value);
+ }
+
+ // Log the JSON request/response.
+ if (FLAG_trace_debug_json) {
+ PrintF("%s\n", request_text->ToCString().get());
+ PrintF("%s\n", answer->ToCString().get());
+ }
+
+ Handle<Object> is_running_args[] = { answer };
+ maybe_result = Execution::Call(
+ isolate_, is_running, cmd_processor, 1, is_running_args);
+ Handle<Object> result;
+ if (!maybe_result.ToHandle(&result)) break;
+ running = result->IsTrue();
+ } else {
+ Handle<Object> exception;
+ if (!maybe_exception.ToHandle(&exception)) break;
+ Handle<Object> result;
+ if (!Object::ToString(isolate_, exception).ToHandle(&result)) break;
+ answer = Handle<String>::cast(result);
+ }
+
+ // Return the result.
+ MessageImpl message = MessageImpl::NewResponse(
+ event, running, exec_state, event_data, answer, command.client_data());
+ InvokeMessageHandler(message);
+ command.Dispose();
+
+ // Return from debug event processing if either the VM is put into the
+ // running state (through a continue command) or auto continue is active
+ // and there are no more commands queued.
+ } while (!running || has_commands());
+ command_queue_.Clear();
+}
+
+
+void Debug::SetEventListener(Handle<Object> callback,
+ Handle<Object> data) {
+ GlobalHandles* global_handles = isolate_->global_handles();
+
+ // Remove existing entry.
+ GlobalHandles::Destroy(event_listener_.location());
+ event_listener_ = Handle<Object>();
+ GlobalHandles::Destroy(event_listener_data_.location());
+ event_listener_data_ = Handle<Object>();
+
+ // Set new entry.
+ if (!callback->IsUndefined() && !callback->IsNull()) {
+ event_listener_ = global_handles->Create(*callback);
+ if (data.is_null()) data = isolate_->factory()->undefined_value();
+ event_listener_data_ = global_handles->Create(*data);
+ }
+
+ UpdateState();
+}
+
+
+void Debug::SetMessageHandler(v8::Debug::MessageHandler handler) {
+ message_handler_ = handler;
+ UpdateState();
+ if (handler == NULL && in_debug_scope()) {
+ // Send an empty command to the debugger if in a break to make JavaScript
+ // run again if the debugger is closed.
+ EnqueueCommandMessage(Vector<const uint16_t>::empty());
+ }
+}
+
+
+
+void Debug::UpdateState() {
+ bool is_active = message_handler_ != NULL || !event_listener_.is_null();
+ if (is_active || in_debug_scope()) {
+ // Note that the debug context could have already been loaded to
+ // bootstrap test cases.
+ isolate_->compilation_cache()->Disable();
+ is_active = Load();
+ } else if (is_loaded()) {
+ isolate_->compilation_cache()->Enable();
+ Unload();
+ }
+ is_active_ = is_active;
+}
+
+
+// Calls the registered debug message handler. This callback is part of the
+// public API.
+void Debug::InvokeMessageHandler(MessageImpl message) {
+ if (message_handler_ != NULL) message_handler_(message);
+}
+
+
+// Puts a command coming from the public API on the queue. Creates
+// a copy of the command string managed by the debugger. Up to this
+// point, the command data was managed by the API client. Called
+// by the API client thread.
+void Debug::EnqueueCommandMessage(Vector<const uint16_t> command,
+ v8::Debug::ClientData* client_data) {
+ // Need to cast away const.
+ CommandMessage message = CommandMessage::New(
+ Vector<uint16_t>(const_cast<uint16_t*>(command.start()),
+ command.length()),
+ client_data);
+ isolate_->logger()->DebugTag("Put command on command_queue.");
+ command_queue_.Put(message);
+ command_received_.Signal();
+
+ // Set the debug command break flag to have the command processed.
+ if (!in_debug_scope()) isolate_->stack_guard()->RequestDebugCommand();
+}
+
+
+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() {
+ // Ignore debug break during bootstrapping.
+ if (isolate_->bootstrapper()->IsActive()) return;
+ // Just continue if breaks are disabled.
+ if (break_disabled()) return;
+ // Ignore debug break if debugger is not active.
+ if (!is_active()) return;
+
+ StackLimitCheck check(isolate_);
+ if (check.HasOverflowed()) return;
+
+ { JavaScriptFrameIterator it(isolate_);
+ DCHECK(!it.done());
+ Object* fun = it.frame()->function();
+ if (fun && fun->IsJSFunction()) {
+ // Don't stop in builtin functions.
+ if (!JSFunction::cast(fun)->shared()->IsSubjectToDebugging()) return;
+ JSGlobalObject* global =
+ JSFunction::cast(fun)->context()->global_object();
+ // Don't stop in debugger functions.
+ if (IsDebugGlobal(global)) return;
+ }
+ }
+
+ // Collect the break state before clearing the flags.
+ bool debug_command_only = isolate_->stack_guard()->CheckDebugCommand() &&
+ !isolate_->stack_guard()->CheckDebugBreak();
+
+ isolate_->stack_guard()->ClearDebugBreak();
+
+ // Clear stepping to avoid duplicate breaks.
+ ClearStepping();
+
+ ProcessDebugMessages(debug_command_only);
+}
+
+
+void Debug::ProcessDebugMessages(bool debug_command_only) {
+ isolate_->stack_guard()->ClearDebugCommand();
+
+ StackLimitCheck check(isolate_);
+ if (check.HasOverflowed()) return;
+
+ HandleScope scope(isolate_);
+ DebugScope debug_scope(this);
+ if (debug_scope.failed()) return;
+
+ // Notify the debug event listeners. Indicate auto continue if the break was
+ // a debug command break.
+ OnDebugBreak(isolate_->factory()->undefined_value(), debug_command_only);
+}
+
+
+DebugScope::DebugScope(Debug* debug)
+ : debug_(debug),
+ prev_(debug->debugger_entry()),
+ save_(debug_->isolate_),
+ no_termination_exceptons_(debug_->isolate_,
+ StackGuard::TERMINATE_EXECUTION) {
+ // Link recursive debugger entry.
+ base::NoBarrier_Store(&debug_->thread_local_.current_debug_scope_,
+ reinterpret_cast<base::AtomicWord>(this));
+
+ // Store the previous break id and frame id.
+ break_id_ = debug_->break_id();
+ break_frame_id_ = debug_->break_frame_id();
+
+ // Create the new break info. If there is no JavaScript frames there is no
+ // break frame id.
+ JavaScriptFrameIterator it(isolate());
+ bool has_js_frames = !it.done();
+ debug_->thread_local_.break_frame_id_ = has_js_frames ? it.frame()->id()
+ : StackFrame::NO_ID;
+ debug_->SetNextBreakId();
+
+ debug_->UpdateState();
+ // Make sure that debugger is loaded and enter the debugger context.
+ // The previous context is kept in save_.
+ failed_ = !debug_->is_loaded();
+ if (!failed_) isolate()->set_context(*debug->debug_context());
+}
+
+
+DebugScope::~DebugScope() {
+ if (!failed_ && prev_ == NULL) {
+ // Clear mirror cache when leaving the debugger. Skip this if there is a
+ // pending exception as clearing the mirror cache calls back into
+ // JavaScript. This can happen if the v8::Debug::Call is used in which
+ // case the exception should end up in the calling code.
+ if (!isolate()->has_pending_exception()) debug_->ClearMirrorCache();
+
+ // If there are commands in the queue when leaving the debugger request
+ // that these commands are processed.
+ if (debug_->has_commands()) isolate()->stack_guard()->RequestDebugCommand();
+ }
+
+ // Leaving this debugger entry.
+ base::NoBarrier_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();
+}
+
+
+MessageImpl MessageImpl::NewEvent(DebugEvent event,
+ bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data) {
+ MessageImpl message(true, event, running,
+ exec_state, event_data, Handle<String>(), NULL);
+ return message;
+}
+
+
+MessageImpl MessageImpl::NewResponse(DebugEvent event,
+ bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<String> response_json,
+ v8::Debug::ClientData* client_data) {
+ MessageImpl message(false, event, running,
+ exec_state, event_data, response_json, client_data);
+ return message;
+}
+
+
+MessageImpl::MessageImpl(bool is_event,
+ DebugEvent event,
+ bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<String> response_json,
+ v8::Debug::ClientData* client_data)
+ : is_event_(is_event),
+ event_(event),
+ running_(running),
+ exec_state_(exec_state),
+ event_data_(event_data),
+ response_json_(response_json),
+ client_data_(client_data) {}
+
+
+bool MessageImpl::IsEvent() const {
+ return is_event_;
+}
+
+
+bool MessageImpl::IsResponse() const {
+ return !is_event_;
+}
+
+
+DebugEvent MessageImpl::GetEvent() const {
+ return event_;
+}
+
+
+bool MessageImpl::WillStartRunning() const {
+ return running_;
+}
+
+
+v8::Local<v8::Object> MessageImpl::GetExecutionState() const {
+ return v8::Utils::ToLocal(exec_state_);
+}
+
+
+v8::Isolate* MessageImpl::GetIsolate() const {
+ return reinterpret_cast<v8::Isolate*>(exec_state_->GetIsolate());
+}
+
+
+v8::Local<v8::Object> MessageImpl::GetEventData() const {
+ return v8::Utils::ToLocal(event_data_);
+}
+
+
+v8::Local<v8::String> MessageImpl::GetJSON() const {
+ Isolate* isolate = event_data_->GetIsolate();
+ v8::EscapableHandleScope scope(reinterpret_cast<v8::Isolate*>(isolate));
+
+ if (IsEvent()) {
+ // Call toJSONProtocol on the debug event object.
+ Handle<Object> fun = Object::GetProperty(
+ isolate, event_data_, "toJSONProtocol").ToHandleChecked();
+ if (!fun->IsJSFunction()) {
+ return v8::Local<v8::String>();
+ }
+
+ MaybeHandle<Object> maybe_json =
+ Execution::TryCall(isolate, fun, event_data_, 0, NULL);
+ Handle<Object> json;
+ if (!maybe_json.ToHandle(&json) || !json->IsString()) {
+ return v8::Local<v8::String>();
+ }
+ return scope.Escape(v8::Utils::ToLocal(Handle<String>::cast(json)));
+ } else {
+ return v8::Utils::ToLocal(response_json_);
+ }
+}
+
+
+v8::Local<v8::Context> MessageImpl::GetEventContext() const {
+ Isolate* isolate = event_data_->GetIsolate();
+ v8::Local<v8::Context> context = GetDebugEventContext(isolate);
+ // Isolate::context() may be NULL when "script collected" event occurs.
+ DCHECK(!context.IsEmpty());
+ return context;
+}
+
+
+v8::Debug::ClientData* MessageImpl::GetClientData() const {
+ return client_data_;
+}
+
+
+EventDetailsImpl::EventDetailsImpl(DebugEvent event,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<Object> callback_data,
+ v8::Debug::ClientData* client_data)
+ : event_(event),
+ exec_state_(exec_state),
+ event_data_(event_data),
+ callback_data_(callback_data),
+ client_data_(client_data) {}
+
+
+DebugEvent EventDetailsImpl::GetEvent() const {
+ return event_;
+}
+
+
+v8::Local<v8::Object> EventDetailsImpl::GetExecutionState() const {
+ return v8::Utils::ToLocal(exec_state_);
+}
+
+
+v8::Local<v8::Object> EventDetailsImpl::GetEventData() const {
+ return v8::Utils::ToLocal(event_data_);
+}
+
+
+v8::Local<v8::Context> EventDetailsImpl::GetEventContext() const {
+ return GetDebugEventContext(exec_state_->GetIsolate());
+}
+
+
+v8::Local<v8::Value> EventDetailsImpl::GetCallbackData() const {
+ return v8::Utils::ToLocal(callback_data_);
+}
+
+
+v8::Debug::ClientData* EventDetailsImpl::GetClientData() const {
+ return client_data_;
+}
+
+
+CommandMessage::CommandMessage() : text_(Vector<uint16_t>::empty()),
+ client_data_(NULL) {
+}
+
+
+CommandMessage::CommandMessage(const Vector<uint16_t>& text,
+ v8::Debug::ClientData* data)
+ : text_(text),
+ client_data_(data) {
+}
+
+
+void CommandMessage::Dispose() {
+ text_.Dispose();
+ delete client_data_;
+ client_data_ = NULL;
+}
+
+
+CommandMessage CommandMessage::New(const Vector<uint16_t>& command,
+ v8::Debug::ClientData* data) {
+ return CommandMessage(command.Clone(), data);
+}
+
+
+CommandMessageQueue::CommandMessageQueue(int size) : start_(0), end_(0),
+ size_(size) {
+ messages_ = NewArray<CommandMessage>(size);
+}
+
+
+CommandMessageQueue::~CommandMessageQueue() {
+ while (!IsEmpty()) Get().Dispose();
+ DeleteArray(messages_);
+}
+
+
+CommandMessage CommandMessageQueue::Get() {
+ DCHECK(!IsEmpty());
+ int result = start_;
+ start_ = (start_ + 1) % size_;
+ return messages_[result];
+}
+
+
+void CommandMessageQueue::Put(const CommandMessage& message) {
+ if ((end_ + 1) % size_ == start_) {
+ Expand();
+ }
+ messages_[end_] = message;
+ end_ = (end_ + 1) % size_;
+}
+
+
+void CommandMessageQueue::Expand() {
+ CommandMessageQueue new_queue(size_ * 2);
+ while (!IsEmpty()) {
+ new_queue.Put(Get());
+ }
+ CommandMessage* array_to_free = messages_;
+ *this = new_queue;
+ new_queue.messages_ = array_to_free;
+ // Make the new_queue empty so that it doesn't call Dispose on any messages.
+ new_queue.start_ = new_queue.end_;
+ // Automatic destructor called on new_queue, freeing array_to_free.
+}
+
+
+LockingCommandMessageQueue::LockingCommandMessageQueue(Logger* logger, int size)
+ : logger_(logger), queue_(size) {}
+
+
+bool LockingCommandMessageQueue::IsEmpty() const {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ return queue_.IsEmpty();
+}
+
+
+CommandMessage LockingCommandMessageQueue::Get() {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ CommandMessage result = queue_.Get();
+ logger_->DebugEvent("Get", result.text());
+ return result;
+}
+
+
+void LockingCommandMessageQueue::Put(const CommandMessage& message) {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ queue_.Put(message);
+ logger_->DebugEvent("Put", message.text());
+}
+
+
+void LockingCommandMessageQueue::Clear() {
+ base::LockGuard<base::Mutex> lock_guard(&mutex_);
+ queue_.Clear();
+}
+
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/debug.h b/src/debug/debug.h
new file mode 100644
index 0000000..7dcc2b5
--- /dev/null
+++ b/src/debug/debug.h
@@ -0,0 +1,749 @@
+// 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.
+
+#ifndef V8_DEBUG_DEBUG_H_
+#define V8_DEBUG_DEBUG_H_
+
+#include "src/allocation.h"
+#include "src/arguments.h"
+#include "src/assembler.h"
+#include "src/base/atomicops.h"
+#include "src/base/platform/platform.h"
+#include "src/debug/liveedit.h"
+#include "src/execution.h"
+#include "src/factory.h"
+#include "src/flags.h"
+#include "src/frames.h"
+#include "src/hashmap.h"
+#include "src/runtime/runtime.h"
+#include "src/string-stream.h"
+#include "src/v8threads.h"
+
+#include "include/v8-debug.h"
+
+namespace v8 {
+namespace internal {
+
+
+// Forward declarations.
+class DebugScope;
+
+
+// Step actions. NOTE: These values are in macros.py as well.
+enum StepAction : int8_t {
+ StepNone = -1, // Stepping not prepared.
+ StepOut = 0, // Step out of the current function.
+ StepNext = 1, // Step to the next statement in the current function.
+ StepIn = 2, // Step into new functions invoked or the next statement
+ // in the current function.
+ StepFrame = 3 // Step into a new frame or return to previous frame.
+};
+
+
+// Type of exception break. NOTE: These values are in macros.py as well.
+enum ExceptionBreakType {
+ BreakException = 0,
+ BreakUncaughtException = 1
+};
+
+
+// Type of exception break.
+enum BreakLocatorType { ALL_BREAK_LOCATIONS, CALLS_AND_RETURNS };
+
+
+// The different types of breakpoint position alignments.
+// Must match Debug.BreakPositionAlignment in debug.js
+enum BreakPositionAlignment {
+ STATEMENT_ALIGNED = 0,
+ BREAK_POSITION_ALIGNED = 1
+};
+
+
+class BreakLocation {
+ public:
+ // Find the break point at the supplied address, or the closest one before
+ // the address.
+ static BreakLocation FromAddress(Handle<DebugInfo> debug_info, Address pc);
+
+ static void FromAddressSameStatement(Handle<DebugInfo> debug_info, Address pc,
+ List<BreakLocation>* result_out);
+
+ static BreakLocation FromPosition(Handle<DebugInfo> debug_info, int position,
+ BreakPositionAlignment alignment);
+
+ bool IsDebugBreak() const;
+
+ inline bool IsReturn() const {
+ return RelocInfo::IsDebugBreakSlotAtReturn(rmode_);
+ }
+ inline bool IsCall() const {
+ return RelocInfo::IsDebugBreakSlotAtCall(rmode_);
+ }
+ inline bool HasBreakPoint() const {
+ return debug_info_->HasBreakPoint(pc_offset_);
+ }
+
+ Handle<Object> BreakPointObjects() const;
+
+ void SetBreakPoint(Handle<Object> break_point_object);
+ void ClearBreakPoint(Handle<Object> break_point_object);
+
+ void SetOneShot();
+ void ClearOneShot();
+
+
+ inline RelocInfo rinfo() const {
+ return RelocInfo(debug_info_->GetIsolate(), pc(), rmode(), data_, code());
+ }
+
+ inline int position() const { return position_; }
+ inline int statement_position() const { return statement_position_; }
+
+ inline Address pc() const { return code()->entry() + pc_offset_; }
+
+ inline RelocInfo::Mode rmode() const { return rmode_; }
+
+ inline Code* code() const { return debug_info_->code(); }
+
+ private:
+ BreakLocation(Handle<DebugInfo> debug_info, RelocInfo* rinfo, int position,
+ int statement_position);
+
+ class Iterator {
+ public:
+ Iterator(Handle<DebugInfo> debug_info, BreakLocatorType type);
+
+ BreakLocation GetBreakLocation() {
+ return BreakLocation(debug_info_, rinfo(), position(),
+ statement_position());
+ }
+
+ inline bool Done() const { return reloc_iterator_.done(); }
+ void Next();
+
+ void SkipTo(int count) {
+ while (count-- > 0) Next();
+ }
+
+ inline RelocInfo::Mode rmode() { return reloc_iterator_.rinfo()->rmode(); }
+ inline RelocInfo* rinfo() { return reloc_iterator_.rinfo(); }
+ inline Address pc() { return rinfo()->pc(); }
+ int break_index() const { return break_index_; }
+ inline int position() const { return position_; }
+ inline int statement_position() const { return statement_position_; }
+
+ private:
+ static int GetModeMask(BreakLocatorType type);
+
+ Handle<DebugInfo> debug_info_;
+ RelocIterator reloc_iterator_;
+ int break_index_;
+ int position_;
+ int statement_position_;
+
+ DisallowHeapAllocation no_gc_;
+
+ DISALLOW_COPY_AND_ASSIGN(Iterator);
+ };
+
+ friend class Debug;
+
+ static int BreakIndexFromAddress(Handle<DebugInfo> debug_info, Address pc);
+
+ void SetDebugBreak();
+ void ClearDebugBreak();
+
+ inline bool IsDebuggerStatement() const {
+ return RelocInfo::IsDebuggerStatement(rmode_);
+ }
+ inline bool IsDebugBreakSlot() const {
+ return RelocInfo::IsDebugBreakSlot(rmode_);
+ }
+
+ Handle<DebugInfo> debug_info_;
+ int pc_offset_;
+ RelocInfo::Mode rmode_;
+ intptr_t data_;
+ int position_;
+ int statement_position_;
+};
+
+
+// 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();
+
+ DebugInfoListNode* next() { return next_; }
+ void set_next(DebugInfoListNode* next) { next_ = next; }
+ Handle<DebugInfo> debug_info() { return Handle<DebugInfo>(debug_info_); }
+
+ private:
+ // Global (weak) handle to the debug info object.
+ DebugInfo** debug_info_;
+
+ // Next pointer for linked list.
+ DebugInfoListNode* next_;
+};
+
+
+
+// Message delivered to the message handler callback. This is either a debugger
+// event or the response to a command.
+class MessageImpl: public v8::Debug::Message {
+ public:
+ // Create a message object for a debug event.
+ static MessageImpl NewEvent(DebugEvent event,
+ bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data);
+
+ // Create a message object for the response to a debug command.
+ static MessageImpl NewResponse(DebugEvent event,
+ bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<String> response_json,
+ v8::Debug::ClientData* client_data);
+
+ // Implementation of interface v8::Debug::Message.
+ virtual bool IsEvent() const;
+ virtual bool IsResponse() const;
+ virtual DebugEvent GetEvent() const;
+ virtual bool WillStartRunning() const;
+ virtual v8::Local<v8::Object> GetExecutionState() const;
+ virtual v8::Local<v8::Object> GetEventData() const;
+ virtual v8::Local<v8::String> GetJSON() const;
+ virtual v8::Local<v8::Context> GetEventContext() const;
+ virtual v8::Debug::ClientData* GetClientData() const;
+ virtual v8::Isolate* GetIsolate() const;
+
+ private:
+ MessageImpl(bool is_event,
+ DebugEvent event,
+ bool running,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<String> response_json,
+ v8::Debug::ClientData* client_data);
+
+ bool is_event_; // Does this message represent a debug event?
+ DebugEvent event_; // Debug event causing the break.
+ bool running_; // Will the VM start running after this event?
+ Handle<JSObject> exec_state_; // Current execution state.
+ Handle<JSObject> event_data_; // Data associated with the event.
+ Handle<String> response_json_; // Response JSON if message holds a response.
+ v8::Debug::ClientData* client_data_; // Client data passed with the request.
+};
+
+
+// Details of the debug event delivered to the debug event listener.
+class EventDetailsImpl : public v8::Debug::EventDetails {
+ public:
+ EventDetailsImpl(DebugEvent event,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ Handle<Object> callback_data,
+ v8::Debug::ClientData* client_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;
+ 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.
+ v8::Debug::ClientData* client_data_; // Data passed to DebugBreakForCommand.
+};
+
+
+// Message send by user to v8 debugger or debugger output message.
+// In addition to command text it may contain a pointer to some user data
+// which are expected to be passed along with the command reponse to message
+// handler.
+class CommandMessage {
+ public:
+ static CommandMessage New(const Vector<uint16_t>& command,
+ v8::Debug::ClientData* data);
+ CommandMessage();
+
+ // Deletes user data and disposes of the text.
+ void Dispose();
+ Vector<uint16_t> text() const { return text_; }
+ v8::Debug::ClientData* client_data() const { return client_data_; }
+ private:
+ CommandMessage(const Vector<uint16_t>& text,
+ v8::Debug::ClientData* data);
+
+ Vector<uint16_t> text_;
+ v8::Debug::ClientData* client_data_;
+};
+
+
+// A Queue of CommandMessage objects. A thread-safe version is
+// LockingCommandMessageQueue, based on this class.
+class CommandMessageQueue BASE_EMBEDDED {
+ public:
+ explicit CommandMessageQueue(int size);
+ ~CommandMessageQueue();
+ bool IsEmpty() const { return start_ == end_; }
+ CommandMessage Get();
+ void Put(const CommandMessage& message);
+ void Clear() { start_ = end_ = 0; } // Queue is empty after Clear().
+ private:
+ // Doubles the size of the message queue, and copies the messages.
+ void Expand();
+
+ CommandMessage* messages_;
+ int start_;
+ int end_;
+ int size_; // The size of the queue buffer. Queue can hold size-1 messages.
+};
+
+
+// LockingCommandMessageQueue is a thread-safe circular buffer of CommandMessage
+// messages. The message data is not managed by LockingCommandMessageQueue.
+// Pointers to the data are passed in and out. Implemented by adding a
+// Mutex to CommandMessageQueue. Includes logging of all puts and gets.
+class LockingCommandMessageQueue BASE_EMBEDDED {
+ public:
+ LockingCommandMessageQueue(Logger* logger, int size);
+ bool IsEmpty() const;
+ CommandMessage Get();
+ void Put(const CommandMessage& message);
+ void Clear();
+ private:
+ Logger* logger_;
+ CommandMessageQueue queue_;
+ mutable base::Mutex mutex_;
+ DISALLOW_COPY_AND_ASSIGN(LockingCommandMessageQueue);
+};
+
+
+class DebugFeatureTracker {
+ public:
+ enum Feature {
+ kActive = 1,
+ kBreakPoint = 2,
+ kStepping = 3,
+ kHeapSnapshot = 4,
+ kAllocationTracking = 5,
+ kProfiler = 6,
+ kLiveEdit = 7,
+ };
+
+ explicit DebugFeatureTracker(Isolate* isolate)
+ : isolate_(isolate), bitfield_(0) {}
+ void Track(Feature feature);
+
+ private:
+ Isolate* isolate_;
+ uint32_t bitfield_;
+};
+
+
+// This class contains the debugger support. The main purpose is to handle
+// setting break points in the code.
+//
+// This class controls the debug info for all functions which currently have
+// 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 {
+ public:
+ // Debug event triggers.
+ void OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue);
+
+ void OnThrow(Handle<Object> exception);
+ void OnPromiseReject(Handle<JSObject> promise, Handle<Object> value);
+ void OnCompileError(Handle<Script> script);
+ void OnBeforeCompile(Handle<Script> script);
+ void OnAfterCompile(Handle<Script> script);
+ void OnPromiseEvent(Handle<JSObject> data);
+ void OnAsyncTaskEvent(Handle<JSObject> data);
+
+ // API facing.
+ void SetEventListener(Handle<Object> callback, Handle<Object> data);
+ void SetMessageHandler(v8::Debug::MessageHandler handler);
+ void EnqueueCommandMessage(Vector<const uint16_t> command,
+ v8::Debug::ClientData* client_data = NULL);
+ MUST_USE_RESULT MaybeHandle<Object> Call(Handle<Object> fun,
+ Handle<Object> data);
+ Handle<Context> GetDebugContext();
+ void HandleDebugBreak();
+ void ProcessDebugMessages(bool debug_command_only);
+
+ // Internal logic
+ bool Load();
+ void Break(Arguments args, JavaScriptFrame*);
+ void SetAfterBreakTarget(JavaScriptFrame* frame);
+
+ // 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);
+ void ClearAllBreakPoints();
+ void FloodWithOneShot(Handle<JSFunction> function,
+ BreakLocatorType type = ALL_BREAK_LOCATIONS);
+ void ChangeBreakOnException(ExceptionBreakType type, bool enable);
+ bool IsBreakOnException(ExceptionBreakType type);
+
+ // Stepping handling.
+ void PrepareStep(StepAction step_action);
+ void PrepareStepIn(Handle<JSFunction> function);
+ void PrepareStepOnThrow();
+ void ClearStepping();
+ void ClearStepOut();
+ void EnableStepIn();
+
+ void GetStepinPositions(JavaScriptFrame* frame, StackFrame::Id frame_id,
+ List<int>* results_out);
+
+ bool PrepareFunctionForBreakPoints(Handle<SharedFunctionInfo> shared);
+
+ // Returns whether the operation succeeded. Compilation can only be triggered
+ // if a valid closure is passed as the second argument, otherwise the shared
+ // function needs to be compiled already.
+ bool EnsureDebugInfo(Handle<SharedFunctionInfo> shared,
+ Handle<JSFunction> function);
+ void CreateDebugInfo(Handle<SharedFunctionInfo> shared);
+ static Handle<DebugInfo> GetDebugInfo(Handle<SharedFunctionInfo> shared);
+
+ template <typename C>
+ bool CompileToRevealInnerFunctions(C* compilable);
+
+ // 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);
+
+ // Check whether this frame is just about to return.
+ bool IsBreakAtReturn(JavaScriptFrame* frame);
+
+ // Support for LiveEdit
+ void FramesHaveBeenDropped(StackFrame::Id new_break_frame_id,
+ LiveEdit::FrameDropMode mode);
+
+ // Threading support.
+ char* ArchiveDebug(char* to);
+ char* RestoreDebug(char* from);
+ static int ArchiveSpacePerThread();
+ void FreeThreadResources() { }
+
+ // Record function from which eval was called.
+ static void RecordEvalCaller(Handle<Script> script);
+
+ bool CheckExecutionState(int id) {
+ return is_active() && !debug_context().is_null() && break_id() != 0 &&
+ break_id() == id;
+ }
+
+ // 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_);
+ }
+ 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_; }
+
+ // Support for embedding into generated code.
+ Address is_active_address() {
+ return reinterpret_cast<Address>(&is_active_);
+ }
+
+ Address after_break_target_address() {
+ return reinterpret_cast<Address>(&after_break_target_);
+ }
+
+ Address step_in_enabled_address() {
+ return reinterpret_cast<Address>(&thread_local_.step_in_enabled_);
+ }
+
+ StepAction last_step_action() { return thread_local_.last_step_action_; }
+
+ DebugFeatureTracker* feature_tracker() { return &feature_tracker_; }
+
+ private:
+ explicit Debug(Isolate* isolate);
+
+ void UpdateState();
+ void Unload();
+ void SetNextBreakId() {
+ thread_local_.break_id_ = ++thread_local_.break_count_;
+ }
+
+ // Check whether there are commands in the command queue.
+ inline bool has_commands() const { return !command_queue_.IsEmpty(); }
+ inline bool ignore_events() const { return is_suppressed_ || !is_active_; }
+ inline bool break_disabled() const {
+ return break_disabled_ || in_debug_event_listener_;
+ }
+
+ void OnException(Handle<Object> exception, Handle<Object> promise);
+
+ // 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> MakePromiseEvent(
+ Handle<JSObject> promise_event);
+ MUST_USE_RESULT MaybeHandle<Object> MakeAsyncTaskEvent(
+ Handle<JSObject> task_event);
+
+ // Mirror cache handling.
+ void ClearMirrorCache();
+
+ MaybeHandle<Object> PromiseHasUserDefinedRejectHandler(
+ Handle<JSObject> promise);
+
+ void CallEventCallback(v8::DebugEvent event,
+ Handle<Object> exec_state,
+ Handle<Object> event_data,
+ v8::Debug::ClientData* client_data);
+ void ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script);
+ void ProcessDebugEvent(v8::DebugEvent event,
+ Handle<JSObject> event_data,
+ bool auto_continue);
+ void NotifyMessageHandler(v8::DebugEvent event,
+ Handle<JSObject> exec_state,
+ Handle<JSObject> event_data,
+ bool auto_continue);
+ void InvokeMessageHandler(MessageImpl message);
+
+ void ClearOneShot();
+ void ActivateStepOut(StackFrame* frame);
+ void RemoveDebugInfoAndClearFromShared(Handle<DebugInfo> debug_info);
+ Handle<Object> CheckBreakPoints(Handle<Object> break_point);
+ bool CheckBreakPoint(Handle<Object> break_point_object);
+ MaybeHandle<Object> CallFunction(const char* name, int argc,
+ Handle<Object> args[]);
+
+ inline void AssertDebugContext() {
+ DCHECK(isolate_->context() == *debug_context());
+ DCHECK(in_debug_scope());
+ }
+
+ void ThreadInit();
+
+ // Global handles.
+ Handle<Context> debug_context_;
+ Handle<Object> event_listener_;
+ Handle<Object> event_listener_data_;
+
+ v8::Debug::MessageHandler message_handler_;
+
+ static const int kQueueInitialSize = 4;
+ base::Semaphore command_received_; // Signaled for each command received.
+ LockingCommandMessageQueue command_queue_;
+
+ bool is_active_;
+ bool is_suppressed_;
+ bool live_edit_enabled_;
+ bool break_disabled_;
+ bool break_points_active_;
+ bool in_debug_event_listener_;
+ bool break_on_exception_;
+ bool break_on_uncaught_exception_;
+
+ DebugInfoListNode* debug_info_list_; // List of active debug info objects.
+
+ // Storage location for jump when exiting debug break calls.
+ // Note that this address is not GC safe. It should be computed immediately
+ // before returning to the DebugBreakCallHelper.
+ Address after_break_target_;
+
+ // Used to collect histogram data on debugger feature usage.
+ DebugFeatureTracker feature_tracker_;
+
+ // Per-thread data.
+ class ThreadLocal {
+ public:
+ // 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_;
+
+ // Step action for last step performed.
+ StepAction last_step_action_;
+
+ // Source statement position from last step next action.
+ int last_statement_position_;
+
+ // Frame pointer from last step next or step frame action.
+ Address last_fp_;
+
+ // Frame pointer of the target frame we want to arrive at.
+ Address target_fp_;
+
+ // Whether functions are flooded on entry for step-in and step-frame.
+ // If we stepped out to the embedder, disable flooding to spill stepping
+ // to the next call that the embedder makes.
+ bool step_in_enabled_;
+
+ // Stores the way how LiveEdit has patched the stack. It is used when
+ // debugger returns control back to user script.
+ LiveEdit::FrameDropMode frame_drop_mode_;
+ };
+
+ // Storage location for registers when handling debug break calls
+ ThreadLocal thread_local_;
+
+ Isolate* isolate_;
+
+ friend class Isolate;
+ friend class DebugScope;
+ friend class DisableBreak;
+ friend class LiveEdit;
+ friend class SuppressDebug;
+
+ friend Handle<FixedArray> GetDebuggedFunctions(); // In test-debug.cc
+ friend void CheckDebuggerUnloaded(bool check_functions); // In test-debug.cc
+
+ DISALLOW_COPY_AND_ASSIGN(Debug);
+};
+
+
+// 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 {
+ 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(); }
+
+ 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_;
+};
+
+
+// Stack allocated class for disabling break.
+class DisableBreak BASE_EMBEDDED {
+ public:
+ explicit DisableBreak(Debug* debug, bool disable_break)
+ : debug_(debug),
+ previous_break_disabled_(debug->break_disabled_),
+ previous_in_debug_event_listener_(debug->in_debug_event_listener_) {
+ debug_->break_disabled_ = disable_break;
+ debug_->in_debug_event_listener_ = disable_break;
+ }
+ ~DisableBreak() {
+ debug_->break_disabled_ = previous_break_disabled_;
+ debug_->in_debug_event_listener_ = previous_in_debug_event_listener_;
+ }
+
+ private:
+ Debug* debug_;
+ bool previous_break_disabled_;
+ bool previous_in_debug_event_listener_;
+ DISALLOW_COPY_AND_ASSIGN(DisableBreak);
+};
+
+
+class SuppressDebug BASE_EMBEDDED {
+ public:
+ explicit SuppressDebug(Debug* debug)
+ : debug_(debug), old_state_(debug->is_suppressed_) {
+ debug_->is_suppressed_ = true;
+ }
+ ~SuppressDebug() { debug_->is_suppressed_ = old_state_; }
+
+ private:
+ Debug* debug_;
+ bool old_state_;
+ DISALLOW_COPY_AND_ASSIGN(SuppressDebug);
+};
+
+
+// Code generator routines.
+class DebugCodegen : public AllStatic {
+ public:
+ enum DebugBreakCallHelperMode {
+ SAVE_RESULT_REGISTER,
+ IGNORE_RESULT_REGISTER
+ };
+
+ static void GenerateDebugBreakStub(MacroAssembler* masm,
+ DebugBreakCallHelperMode mode);
+
+ // FrameDropper is a code replacement for a JavaScript frame with possibly
+ // several frames above.
+ // There is no calling conventions here, because it never actually gets
+ // called, it only gets returned to.
+ static void GenerateFrameDropperLiveEdit(MacroAssembler* masm);
+
+
+ static void GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode);
+
+ static void PatchDebugBreakSlot(Isolate* isolate, Address pc,
+ Handle<Code> code);
+ static void ClearDebugBreakSlot(Isolate* isolate, Address pc);
+};
+
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_DEBUG_DEBUG_H_
diff --git a/src/debug/debug.js b/src/debug/debug.js
new file mode 100644
index 0000000..bc2c696
--- /dev/null
+++ b/src/debug/debug.js
@@ -0,0 +1,2612 @@
+// 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 JSONParse = global.JSON.parse;
+var JSONStringify = global.JSON.stringify;
+var LookupMirror = global.LookupMirror;
+var MakeError;
+var MakeTypeError;
+var MakeMirror = global.MakeMirror;
+var MakeMirrorSerializer = global.MakeMirrorSerializer;
+var MathMin = global.Math.min;
+var Mirror = global.Mirror;
+var MirrorType;
+var ParseInt = global.parseInt;
+var ValueMirror = global.ValueMirror;
+
+utils.Import(function(from) {
+ MakeError = from.MakeError;
+ MakeTypeError = from.MakeTypeError;
+ MirrorType = from.MirrorType;
+});
+
+//----------------------------------------------------------------------------
+
+// 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,
+ NewFunction: 3,
+ BeforeCompile: 4,
+ AfterCompile: 5,
+ CompileError: 6,
+ PromiseEvent: 7,
+ AsyncTaskEvent: 8 };
+
+// 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,
+ StepFrame: 3 };
+
+// The different types of scripts matching enum ScriptType in objects.h.
+Debug.ScriptType = { Native: 0,
+ Extension: 1,
+ Normal: 2 };
+
+// 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.hit_count_ = 0;
+ this.active_ = true;
+ this.condition_ = null;
+ this.ignoreCount_ = 0;
+}
+
+
+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.hit_count = function() {
+ return this.hit_count_;
+};
+
+
+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.ignoreCount = function() {
+ return this.ignoreCount_;
+};
+
+
+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.setIgnoreCount = function(ignoreCount) {
+ this.ignoreCount_ = ignoreCount;
+};
+
+
+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;
+ }
+ }
+
+ // Update the hit count.
+ this.hit_count_++;
+ if (this.script_break_point_) {
+ this.script_break_point_.hit_count_++;
+ }
+
+ // If the break point has an ignore count it is not triggered.
+ if (this.ignoreCount_ > 0) {
+ this.ignoreCount_--;
+ 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 MakeError(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.hit_count_ = 0;
+ this.active_ = true;
+ this.condition_ = null;
+ this.ignoreCount_ = 0;
+ this.break_points_ = [];
+}
+
+
+// Creates a clone of script breakpoint that is linked to another script.
+ScriptBreakPoint.prototype.cloneForOtherScript = function (other_script) {
+ var copy = new ScriptBreakPoint(Debug.ScriptBreakPointType.ScriptId,
+ other_script.id, this.line_, this.column_, this.groupId_,
+ this.position_alignment_);
+ copy.number_ = next_break_point_number++;
+ script_break_points.push(copy);
+
+ copy.hit_count_ = this.hit_count_;
+ copy.active_ = this.active_;
+ copy.condition_ = this.condition_;
+ copy.ignoreCount_ = this.ignoreCount_;
+ return copy;
+};
+
+
+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.hit_count = function() {
+ return this.hit_count_;
+};
+
+
+ScriptBreakPoint.prototype.active = function() {
+ return this.active_;
+};
+
+
+ScriptBreakPoint.prototype.condition = function() {
+ return this.condition_;
+};
+
+
+ScriptBreakPoint.prototype.ignoreCount = function() {
+ return this.ignoreCount_;
+};
+
+
+ScriptBreakPoint.prototype.enable = function() {
+ this.active_ = true;
+};
+
+
+ScriptBreakPoint.prototype.disable = function() {
+ this.active_ = false;
+};
+
+
+ScriptBreakPoint.prototype.setCondition = function(condition) {
+ this.condition_ = condition;
+};
+
+
+ScriptBreakPoint.prototype.setIgnoreCount = function(ignoreCount) {
+ this.ignoreCount_ = ignoreCount;
+
+ // Set ignore count on all break points created from this script break point.
+ for (var i = 0; i < this.break_points_.length; i++) {
+ this.break_points_[i].setIgnoreCount(ignoreCount);
+ }
+};
+
+
+// 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 + script.lineCount())) {
+ 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 MakeError(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 = script.sourceLine(this.line());
+
+ // Allocate array for caching the columns where the actual source starts.
+ if (!script.sourceColumnStart_) {
+ script.sourceColumnStart_ = new GlobalArray(script.lineCount());
+ }
+
+ // 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);
+ break_point.setIgnoreCount(this.ignoreCount());
+ 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_ = [];
+};
+
+
+// Function called from runtime when a new script is compiled to set any script
+// break points set in this script.
+function UpdateScriptBreakPoints(script) {
+ for (var i = 0; i < script_break_points.length; i++) {
+ var break_point = script_break_points[i];
+ if ((break_point.type() == Debug.ScriptBreakPointType.ScriptName ||
+ break_point.type() == Debug.ScriptBreakPointType.ScriptRegExp) &&
+ break_point.matchesScript(script)) {
+ break_point.set(script);
+ }
+ }
+}
+
+
+function GetScriptBreakPoints(script) {
+ var result = [];
+ for (var i = 0; i < script_break_points.length; i++) {
+ if (script_break_points[i].matchesScript(script)) {
+ result.push(script_break_points[i]);
+ }
+ }
+ return result;
+}
+
+
+Debug.setListener = function(listener, opt_data) {
+ if (!IS_FUNCTION(listener) && !IS_UNDEFINED(listener) && !IS_NULL(listener)) {
+ throw MakeTypeError(kDebuggerType);
+ }
+ %SetDebugEventListener(listener, opt_data);
+};
+
+
+Debug.breakLocations = function(f, opt_position_aligment) {
+ if (!IS_FUNCTION(f)) throw MakeTypeError(kDebuggerType);
+ var position_aligment = IS_UNDEFINED(opt_position_aligment)
+ ? Debug.BreakPositionAlignment.Statement : opt_position_aligment;
+ return %GetBreakLocations(f, position_aligment);
+};
+
+// 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 (IS_REGEXP(func_or_script_name)) {
+ var scripts = Debug.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 MakeTypeError(kDebuggerType);
+ return %FunctionGetSourceCode(f);
+};
+
+
+Debug.sourcePosition = function(f) {
+ if (!IS_FUNCTION(f)) throw MakeTypeError(kDebuggerType);
+ return %FunctionGetScriptSourcePosition(f);
+};
+
+
+Debug.findFunctionSourceLocation = function(func, opt_line, opt_column) {
+ var script = %FunctionGetScript(func);
+ var script_offset = %FunctionGetScriptSourcePosition(func);
+ return script.locationFromLine(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 = script.locationFromLine(opt_line, opt_column);
+ 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 MakeTypeError(kDebuggerType);
+ // Break points in API functions are not supported.
+ if (%FunctionIsAPIFunction(func)) {
+ throw MakeError(kDebugger, 'Cannot set break point in native code.');
+ }
+ // Find source position relative to start of the function
+ var break_position =
+ this.findFunctionSourceLocation(func, opt_line, opt_column).position;
+ var source_position = break_position - this.sourcePosition(func);
+ // 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 MakeError(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) {
+ // Adjust the source position to be script relative.
+ source_position += %FunctionGetScriptSourcePosition(func);
+ // 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);
+ actual_position += this.sourcePosition(func);
+ 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 scripts = this.scripts();
+ var position_alignment = IS_UNDEFINED(opt_position_alignment)
+ ? Debug.BreakPositionAlignment.Statement : opt_position_alignment;
+ for (var i = 0; i < scripts.length; i++) {
+ if (script_id == scripts[i].id) {
+ break_point.actual_position = %SetScriptBreakPoint(scripts[i], position,
+ position_alignment, break_point);
+ break;
+ }
+ }
+ 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.changeBreakPointIgnoreCount = function(break_point_number, ignoreCount) {
+ if (ignoreCount < 0) throw MakeError(kDebugger, 'Invalid argument');
+ var break_point = this.findBreakPoint(break_point_number, false);
+ break_point.setIgnoreCount(ignoreCount);
+};
+
+
+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 MakeError(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.changeScriptBreakPointIgnoreCount = function(
+ break_point_number, ignoreCount) {
+ if (ignoreCount < 0) throw MakeError(kDebugger, 'Invalid argument');
+ var script_break_point = this.findScriptBreakPoint(break_point_number, false);
+ script_break_point.setIgnoreCount(ignoreCount);
+};
+
+
+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 MakeError(kDebuggerType);
+ var source = full ? this.scriptSource(f) : this.source(f);
+ var offset = full ? this.sourcePosition(f) : 0;
+ var locations = this.breakLocations(f, opt_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();
+};
+
+
+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 ||
+ action === Debug.StepAction.StepFrame) {
+ return %PrepareStep(this.break_id, action);
+ }
+ throw MakeTypeError(kDebuggerType);
+};
+
+ExecutionState.prototype.evaluateGlobal = function(source, disable_break,
+ opt_additional_context) {
+ return MakeMirror(%DebugEvaluateGlobal(this.break_id, source,
+ TO_BOOLEAN(disable_break),
+ opt_additional_context));
+};
+
+ExecutionState.prototype.frameCount = function() {
+ return %GetFrameCount(this.break_id);
+};
+
+ExecutionState.prototype.threadCount = function() {
+ return %GetThreadCount(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 MakeTypeError(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 MakeTypeError(kDebuggerFrame);
+ }
+ this.selected_frame = i;
+};
+
+ExecutionState.prototype.selectedFrame = function() {
+ return this.selected_frame;
+};
+
+ExecutionState.prototype.debugCommandProcessor = function(opt_is_running) {
+ return new DebugCommandProcessor(this, opt_is_running);
+};
+
+
+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_;
+};
+
+
+BreakEvent.prototype.toJSONProtocol = function() {
+ var o = { seq: next_response_seq++,
+ type: "event",
+ event: "break",
+ body: { invocationText: this.frame_.invocationText() }
+ };
+
+ // Add script related information to the event if available.
+ var script = this.func().script();
+ if (script) {
+ o.body.sourceLine = this.sourceLine(),
+ o.body.sourceColumn = this.sourceColumn(),
+ o.body.sourceLineText = this.sourceLineText(),
+ o.body.script = MakeScriptObject_(script, false);
+ }
+
+ // Add an Array of break points hit if any.
+ if (this.breakPointsHit()) {
+ o.body.breakpoints = [];
+ for (var i = 0; i < this.breakPointsHit().length; i++) {
+ // Find the break point number. For break points originating from a
+ // script break point supply the script break point number.
+ var breakpoint = this.breakPointsHit()[i];
+ var script_break_point = breakpoint.script_break_point();
+ var number;
+ if (script_break_point) {
+ number = script_break_point.number();
+ } else {
+ number = breakpoint.number();
+ }
+ o.body.breakpoints.push(number);
+ }
+ }
+ return JSONStringify(ObjectToProtocolObject_(o));
+};
+
+
+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();
+};
+
+
+ExceptionEvent.prototype.toJSONProtocol = function() {
+ var o = new ProtocolMessage();
+ o.event = "exception";
+ o.body = { uncaught: this.uncaught_,
+ exception: MakeMirror(this.exception_)
+ };
+
+ // Exceptions might happen whithout any JavaScript frames.
+ if (this.exec_state_.frameCount() > 0) {
+ o.body.sourceLine = this.sourceLine();
+ o.body.sourceColumn = this.sourceColumn();
+ o.body.sourceLineText = this.sourceLineText();
+
+ // Add script information to the event if available.
+ var script = this.func().script();
+ if (script) {
+ o.body.script = MakeScriptObject_(script, false);
+ }
+ } else {
+ o.body.sourceLine = -1;
+ }
+
+ return o.toJSONProtocol();
+};
+
+
+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_;
+};
+
+
+CompileEvent.prototype.toJSONProtocol = function() {
+ var o = new ProtocolMessage();
+ o.running = true;
+ switch (this.type_) {
+ case Debug.DebugEvent.BeforeCompile:
+ o.event = "beforeCompile";
+ break;
+ case Debug.DebugEvent.AfterCompile:
+ o.event = "afterCompile";
+ break;
+ case Debug.DebugEvent.CompileError:
+ o.event = "compileError";
+ break;
+ }
+ o.body = {};
+ o.body.script = this.script_;
+
+ return o.toJSONProtocol();
+};
+
+
+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 MakePromiseEvent(event_data) {
+ return new PromiseEvent(event_data);
+}
+
+
+function PromiseEvent(event_data) {
+ this.promise_ = event_data.promise;
+ this.parentPromise_ = event_data.parentPromise;
+ this.status_ = event_data.status;
+ this.value_ = event_data.value;
+}
+
+
+PromiseEvent.prototype.promise = function() {
+ return MakeMirror(this.promise_);
+}
+
+
+PromiseEvent.prototype.parentPromise = function() {
+ return MakeMirror(this.parentPromise_);
+}
+
+
+PromiseEvent.prototype.status = function() {
+ return this.status_;
+}
+
+
+PromiseEvent.prototype.value = function() {
+ return MakeMirror(this.value_);
+}
+
+
+function MakeAsyncTaskEvent(event_data) {
+ return new AsyncTaskEvent(event_data);
+}
+
+
+function AsyncTaskEvent(event_data) {
+ this.type_ = event_data.type;
+ this.name_ = event_data.name;
+ this.id_ = event_data.id;
+}
+
+
+AsyncTaskEvent.prototype.type = function() {
+ return this.type_;
+}
+
+
+AsyncTaskEvent.prototype.name = function() {
+ return this.name_;
+}
+
+
+AsyncTaskEvent.prototype.id = function() {
+ return this.id_;
+}
+
+
+function DebugCommandProcessor(exec_state, opt_is_running) {
+ this.exec_state_ = exec_state;
+ this.running_ = opt_is_running || false;
+}
+
+
+DebugCommandProcessor.prototype.processDebugRequest = function (request) {
+ return this.processDebugJSONRequest(request);
+};
+
+
+function ProtocolMessage(request) {
+ // Update sequence number.
+ this.seq = next_response_seq++;
+
+ if (request) {
+ // If message is based on a request this is a response. Fill the initial
+ // response from the request.
+ this.type = 'response';
+ this.request_seq = request.seq;
+ this.command = request.command;
+ } else {
+ // If message is not based on a request it is a dabugger generated event.
+ this.type = 'event';
+ }
+ this.success = true;
+ // Handler may set this field to control debugger state.
+ this.running = UNDEFINED;
+}
+
+
+ProtocolMessage.prototype.setOption = function(name, value) {
+ if (!this.options_) {
+ this.options_ = {};
+ }
+ this.options_[name] = value;
+};
+
+
+ProtocolMessage.prototype.failed = function(message, opt_details) {
+ this.success = false;
+ this.message = message;
+ if (IS_OBJECT(opt_details)) {
+ this.error_details = opt_details;
+ }
+};
+
+
+ProtocolMessage.prototype.toJSONProtocol = function() {
+ // Encode the protocol header.
+ var json = {};
+ json.seq= this.seq;
+ if (this.request_seq) {
+ json.request_seq = this.request_seq;
+ }
+ json.type = this.type;
+ if (this.event) {
+ json.event = this.event;
+ }
+ if (this.command) {
+ json.command = this.command;
+ }
+ if (this.success) {
+ json.success = this.success;
+ } else {
+ json.success = false;
+ }
+ if (this.body) {
+ // Encode the body part.
+ var bodyJson;
+ var serializer = MakeMirrorSerializer(true, this.options_);
+ if (this.body instanceof Mirror) {
+ bodyJson = serializer.serializeValue(this.body);
+ } else if (this.body instanceof GlobalArray) {
+ bodyJson = [];
+ for (var i = 0; i < this.body.length; i++) {
+ if (this.body[i] instanceof Mirror) {
+ bodyJson.push(serializer.serializeValue(this.body[i]));
+ } else {
+ bodyJson.push(ObjectToProtocolObject_(this.body[i], serializer));
+ }
+ }
+ } else {
+ bodyJson = ObjectToProtocolObject_(this.body, serializer);
+ }
+ json.body = bodyJson;
+ json.refs = serializer.serializeReferencedObjects();
+ }
+ if (this.message) {
+ json.message = this.message;
+ }
+ if (this.error_details) {
+ json.error_details = this.error_details;
+ }
+ json.running = this.running;
+ return JSONStringify(json);
+};
+
+
+DebugCommandProcessor.prototype.createResponse = function(request) {
+ return new ProtocolMessage(request);
+};
+
+
+DebugCommandProcessor.prototype.processDebugJSONRequest = function(
+ json_request) {
+ var request; // Current request.
+ var response; // Generated response.
+ try {
+ try {
+ // Convert the JSON string to an object.
+ request = JSONParse(json_request);
+
+ // Create an initial response.
+ response = this.createResponse(request);
+
+ if (!request.type) {
+ throw MakeError(kDebugger, 'Type not specified');
+ }
+
+ if (request.type != 'request') {
+ throw MakeError(kDebugger,
+ "Illegal type '" + request.type + "' in request");
+ }
+
+ if (!request.command) {
+ throw MakeError(kDebugger, 'Command not specified');
+ }
+
+ if (request.arguments) {
+ var args = request.arguments;
+ // TODO(yurys): remove request.arguments.compactFormat check once
+ // ChromeDevTools are switched to 'inlineRefs'
+ if (args.inlineRefs || args.compactFormat) {
+ response.setOption('inlineRefs', true);
+ }
+ if (!IS_UNDEFINED(args.maxStringLength)) {
+ response.setOption('maxStringLength', args.maxStringLength);
+ }
+ }
+
+ var key = request.command.toLowerCase();
+ var handler = DebugCommandProcessor.prototype.dispatch_[key];
+ if (IS_FUNCTION(handler)) {
+ %_Call(handler, this, request, response);
+ } else {
+ throw MakeError(kDebugger,
+ 'Unknown command "' + request.command + '" in request');
+ }
+ } catch (e) {
+ // If there is no response object created one (without command).
+ if (!response) {
+ response = this.createResponse();
+ }
+ response.success = false;
+ response.message = TO_STRING(e);
+ }
+
+ // Return the response as a JSON encoded string.
+ try {
+ if (!IS_UNDEFINED(response.running)) {
+ // Response controls running state.
+ this.running_ = response.running;
+ }
+ response.running = this.running_;
+ return response.toJSONProtocol();
+ } catch (e) {
+ // Failed to generate response - return generic error.
+ return '{"seq":' + response.seq + ',' +
+ '"request_seq":' + request.seq + ',' +
+ '"type":"response",' +
+ '"success":false,' +
+ '"message":"Internal error: ' + TO_STRING(e) + '"}';
+ }
+ } catch (e) {
+ // Failed in one of the catch blocks above - most generic error.
+ return '{"seq":0,"type":"response","success":false,"message":"Internal error"}';
+ }
+};
+
+
+DebugCommandProcessor.prototype.continueRequest_ = function(request, response) {
+ // Check for arguments for continue.
+ if (request.arguments) {
+ var action = Debug.StepAction.StepIn;
+
+ // Pull out arguments.
+ var stepaction = request.arguments.stepaction;
+
+ // Get the stepaction argument.
+ if (stepaction) {
+ if (stepaction == 'in') {
+ action = Debug.StepAction.StepIn;
+ } else if (stepaction == 'next') {
+ action = Debug.StepAction.StepNext;
+ } else if (stepaction == 'out') {
+ action = Debug.StepAction.StepOut;
+ } else {
+ throw MakeError(kDebugger,
+ 'Invalid stepaction argument "' + stepaction + '".');
+ }
+ }
+
+ // Set up the VM for stepping.
+ this.exec_state_.prepareStep(action);
+ }
+
+ // VM should be running after executing this request.
+ response.running = true;
+};
+
+
+DebugCommandProcessor.prototype.breakRequest_ = function(request, response) {
+ // Ignore as break command does not do anything when broken.
+};
+
+
+DebugCommandProcessor.prototype.setBreakPointRequest_ =
+ function(request, response) {
+ // Check for legal request.
+ if (!request.arguments) {
+ response.failed('Missing arguments');
+ return;
+ }
+
+ // Pull out arguments.
+ var type = request.arguments.type;
+ var target = request.arguments.target;
+ var line = request.arguments.line;
+ var column = request.arguments.column;
+ var enabled = IS_UNDEFINED(request.arguments.enabled) ?
+ true : request.arguments.enabled;
+ var condition = request.arguments.condition;
+ var ignoreCount = request.arguments.ignoreCount;
+ var groupId = request.arguments.groupId;
+
+ // Check for legal arguments.
+ if (!type || IS_UNDEFINED(target)) {
+ response.failed('Missing argument "type" or "target"');
+ return;
+ }
+
+ // Either function or script break point.
+ var break_point_number;
+ if (type == 'function') {
+ // Handle function break point.
+ if (!IS_STRING(target)) {
+ response.failed('Argument "target" is not a string value');
+ return;
+ }
+ var f;
+ try {
+ // Find the function through a global evaluate.
+ f = this.exec_state_.evaluateGlobal(target).value();
+ } catch (e) {
+ response.failed('Error: "' + TO_STRING(e) +
+ '" evaluating "' + target + '"');
+ return;
+ }
+ if (!IS_FUNCTION(f)) {
+ response.failed('"' + target + '" does not evaluate to a function');
+ return;
+ }
+
+ // Set function break point.
+ break_point_number = Debug.setBreakPoint(f, line, column, condition);
+ } else if (type == 'handle') {
+ // Find the object pointed by the specified handle.
+ var handle = ParseInt(target, 10);
+ var mirror = LookupMirror(handle);
+ if (!mirror) {
+ return response.failed('Object #' + handle + '# not found');
+ }
+ if (!mirror.isFunction()) {
+ return response.failed('Object #' + handle + '# is not a function');
+ }
+
+ // Set function break point.
+ break_point_number = Debug.setBreakPoint(mirror.value(),
+ line, column, condition);
+ } else if (type == 'script') {
+ // set script break point.
+ break_point_number =
+ Debug.setScriptBreakPointByName(target, line, column, condition,
+ groupId);
+ } else if (type == 'scriptId') {
+ break_point_number =
+ Debug.setScriptBreakPointById(target, line, column, condition, groupId);
+ } else if (type == 'scriptRegExp') {
+ break_point_number =
+ Debug.setScriptBreakPointByRegExp(target, line, column, condition,
+ groupId);
+ } else {
+ response.failed('Illegal type "' + type + '"');
+ return;
+ }
+
+ // Set additional break point properties.
+ var break_point = Debug.findBreakPoint(break_point_number);
+ if (ignoreCount) {
+ Debug.changeBreakPointIgnoreCount(break_point_number, ignoreCount);
+ }
+ if (!enabled) {
+ Debug.disableBreakPoint(break_point_number);
+ }
+
+ // Add the break point number to the response.
+ response.body = { type: type,
+ breakpoint: break_point_number };
+
+ // Add break point information to the response.
+ if (break_point instanceof ScriptBreakPoint) {
+ if (break_point.type() == Debug.ScriptBreakPointType.ScriptId) {
+ response.body.type = 'scriptId';
+ response.body.script_id = break_point.script_id();
+ } else if (break_point.type() == Debug.ScriptBreakPointType.ScriptName) {
+ response.body.type = 'scriptName';
+ response.body.script_name = break_point.script_name();
+ } else if (break_point.type() == Debug.ScriptBreakPointType.ScriptRegExp) {
+ response.body.type = 'scriptRegExp';
+ response.body.script_regexp = break_point.script_regexp_object().source;
+ } else {
+ throw MakeError(kDebugger,
+ "Unexpected breakpoint type: " + break_point.type());
+ }
+ response.body.line = break_point.line();
+ response.body.column = break_point.column();
+ response.body.actual_locations = break_point.actual_locations();
+ } else {
+ response.body.type = 'function';
+ response.body.actual_locations = [break_point.actual_location];
+ }
+};
+
+
+DebugCommandProcessor.prototype.changeBreakPointRequest_ = function(
+ request, response) {
+ // Check for legal request.
+ if (!request.arguments) {
+ response.failed('Missing arguments');
+ return;
+ }
+
+ // Pull out arguments.
+ var break_point = TO_NUMBER(request.arguments.breakpoint);
+ var enabled = request.arguments.enabled;
+ var condition = request.arguments.condition;
+ var ignoreCount = request.arguments.ignoreCount;
+
+ // Check for legal arguments.
+ if (!break_point) {
+ response.failed('Missing argument "breakpoint"');
+ return;
+ }
+
+ // Change enabled state if supplied.
+ if (!IS_UNDEFINED(enabled)) {
+ if (enabled) {
+ Debug.enableBreakPoint(break_point);
+ } else {
+ Debug.disableBreakPoint(break_point);
+ }
+ }
+
+ // Change condition if supplied
+ if (!IS_UNDEFINED(condition)) {
+ Debug.changeBreakPointCondition(break_point, condition);
+ }
+
+ // Change ignore count if supplied
+ if (!IS_UNDEFINED(ignoreCount)) {
+ Debug.changeBreakPointIgnoreCount(break_point, ignoreCount);
+ }
+};
+
+
+DebugCommandProcessor.prototype.clearBreakPointGroupRequest_ = function(
+ request, response) {
+ // Check for legal request.
+ if (!request.arguments) {
+ response.failed('Missing arguments');
+ return;
+ }
+
+ // Pull out arguments.
+ var group_id = request.arguments.groupId;
+
+ // Check for legal arguments.
+ if (!group_id) {
+ response.failed('Missing argument "groupId"');
+ return;
+ }
+
+ var cleared_break_points = [];
+ var new_script_break_points = [];
+ for (var i = 0; i < script_break_points.length; i++) {
+ var next_break_point = script_break_points[i];
+ if (next_break_point.groupId() == group_id) {
+ cleared_break_points.push(next_break_point.number());
+ next_break_point.clear();
+ } else {
+ new_script_break_points.push(next_break_point);
+ }
+ }
+ script_break_points = new_script_break_points;
+
+ // Add the cleared break point numbers to the response.
+ response.body = { breakpoints: cleared_break_points };
+};
+
+
+DebugCommandProcessor.prototype.clearBreakPointRequest_ = function(
+ request, response) {
+ // Check for legal request.
+ if (!request.arguments) {
+ response.failed('Missing arguments');
+ return;
+ }
+
+ // Pull out arguments.
+ var break_point = TO_NUMBER(request.arguments.breakpoint);
+
+ // Check for legal arguments.
+ if (!break_point) {
+ response.failed('Missing argument "breakpoint"');
+ return;
+ }
+
+ // Clear break point.
+ Debug.clearBreakPoint(break_point);
+
+ // Add the cleared break point number to the response.
+ response.body = { breakpoint: break_point };
+};
+
+
+DebugCommandProcessor.prototype.listBreakpointsRequest_ = function(
+ request, response) {
+ var array = [];
+ for (var i = 0; i < script_break_points.length; i++) {
+ var break_point = script_break_points[i];
+
+ var description = {
+ number: break_point.number(),
+ line: break_point.line(),
+ column: break_point.column(),
+ groupId: break_point.groupId(),
+ hit_count: break_point.hit_count(),
+ active: break_point.active(),
+ condition: break_point.condition(),
+ ignoreCount: break_point.ignoreCount(),
+ actual_locations: break_point.actual_locations()
+ };
+
+ if (break_point.type() == Debug.ScriptBreakPointType.ScriptId) {
+ description.type = 'scriptId';
+ description.script_id = break_point.script_id();
+ } else if (break_point.type() == Debug.ScriptBreakPointType.ScriptName) {
+ description.type = 'scriptName';
+ description.script_name = break_point.script_name();
+ } else if (break_point.type() == Debug.ScriptBreakPointType.ScriptRegExp) {
+ description.type = 'scriptRegExp';
+ description.script_regexp = break_point.script_regexp_object().source;
+ } else {
+ throw MakeError(kDebugger,
+ "Unexpected breakpoint type: " + break_point.type());
+ }
+ array.push(description);
+ }
+
+ response.body = {
+ breakpoints: array,
+ breakOnExceptions: Debug.isBreakOnException(),
+ breakOnUncaughtExceptions: Debug.isBreakOnUncaughtException()
+ };
+};
+
+
+DebugCommandProcessor.prototype.disconnectRequest_ =
+ function(request, response) {
+ Debug.disableAllBreakPoints();
+ this.continueRequest_(request, response);
+};
+
+
+DebugCommandProcessor.prototype.setExceptionBreakRequest_ =
+ function(request, response) {
+ // Check for legal request.
+ if (!request.arguments) {
+ response.failed('Missing arguments');
+ return;
+ }
+
+ // Pull out and check the 'type' argument:
+ var type = request.arguments.type;
+ if (!type) {
+ response.failed('Missing argument "type"');
+ return;
+ }
+
+ // Initialize the default value of enable:
+ var enabled;
+ if (type == 'all') {
+ enabled = !Debug.isBreakOnException();
+ } else if (type == 'uncaught') {
+ enabled = !Debug.isBreakOnUncaughtException();
+ }
+
+ // Pull out and check the 'enabled' argument if present:
+ if (!IS_UNDEFINED(request.arguments.enabled)) {
+ enabled = request.arguments.enabled;
+ if ((enabled != true) && (enabled != false)) {
+ response.failed('Illegal value for "enabled":"' + enabled + '"');
+ }
+ }
+
+ // Now set the exception break state:
+ if (type == 'all') {
+ %ChangeBreakOnException(Debug.ExceptionBreak.Caught, enabled);
+ } else if (type == 'uncaught') {
+ %ChangeBreakOnException(Debug.ExceptionBreak.Uncaught, enabled);
+ } else {
+ response.failed('Unknown "type":"' + type + '"');
+ }
+
+ // Add the cleared break point number to the response.
+ response.body = { 'type': type, 'enabled': enabled };
+};
+
+
+DebugCommandProcessor.prototype.backtraceRequest_ = function(
+ request, response) {
+ // Get the number of frames.
+ var total_frames = this.exec_state_.frameCount();
+
+ // Create simple response if there are no frames.
+ if (total_frames == 0) {
+ response.body = {
+ totalFrames: total_frames
+ };
+ return;
+ }
+
+ // Default frame range to include in backtrace.
+ var from_index = 0;
+ var to_index = kDefaultBacktraceLength;
+
+ // Get the range from the arguments.
+ if (request.arguments) {
+ if (request.arguments.fromFrame) {
+ from_index = request.arguments.fromFrame;
+ }
+ if (request.arguments.toFrame) {
+ to_index = request.arguments.toFrame;
+ }
+ if (request.arguments.bottom) {
+ var tmp_index = total_frames - from_index;
+ from_index = total_frames - to_index;
+ to_index = tmp_index;
+ }
+ if (from_index < 0 || to_index < 0) {
+ return response.failed('Invalid frame number');
+ }
+ }
+
+ // Adjust the index.
+ to_index = MathMin(total_frames, to_index);
+
+ if (to_index <= from_index) {
+ var error = 'Invalid frame range';
+ return response.failed(error);
+ }
+
+ // Create the response body.
+ var frames = [];
+ for (var i = from_index; i < to_index; i++) {
+ frames.push(this.exec_state_.frame(i));
+ }
+ response.body = {
+ fromFrame: from_index,
+ toFrame: to_index,
+ totalFrames: total_frames,
+ frames: frames
+ };
+};
+
+
+DebugCommandProcessor.prototype.frameRequest_ = function(request, response) {
+ // No frames no source.
+ if (this.exec_state_.frameCount() == 0) {
+ return response.failed('No frames');
+ }
+
+ // With no arguments just keep the selected frame.
+ if (request.arguments) {
+ var index = request.arguments.number;
+ if (index < 0 || this.exec_state_.frameCount() <= index) {
+ return response.failed('Invalid frame number');
+ }
+
+ this.exec_state_.setSelectedFrame(request.arguments.number);
+ }
+ response.body = this.exec_state_.frame();
+};
+
+
+DebugCommandProcessor.prototype.resolveFrameFromScopeDescription_ =
+ function(scope_description) {
+ // Get the frame for which the scope or scopes are requested.
+ // With no frameNumber argument use the currently selected frame.
+ if (scope_description && !IS_UNDEFINED(scope_description.frameNumber)) {
+ var frame_index = scope_description.frameNumber;
+ if (frame_index < 0 || this.exec_state_.frameCount() <= frame_index) {
+ throw MakeTypeError(kDebuggerFrame);
+ }
+ return this.exec_state_.frame(frame_index);
+ } else {
+ return this.exec_state_.frame();
+ }
+};
+
+
+// Gets scope host object from request. It is either a function
+// ('functionHandle' argument must be specified) or a stack frame
+// ('frameNumber' may be specified and the current frame is taken by default).
+DebugCommandProcessor.prototype.resolveScopeHolder_ =
+ function(scope_description) {
+ if (scope_description && "functionHandle" in scope_description) {
+ if (!IS_NUMBER(scope_description.functionHandle)) {
+ throw MakeError(kDebugger, 'Function handle must be a number');
+ }
+ var function_mirror = LookupMirror(scope_description.functionHandle);
+ if (!function_mirror) {
+ throw MakeError(kDebugger, 'Failed to find function object by handle');
+ }
+ if (!function_mirror.isFunction()) {
+ throw MakeError(kDebugger,
+ 'Value of non-function type is found by handle');
+ }
+ return function_mirror;
+ } else {
+ // No frames no scopes.
+ if (this.exec_state_.frameCount() == 0) {
+ throw MakeError(kDebugger, 'No scopes');
+ }
+
+ // Get the frame for which the scopes are requested.
+ var frame = this.resolveFrameFromScopeDescription_(scope_description);
+ return frame;
+ }
+}
+
+
+DebugCommandProcessor.prototype.scopesRequest_ = function(request, response) {
+ var scope_holder = this.resolveScopeHolder_(request.arguments);
+
+ // Fill all scopes for this frame or function.
+ var total_scopes = scope_holder.scopeCount();
+ var scopes = [];
+ for (var i = 0; i < total_scopes; i++) {
+ scopes.push(scope_holder.scope(i));
+ }
+ response.body = {
+ fromScope: 0,
+ toScope: total_scopes,
+ totalScopes: total_scopes,
+ scopes: scopes
+ };
+};
+
+
+DebugCommandProcessor.prototype.scopeRequest_ = function(request, response) {
+ // Get the frame or function for which the scope is requested.
+ var scope_holder = this.resolveScopeHolder_(request.arguments);
+
+ // With no scope argument just return top scope.
+ var scope_index = 0;
+ if (request.arguments && !IS_UNDEFINED(request.arguments.number)) {
+ scope_index = TO_NUMBER(request.arguments.number);
+ if (scope_index < 0 || scope_holder.scopeCount() <= scope_index) {
+ return response.failed('Invalid scope number');
+ }
+ }
+
+ response.body = scope_holder.scope(scope_index);
+};
+
+
+// Reads value from protocol description. Description may be in form of type
+// (for singletons), raw value (primitive types supported in JSON),
+// string value description plus type (for primitive values) or handle id.
+// Returns raw value or throws exception.
+DebugCommandProcessor.resolveValue_ = function(value_description) {
+ if ("handle" in value_description) {
+ var value_mirror = LookupMirror(value_description.handle);
+ if (!value_mirror) {
+ throw MakeError(kDebugger, "Failed to resolve value by handle, ' #" +
+ value_description.handle + "# not found");
+ }
+ return value_mirror.value();
+ } else if ("stringDescription" in value_description) {
+ if (value_description.type == MirrorType.BOOLEAN_TYPE) {
+ return TO_BOOLEAN(value_description.stringDescription);
+ } else if (value_description.type == MirrorType.NUMBER_TYPE) {
+ return TO_NUMBER(value_description.stringDescription);
+ } if (value_description.type == MirrorType.STRING_TYPE) {
+ return TO_STRING(value_description.stringDescription);
+ } else {
+ throw MakeError(kDebugger, "Unknown type");
+ }
+ } else if ("value" in value_description) {
+ return value_description.value;
+ } else if (value_description.type == MirrorType.UNDEFINED_TYPE) {
+ return UNDEFINED;
+ } else if (value_description.type == MirrorType.NULL_TYPE) {
+ return null;
+ } else {
+ throw MakeError(kDebugger, "Failed to parse value description");
+ }
+};
+
+
+DebugCommandProcessor.prototype.setVariableValueRequest_ =
+ function(request, response) {
+ if (!request.arguments) {
+ response.failed('Missing arguments');
+ return;
+ }
+
+ if (IS_UNDEFINED(request.arguments.name)) {
+ response.failed('Missing variable name');
+ }
+ var variable_name = request.arguments.name;
+
+ var scope_description = request.arguments.scope;
+
+ // Get the frame or function for which the scope is requested.
+ var scope_holder = this.resolveScopeHolder_(scope_description);
+
+ if (IS_UNDEFINED(scope_description.number)) {
+ response.failed('Missing scope number');
+ }
+ var scope_index = TO_NUMBER(scope_description.number);
+
+ var scope = scope_holder.scope(scope_index);
+
+ var new_value =
+ DebugCommandProcessor.resolveValue_(request.arguments.newValue);
+
+ scope.setVariableValue(variable_name, new_value);
+
+ var new_value_mirror = MakeMirror(new_value);
+
+ response.body = {
+ newValue: new_value_mirror
+ };
+};
+
+
+DebugCommandProcessor.prototype.evaluateRequest_ = function(request, response) {
+ if (!request.arguments) {
+ return response.failed('Missing arguments');
+ }
+
+ // Pull out arguments.
+ var expression = request.arguments.expression;
+ var frame = request.arguments.frame;
+ var global = request.arguments.global;
+ var disable_break = request.arguments.disable_break;
+ var additional_context = request.arguments.additional_context;
+
+ // The expression argument could be an integer so we convert it to a
+ // string.
+ try {
+ expression = TO_STRING(expression);
+ } catch(e) {
+ return response.failed('Failed to convert expression argument to string');
+ }
+
+ // Check for legal arguments.
+ if (!IS_UNDEFINED(frame) && global) {
+ return response.failed('Arguments "frame" and "global" are exclusive');
+ }
+
+ var additional_context_object;
+ if (additional_context) {
+ additional_context_object = {};
+ for (var i = 0; i < additional_context.length; i++) {
+ var mapping = additional_context[i];
+
+ if (!IS_STRING(mapping.name)) {
+ return response.failed("Context element #" + i +
+ " doesn't contain name:string property");
+ }
+
+ var raw_value = DebugCommandProcessor.resolveValue_(mapping);
+ additional_context_object[mapping.name] = raw_value;
+ }
+ }
+
+ // Global evaluate.
+ if (global) {
+ // Evaluate in the native context.
+ response.body = this.exec_state_.evaluateGlobal(
+ expression, TO_BOOLEAN(disable_break), additional_context_object);
+ return;
+ }
+
+ // Default value for disable_break is true.
+ if (IS_UNDEFINED(disable_break)) {
+ disable_break = true;
+ }
+
+ // No frames no evaluate in frame.
+ if (this.exec_state_.frameCount() == 0) {
+ return response.failed('No frames');
+ }
+
+ // Check whether a frame was specified.
+ if (!IS_UNDEFINED(frame)) {
+ var frame_number = TO_NUMBER(frame);
+ if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
+ return response.failed('Invalid frame "' + frame + '"');
+ }
+ // Evaluate in the specified frame.
+ response.body = this.exec_state_.frame(frame_number).evaluate(
+ expression, TO_BOOLEAN(disable_break), additional_context_object);
+ return;
+ } else {
+ // Evaluate in the selected frame.
+ response.body = this.exec_state_.frame().evaluate(
+ expression, TO_BOOLEAN(disable_break), additional_context_object);
+ return;
+ }
+};
+
+
+DebugCommandProcessor.prototype.lookupRequest_ = function(request, response) {
+ if (!request.arguments) {
+ return response.failed('Missing arguments');
+ }
+
+ // Pull out arguments.
+ var handles = request.arguments.handles;
+
+ // Check for legal arguments.
+ if (IS_UNDEFINED(handles)) {
+ return response.failed('Argument "handles" missing');
+ }
+
+ // Set 'includeSource' option for script lookup.
+ if (!IS_UNDEFINED(request.arguments.includeSource)) {
+ var includeSource = TO_BOOLEAN(request.arguments.includeSource);
+ response.setOption('includeSource', includeSource);
+ }
+
+ // Lookup handles.
+ var mirrors = {};
+ for (var i = 0; i < handles.length; i++) {
+ var handle = handles[i];
+ var mirror = LookupMirror(handle);
+ if (!mirror) {
+ return response.failed('Object #' + handle + '# not found');
+ }
+ mirrors[handle] = mirror;
+ }
+ response.body = mirrors;
+};
+
+
+DebugCommandProcessor.prototype.referencesRequest_ =
+ function(request, response) {
+ if (!request.arguments) {
+ return response.failed('Missing arguments');
+ }
+
+ // Pull out arguments.
+ var type = request.arguments.type;
+ var handle = request.arguments.handle;
+
+ // Check for legal arguments.
+ if (IS_UNDEFINED(type)) {
+ return response.failed('Argument "type" missing');
+ }
+ if (IS_UNDEFINED(handle)) {
+ return response.failed('Argument "handle" missing');
+ }
+ if (type != 'referencedBy' && type != 'constructedBy') {
+ return response.failed('Invalid type "' + type + '"');
+ }
+
+ // Lookup handle and return objects with references the object.
+ var mirror = LookupMirror(handle);
+ if (mirror) {
+ if (type == 'referencedBy') {
+ response.body = mirror.referencedBy();
+ } else {
+ response.body = mirror.constructedBy();
+ }
+ } else {
+ return response.failed('Object #' + handle + '# not found');
+ }
+};
+
+
+DebugCommandProcessor.prototype.sourceRequest_ = function(request, response) {
+ // No frames no source.
+ if (this.exec_state_.frameCount() == 0) {
+ return response.failed('No source');
+ }
+
+ var from_line;
+ var to_line;
+ var frame = this.exec_state_.frame();
+ if (request.arguments) {
+ // Pull out arguments.
+ from_line = request.arguments.fromLine;
+ to_line = request.arguments.toLine;
+
+ if (!IS_UNDEFINED(request.arguments.frame)) {
+ var frame_number = TO_NUMBER(request.arguments.frame);
+ if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
+ return response.failed('Invalid frame "' + frame + '"');
+ }
+ frame = this.exec_state_.frame(frame_number);
+ }
+ }
+
+ // Get the script selected.
+ var script = frame.func().script();
+ if (!script) {
+ return response.failed('No source');
+ }
+
+ // Get the source slice and fill it into the response.
+ var slice = script.sourceSlice(from_line, to_line);
+ if (!slice) {
+ return response.failed('Invalid line interval');
+ }
+ response.body = {};
+ response.body.source = slice.sourceText();
+ response.body.fromLine = slice.from_line;
+ response.body.toLine = slice.to_line;
+ response.body.fromPosition = slice.from_position;
+ response.body.toPosition = slice.to_position;
+ response.body.totalLines = script.lineCount();
+};
+
+
+DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
+ var types = ScriptTypeFlag(Debug.ScriptType.Normal);
+ var includeSource = false;
+ var idsToInclude = null;
+ if (request.arguments) {
+ // Pull out arguments.
+ if (!IS_UNDEFINED(request.arguments.types)) {
+ types = TO_NUMBER(request.arguments.types);
+ if (IsNaN(types) || types < 0) {
+ return response.failed('Invalid types "' +
+ request.arguments.types + '"');
+ }
+ }
+
+ if (!IS_UNDEFINED(request.arguments.includeSource)) {
+ includeSource = TO_BOOLEAN(request.arguments.includeSource);
+ response.setOption('includeSource', includeSource);
+ }
+
+ if (IS_ARRAY(request.arguments.ids)) {
+ idsToInclude = {};
+ var ids = request.arguments.ids;
+ for (var i = 0; i < ids.length; i++) {
+ idsToInclude[ids[i]] = true;
+ }
+ }
+
+ var filterStr = null;
+ var filterNum = null;
+ if (!IS_UNDEFINED(request.arguments.filter)) {
+ var num = TO_NUMBER(request.arguments.filter);
+ if (!IsNaN(num)) {
+ filterNum = num;
+ }
+ filterStr = request.arguments.filter;
+ }
+ }
+
+ // Collect all scripts in the heap.
+ var scripts = %DebugGetLoadedScripts();
+
+ response.body = [];
+
+ for (var i = 0; i < scripts.length; i++) {
+ if (idsToInclude && !idsToInclude[scripts[i].id]) {
+ continue;
+ }
+ if (filterStr || filterNum) {
+ var script = scripts[i];
+ var found = false;
+ if (filterNum && !found) {
+ if (script.id && script.id === filterNum) {
+ found = true;
+ }
+ }
+ if (filterStr && !found) {
+ if (script.name && script.name.indexOf(filterStr) >= 0) {
+ found = true;
+ }
+ }
+ if (!found) continue;
+ }
+ if (types & ScriptTypeFlag(scripts[i].type)) {
+ response.body.push(MakeMirror(scripts[i]));
+ }
+ }
+};
+
+
+DebugCommandProcessor.prototype.threadsRequest_ = function(request, response) {
+ // Get the number of threads.
+ var total_threads = this.exec_state_.threadCount();
+
+ // Get information for all threads.
+ var threads = [];
+ for (var i = 0; i < total_threads; i++) {
+ var details = %GetThreadDetails(this.exec_state_.break_id, i);
+ var thread_info = { current: details[0],
+ id: details[1]
+ };
+ threads.push(thread_info);
+ }
+
+ // Create the response body.
+ response.body = {
+ totalThreads: total_threads,
+ threads: threads
+ };
+};
+
+
+DebugCommandProcessor.prototype.suspendRequest_ = function(request, response) {
+ response.running = false;
+};
+
+
+DebugCommandProcessor.prototype.versionRequest_ = function(request, response) {
+ response.body = {
+ V8Version: %GetV8Version()
+ };
+};
+
+
+DebugCommandProcessor.prototype.changeLiveRequest_ = function(
+ request, response) {
+ if (!request.arguments) {
+ return response.failed('Missing arguments');
+ }
+ var script_id = request.arguments.script_id;
+ var preview_only = !!request.arguments.preview_only;
+
+ var scripts = %DebugGetLoadedScripts();
+
+ var the_script = null;
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i].id == script_id) {
+ the_script = scripts[i];
+ }
+ }
+ if (!the_script) {
+ response.failed('Script not found');
+ return;
+ }
+
+ var change_log = new GlobalArray();
+
+ if (!IS_STRING(request.arguments.new_source)) {
+ throw "new_source argument expected";
+ }
+
+ var new_source = request.arguments.new_source;
+
+ var result_description;
+ try {
+ result_description = Debug.LiveEdit.SetScriptSource(the_script,
+ new_source, preview_only, change_log);
+ } catch (e) {
+ if (e instanceof Debug.LiveEdit.Failure && "details" in e) {
+ response.failed(e.message, e.details);
+ return;
+ }
+ throw e;
+ }
+ response.body = {change_log: change_log, result: result_description};
+
+ if (!preview_only && !this.running_ && result_description.stack_modified) {
+ response.body.stepin_recommended = true;
+ }
+};
+
+
+DebugCommandProcessor.prototype.restartFrameRequest_ = function(
+ request, response) {
+ if (!request.arguments) {
+ return response.failed('Missing arguments');
+ }
+ var frame = request.arguments.frame;
+
+ // No frames to evaluate in frame.
+ if (this.exec_state_.frameCount() == 0) {
+ return response.failed('No frames');
+ }
+
+ var frame_mirror;
+ // Check whether a frame was specified.
+ if (!IS_UNDEFINED(frame)) {
+ var frame_number = TO_NUMBER(frame);
+ if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
+ return response.failed('Invalid frame "' + frame + '"');
+ }
+ // Restart specified frame.
+ frame_mirror = this.exec_state_.frame(frame_number);
+ } else {
+ // Restart selected frame.
+ frame_mirror = this.exec_state_.frame();
+ }
+
+ var result_description = Debug.LiveEdit.RestartFrame(frame_mirror);
+ response.body = {result: result_description};
+};
+
+
+DebugCommandProcessor.prototype.debuggerFlagsRequest_ = function(request,
+ response) {
+ // Check for legal request.
+ if (!request.arguments) {
+ response.failed('Missing arguments');
+ return;
+ }
+
+ // Pull out arguments.
+ var flags = request.arguments.flags;
+
+ response.body = { flags: [] };
+ if (!IS_UNDEFINED(flags)) {
+ for (var i = 0; i < flags.length; i++) {
+ var name = flags[i].name;
+ var debugger_flag = debugger_flags[name];
+ if (!debugger_flag) {
+ continue;
+ }
+ if ('value' in flags[i]) {
+ debugger_flag.setValue(flags[i].value);
+ }
+ response.body.flags.push({ name: name, value: debugger_flag.getValue() });
+ }
+ } else {
+ for (var name in debugger_flags) {
+ var value = debugger_flags[name].getValue();
+ response.body.flags.push({ name: name, value: value });
+ }
+ }
+};
+
+
+DebugCommandProcessor.prototype.v8FlagsRequest_ = function(request, response) {
+ var flags = request.arguments.flags;
+ if (!flags) flags = '';
+ %SetFlags(flags);
+};
+
+
+DebugCommandProcessor.prototype.gcRequest_ = function(request, response) {
+ var type = request.arguments.type;
+ if (!type) type = 'all';
+
+ var before = %GetHeapUsage();
+ %CollectGarbage(type);
+ var after = %GetHeapUsage();
+
+ response.body = { "before": before, "after": after };
+};
+
+
+DebugCommandProcessor.prototype.dispatch_ = (function() {
+ var proto = DebugCommandProcessor.prototype;
+ return {
+ "continue": proto.continueRequest_,
+ "break" : proto.breakRequest_,
+ "setbreakpoint" : proto.setBreakPointRequest_,
+ "changebreakpoint": proto.changeBreakPointRequest_,
+ "clearbreakpoint": proto.clearBreakPointRequest_,
+ "clearbreakpointgroup": proto.clearBreakPointGroupRequest_,
+ "disconnect": proto.disconnectRequest_,
+ "setexceptionbreak": proto.setExceptionBreakRequest_,
+ "listbreakpoints": proto.listBreakpointsRequest_,
+ "backtrace": proto.backtraceRequest_,
+ "frame": proto.frameRequest_,
+ "scopes": proto.scopesRequest_,
+ "scope": proto.scopeRequest_,
+ "setvariablevalue": proto.setVariableValueRequest_,
+ "evaluate": proto.evaluateRequest_,
+ "lookup": proto.lookupRequest_,
+ "references": proto.referencesRequest_,
+ "source": proto.sourceRequest_,
+ "scripts": proto.scriptsRequest_,
+ "threads": proto.threadsRequest_,
+ "suspend": proto.suspendRequest_,
+ "version": proto.versionRequest_,
+ "changelive": proto.changeLiveRequest_,
+ "restartframe": proto.restartFrameRequest_,
+ "flags": proto.debuggerFlagsRequest_,
+ "v8flag": proto.v8FlagsRequest_,
+ "gc": proto.gcRequest_,
+ };
+})();
+
+
+// Check whether the previously processed command caused the VM to become
+// running.
+DebugCommandProcessor.prototype.isRunning = function() {
+ return this.running_;
+};
+
+
+DebugCommandProcessor.prototype.systemBreak = function(cmd, args) {
+ return %SystemBreak();
+};
+
+
+/**
+ * Convert an Object to its debugger protocol representation. The representation
+ * may be serilized to a JSON object using JSON.stringify().
+ * This implementation simply runs through all string property names, converts
+ * each property value to a protocol value and adds the property to the result
+ * object. For type "object" the function will be called recursively. Note that
+ * circular structures will cause infinite recursion.
+ * @param {Object} object The object to format as protocol object.
+ * @param {MirrorSerializer} mirror_serializer The serializer to use if any
+ * mirror objects are encountered.
+ * @return {Object} Protocol object value.
+ */
+function ObjectToProtocolObject_(object, mirror_serializer) {
+ var content = {};
+ for (var key in object) {
+ // Only consider string keys.
+ if (typeof key == 'string') {
+ // Format the value based on its type.
+ var property_value_json = ValueToProtocolValue_(object[key],
+ mirror_serializer);
+ // Add the property if relevant.
+ if (!IS_UNDEFINED(property_value_json)) {
+ content[key] = property_value_json;
+ }
+ }
+ }
+
+ return content;
+}
+
+
+/**
+ * Convert an array to its debugger protocol representation. It will convert
+ * each array element to a protocol value.
+ * @param {Array} array The array to format as protocol array.
+ * @param {MirrorSerializer} mirror_serializer The serializer to use if any
+ * mirror objects are encountered.
+ * @return {Array} Protocol array value.
+ */
+function ArrayToProtocolArray_(array, mirror_serializer) {
+ var json = [];
+ for (var i = 0; i < array.length; i++) {
+ json.push(ValueToProtocolValue_(array[i], mirror_serializer));
+ }
+ return json;
+}
+
+
+/**
+ * Convert a value to its debugger protocol representation.
+ * @param {*} value The value to format as protocol value.
+ * @param {MirrorSerializer} mirror_serializer The serializer to use if any
+ * mirror objects are encountered.
+ * @return {*} Protocol value.
+ */
+function ValueToProtocolValue_(value, mirror_serializer) {
+ // Format the value based on its type.
+ var json;
+ switch (typeof value) {
+ case 'object':
+ if (value instanceof Mirror) {
+ json = mirror_serializer.serializeValue(value);
+ } else if (IS_ARRAY(value)){
+ json = ArrayToProtocolArray_(value, mirror_serializer);
+ } else {
+ json = ObjectToProtocolObject_(value, mirror_serializer);
+ }
+ break;
+
+ case 'boolean':
+ case 'string':
+ case 'number':
+ json = value;
+ break;
+
+ default:
+ json = null;
+ }
+ return json;
+}
+
+
+// -------------------------------------------------------------------
+// Exports
+
+utils.InstallConstants(global, [
+ "Debug", Debug,
+ "DebugCommandProcessor", DebugCommandProcessor,
+ "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,
+ "MakePromiseEvent", MakePromiseEvent,
+ "MakeAsyncTaskEvent", MakeAsyncTaskEvent,
+ "IsBreakPointTriggered", IsBreakPointTriggered,
+ "UpdateScriptBreakPoints", UpdateScriptBreakPoints,
+]);
+
+// Export to liveedit.js
+utils.Export(function(to) {
+ to.GetScriptBreakPoints = GetScriptBreakPoints;
+});
+
+})
diff --git a/src/debug/ia32/debug-ia32.cc b/src/debug/ia32/debug-ia32.cc
new file mode 100644
index 0000000..d489a01
--- /dev/null
+++ b/src/debug/ia32/debug-ia32.cc
@@ -0,0 +1,141 @@
+// 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_IA32
+
+#include "src/codegen.h"
+#include "src/debug/debug.h"
+#include "src/ia32/frames-ia32.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_EQ(Code::BUILTIN, code->kind());
+ 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));
+}
+
+
+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)));
+
+ if (mode == SAVE_RESULT_REGISTER) __ push(eax);
+
+ __ Move(eax, Immediate(0)); // No arguments.
+ __ 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)};
+ __ Move(reg, Immediate(kDebugZapValue));
+ }
+ }
+
+ if (mode == SAVE_RESULT_REGISTER) __ pop(eax);
+
+ __ 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, -1 * kPointerSize));
+
+ __ pop(edi); // Function.
+ __ pop(ebp);
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(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_IA32
diff --git a/src/debug/liveedit.cc b/src/debug/liveedit.cc
new file mode 100644
index 0000000..f1f3f23
--- /dev/null
+++ b/src/debug/liveedit.cc
@@ -0,0 +1,2046 @@
+// Copyright 2012 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/debug/liveedit.h"
+
+#include "src/ast/scopeinfo.h"
+#include "src/ast/scopes.h"
+#include "src/code-stubs.h"
+#include "src/compilation-cache.h"
+#include "src/compiler.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/parsing/parser.h"
+#include "src/v8.h"
+#include "src/v8memory.h"
+
+namespace v8 {
+namespace internal {
+
+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();
+}
+
+
+// A simple implementation of dynamic programming algorithm. It solves
+// the problem of finding the difference of 2 arrays. It uses a table of results
+// of subproblems. Each cell contains a number together with 2-bit flag
+// that helps building the chunk list.
+class Differencer {
+ public:
+ explicit Differencer(Comparator::Input* input)
+ : input_(input), len1_(input->GetLength1()), len2_(input->GetLength2()) {
+ buffer_ = NewArray<int>(len1_ * len2_);
+ }
+ ~Differencer() {
+ DeleteArray(buffer_);
+ }
+
+ void Initialize() {
+ int array_size = len1_ * len2_;
+ for (int i = 0; i < array_size; i++) {
+ buffer_[i] = kEmptyCellValue;
+ }
+ }
+
+ // Makes sure that result for the full problem is calculated and stored
+ // in the table together with flags showing a path through subproblems.
+ void FillTable() {
+ CompareUpToTail(0, 0);
+ }
+
+ void SaveResult(Comparator::Output* chunk_writer) {
+ ResultWriter writer(chunk_writer);
+
+ int pos1 = 0;
+ int pos2 = 0;
+ while (true) {
+ if (pos1 < len1_) {
+ if (pos2 < len2_) {
+ Direction dir = get_direction(pos1, pos2);
+ switch (dir) {
+ case EQ:
+ writer.eq();
+ pos1++;
+ pos2++;
+ break;
+ case SKIP1:
+ writer.skip1(1);
+ pos1++;
+ break;
+ case SKIP2:
+ case SKIP_ANY:
+ writer.skip2(1);
+ pos2++;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ } else {
+ writer.skip1(len1_ - pos1);
+ break;
+ }
+ } else {
+ if (len2_ != pos2) {
+ writer.skip2(len2_ - pos2);
+ }
+ break;
+ }
+ }
+ writer.close();
+ }
+
+ private:
+ Comparator::Input* input_;
+ int* buffer_;
+ int len1_;
+ int len2_;
+
+ enum Direction {
+ EQ = 0,
+ SKIP1,
+ SKIP2,
+ SKIP_ANY,
+
+ MAX_DIRECTION_FLAG_VALUE = SKIP_ANY
+ };
+
+ // Computes result for a subtask and optionally caches it in the buffer table.
+ // All results values are shifted to make space for flags in the lower bits.
+ int CompareUpToTail(int pos1, int pos2) {
+ if (pos1 < len1_) {
+ if (pos2 < len2_) {
+ int cached_res = get_value4(pos1, pos2);
+ if (cached_res == kEmptyCellValue) {
+ Direction dir;
+ int res;
+ if (input_->Equals(pos1, pos2)) {
+ res = CompareUpToTail(pos1 + 1, pos2 + 1);
+ dir = EQ;
+ } else {
+ int res1 = CompareUpToTail(pos1 + 1, pos2) +
+ (1 << kDirectionSizeBits);
+ int res2 = CompareUpToTail(pos1, pos2 + 1) +
+ (1 << kDirectionSizeBits);
+ if (res1 == res2) {
+ res = res1;
+ dir = SKIP_ANY;
+ } else if (res1 < res2) {
+ res = res1;
+ dir = SKIP1;
+ } else {
+ res = res2;
+ dir = SKIP2;
+ }
+ }
+ set_value4_and_dir(pos1, pos2, res, dir);
+ cached_res = res;
+ }
+ return cached_res;
+ } else {
+ return (len1_ - pos1) << kDirectionSizeBits;
+ }
+ } else {
+ return (len2_ - pos2) << kDirectionSizeBits;
+ }
+ }
+
+ inline int& get_cell(int i1, int i2) {
+ return buffer_[i1 + i2 * len1_];
+ }
+
+ // 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);
+ get_cell(i1, i2) = value4 | dir;
+ }
+
+ int get_value4(int i1, int i2) {
+ return get_cell(i1, i2) & (kMaxUInt32 ^ kDirectionMask);
+ }
+ Direction get_direction(int i1, int i2) {
+ return static_cast<Direction>(get_cell(i1, i2) & kDirectionMask);
+ }
+
+ static const int kDirectionSizeBits = 2;
+ static const int kDirectionMask = (1 << kDirectionSizeBits) - 1;
+ static const int kEmptyCellValue = ~0u << kDirectionSizeBits;
+
+ // This method only holds static assert statement (unfortunately you cannot
+ // place one in class scope).
+ void StaticAssertHolder() {
+ STATIC_ASSERT(MAX_DIRECTION_FLAG_VALUE < (1 << kDirectionSizeBits));
+ }
+
+ class ResultWriter {
+ public:
+ explicit ResultWriter(Comparator::Output* chunk_writer)
+ : chunk_writer_(chunk_writer), pos1_(0), pos2_(0),
+ pos1_begin_(-1), pos2_begin_(-1), has_open_chunk_(false) {
+ }
+ void eq() {
+ FlushChunk();
+ pos1_++;
+ pos2_++;
+ }
+ void skip1(int len1) {
+ StartChunk();
+ pos1_ += len1;
+ }
+ void skip2(int len2) {
+ StartChunk();
+ pos2_ += len2;
+ }
+ void close() {
+ FlushChunk();
+ }
+
+ private:
+ Comparator::Output* chunk_writer_;
+ int pos1_;
+ int pos2_;
+ int pos1_begin_;
+ int pos2_begin_;
+ bool has_open_chunk_;
+
+ void StartChunk() {
+ if (!has_open_chunk_) {
+ pos1_begin_ = pos1_;
+ pos2_begin_ = pos2_;
+ has_open_chunk_ = true;
+ }
+ }
+
+ void FlushChunk() {
+ if (has_open_chunk_) {
+ chunk_writer_->AddChunk(pos1_begin_, pos2_begin_,
+ pos1_ - pos1_begin_, pos2_ - pos2_begin_);
+ has_open_chunk_ = false;
+ }
+ }
+ };
+};
+
+
+void Comparator::CalculateDifference(Comparator::Input* input,
+ Comparator::Output* result_writer) {
+ Differencer differencer(input);
+ differencer.Initialize();
+ differencer.FillTable();
+ differencer.SaveResult(result_writer);
+}
+
+
+static 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;
+ }
+ }
+ 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
+// call per comparison.
+class SubrangableInput : public Comparator::Input {
+ public:
+ virtual void SetSubrange1(int offset, int len) = 0;
+ virtual void SetSubrange2(int offset, int len) = 0;
+};
+
+
+class SubrangableOutput : public Comparator::Output {
+ public:
+ virtual void SetSubrange1(int offset, int len) = 0;
+ 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) {
+ const int len1 = input->GetLength1();
+ const int len2 = input->GetLength2();
+
+ int common_prefix_len;
+ int common_suffix_len;
+
+ {
+ common_prefix_len = 0;
+ int prefix_limit = 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);
+
+ while (common_suffix_len < suffix_limit &&
+ input->Equals(len1 - common_suffix_len - 1,
+ len2 - common_suffix_len - 1)) {
+ common_suffix_len++;
+ }
+ }
+
+ if (common_prefix_len > 0 || common_suffix_len > 0) {
+ int new_len1 = len1 - common_suffix_len - common_prefix_len;
+ int new_len2 = len2 - common_suffix_len - common_prefix_len;
+
+ input->SetSubrange1(common_prefix_len, new_len1);
+ input->SetSubrange2(common_prefix_len, new_len2);
+
+ output->SetSubrange1(common_prefix_len, new_len1);
+ output->SetSubrange2(common_prefix_len, new_len2);
+ }
+}
+
+
+// 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.
+class TokensCompareInput : public Comparator::Input {
+ public:
+ TokensCompareInput(Handle<String> s1, int offset1, int len1,
+ Handle<String> s2, int offset2, int len2)
+ : 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) {
+ return s1_->Get(offset1_ + index1) == s2_->Get(offset2_ + index2);
+ }
+
+ private:
+ Handle<String> s1_;
+ int offset1_;
+ int len1_;
+ Handle<String> s2_;
+ int offset2_;
+ int len2_;
+};
+
+
+// Stores compare result in JSArray. 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) {
+ }
+
+ void AddChunk(int pos1, int pos2, int len1, int len2) {
+ array_writer_->WriteChunk(pos1 + offset1_, pos2 + offset2_, len1, len2);
+ }
+
+ private:
+ CompareOutputArrayWriter* array_writer_;
+ 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()) {
+ }
+ 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 GetLineEnd(int index) {
+ if (index == ends_array_->length()) {
+ // End of the last line is always an end of the whole string.
+ // If the string ends with a new line character, the last line is an
+ // empty string after this character.
+ return string_len_;
+ } else {
+ return GetPosAfterNewLine(index);
+ }
+ }
+
+ private:
+ Handle<FixedArray> ends_array_;
+ int string_len_;
+
+ int GetPosAfterNewLine(int index) {
+ return Smi::cast(ends_array_->get(index))->value() + 1;
+ }
+};
+
+
+// Represents 2 strings as 2 arrays of lines.
+class LineArrayCompareInput : public SubrangableInput {
+ public:
+ LineArrayCompareInput(Handle<String> s1, Handle<String> s2,
+ LineEndsWrapper line_ends1, LineEndsWrapper line_ends2)
+ : s1_(s1), s2_(s2), line_ends1_(line_ends1),
+ line_ends2_(line_ends2),
+ subrange_offset1_(0), subrange_offset2_(0),
+ 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) {
+ index1 += subrange_offset1_;
+ index2 += subrange_offset2_;
+
+ int line_start1 = line_ends1_.GetLineStart(index1);
+ int line_start2 = line_ends2_.GetLineStart(index2);
+ int line_end1 = line_ends1_.GetLineEnd(index1);
+ int line_end2 = line_ends2_.GetLineEnd(index2);
+ int len1 = line_end1 - line_start1;
+ int len2 = line_end2 - line_start2;
+ if (len1 != len2) {
+ return false;
+ }
+ return CompareSubstrings(s1_, line_start1, s2_, line_start2,
+ len1);
+ }
+ void SetSubrange1(int offset, int len) {
+ subrange_offset1_ = offset;
+ subrange_len1_ = len;
+ }
+ void SetSubrange2(int offset, int len) {
+ subrange_offset2_ = offset;
+ subrange_len2_ = len;
+ }
+
+ private:
+ Handle<String> s1_;
+ Handle<String> s2_;
+ LineEndsWrapper line_ends1_;
+ LineEndsWrapper line_ends2_;
+ int subrange_offset1_;
+ int subrange_offset2_;
+ int subrange_len1_;
+ int subrange_len2_;
+};
+
+
+// Stores compare result in JSArray. For each chunk tries to conduct
+// a fine-grained nested diff token-wise.
+class TokenizingLineArrayCompareOutput : public SubrangableOutput {
+ public:
+ TokenizingLineArrayCompareOutput(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) {
+ }
+
+ void AddChunk(int line_pos1, int line_pos2, int line_len1, int line_len2) {
+ line_pos1 += subrange_offset1_;
+ line_pos2 += subrange_offset2_;
+
+ int char_pos1 = line_ends1_.GetLineStart(line_pos1);
+ int char_pos2 = line_ends2_.GetLineStart(line_pos2);
+ int char_len1 = line_ends1_.GetLineStart(line_pos1 + line_len1) - char_pos1;
+ int char_len2 = line_ends2_.GetLineStart(line_pos2 + line_len2) - char_pos2;
+
+ 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());
+
+ TokensCompareInput tokens_input(s1_, char_pos1, char_len1,
+ s2_, char_pos2, char_len2);
+ TokensCompareOutput tokens_output(&array_writer_, char_pos1,
+ char_pos2);
+
+ Comparator::CalculateDifference(&tokens_input, &tokens_output);
+ } else {
+ array_writer_.WriteChunk(char_pos1, char_pos2, char_len1, char_len2);
+ }
+ }
+ void SetSubrange1(int offset, int len) {
+ subrange_offset1_ = offset;
+ }
+ void SetSubrange2(int offset, int len) {
+ subrange_offset2_ = offset;
+ }
+
+ Handle<JSArray> GetResult() {
+ return array_writer_.GetResult();
+ }
+
+ private:
+ static const int CHUNK_LEN_LIMIT = 800;
+
+ CompareOutputArrayWriter array_writer_;
+ LineEndsWrapper line_ends1_;
+ LineEndsWrapper line_ends2_;
+ Handle<String> s1_;
+ Handle<String> s2_;
+ int subrange_offset1_;
+ int subrange_offset2_;
+};
+
+
+Handle<JSArray> LiveEdit::CompareStrings(Handle<String> s1,
+ Handle<String> s2) {
+ s1 = String::Flatten(s1);
+ s2 = String::Flatten(s2);
+
+ LineEndsWrapper line_ends1(s1);
+ LineEndsWrapper line_ends2(s2);
+
+ LineArrayCompareInput input(s1, s2, line_ends1, line_ends2);
+ TokenizingLineArrayCompareOutput output(line_ends1, line_ends2, s1, s2);
+
+ 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 literal_count,
+ int parent_index) {
+ HandleScope scope(isolate());
+ this->SetField(kFunctionNameOffset_, name);
+ this->SetSmiValueField(kStartPositionOffset_, start_position);
+ this->SetSmiValueField(kEndPositionOffset_, end_position);
+ this->SetSmiValueField(kParamNumOffset_, param_num);
+ this->SetSmiValueField(kLiteralNumOffset_, literal_count);
+ this->SetSmiValueField(kParentIndexOffset_, parent_index);
+}
+
+
+void FunctionInfoWrapper::SetFunctionCode(Handle<Code> function_code,
+ Handle<HeapObject> code_scope_info) {
+ Handle<JSValue> code_wrapper = WrapInJSValue(function_code);
+ this->SetField(kCodeOffset_, code_wrapper);
+
+ Handle<JSValue> scope_wrapper = WrapInJSValue(code_scope_info);
+ this->SetField(kCodeScopeInfoOffset_, scope_wrapper);
+}
+
+
+void FunctionInfoWrapper::SetSharedFunctionInfo(
+ Handle<SharedFunctionInfo> info) {
+ Handle<JSValue> info_holder = WrapInJSValue(info);
+ this->SetField(kSharedFunctionInfoOffset_, info_holder);
+}
+
+
+Handle<Code> FunctionInfoWrapper::GetFunctionCode() {
+ Handle<Object> element = this->GetField(kCodeOffset_);
+ Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element);
+ Handle<Object> raw_result = UnwrapJSValue(value_wrapper);
+ CHECK(raw_result->IsCode());
+ return Handle<Code>::cast(raw_result);
+}
+
+
+MaybeHandle<TypeFeedbackVector> FunctionInfoWrapper::GetFeedbackVector() {
+ Handle<Object> element = this->GetField(kSharedFunctionInfoOffset_);
+ if (element->IsJSValue()) {
+ Handle<JSValue> value_wrapper = Handle<JSValue>::cast(element);
+ Handle<Object> raw_result = UnwrapJSValue(value_wrapper);
+ Handle<SharedFunctionInfo> shared =
+ Handle<SharedFunctionInfo>::cast(raw_result);
+ return Handle<TypeFeedbackVector>(shared->feedback_vector(), isolate());
+ } else {
+ // Scripts may never have a SharedFunctionInfo created.
+ return MaybeHandle<TypeFeedbackVector>();
+ }
+}
+
+
+Handle<Object> FunctionInfoWrapper::GetCodeScopeInfo() {
+ Handle<Object> element = this->GetField(kCodeScopeInfoOffset_);
+ return UnwrapJSValue(Handle<JSValue>::cast(element));
+}
+
+
+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);
+}
+
+
+class FunctionInfoListener {
+ public:
+ explicit FunctionInfoListener(Isolate* isolate) {
+ current_parent_index_ = -1;
+ len_ = 0;
+ result_ = isolate->factory()->NewJSArray(10);
+ }
+
+ void FunctionStarted(FunctionLiteral* fun) {
+ HandleScope scope(isolate());
+ FunctionInfoWrapper info = FunctionInfoWrapper::Create(isolate());
+ info.SetInitialProperties(fun->name(), fun->start_position(),
+ fun->end_position(), fun->parameter_count(),
+ fun->materialized_literal_count(),
+ current_parent_index_);
+ current_parent_index_ = len_;
+ SetElementSloppy(result_, len_, info.GetJSArray());
+ len_++;
+ }
+
+ void FunctionDone() {
+ HandleScope scope(isolate());
+ FunctionInfoWrapper info =
+ FunctionInfoWrapper::cast(
+ *Object::GetElement(
+ isolate(), result_, current_parent_index_).ToHandleChecked());
+ current_parent_index_ = info.GetParentIndex();
+ }
+
+ // Saves only function code, because for a script function we
+ // may never create a SharedFunctionInfo object.
+ void FunctionCode(Handle<Code> function_code) {
+ FunctionInfoWrapper info =
+ FunctionInfoWrapper::cast(
+ *Object::GetElement(
+ isolate(), result_, current_parent_index_).ToHandleChecked());
+ info.SetFunctionCode(function_code,
+ Handle<HeapObject>(isolate()->heap()->null_value()));
+ }
+
+ // Saves full information about a function: its code, its scope info
+ // and a SharedFunctionInfo object.
+ void FunctionInfo(Handle<SharedFunctionInfo> shared, Scope* scope,
+ Zone* zone) {
+ if (!shared->IsSharedFunctionInfo()) {
+ return;
+ }
+ FunctionInfoWrapper info =
+ FunctionInfoWrapper::cast(
+ *Object::GetElement(
+ isolate(), result_, current_parent_index_).ToHandleChecked());
+ info.SetFunctionCode(Handle<Code>(shared->code()),
+ Handle<HeapObject>(shared->scope_info()));
+ info.SetSharedFunctionInfo(shared);
+
+ Handle<Object> scope_info_list = SerializeFunctionScope(scope, zone);
+ info.SetFunctionScopeInfo(scope_info_list);
+ }
+
+ Handle<JSArray> GetResult() { return result_; }
+
+ private:
+ Isolate* isolate() const { return result_->GetIsolate(); }
+
+ Handle<Object> SerializeFunctionScope(Scope* scope, Zone* zone) {
+ 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());
+ ZoneList<Variable*> stack_list(current_scope->StackLocalCount(), zone);
+ ZoneList<Variable*> context_list(
+ current_scope->ContextLocalCount(), zone);
+ ZoneList<Variable*> globals_list(current_scope->ContextGlobalCount(),
+ zone);
+ current_scope->CollectStackAndContextLocals(&stack_list, &context_list,
+ &globals_list);
+ context_list.Sort(&Variable::CompareIndex);
+
+ for (int i = 0; i < context_list.length(); i++) {
+ SetElementSloppy(scope_info_list,
+ scope_info_length,
+ context_list[i]->name());
+ scope_info_length++;
+ SetElementSloppy(
+ scope_info_list,
+ scope_info_length,
+ Handle<Smi>(Smi::FromInt(context_list[i]->index()), isolate()));
+ scope_info_length++;
+ }
+ SetElementSloppy(scope_info_list,
+ scope_info_length,
+ Handle<Object>(isolate()->heap()->null_value(),
+ isolate()));
+ scope_info_length++;
+
+ current_scope = current_scope->outer_scope();
+ }
+
+ return scope_info_list;
+ }
+
+ Handle<JSArray> result_;
+ int len_;
+ int current_parent_index_;
+};
+
+
+void LiveEdit::InitializeThreadLocal(Debug* debug) {
+ debug->thread_local_.frame_drop_mode_ = LiveEdit::FRAMES_UNTOUCHED;
+}
+
+
+bool LiveEdit::SetAfterBreakTarget(Debug* debug) {
+ Code* code = NULL;
+ Isolate* isolate = debug->isolate_;
+ switch (debug->thread_local_.frame_drop_mode_) {
+ case FRAMES_UNTOUCHED:
+ return false;
+ case FRAME_DROPPED_IN_DEBUG_SLOT_CALL:
+ // Debug break slot stub does not return normally, instead it manually
+ // cleans the stack and jumps. We should patch the jump address.
+ code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit);
+ break;
+ case FRAME_DROPPED_IN_DIRECT_CALL:
+ // Nothing to do, after_break_target is not used here.
+ return true;
+ case FRAME_DROPPED_IN_RETURN_CALL:
+ code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit);
+ break;
+ case CURRENTLY_SET_MODE:
+ UNREACHABLE();
+ break;
+ }
+ debug->after_break_target_ = code->entry();
+ return true;
+}
+
+
+MaybeHandle<JSArray> LiveEdit::GatherCompileInfo(Handle<Script> script,
+ Handle<String> source) {
+ Isolate* isolate = script->GetIsolate();
+
+ FunctionInfoListener listener(isolate);
+ Handle<Object> original_source =
+ Handle<Object>(script->source(), isolate);
+ script->set_source(*source);
+ isolate->set_active_function_info_listener(&listener);
+
+ {
+ // 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.
+ 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();
+ }
+ }
+
+ // A logical 'finally' section.
+ isolate->set_active_function_info_listener(NULL);
+ script->set_source(*original_source);
+
+ if (rethrow_exception.is_null()) {
+ return listener.GetResult();
+ } else {
+ return isolate->Throw<JSArray>(rethrow_exception);
+ }
+}
+
+
+// Visitor that finds all references to a particular code object,
+// including "CODE_TARGET" references in other code objects and replaces
+// them on the fly.
+class ReplacingVisitor : public ObjectVisitor {
+ public:
+ explicit ReplacingVisitor(Code* original, Code* substitution)
+ : original_(original), substitution_(substitution) {
+ }
+
+ void VisitPointers(Object** start, Object** end) override {
+ for (Object** p = start; p < end; p++) {
+ if (*p == original_) {
+ *p = substitution_;
+ }
+ }
+ }
+
+ void VisitCodeEntry(Address entry) override {
+ if (Code::GetObjectFromEntryAddress(entry) == original_) {
+ Address substitution_entry = substitution_->instruction_start();
+ Memory::Address_at(entry) = substitution_entry;
+ }
+ }
+
+ void VisitCodeTarget(RelocInfo* rinfo) override {
+ if (RelocInfo::IsCodeTarget(rinfo->rmode()) &&
+ Code::GetCodeFromTargetAddress(rinfo->target_address()) == original_) {
+ Address substitution_entry = substitution_->instruction_start();
+ rinfo->set_target_address(substitution_entry);
+ }
+ }
+
+ void VisitDebugTarget(RelocInfo* rinfo) override { VisitCodeTarget(rinfo); }
+
+ private:
+ Code* original_;
+ Code* substitution_;
+};
+
+
+// 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);
+
+ DCHECK(!heap->InNewSpace(*substitution));
+
+ ReplacingVisitor visitor(*original, *substitution);
+
+ // Iterate over all roots. Stack frames may have pointer into original code,
+ // so temporary replace the pointers with offset numbers
+ // in prologue/epilogue.
+ heap->IterateRoots(&visitor, VISIT_ALL);
+
+ // Now iterate over all pointers of all objects, including code_target
+ // implicit pointers.
+ for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) {
+ obj->Iterate(&visitor);
+ }
+}
+
+
+// Patch function literals.
+// Name 'literals' is a misnomer. Rather it's a cache for complex object
+// boilerplates and for a native context. We must clean cached values.
+// Additionally we may need to allocate a new array if number of literals
+// changed.
+class LiteralFixer {
+ public:
+ static void PatchLiterals(FunctionInfoWrapper* compile_info_wrapper,
+ Handle<SharedFunctionInfo> shared_info,
+ Isolate* isolate) {
+ int new_literal_count = compile_info_wrapper->GetLiteralCount();
+ int old_literal_count = shared_info->num_literals();
+
+ if (old_literal_count == new_literal_count) {
+ // If literal count didn't change, simply go over all functions
+ // and clear literal arrays.
+ ClearValuesVisitor visitor;
+ IterateJSFunctions(shared_info, &visitor);
+ } else {
+ // When literal count 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);
+ Handle<TypeFeedbackVector> vector(shared_info->feedback_vector());
+
+ for (int i = 0; i < function_instances->length(); i++) {
+ Handle<JSFunction> fun(JSFunction::cast(function_instances->get(i)));
+ Handle<LiteralsArray> new_literals =
+ LiteralsArray::New(isolate, vector, new_literal_count, TENURED);
+ fun->set_literals(*new_literals);
+ }
+
+ shared_info->set_num_literals(new_literal_count);
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+ // 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 ClearValuesVisitor {
+ public:
+ void visit(JSFunction* fun) {
+ FixedArray* literals = fun->literals();
+ int len = literals->length();
+ for (int j = 0; j < len; j++) {
+ literals->set_undefined(j);
+ }
+ }
+ };
+
+ 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();
+
+ if (shared_info->code()->kind() == Code::FUNCTION) {
+ Handle<Code> code = compile_info_wrapper.GetFunctionCode();
+ ReplaceCodeObject(Handle<Code>(shared_info->code()), code);
+ Handle<Object> code_scope_info = compile_info_wrapper.GetCodeScopeInfo();
+ if (code_scope_info->IsFixedArray()) {
+ shared_info->set_scope_info(ScopeInfo::cast(*code_scope_info));
+ }
+ shared_info->DisableOptimization(kLiveEdit);
+ // Update the type feedback vector, if needed.
+ MaybeHandle<TypeFeedbackVector> feedback_vector =
+ compile_info_wrapper.GetFeedbackVector();
+ if (!feedback_vector.is_null()) {
+ shared_info->set_feedback_vector(*feedback_vector.ToHandleChecked());
+ }
+ }
+
+ 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);
+
+ LiteralFixer::PatchLiterals(&compile_info_wrapper, shared_info, isolate);
+
+ DeoptimizeDependentFunctions(*shared_info);
+ isolate->compilation_cache()->Remove(shared_info);
+}
+
+
+void LiveEdit::FunctionSourceUpdated(Handle<JSArray> shared_info_array) {
+ SharedInfoWrapper shared_info_wrapper(shared_info_array);
+ Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo();
+
+ DeoptimizeDependentFunctions(*shared_info);
+ shared_info_array->GetIsolate()->compilation_cache()->Remove(shared_info);
+}
+
+
+void LiveEdit::SetFunctionScript(Handle<JSValue> function_wrapper,
+ Handle<Object> script_handle) {
+ Handle<SharedFunctionInfo> shared_info =
+ UnwrapSharedFunctionInfoFromJSValue(function_wrapper);
+ CHECK(script_handle->IsScript() || script_handle->IsUndefined());
+ SharedFunctionInfo::SetScript(shared_info, script_handle);
+ shared_info->DisableOptimization(kLiveEdit);
+
+ function_wrapper->GetIsolate()->compilation_cache()->Remove(shared_info);
+}
+
+
+// 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 = Object::GetElement(
+ isolate, position_change_array, i).ToHandleChecked();
+ CHECK(element->IsSmi());
+ int chunk_start = Handle<Smi>::cast(element)->value();
+ if (original_position < chunk_start) {
+ break;
+ }
+ element = Object::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 = Object::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;
+}
+
+
+// Auto-growing buffer for writing relocation info code section. This buffer
+// is a simplified version of buffer from Assembler. Unlike Assembler, this
+// class is platform-independent and it works without dealing with instructions.
+// As specified by RelocInfo format, the buffer is filled in reversed order:
+// from upper to lower addresses.
+// It uses NewArray/DeleteArray for memory management.
+class RelocInfoBuffer {
+ public:
+ RelocInfoBuffer(int buffer_initial_capicity, byte* pc) {
+ buffer_size_ = buffer_initial_capicity + kBufferGap;
+ buffer_ = NewArray<byte>(buffer_size_);
+
+ reloc_info_writer_.Reposition(buffer_ + buffer_size_, pc);
+ }
+ ~RelocInfoBuffer() {
+ DeleteArray(buffer_);
+ }
+
+ // As specified by RelocInfo format, the buffer is filled in reversed order:
+ // from upper to lower addresses.
+ void Write(const RelocInfo* rinfo) {
+ if (buffer_ + kBufferGap >= reloc_info_writer_.pos()) {
+ Grow();
+ }
+ reloc_info_writer_.Write(rinfo);
+ }
+
+ Vector<byte> GetResult() {
+ // Return the bytes from pos up to end of buffer.
+ int result_size =
+ static_cast<int>((buffer_ + buffer_size_) - reloc_info_writer_.pos());
+ return Vector<byte>(reloc_info_writer_.pos(), result_size);
+ }
+
+ private:
+ void Grow() {
+ // Compute new buffer size.
+ int new_buffer_size;
+ if (buffer_size_ < 2 * KB) {
+ new_buffer_size = 4 * KB;
+ } else {
+ new_buffer_size = 2 * buffer_size_;
+ }
+ // Some internal data structures overflow for very large buffers,
+ // they must ensure that kMaximalBufferSize is not too large.
+ if (new_buffer_size > kMaximalBufferSize) {
+ V8::FatalProcessOutOfMemory("RelocInfoBuffer::GrowBuffer");
+ }
+
+ // Set up new buffer.
+ byte* new_buffer = NewArray<byte>(new_buffer_size);
+
+ // Copy the data.
+ int curently_used_size =
+ static_cast<int>(buffer_ + buffer_size_ - reloc_info_writer_.pos());
+ MemMove(new_buffer + new_buffer_size - curently_used_size,
+ reloc_info_writer_.pos(), curently_used_size);
+
+ reloc_info_writer_.Reposition(
+ new_buffer + new_buffer_size - curently_used_size,
+ reloc_info_writer_.last_pc());
+
+ DeleteArray(buffer_);
+ buffer_ = new_buffer;
+ buffer_size_ = new_buffer_size;
+ }
+
+ RelocInfoWriter reloc_info_writer_;
+ byte* buffer_;
+ int buffer_size_;
+
+ static const int kBufferGap = RelocInfoWriter::kMaxSize;
+ static const int kMaximalBufferSize = 512*MB;
+};
+
+
+// Patch positions in code (changes relocation info section) and possibly
+// returns new instance of code.
+static Handle<Code> PatchPositionsInCode(
+ Handle<Code> code,
+ Handle<JSArray> position_change_array) {
+ Isolate* isolate = code->GetIsolate();
+
+ RelocInfoBuffer buffer_writer(code->relocation_size(),
+ code->instruction_start());
+
+ {
+ for (RelocIterator it(*code); !it.done(); it.next()) {
+ RelocInfo* rinfo = it.rinfo();
+ if (RelocInfo::IsPosition(rinfo->rmode())) {
+ int position = static_cast<int>(rinfo->data());
+ int new_position = TranslatePosition(position,
+ position_change_array);
+ if (position != new_position) {
+ RelocInfo info_copy(rinfo->isolate(), rinfo->pc(), rinfo->rmode(),
+ new_position, NULL);
+ buffer_writer.Write(&info_copy);
+ continue;
+ }
+ }
+ if (RelocInfo::IsRealRelocMode(rinfo->rmode())) {
+ buffer_writer.Write(it.rinfo());
+ }
+ }
+ }
+
+ Vector<byte> buffer = buffer_writer.GetResult();
+
+ if (buffer.length() == code->relocation_size()) {
+ // Simply patch relocation area of code.
+ MemCopy(code->relocation_start(), buffer.start(), buffer.length());
+ return code;
+ } else {
+ // Relocation info section now has different size. We cannot simply
+ // rewrite it inside code object. Instead we have to create a new
+ // code object.
+ Handle<Code> result(isolate->factory()->CopyCode(code, buffer));
+ return result;
+ }
+}
+
+
+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->code()->kind() == Code::FUNCTION) {
+ // Patch relocation info section of the code.
+ Handle<Code> patched_code = PatchPositionsInCode(Handle<Code>(info->code()),
+ position_change_array);
+ if (*patched_code != info->code()) {
+ // Replace all references to the code across the heap. In particular,
+ // some stubs may refer to this code and this code may be being executed
+ // on stack (it is safe to substitute the code object on stack, because
+ // we only change the structure of rinfo and leave instructions
+ // untouched).
+ ReplaceCodeObject(Handle<Code>(info->code()), patched_code);
+ }
+ }
+}
+
+
+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_instructions_offset(
+ original->eval_from_instructions_offset());
+
+ // 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();
+ }
+
+ original_script->set_source(*new_source);
+
+ // Drop line ends so that they will be recalculated.
+ original_script->set_line_ends(isolate->heap()->undefined_value());
+
+ return old_script_object;
+}
+
+
+
+void LiveEdit::ReplaceRefToNestedFunction(
+ Handle<JSValue> parent_function_wrapper,
+ Handle<JSValue> orig_function_wrapper,
+ Handle<JSValue> subst_function_wrapper) {
+
+ Handle<SharedFunctionInfo> parent_shared =
+ UnwrapSharedFunctionInfoFromJSValue(parent_function_wrapper);
+ Handle<SharedFunctionInfo> orig_shared =
+ UnwrapSharedFunctionInfoFromJSValue(orig_function_wrapper);
+ Handle<SharedFunctionInfo> subst_shared =
+ UnwrapSharedFunctionInfoFromJSValue(subst_function_wrapper);
+
+ 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);
+ }
+ }
+ }
+}
+
+
+// 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 =
+ Object::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;
+ }
+ }
+ return false;
+}
+
+
+// Iterates over handler chain and removes all elements that are inside
+// frames being dropped.
+static bool FixTryCatchHandler(StackFrame* top_frame,
+ StackFrame* bottom_frame) {
+ Address* pointer_address =
+ &Memory::Address_at(top_frame->isolate()->get_address_from_id(
+ Isolate::kHandlerAddress));
+
+ while (*pointer_address < top_frame->sp()) {
+ pointer_address = &Memory::Address_at(*pointer_address);
+ }
+ Address* above_frame_address = pointer_address;
+ while (*pointer_address < bottom_frame->fp()) {
+ pointer_address = &Memory::Address_at(*pointer_address);
+ }
+ bool change = *above_frame_address != *pointer_address;
+ *above_frame_address = *pointer_address;
+ return change;
+}
+
+
+// Initializes an artificial stack frame. The data it contains is used for:
+// a. successful work of frame dropper code which eventually gets control,
+// b. being compatible with regular stack structure for various stack
+// iterators.
+// Frame structure (conforms InternalFrame structure):
+// -- code
+// -- SMI maker
+// -- function (slot is called "context")
+// -- frame base
+static void SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
+ Handle<Code> code) {
+ DCHECK(bottom_js_frame->is_java_script());
+
+ Address fp = bottom_js_frame->fp();
+
+ // Move function pointer into "context" slot.
+ Memory::Object_at(fp + StandardFrameConstants::kContextOffset) =
+ Memory::Object_at(fp + JavaScriptFrameConstants::kFunctionOffset);
+
+ Memory::Object_at(fp + InternalFrameConstants::kCodeOffset) = *code;
+ Memory::Object_at(fp + StandardFrameConstants::kMarkerOffset) =
+ Smi::FromInt(StackFrame::INTERNAL);
+}
+
+
+// Removes specified range of frames from stack. There may be 1 or more
+// frames in range. Anyway the bottom frame is restarted rather than dropped,
+// and therefore has to be a JavaScript frame.
+// Returns error message or NULL.
+static const char* DropFrames(Vector<StackFrame*> frames, int top_frame_index,
+ int bottom_js_frame_index,
+ LiveEdit::FrameDropMode* mode) {
+ if (!LiveEdit::kFrameDropperSupported) {
+ return "Stack manipulations are not supported in this architecture.";
+ }
+
+ StackFrame* pre_top_frame = frames[top_frame_index - 1];
+ StackFrame* top_frame = frames[top_frame_index];
+ StackFrame* bottom_js_frame = frames[bottom_js_frame_index];
+
+ DCHECK(bottom_js_frame->is_java_script());
+
+ // Check the nature of the top frame.
+ Isolate* isolate = bottom_js_frame->isolate();
+ Code* pre_top_frame_code = pre_top_frame->LookupCode();
+ bool frame_has_padding = true;
+ if (pre_top_frame_code ==
+ isolate->builtins()->builtin(Builtins::kSlot_DebugBreak)) {
+ // OK, we can drop debug break slot.
+ *mode = LiveEdit::FRAME_DROPPED_IN_DEBUG_SLOT_CALL;
+ } else if (pre_top_frame_code ==
+ isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit)) {
+ // OK, we can drop our own code.
+ pre_top_frame = frames[top_frame_index - 2];
+ top_frame = frames[top_frame_index - 1];
+ *mode = LiveEdit::CURRENTLY_SET_MODE;
+ frame_has_padding = false;
+ } else if (pre_top_frame_code ==
+ isolate->builtins()->builtin(Builtins::kReturn_DebugBreak)) {
+ *mode = LiveEdit::FRAME_DROPPED_IN_RETURN_CALL;
+ } else if (pre_top_frame_code->kind() == Code::STUB &&
+ CodeStub::GetMajorKey(pre_top_frame_code) == CodeStub::CEntry) {
+ // Entry from our unit tests on 'debugger' statement.
+ // It's fine, we support this case.
+ *mode = LiveEdit::FRAME_DROPPED_IN_DIRECT_CALL;
+ // We don't have a padding from 'debugger' statement call.
+ // Here the stub is CEntry, it's not debug-only and can't be padded.
+ // If anyone would complain, a proxy padded stub could be added.
+ frame_has_padding = false;
+ } else if (pre_top_frame->type() == StackFrame::ARGUMENTS_ADAPTOR) {
+ // This must be adaptor that remain from the frame dropping that
+ // is still on stack. A frame dropper frame must be above it.
+ DCHECK(frames[top_frame_index - 2]->LookupCode() ==
+ isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit));
+ pre_top_frame = frames[top_frame_index - 3];
+ top_frame = frames[top_frame_index - 2];
+ *mode = LiveEdit::CURRENTLY_SET_MODE;
+ frame_has_padding = false;
+ } else {
+ return "Unknown structure of stack above changing function";
+ }
+
+ Address unused_stack_top = top_frame->sp();
+ int new_frame_size = LiveEdit::kFrameDropperFrameSize * kPointerSize;
+ Address unused_stack_bottom = bottom_js_frame->fp()
+ - new_frame_size + kPointerSize; // Bigger address end is exclusive.
+
+ Address* top_frame_pc_address = top_frame->pc_address();
+
+ // top_frame may be damaged below this point. Do not used it.
+ DCHECK(!(top_frame = NULL));
+
+ if (unused_stack_top > unused_stack_bottom) {
+ if (frame_has_padding) {
+ int shortage_bytes =
+ static_cast<int>(unused_stack_top - unused_stack_bottom);
+
+ Address padding_start = pre_top_frame->fp() -
+ LiveEdit::kFrameDropperFrameSize * kPointerSize;
+
+ Address padding_pointer = padding_start;
+ Smi* padding_object = Smi::FromInt(LiveEdit::kFramePaddingValue);
+ while (Memory::Object_at(padding_pointer) == padding_object) {
+ padding_pointer -= kPointerSize;
+ }
+ int padding_counter =
+ Smi::cast(Memory::Object_at(padding_pointer))->value();
+ if (padding_counter * kPointerSize < shortage_bytes) {
+ return "Not enough space for frame dropper frame "
+ "(even with padding frame)";
+ }
+ Memory::Object_at(padding_pointer) =
+ Smi::FromInt(padding_counter - shortage_bytes / kPointerSize);
+
+ StackFrame* pre_pre_frame = frames[top_frame_index - 2];
+
+ MemMove(padding_start + kPointerSize - shortage_bytes,
+ padding_start + kPointerSize,
+ LiveEdit::kFrameDropperFrameSize * kPointerSize);
+
+ pre_top_frame->UpdateFp(pre_top_frame->fp() - shortage_bytes);
+ pre_pre_frame->SetCallerFp(pre_top_frame->fp());
+ unused_stack_top -= shortage_bytes;
+
+ STATIC_ASSERT(sizeof(Address) == kPointerSize);
+ top_frame_pc_address -= shortage_bytes / kPointerSize;
+ } else {
+ return "Not enough space for frame dropper frame";
+ }
+ }
+
+ // Committing now. After this point we should return only NULL value.
+
+ FixTryCatchHandler(pre_top_frame, bottom_js_frame);
+ // Make sure FixTryCatchHandler is idempotent.
+ DCHECK(!FixTryCatchHandler(pre_top_frame, bottom_js_frame));
+
+ Handle<Code> code = isolate->builtins()->FrameDropper_LiveEdit();
+ *top_frame_pc_address = code->entry();
+ pre_top_frame->SetCallerFp(bottom_js_frame->fp());
+
+ SetUpFrameDropperFrame(bottom_js_frame, code);
+
+ for (Address a = unused_stack_top;
+ a < unused_stack_bottom;
+ a += kPointerSize) {
+ Memory::Object_at(a) = Smi::FromInt(0);
+ }
+
+ return NULL;
+}
+
+
+// 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 =
+ Object::GetElement(isolate, old_shared_array_, i).ToHandleChecked();
+ if (!old_shared.is_identical_to(UnwrapSharedFunctionInfoFromJSValue(
+ Handle<JSValue>::cast(old_element)))) {
+ continue;
+ }
+
+ Handle<Object> new_element =
+ Object::GetElement(isolate, new_shared_array_, i).ToHandleChecked();
+ if (new_element->IsUndefined()) 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;
+ }
+
+ private:
+ Handle<JSArray> old_shared_array_;
+ Handle<JSArray> new_shared_array_;
+ Handle<JSArray> result_;
+};
+
+
+// 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;
+ 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()) {
+ 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 (shared->is_generator()) {
+ 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;
+ }
+ }
+ }
+ }
+
+ // 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();
+ }
+
+ LiveEdit::FrameDropMode drop_mode = LiveEdit::FRAMES_UNTOUCHED;
+ const char* error_message =
+ DropFrames(frames, top_frame_index, bottom_js_frame_index, &drop_mode);
+
+ if (error_message != NULL) {
+ return error_message;
+ }
+
+ // Adjust break_frame after some frames has been dropped.
+ StackFrame::Id new_id = StackFrame::NO_ID;
+ for (int i = bottom_js_frame_index + 1; i < frames.length(); i++) {
+ if (frames[i]->type() == StackFrame::JAVA_SCRIPT) {
+ new_id = frames[i]->id();
+ break;
+ }
+ }
+ debug->FramesHaveBeenDropped(new_id, drop_mode);
+ return NULL;
+}
+
+
+// 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 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 =
+ Object::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;
+}
+
+
+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);
+ 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));
+ 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);
+ Handle<FixedArray> result_elements =
+ JSObject::EnsureWritableFastElements(result);
+
+ // 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;
+}
+
+
+LiveEditFunctionTracker::LiveEditFunctionTracker(Isolate* isolate,
+ FunctionLiteral* fun)
+ : isolate_(isolate) {
+ if (isolate_->active_function_info_listener() != NULL) {
+ isolate_->active_function_info_listener()->FunctionStarted(fun);
+ }
+}
+
+
+LiveEditFunctionTracker::~LiveEditFunctionTracker() {
+ if (isolate_->active_function_info_listener() != NULL) {
+ isolate_->active_function_info_listener()->FunctionDone();
+ }
+}
+
+
+void LiveEditFunctionTracker::RecordFunctionInfo(
+ Handle<SharedFunctionInfo> info, FunctionLiteral* lit,
+ Zone* zone) {
+ if (isolate_->active_function_info_listener() != NULL) {
+ isolate_->active_function_info_listener()->FunctionInfo(info, lit->scope(),
+ zone);
+ }
+}
+
+
+void LiveEditFunctionTracker::RecordRootFunctionInfo(Handle<Code> code) {
+ isolate_->active_function_info_listener()->FunctionCode(code);
+}
+
+
+bool LiveEditFunctionTracker::IsActive(Isolate* isolate) {
+ return isolate->active_function_info_listener() != NULL;
+}
+
+} // namespace internal
+} // namespace v8
diff --git a/src/debug/liveedit.h b/src/debug/liveedit.h
new file mode 100644
index 0000000..f3d6c54
--- /dev/null
+++ b/src/debug/liveedit.h
@@ -0,0 +1,371 @@
+// 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.
+
+#ifndef V8_DEBUG_LIVEEDIT_H_
+#define V8_DEBUG_LIVEEDIT_H_
+
+
+// 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/compiler.h"
+
+namespace v8 {
+namespace internal {
+
+// This class collects some specific information on structure of functions
+// in a particular script. It gets called from compiler all the time, but
+// actually records any data only when liveedit operation is in process;
+// in any other time this class is very cheap.
+//
+// The primary interest of the Tracker is to record function scope structures
+// in order to analyze whether function code maybe safely patched (with new
+// code successfully reading existing data from function scopes). The Tracker
+// also collects compiled function codes.
+class LiveEditFunctionTracker {
+ public:
+ explicit LiveEditFunctionTracker(Isolate* isolate, FunctionLiteral* fun);
+ ~LiveEditFunctionTracker();
+ void RecordFunctionInfo(Handle<SharedFunctionInfo> info,
+ FunctionLiteral* lit, Zone* zone);
+ void RecordRootFunctionInfo(Handle<Code> code);
+
+ static bool IsActive(Isolate* isolate);
+
+ private:
+ Isolate* isolate_;
+};
+
+
+class LiveEdit : AllStatic {
+ public:
+ // Describes how exactly a frame has been dropped from stack.
+ enum FrameDropMode {
+ // No frame has been dropped.
+ FRAMES_UNTOUCHED,
+ // The top JS frame had been calling debug break slot stub. Patch the
+ // address this stub jumps to in the end.
+ FRAME_DROPPED_IN_DEBUG_SLOT_CALL,
+ // The top JS frame had been calling some C++ function. The return address
+ // gets patched automatically.
+ FRAME_DROPPED_IN_DIRECT_CALL,
+ FRAME_DROPPED_IN_RETURN_CALL,
+ CURRENTLY_SET_MODE
+ };
+
+ static void InitializeThreadLocal(Debug* debug);
+
+ static bool SetAfterBreakTarget(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 FunctionSourceUpdated(Handle<JSArray> shared_info_array);
+
+ // 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);
+
+ // 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);
+
+ // Architecture-specific constant.
+ static const bool kFrameDropperSupported;
+
+ /**
+ * Defines layout of a stack frame that supports padding. This is a regular
+ * internal frame that has a flexible stack structure. LiveEdit can shift
+ * its lower part up the stack, taking up the 'padding' space when additional
+ * stack memory is required.
+ * Such frame is expected immediately above the topmost JavaScript frame.
+ *
+ * Stack Layout:
+ * --- Top
+ * LiveEdit routine frames
+ * ---
+ * C frames of debug handler
+ * ---
+ * ...
+ * ---
+ * An internal frame that has n padding words:
+ * - any number of words as needed by code -- upper part of frame
+ * - padding size: a Smi storing n -- current size of padding
+ * - padding: n words filled with kPaddingValue in form of Smi
+ * - 3 context/type words of a regular InternalFrame
+ * - fp
+ * ---
+ * Topmost JavaScript frame
+ * ---
+ * ...
+ * --- Bottom
+ */
+ // A size of frame base including fp. Padding words starts right above
+ // the base.
+ static const int kFrameDropperFrameSize =
+ 4 + StandardFrameConstants::kCPSlotCount;
+ // A number of words that should be reserved on stack for the LiveEdit use.
+ // Stored on stack in form of Smi.
+ static const int kFramePaddingInitialSize = 1;
+ // A value that padding words are filled with (in form of Smi). Going
+ // bottom-top, the first word not having this value is a counter word.
+ static const int kFramePaddingValue = kFramePaddingInitialSize + 1;
+};
+
+
+// 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 Object::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 literal_count,
+ int parent_index);
+
+ void SetFunctionCode(Handle<Code> function_code,
+ Handle<HeapObject> code_scope_info);
+
+ void SetFunctionScopeInfo(Handle<Object> scope_info_array) {
+ this->SetField(kFunctionScopeInfoOffset_, scope_info_array);
+ }
+
+ void SetSharedFunctionInfo(Handle<SharedFunctionInfo> info);
+
+ int GetLiteralCount() {
+ return this->GetSmiValueField(kLiteralNumOffset_);
+ }
+
+ int GetParentIndex() {
+ return this->GetSmiValueField(kParentIndexOffset_);
+ }
+
+ Handle<Code> GetFunctionCode();
+
+ MaybeHandle<TypeFeedbackVector> GetFeedbackVector();
+
+ Handle<Object> GetCodeScopeInfo();
+
+ 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 kCodeOffset_ = 4;
+ static const int kCodeScopeInfoOffset_ = 5;
+ static const int kFunctionScopeInfoOffset_ = 6;
+ static const int kParentIndexOffset_ = 7;
+ static const int kSharedFunctionInfoOffset_ = 8;
+ static const int kLiteralNumOffset_ = 9;
+ static const int kSize_ = 10;
+
+ 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(
+ Object::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_ */
diff --git a/src/debug/liveedit.js b/src/debug/liveedit.js
new file mode 100644
index 0000000..85e55c4
--- /dev/null
+++ b/src/debug/liveedit.js
@@ -0,0 +1,1132 @@
+// 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 GetScriptBreakPoints;
+ var GlobalArray = global.Array;
+ var MathFloor = global.Math.floor;
+ var SyntaxError = global.SyntaxError;
+
+ utils.Import(function(from) {
+ GetScriptBreakPoints = from.GetScriptBreakPoints;
+ });
+
+ // -------------------------------------------------------------------
+
+ // 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 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;
+
+ // Start with breakpoints. Convert their line/column positions and
+ // temporary remove.
+ var break_points_restorer = TemporaryRemoveBreakPoints(script, change_log);
+
+ 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;
+ }
+
+ // Link to an actual script all the functions that we are going to use.
+ for (var i = 0; i < link_to_original_script_list.length; i++) {
+ %LiveEditFunctionSetScript(
+ link_to_original_script_list[i].info.shared_function_info, script);
+ }
+
+ 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) {
+ update_positions_list[i].live_shared_function_infos.
+ forEach(function (info) {
+ %LiveEditFunctionSourceUpdated(info.raw_array);
+ });
+ }
+ }
+
+ break_points_restorer(pos_translator, old_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 } );
+ }
+ }
+
+
+ // Returns function that restores breakpoints.
+ function TemporaryRemoveBreakPoints(original_script, change_log) {
+ var script_break_points = GetScriptBreakPoints(original_script);
+
+ var break_points_update_report = [];
+ change_log.push( { break_points_update: break_points_update_report } );
+
+ var break_point_old_positions = [];
+ for (var i = 0; i < script_break_points.length; i++) {
+ var break_point = script_break_points[i];
+
+ break_point.clear();
+
+ // TODO(LiveEdit): be careful with resource offset here.
+ var break_point_position = FindScriptSourcePosition(original_script,
+ break_point.line(), break_point.column());
+
+ var old_position_description = {
+ position: break_point_position,
+ line: break_point.line(),
+ column: break_point.column()
+ };
+ break_point_old_positions.push(old_position_description);
+ }
+
+
+ // Restores breakpoints and creates their copies in the "old" copy of
+ // the script.
+ return function (pos_translator, old_script_copy_opt) {
+ // Update breakpoints (change positions and restore them in old version
+ // of script.
+ for (var i = 0; i < script_break_points.length; i++) {
+ var break_point = script_break_points[i];
+ if (old_script_copy_opt) {
+ var clone = break_point.cloneForOtherScript(old_script_copy_opt);
+ clone.set(old_script_copy_opt);
+
+ break_points_update_report.push( {
+ type: "copied_to_old",
+ id: break_point.number(),
+ new_id: clone.number(),
+ positions: break_point_old_positions[i]
+ } );
+ }
+
+ var updated_position = pos_translator.Translate(
+ break_point_old_positions[i].position,
+ PosTranslator.ShiftWithTopInsideChunkHandler);
+
+ var new_location =
+ original_script.locationFromPosition(updated_position, false);
+
+ break_point.update_positions(new_location.line, new_location.column);
+
+ var new_position_description = {
+ position: updated_position,
+ line: new_location.line,
+ column: new_location.column
+ };
+
+ break_point.set(original_script);
+
+ break_points_update_report.push( { type: "position_changed",
+ id: break_point.number(),
+ old_positions: break_point_old_positions[i],
+ new_positions: new_position_description
+ } );
+ }
+ };
+ }
+
+
+ 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 {
+ 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.code = raw_array[4];
+ this.code_scope_info = raw_array[5];
+ this.scope_info = raw_array[6];
+ this.outer_index = raw_array[7];
+ this.shared_function_info = raw_array[8];
+ 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;
+ }
+
+ // A testing entry.
+ function GetPcFromSourcePos(func, source_pos) {
+ return %GetFunctionCodePositionFromSource(func, source_pos);
+ }
+
+ // 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.GetPcFromSourcePos = GetPcFromSourcePos;
+
+ LiveEdit.TestApi = {
+ PosTranslator: PosTranslator,
+ CompareStrings: CompareStrings,
+ ApplySingleChunkPatch: ApplySingleChunkPatch
+ };
+
+ global.Debug.LiveEdit = LiveEdit;
+
+})
diff --git a/src/debug/mips/OWNERS b/src/debug/mips/OWNERS
new file mode 100644
index 0000000..89455a4
--- /dev/null
+++ b/src/debug/mips/OWNERS
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 0000000..c5c58d0
--- /dev/null
+++ b/src/debug/mips/debug-mips.cc
@@ -0,0 +1,143 @@
+// 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_MIPS
+
+#include "src/codegen.h"
+#include "src/debug/debug.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_EQ(Code::BUILTIN, code->kind());
+ 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);
+}
+
+
+void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
+ DebugBreakCallHelperMode mode) {
+ __ RecordComment("Debug break");
+ {
+ FrameScope scope(masm, StackFrame::INTERNAL);
+
+ // Load padding words on stack.
+ __ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingValue)));
+ __ Subu(sp, sp,
+ Operand(kPointerSize * LiveEdit::kFramePaddingInitialSize));
+ for (int i = LiveEdit::kFramePaddingInitialSize - 1; i >= 0; i--) {
+ __ sw(at, MemOperand(sp, kPointerSize * i));
+ }
+ __ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
+ __ push(at);
+
+ if (mode == SAVE_RESULT_REGISTER) __ push(v0);
+
+ __ PrepareCEntryArgs(0); // No arguments.
+ __ 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)};
+ __ li(reg, kDebugZapValue);
+ }
+ }
+
+ if (mode == SAVE_RESULT_REGISTER) __ pop(v0);
+
+ // Don't bother removing padding bytes pushed on the stack
+ // as the frame is going to be restored right away.
+
+ // Leave the internal frame.
+ }
+
+ // 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());
+ __ li(t9, Operand(after_break_target));
+ __ lw(t9, MemOperand(t9));
+ __ Jump(t9);
+}
+
+
+void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ // We do not know our frame height, but set sp based on fp.
+ __ Subu(sp, fp, Operand(kPointerSize));
+
+ __ Pop(ra, fp, a1); // Return address, Frame, Function.
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(a1, no_reg, dummy, dummy);
+
+ // Load context from the function.
+ __ lw(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
+
+ // Clear new.target as a safety measure.
+ __ LoadRoot(a3, Heap::kUndefinedValueRootIndex);
+
+ // Get function code.
+ __ lw(at, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
+ __ lw(at, FieldMemOperand(at, SharedFunctionInfo::kCodeOffset));
+ __ Addu(t9, at, Operand(Code::kHeaderSize - kHeapObjectTag));
+
+ // Re-run JSFunction, a1 is function, cp is context.
+ __ Jump(t9);
+}
+
+
+const bool LiveEdit::kFrameDropperSupported = true;
+
+#undef __
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_TARGET_ARCH_MIPS
diff --git a/src/debug/mips64/OWNERS b/src/debug/mips64/OWNERS
new file mode 100644
index 0000000..89455a4
--- /dev/null
+++ b/src/debug/mips64/OWNERS
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 0000000..1d65fd9
--- /dev/null
+++ b/src/debug/mips64/debug-mips64.cc
@@ -0,0 +1,145 @@
+// 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_MIPS64
+
+#include "src/codegen.h"
+#include "src/debug/debug.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_EQ(Code::BUILTIN, code->kind());
+ 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);
+}
+
+
+void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
+ DebugBreakCallHelperMode mode) {
+ __ RecordComment("Debug break");
+ {
+ FrameScope scope(masm, StackFrame::INTERNAL);
+
+ // Load padding words on stack.
+ __ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingValue)));
+ __ Dsubu(sp, sp,
+ Operand(kPointerSize * LiveEdit::kFramePaddingInitialSize));
+ for (int i = LiveEdit::kFramePaddingInitialSize - 1; i >= 0; i--) {
+ __ sd(at, MemOperand(sp, kPointerSize * i));
+ }
+ __ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
+ __ push(at);
+
+ if (mode == SAVE_RESULT_REGISTER) __ push(v0);
+
+ __ PrepareCEntryArgs(0); // No arguments.
+ __ 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)};
+ __ li(reg, kDebugZapValue);
+ }
+ }
+
+ if (mode == SAVE_RESULT_REGISTER) __ pop(v0);
+
+ // Don't bother removing padding bytes pushed on the stack
+ // as the frame is going to be restored right away.
+
+ // Leave the internal frame.
+ }
+
+ // 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());
+ __ li(t9, Operand(after_break_target));
+ __ ld(t9, MemOperand(t9));
+ __ Jump(t9);
+}
+
+
+void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ // We do not know our frame height, but set sp based on fp.
+ __ Dsubu(sp, fp, Operand(kPointerSize));
+
+ __ Pop(ra, fp, a1); // Return address, Frame, Function.
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(a1, no_reg, dummy, dummy);
+
+ // Load context from the function.
+ __ ld(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
+
+ // Clear new.target as a safety measure.
+ __ LoadRoot(a3, Heap::kUndefinedValueRootIndex);
+
+ // Get function code.
+ __ ld(at, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
+ __ ld(at, FieldMemOperand(at, SharedFunctionInfo::kCodeOffset));
+ __ Daddu(t9, at, Operand(Code::kHeaderSize - kHeapObjectTag));
+
+ // Re-run JSFunction, a1 is function, cp is context.
+ __ Jump(t9);
+}
+
+
+const bool LiveEdit::kFrameDropperSupported = true;
+
+#undef __
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_TARGET_ARCH_MIPS64
diff --git a/src/debug/mirrors.js b/src/debug/mirrors.js
new file mode 100644
index 0000000..1fd5fa9
--- /dev/null
+++ b/src/debug/mirrors.js
@@ -0,0 +1,3053 @@
+// 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 ErrorToString;
+var GlobalArray = global.Array;
+var IsNaN = global.isNaN;
+var JSONStringify = global.JSON.stringify;
+var MakeError;
+var MapEntries;
+var MapIteratorNext;
+var MathMin = global.Math.min;
+var promiseStatusSymbol = utils.ImportNow("promise_status_symbol");
+var promiseValueSymbol = utils.ImportNow("promise_value_symbol");
+var SetIteratorNext;
+var SetValues;
+var SymbolToString;
+
+utils.Import(function(from) {
+ ErrorToString = from.ErrorToString;
+ MakeError = from.MakeError;
+ MapEntries = from.MapEntries;
+ MapIteratorNext = from.MapIteratorNext;
+ SetIteratorNext = from.SetIteratorNext;
+ SetValues = from.SetValues;
+ SymbolToString = from.SymbolToString;
+});
+
+// ----------------------------------------------------------------------------
+
+// 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',
+}
+
+
+// Handle id counters.
+var next_handle_ = 0;
+var next_transient_handle_ = -1;
+
+// Mirror cache.
+var mirror_cache_ = [];
+var mirror_cache_enabled_ = true;
+
+
+function MirrorCacheIsEmpty() {
+ return next_handle_ == 0 && mirror_cache_.length == 0;
+}
+
+
+function ToggleMirrorCache(value) {
+ mirror_cache_enabled_ = value;
+ ClearMirrorCache();
+}
+
+
+function ClearMirrorCache(value) {
+ next_handle_ = 0;
+ mirror_cache_ = [];
+}
+
+
+function ObjectIsPromise(value) {
+ return IS_RECEIVER(value) &&
+ !IS_UNDEFINED(%DebugGetProperty(value, promiseStatusSymbol));
+}
+
+
+/**
+ * Returns the mirror for a specified value or object.
+ *
+ * @param {value or Object} value the value or object to retreive the mirror for
+ * @param {boolean} transient indicate whether this object is transient and
+ * should not be added to the mirror cache. The default is not transient.
+ * @returns {Mirror} the mirror reflects the passed value or object
+ */
+function MakeMirror(value, opt_transient) {
+ var mirror;
+
+ // Look for non transient mirrors in the mirror cache.
+ if (!opt_transient && mirror_cache_enabled_) {
+ for (var id in mirror_cache_) {
+ mirror = mirror_cache_[id];
+ if (mirror.value() === value) {
+ return mirror;
+ }
+ // Special check for NaN as NaN == NaN is false.
+ if (mirror.isNumber() && IsNaN(mirror.value()) &&
+ typeof value == 'number' && IsNaN(value)) {
+ return 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 (IS_REGEXP(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 (ObjectIsPromise(value)) {
+ mirror = new PromiseMirror(value);
+ } else if (IS_GENERATOR(value)) {
+ mirror = new GeneratorMirror(value);
+ } else {
+ mirror = new ObjectMirror(value, MirrorType.OBJECT_TYPE, opt_transient);
+ }
+
+ if (mirror_cache_enabled_) mirror_cache_[mirror.handle()] = mirror;
+ return mirror;
+}
+
+
+/**
+ * Returns the mirror for a specified mirror handle.
+ *
+ * @param {number} handle the handle to find the mirror for
+ * @returns {Mirror or undefiend} the mirror with the requested handle or
+ * undefined if no mirror with the requested handle was found
+ */
+function LookupMirror(handle) {
+ if (!mirror_cache_enabled_) {
+ throw MakeError(kDebugger, "Mirror cache is disabled");
+ }
+ return mirror_cache_[handle];
+}
+
+
+/**
+ * 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 PropertyType enum from property-details.h
+var PropertyType = {};
+PropertyType.Data = 0;
+PropertyType.DataConstant = 2;
+PropertyType.AccessorConstant = 3;
+
+
+// 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 };
+
+/**
+ * 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;
+};
+
+
+/**
+ * Allocate a handle id for this object.
+ */
+Mirror.prototype.allocateHandle_ = function() {
+ if (mirror_cache_enabled_) this.handle_ = next_handle_++;
+};
+
+
+/**
+ * Allocate a transient handle id for this object. Transient handles are
+ * negative.
+ */
+Mirror.prototype.allocateTransientHandle_ = function() {
+ this.handle_ = next_transient_handle_--;
+};
+
+
+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
+ * @param {boolean} transient indicate whether this object is transient with a
+ * transient handle
+ * @constructor
+ * @extends Mirror
+ */
+function ValueMirror(type, value, transient) {
+ %_Call(Mirror, this, type);
+ this.value_ = value;
+ if (!transient) {
+ this.allocateHandle_();
+ } else {
+ this.allocateTransientHandle_();
+ }
+}
+inherits(ValueMirror, Mirror);
+
+
+Mirror.prototype.handle = function() {
+ return this.handle_;
+};
+
+
+/**
+ * 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 %_Call(SymbolToString, this.value_);
+}
+
+
+/**
+ * Mirror object for objects.
+ * @param {object} value The object reflected by this mirror
+ * @param {boolean} transient indicate whether this object is transient with a
+ * transient handle
+ * @constructor
+ * @extends ValueMirror
+ */
+function ObjectMirror(value, type, transient) {
+ type = type || MirrorType.OBJECT_TYPE;
+ %_Call(ValueMirror, this, type, value, transient);
+}
+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_, TO_NAME(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.AccessorConstant) {
+ if (%_ObjectEquals(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, index);
+ }
+};
+
+
+FunctionMirror.prototype.toText = function() {
+ return this.source();
+};
+
+
+/**
+ * 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.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 = %_Call(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 = %DebugGetProperty(value, promiseStatusSymbol);
+ if (status == 0) return "pending";
+ if (status == 1) return "resolved";
+ return "rejected";
+}
+
+
+function PromiseGetValue_(value) {
+ return %DebugGetProperty(value, promiseValueSymbol);
+}
+
+
+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 < 0) return "running";
+ if (continuation == 0) 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.context = function() {
+ if (!this.context_) {
+ this.context_ = new ContextMirror(%GeneratorGetContext(this.value_));
+ }
+ return this.context_;
+};
+
+
+GeneratorMirror.prototype.receiver = function() {
+ if (!this.receiver_) {
+ this.receiver_ = MakeMirror(%GeneratorGetReceiver(this.value_));
+ }
+ return this.receiver_;
+};
+
+
+/**
+ * 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.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 {booolean} 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 %DebugPropertyTypeFromDetails(this.details_);
+};
+
+
+PropertyMirror.prototype.insertionIndex = function() {
+ return %DebugPropertyIndexFromDetails(this.details_);
+};
+
+
+/**
+ * Returns whether this property has a getter defined through __defineGetter__.
+ * @return {booolean} 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 {booolean} 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.AccessorConstant) &&
+ !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 kFrameDetailsArgumentCountIndex = 3;
+var kFrameDetailsLocalCountIndex = 4;
+var kFrameDetailsSourcePositionIndex = 5;
+var kFrameDetailsConstructCallIndex = 6;
+var kFrameDetailsAtReturnIndex = 7;
+var kFrameDetailsFlagsIndex = 8;
+var kFrameDetailsFirstDynamicIndex = 9;
+
+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: Argument count
+ * 4: Local count
+ * 5: Source position
+ * 6: Construct call
+ * 7: Is at return
+ * 8: 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.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_;
+};
+
+
+FrameDetails.prototype.stepInPositionsImpl = function() {
+ return %GetStepInPositions(this.break_id_, this.frameId());
+};
+
+
+/**
+ * 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.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 func = this.func();
+ if (func.resolved()) {
+ var script = func.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, 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, i, scopeDetails[i]));
+ }
+ return result;
+};
+
+
+FrameMirror.prototype.stepInPositions = function() {
+ var script = this.func().script();
+ var funcOffset = this.func().sourcePosition_();
+
+ var stepInRaw = this.details_.stepInPositionsImpl();
+ var result = [];
+ if (stepInRaw) {
+ for (var i = 0; i < stepInRaw.length; i++) {
+ var posStruct = {};
+ var offset = script.locationFromPosition(funcOffset + stepInRaw[i],
+ true);
+ serializeLocationFields(offset, posStruct);
+ var item = {
+ position: posStruct
+ };
+ result.push(item);
+ }
+ }
+
+ return result;
+};
+
+
+FrameMirror.prototype.evaluate = function(source, disable_break,
+ opt_context_object) {
+ return MakeMirror(%DebugEvaluate(this.break_id_,
+ this.details_.frameId(),
+ this.details_.inlinedFrameIndex(),
+ source,
+ TO_BOOLEAN(disable_break),
+ opt_context_object));
+};
+
+
+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.name();
+ } else {
+ result += '[';
+ result += property.name();
+ 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;
+
+function ScopeDetails(frame, fun, 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 {
+ this.details_ = opt_details || %GetFunctionScopeDetails(fun.value(), index);
+ this.fun_value_ = fun.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.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 {
+ raw_res = %SetScopeVariableValue(this.fun_value_, null, null, this.index_,
+ name, new_value);
+ }
+ if (!raw_res) throw MakeError(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 {number} index The scope index in the frame
+ * @param {Array=} opt_details Raw scope details data
+ * @constructor
+ * @extends Mirror
+ */
+function ScopeMirror(frame, fun, 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, 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 transient mirror
+ // as these objects are created on the fly materializing the local
+ // or closure scopes and therefore will not preserve identity.
+ var transient = this.scopeType() == ScopeType.Local ||
+ this.scopeType() == ScopeType.Closure ||
+ this.scopeType() == ScopeType.Script;
+ return MakeMirror(this.details_.object(), transient);
+};
+
+
+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);
+ this.allocateHandle_();
+}
+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) {
+ %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 this.script_.lineCount();
+};
+
+
+ScriptMirror.prototype.locationFromPosition = function(
+ position, include_resource_offset) {
+ return this.script_.locationFromPosition(position, include_resource_offset);
+};
+
+
+ScriptMirror.prototype.sourceSlice = function (opt_from_line, opt_to_line) {
+ return this.script_.sourceSlice(opt_from_line, opt_to_line);
+};
+
+
+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;
+ this.allocateHandle_();
+}
+inherits(ContextMirror, Mirror);
+
+
+ContextMirror.prototype.data = function() {
+ return this.data_;
+};
+
+
+/**
+ * Returns a mirror serializer
+ *
+ * @param {boolean} details Set to true to include details
+ * @param {Object} options Options comtrolling the serialization
+ * The following options can be set:
+ * includeSource: include ths full source of scripts
+ * @returns {MirrorSerializer} mirror serializer
+ */
+function MakeMirrorSerializer(details, options) {
+ return new JSONProtocolSerializer(details, options);
+}
+
+
+/**
+ * Object for serializing a mirror objects and its direct references.
+ * @param {boolean} details Indicates whether to include details for the mirror
+ * serialized
+ * @constructor
+ */
+function JSONProtocolSerializer(details, options) {
+ this.details_ = details;
+ this.options_ = options;
+ this.mirrors_ = [ ];
+}
+
+
+/**
+ * Returns a serialization of an object reference. The referenced object are
+ * added to the serialization state.
+ *
+ * @param {Mirror} mirror The mirror to serialize
+ * @returns {String} JSON serialization
+ */
+JSONProtocolSerializer.prototype.serializeReference = function(mirror) {
+ return this.serialize_(mirror, true, true);
+};
+
+
+/**
+ * Returns a serialization of an object value. The referenced objects are
+ * added to the serialization state.
+ *
+ * @param {Mirror} mirror The mirror to serialize
+ * @returns {String} JSON serialization
+ */
+JSONProtocolSerializer.prototype.serializeValue = function(mirror) {
+ var json = this.serialize_(mirror, false, true);
+ return json;
+};
+
+
+/**
+ * Returns a serialization of all the objects referenced.
+ *
+ * @param {Mirror} mirror The mirror to serialize.
+ * @returns {Array.<Object>} Array of the referenced objects converted to
+ * protcol objects.
+ */
+JSONProtocolSerializer.prototype.serializeReferencedObjects = function() {
+ // Collect the protocol representation of the referenced objects in an array.
+ var content = [];
+
+ // Get the number of referenced objects.
+ var count = this.mirrors_.length;
+
+ for (var i = 0; i < count; i++) {
+ content.push(this.serialize_(this.mirrors_[i], false, false));
+ }
+
+ return content;
+};
+
+
+JSONProtocolSerializer.prototype.includeSource_ = function() {
+ return this.options_ && this.options_.includeSource;
+};
+
+
+JSONProtocolSerializer.prototype.inlineRefs_ = function() {
+ return this.options_ && this.options_.inlineRefs;
+};
+
+
+JSONProtocolSerializer.prototype.maxStringLength_ = function() {
+ if (IS_UNDEFINED(this.options_) ||
+ IS_UNDEFINED(this.options_.maxStringLength)) {
+ return kMaxProtocolStringLength;
+ }
+ return this.options_.maxStringLength;
+};
+
+
+JSONProtocolSerializer.prototype.add_ = function(mirror) {
+ // If this mirror is already in the list just return.
+ for (var i = 0; i < this.mirrors_.length; i++) {
+ if (this.mirrors_[i] === mirror) {
+ return;
+ }
+ }
+
+ // Add the mirror to the list of mirrors to be serialized.
+ this.mirrors_.push(mirror);
+};
+
+
+/**
+ * Formats mirror object to protocol reference object with some data that can
+ * be used to display the value in debugger.
+ * @param {Mirror} mirror Mirror to serialize.
+ * @return {Object} Protocol reference object.
+ */
+JSONProtocolSerializer.prototype.serializeReferenceWithDisplayData_ =
+ function(mirror) {
+ var o = {};
+ o.ref = mirror.handle();
+ o.type = mirror.type();
+ switch (mirror.type()) {
+ case MirrorType.UNDEFINED_TYPE:
+ case MirrorType.NULL_TYPE:
+ case MirrorType.BOOLEAN_TYPE:
+ case MirrorType.NUMBER_TYPE:
+ o.value = mirror.value();
+ break;
+ case MirrorType.STRING_TYPE:
+ o.value = mirror.getTruncatedValue(this.maxStringLength_());
+ break;
+ case MirrorType.SYMBOL_TYPE:
+ o.description = mirror.description();
+ break;
+ case MirrorType.FUNCTION_TYPE:
+ o.name = mirror.name();
+ o.inferredName = mirror.inferredName();
+ if (mirror.script()) {
+ o.scriptId = mirror.script().id();
+ }
+ break;
+ case MirrorType.ERROR_TYPE:
+ case MirrorType.REGEXP_TYPE:
+ o.value = mirror.toText();
+ break;
+ case MirrorType.OBJECT_TYPE:
+ o.className = mirror.className();
+ break;
+ }
+ return o;
+};
+
+
+JSONProtocolSerializer.prototype.serialize_ = function(mirror, reference,
+ details) {
+ // If serializing a reference to a mirror just return the reference and add
+ // the mirror to the referenced mirrors.
+ if (reference &&
+ (mirror.isValue() || mirror.isScript() || mirror.isContext())) {
+ if (this.inlineRefs_() && mirror.isValue()) {
+ return this.serializeReferenceWithDisplayData_(mirror);
+ } else {
+ this.add_(mirror);
+ return {'ref' : mirror.handle()};
+ }
+ }
+
+ // Collect the JSON property/value pairs.
+ var content = {};
+
+ // Add the mirror handle.
+ if (mirror.isValue() || mirror.isScript() || mirror.isContext()) {
+ content.handle = mirror.handle();
+ }
+
+ // Always add the type.
+ content.type = mirror.type();
+
+ switch (mirror.type()) {
+ case MirrorType.UNDEFINED_TYPE:
+ case MirrorType.NULL_TYPE:
+ // Undefined and null are represented just by their type.
+ break;
+
+ case MirrorType.BOOLEAN_TYPE:
+ // Boolean values are simply represented by their value.
+ content.value = mirror.value();
+ break;
+
+ case MirrorType.NUMBER_TYPE:
+ // Number values are simply represented by their value.
+ content.value = NumberToJSON_(mirror.value());
+ break;
+
+ case MirrorType.STRING_TYPE:
+ // String values might have their value cropped to keep down size.
+ if (this.maxStringLength_() != -1 &&
+ mirror.length() > this.maxStringLength_()) {
+ var str = mirror.getTruncatedValue(this.maxStringLength_());
+ content.value = str;
+ content.fromIndex = 0;
+ content.toIndex = this.maxStringLength_();
+ } else {
+ content.value = mirror.value();
+ }
+ content.length = mirror.length();
+ break;
+
+ case MirrorType.SYMBOL_TYPE:
+ content.description = mirror.description();
+ break;
+
+ case MirrorType.OBJECT_TYPE:
+ case MirrorType.FUNCTION_TYPE:
+ case MirrorType.ERROR_TYPE:
+ case MirrorType.REGEXP_TYPE:
+ case MirrorType.PROMISE_TYPE:
+ case MirrorType.GENERATOR_TYPE:
+ // Add object representation.
+ this.serializeObject_(mirror, content, details);
+ break;
+
+ case MirrorType.PROPERTY_TYPE:
+ case MirrorType.INTERNAL_PROPERTY_TYPE:
+ throw MakeError(kDebugger,
+ 'PropertyMirror cannot be serialized independently');
+ break;
+
+ case MirrorType.FRAME_TYPE:
+ // Add object representation.
+ this.serializeFrame_(mirror, content);
+ break;
+
+ case MirrorType.SCOPE_TYPE:
+ // Add object representation.
+ this.serializeScope_(mirror, content);
+ break;
+
+ case MirrorType.SCRIPT_TYPE:
+ // Script is represented by id, name and source attributes.
+ if (mirror.name()) {
+ content.name = mirror.name();
+ }
+ content.id = mirror.id();
+ content.lineOffset = mirror.lineOffset();
+ content.columnOffset = mirror.columnOffset();
+ content.lineCount = mirror.lineCount();
+ if (mirror.data()) {
+ content.data = mirror.data();
+ }
+ if (this.includeSource_()) {
+ content.source = mirror.source();
+ } else {
+ var sourceStart = mirror.source().substring(0, 80);
+ content.sourceStart = sourceStart;
+ }
+ content.sourceLength = mirror.source().length;
+ content.scriptType = mirror.scriptType();
+ content.compilationType = mirror.compilationType();
+ // For compilation type eval emit information on the script from which
+ // eval was called if a script is present.
+ if (mirror.compilationType() == 1 &&
+ mirror.evalFromScript()) {
+ content.evalFromScript =
+ this.serializeReference(mirror.evalFromScript());
+ var evalFromLocation = mirror.evalFromLocation();
+ if (evalFromLocation) {
+ content.evalFromLocation = { line: evalFromLocation.line,
+ column: evalFromLocation.column };
+ }
+ if (mirror.evalFromFunctionName()) {
+ content.evalFromFunctionName = mirror.evalFromFunctionName();
+ }
+ }
+ if (mirror.context()) {
+ content.context = this.serializeReference(mirror.context());
+ }
+ break;
+
+ case MirrorType.CONTEXT_TYPE:
+ content.data = mirror.data();
+ break;
+ }
+
+ // Always add the text representation.
+ content.text = mirror.toText();
+
+ // Create and return the JSON string.
+ return content;
+};
+
+
+/**
+ * Serialize object information to the following JSON format.
+ *
+ * {"className":"<class name>",
+ * "constructorFunction":{"ref":<number>},
+ * "protoObject":{"ref":<number>},
+ * "prototypeObject":{"ref":<number>},
+ * "namedInterceptor":<boolean>,
+ * "indexedInterceptor":<boolean>,
+ * "properties":[<properties>],
+ * "internalProperties":[<internal properties>]}
+ */
+JSONProtocolSerializer.prototype.serializeObject_ = function(mirror, content,
+ details) {
+ // Add general object properties.
+ content.className = mirror.className();
+ content.constructorFunction =
+ this.serializeReference(mirror.constructorFunction());
+ content.protoObject = this.serializeReference(mirror.protoObject());
+ content.prototypeObject = this.serializeReference(mirror.prototypeObject());
+
+ // Add flags to indicate whether there are interceptors.
+ if (mirror.hasNamedInterceptor()) {
+ content.namedInterceptor = true;
+ }
+ if (mirror.hasIndexedInterceptor()) {
+ content.indexedInterceptor = true;
+ }
+
+ if (mirror.isFunction()) {
+ // Add function specific properties.
+ content.name = mirror.name();
+ if (!IS_UNDEFINED(mirror.inferredName())) {
+ content.inferredName = mirror.inferredName();
+ }
+ content.resolved = mirror.resolved();
+ if (mirror.resolved()) {
+ content.source = mirror.source();
+ }
+ if (mirror.script()) {
+ content.script = this.serializeReference(mirror.script());
+ content.scriptId = mirror.script().id();
+
+ serializeLocationFields(mirror.sourceLocation(), content);
+ }
+
+ content.scopes = [];
+ for (var i = 0; i < mirror.scopeCount(); i++) {
+ var scope = mirror.scope(i);
+ content.scopes.push({
+ type: scope.scopeType(),
+ index: i
+ });
+ }
+ }
+
+ if (mirror.isGenerator()) {
+ // Add generator specific properties.
+
+ // Either 'running', 'closed', or 'suspended'.
+ content.status = mirror.status();
+
+ content.func = this.serializeReference(mirror.func())
+ content.receiver = this.serializeReference(mirror.receiver())
+
+ // If the generator is suspended, the content add line/column properties.
+ serializeLocationFields(mirror.sourceLocation(), content);
+
+ // TODO(wingo): Also serialize a reference to the context (scope chain).
+ }
+
+ if (mirror.isDate()) {
+ // Add date specific properties.
+ content.value = mirror.value();
+ }
+
+ if (mirror.isPromise()) {
+ // Add promise specific properties.
+ content.status = mirror.status();
+ content.promiseValue = this.serializeReference(mirror.promiseValue());
+ }
+
+ // Add actual properties - named properties followed by indexed properties.
+ var properties = mirror.propertyNames();
+ for (var i = 0; i < properties.length; i++) {
+ var propertyMirror = mirror.property(properties[i]);
+ properties[i] = this.serializeProperty_(propertyMirror);
+ if (details) {
+ this.add_(propertyMirror.value());
+ }
+ }
+ content.properties = properties;
+
+ var internalProperties = mirror.internalProperties();
+ if (internalProperties.length > 0) {
+ var ip = [];
+ for (var i = 0; i < internalProperties.length; i++) {
+ ip.push(this.serializeInternalProperty_(internalProperties[i]));
+ }
+ content.internalProperties = ip;
+ }
+};
+
+
+/**
+ * Serialize location information to the following JSON format:
+ *
+ * "position":"<position>",
+ * "line":"<line>",
+ * "column":"<column>",
+ *
+ * @param {SourceLocation} location The location to serialize, may be undefined.
+ */
+function serializeLocationFields (location, content) {
+ if (!location) {
+ return;
+ }
+ content.position = location.position;
+ var line = location.line;
+ if (!IS_UNDEFINED(line)) {
+ content.line = line;
+ }
+ var column = location.column;
+ if (!IS_UNDEFINED(column)) {
+ content.column = column;
+ }
+}
+
+
+/**
+ * Serialize property information to the following JSON format for building the
+ * array of properties.
+ *
+ * {"name":"<property name>",
+ * "attributes":<number>,
+ * "propertyType":<number>,
+ * "ref":<number>}
+ *
+ * If the attribute for the property is PropertyAttribute.None it is not added.
+ * Here are a couple of examples.
+ *
+ * {"name":"hello","propertyType":0,"ref":1}
+ * {"name":"length","attributes":7,"propertyType":3,"ref":2}
+ *
+ * @param {PropertyMirror} propertyMirror The property to serialize.
+ * @returns {Object} Protocol object representing the property.
+ */
+JSONProtocolSerializer.prototype.serializeProperty_ = function(propertyMirror) {
+ var result = {};
+
+ result.name = propertyMirror.name();
+ var propertyValue = propertyMirror.value();
+ if (this.inlineRefs_() && propertyValue.isValue()) {
+ result.value = this.serializeReferenceWithDisplayData_(propertyValue);
+ } else {
+ if (propertyMirror.attributes() != PropertyAttribute.None) {
+ result.attributes = propertyMirror.attributes();
+ }
+ result.propertyType = propertyMirror.propertyType();
+ result.ref = propertyValue.handle();
+ }
+ return result;
+};
+
+
+/**
+ * Serialize internal property information to the following JSON format for
+ * building the array of properties.
+ *
+ * {"name":"<property name>",
+ * "ref":<number>}
+ *
+ * {"name":"[[BoundThis]]","ref":117}
+ *
+ * @param {InternalPropertyMirror} propertyMirror The property to serialize.
+ * @returns {Object} Protocol object representing the property.
+ */
+JSONProtocolSerializer.prototype.serializeInternalProperty_ =
+ function(propertyMirror) {
+ var result = {};
+
+ result.name = propertyMirror.name();
+ var propertyValue = propertyMirror.value();
+ if (this.inlineRefs_() && propertyValue.isValue()) {
+ result.value = this.serializeReferenceWithDisplayData_(propertyValue);
+ } else {
+ result.ref = propertyValue.handle();
+ }
+ return result;
+};
+
+
+JSONProtocolSerializer.prototype.serializeFrame_ = function(mirror, content) {
+ content.index = mirror.index();
+ content.receiver = this.serializeReference(mirror.receiver());
+ var func = mirror.func();
+ content.func = this.serializeReference(func);
+ var script = func.script();
+ if (script) {
+ content.script = this.serializeReference(script);
+ }
+ content.constructCall = mirror.isConstructCall();
+ content.atReturn = mirror.isAtReturn();
+ if (mirror.isAtReturn()) {
+ content.returnValue = this.serializeReference(mirror.returnValue());
+ }
+ content.debuggerFrame = mirror.isDebuggerFrame();
+ var x = new GlobalArray(mirror.argumentCount());
+ for (var i = 0; i < mirror.argumentCount(); i++) {
+ var arg = {};
+ var argument_name = mirror.argumentName(i);
+ if (argument_name) {
+ arg.name = argument_name;
+ }
+ arg.value = this.serializeReference(mirror.argumentValue(i));
+ x[i] = arg;
+ }
+ content.arguments = x;
+ var x = new GlobalArray(mirror.localCount());
+ for (var i = 0; i < mirror.localCount(); i++) {
+ var local = {};
+ local.name = mirror.localName(i);
+ local.value = this.serializeReference(mirror.localValue(i));
+ x[i] = local;
+ }
+ content.locals = x;
+ serializeLocationFields(mirror.sourceLocation(), content);
+ var source_line_text = mirror.sourceLineText();
+ if (!IS_UNDEFINED(source_line_text)) {
+ content.sourceLineText = source_line_text;
+ }
+
+ content.scopes = [];
+ for (var i = 0; i < mirror.scopeCount(); i++) {
+ var scope = mirror.scope(i);
+ content.scopes.push({
+ type: scope.scopeType(),
+ index: i
+ });
+ }
+};
+
+
+JSONProtocolSerializer.prototype.serializeScope_ = function(mirror, content) {
+ content.index = mirror.scopeIndex();
+ content.frameIndex = mirror.frameIndex();
+ content.type = mirror.scopeType();
+ content.object = this.inlineRefs_() ?
+ this.serializeValue(mirror.scopeObject()) :
+ this.serializeReference(mirror.scopeObject());
+};
+
+
+/**
+ * Convert a number to a protocol value. For all finite numbers the number
+ * itself is returned. For non finite numbers NaN, Infinite and
+ * -Infinite the string representation "NaN", "Infinite" or "-Infinite"
+ * (not including the quotes) is returned.
+ *
+ * @param {number} value The number value to convert to a protocol value.
+ * @returns {number|string} Protocol value.
+ */
+function NumberToJSON_(value) {
+ if (IsNaN(value)) {
+ return 'NaN';
+ }
+ if (!NUMBER_IS_FINITE(value)) {
+ if (value > 0) {
+ return 'Infinity';
+ } else {
+ return '-Infinity';
+ }
+ }
+ return value;
+}
+
+// ----------------------------------------------------------------------------
+// Exports
+
+utils.InstallFunctions(global, DONT_ENUM, [
+ "MakeMirror", MakeMirror,
+ "MakeMirrorSerializer", MakeMirrorSerializer,
+ "LookupMirror", LookupMirror,
+ "ToggleMirrorCache", ToggleMirrorCache,
+ "MirrorCacheIsEmpty", MirrorCacheIsEmpty,
+]);
+
+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,
+]);
+
+// Functions needed by the debugger runtime.
+utils.InstallFunctions(utils, DONT_ENUM, [
+ "ClearMirrorCache", ClearMirrorCache
+]);
+
+// Export to debug.js
+utils.Export(function(to) {
+ to.MirrorType = MirrorType;
+});
+})
diff --git a/src/debug/ppc/OWNERS b/src/debug/ppc/OWNERS
new file mode 100644
index 0000000..eb007cb
--- /dev/null
+++ b/src/debug/ppc/OWNERS
@@ -0,0 +1,5 @@
+jyan@ca.ibm.com
+dstence@us.ibm.com
+joransiu@ca.ibm.com
+mbrandy@us.ibm.com
+michael_dawson@ca.ibm.com
diff --git a/src/debug/ppc/debug-ppc.cc b/src/debug/ppc/debug-ppc.cc
new file mode 100644
index 0000000..c5ddab8
--- /dev/null
+++ b/src/debug/ppc/debug-ppc.cc
@@ -0,0 +1,151 @@
+// Copyright 2014 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_PPC
+
+#include "src/codegen.h"
+#include "src/debug/debug.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_EQ(Code::BUILTIN, code->kind());
+ 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();
+}
+
+
+void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
+ DebugBreakCallHelperMode mode) {
+ __ RecordComment("Debug break");
+ {
+ FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
+
+ // Load padding words on stack.
+ __ LoadSmiLiteral(ip, Smi::FromInt(LiveEdit::kFramePaddingValue));
+ for (int i = 0; i < LiveEdit::kFramePaddingInitialSize; i++) {
+ __ push(ip);
+ }
+ __ LoadSmiLiteral(ip, Smi::FromInt(LiveEdit::kFramePaddingInitialSize));
+ __ push(ip);
+
+ if (mode == SAVE_RESULT_REGISTER) __ push(r3);
+
+ __ mov(r3, Operand::Zero()); // no arguments
+ __ 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)};
+ __ mov(reg, Operand(kDebugZapValue));
+ }
+ }
+
+ if (mode == SAVE_RESULT_REGISTER) __ pop(r3);
+
+ // Don't bother removing padding bytes pushed on the stack
+ // as the frame is going to be restored right away.
+
+ // Leave the internal frame.
+ }
+
+ // 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());
+ __ mov(ip, Operand(after_break_target));
+ __ LoadP(ip, MemOperand(ip));
+ __ JumpToJSEntry(ip);
+}
+
+
+void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ // Load the function pointer off of our current stack frame.
+ __ LoadP(r4, MemOperand(fp, StandardFrameConstants::kConstantPoolOffset -
+ kPointerSize));
+
+ // Pop return address and frame
+ __ LeaveFrame(StackFrame::INTERNAL);
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(r4, no_reg, dummy, dummy);
+
+ // Load context from the function.
+ __ LoadP(cp, FieldMemOperand(r4, JSFunction::kContextOffset));
+
+ // Clear new.target as a safety measure.
+ __ LoadRoot(r6, Heap::kUndefinedValueRootIndex);
+
+ // Get function code.
+ __ LoadP(ip, FieldMemOperand(r4, JSFunction::kSharedFunctionInfoOffset));
+ __ LoadP(ip, FieldMemOperand(ip, SharedFunctionInfo::kCodeOffset));
+ __ addi(ip, ip, Operand(Code::kHeaderSize - kHeapObjectTag));
+
+ // Re-run JSFunction, r4 is function, cp is context.
+ __ Jump(ip);
+}
+
+
+const bool LiveEdit::kFrameDropperSupported = true;
+
+#undef __
+} // namespace internal
+} // namespace v8
+
+#endif // V8_TARGET_ARCH_PPC
diff --git a/src/debug/x64/debug-x64.cc b/src/debug/x64/debug-x64.cc
new file mode 100644
index 0000000..0d56ea7
--- /dev/null
+++ b/src/debug/x64/debug-x64.cc
@@ -0,0 +1,141 @@
+// 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_X64
+
+#include "src/assembler.h"
+#include "src/codegen.h"
+#include "src/debug/debug.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_EQ(Code::BUILTIN, code->kind());
+ 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));
+}
+
+
+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(Smi::FromInt(LiveEdit::kFramePaddingValue));
+ }
+ __ Push(Smi::FromInt(LiveEdit::kFramePaddingInitialSize));
+
+ if (mode == SAVE_RESULT_REGISTER) __ Push(rax);
+
+ __ Set(rax, 0); // No arguments (argc == 0).
+ __ Move(rbx, 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)};
+ __ Set(reg, kDebugZapValue);
+ }
+ }
+
+ if (mode == SAVE_RESULT_REGISTER) __ Pop(rax);
+
+ // Read current padding counter and skip corresponding number of words.
+ __ Pop(kScratchRegister);
+ __ SmiToInteger32(kScratchRegister, kScratchRegister);
+ __ leap(rsp, Operand(rsp, kScratchRegister, times_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.
+ __ addp(rsp, Immediate(kPCOnStackSize));
+
+ // 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());
+ __ Move(kScratchRegister, after_break_target);
+ __ Jump(Operand(kScratchRegister, 0));
+}
+
+
+void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ // We do not know our frame height, but set rsp based on rbp.
+ __ leap(rsp, Operand(rbp, -1 * kPointerSize));
+
+ __ Pop(rdi); // Function.
+ __ popq(rbp);
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(rdi, no_reg, dummy, dummy);
+
+ // Load context from the function.
+ __ movp(rsi, FieldOperand(rdi, JSFunction::kContextOffset));
+
+ // Clear new.target as a safety measure.
+ __ LoadRoot(rdx, Heap::kUndefinedValueRootIndex);
+
+ // Get function code.
+ __ movp(rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset));
+ __ movp(rbx, FieldOperand(rbx, SharedFunctionInfo::kCodeOffset));
+ __ leap(rbx, FieldOperand(rbx, Code::kHeaderSize));
+
+ // Re-run JSFunction, rdi is function, rsi is context.
+ __ jmp(rbx);
+}
+
+const bool LiveEdit::kFrameDropperSupported = true;
+
+#undef __
+
+} // namespace internal
+} // namespace v8
+
+#endif // V8_TARGET_ARCH_X64
diff --git a/src/debug/x87/OWNERS b/src/debug/x87/OWNERS
new file mode 100644
index 0000000..dd9998b
--- /dev/null
+++ b/src/debug/x87/OWNERS
@@ -0,0 +1 @@
+weiliang.lin@intel.com
diff --git a/src/debug/x87/debug-x87.cc b/src/debug/x87/debug-x87.cc
new file mode 100644
index 0000000..8c04e02
--- /dev/null
+++ b/src/debug/x87/debug-x87.cc
@@ -0,0 +1,141 @@
+// 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/codegen.h"
+#include "src/debug/debug.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_EQ(Code::BUILTIN, code->kind());
+ 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));
+}
+
+
+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)));
+
+ if (mode == SAVE_RESULT_REGISTER) __ push(eax);
+
+ __ Move(eax, Immediate(0)); // No arguments.
+ __ 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)};
+ __ Move(reg, Immediate(kDebugZapValue));
+ }
+ }
+
+ if (mode == SAVE_RESULT_REGISTER) __ pop(eax);
+
+ __ 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, -1 * kPointerSize));
+
+ __ pop(edi); // Function.
+ __ pop(ebp);
+
+ ParameterCount dummy(0);
+ __ FloodFunctionIfStepping(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