}
+void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
+ masm->Abort("LiveEdit frame dropping is not supported on arm");
+}
+
+void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ masm->Abort("LiveEdit frame dropping is not supported on arm");
+}
+
#undef __
+
+void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
+ Handle<Code> code) {
+ UNREACHABLE();
+}
+const int Debug::kFrameDropperFrameSize = -1;
+
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal
static void Generate_StubNoRegisters_DebugBreak(MacroAssembler* masm) {
Debug::GenerateStubNoRegistersDebugBreak(masm);
}
+
+static void Generate_PlainReturn_LiveEdit(MacroAssembler* masm) {
+ Debug::GeneratePlainReturnLiveEdit(masm);
+}
+
+static void Generate_FrameDropper_LiveEdit(MacroAssembler* masm) {
+ Debug::GenerateFrameDropperLiveEdit(masm);
+}
#endif
Object* Builtins::builtins_[builtin_count] = { NULL, };
V(LoadIC_DebugBreak, LOAD_IC, DEBUG_BREAK) \
V(KeyedLoadIC_DebugBreak, KEYED_LOAD_IC, DEBUG_BREAK) \
V(StoreIC_DebugBreak, STORE_IC, DEBUG_BREAK) \
- V(KeyedStoreIC_DebugBreak, KEYED_STORE_IC, DEBUG_BREAK)
+ V(KeyedStoreIC_DebugBreak, KEYED_STORE_IC, DEBUG_BREAK) \
+ V(PlainReturn_LiveEdit, BUILTIN, DEBUG_BREAK) \
+ V(FrameDropper_LiveEdit, BUILTIN, DEBUG_BREAK)
#else
#define BUILTIN_LIST_DEBUG_A(V)
#endif
return %DebugDisassembleConstructor(f);
};
+Debug.ExecuteInDebugContext = function(f, without_debugger) {
+ if (!IS_FUNCTION(f)) throw new Error('Parameters have wrong types.');
+ return %ExecuteInDebugContext(f, !!without_debugger);
+};
+
Debug.sourcePosition = function(f) {
if (!IS_FUNCTION(f)) throw new Error('Parameters have wrong types.');
return %FunctionGetScriptSourcePosition(f);
if (e instanceof Debug.LiveEditChangeScript.Failure) {
// Let's treat it as a "success" so that body with change_log will be
// sent back. "change_log" will have "failure" field set.
- change_log.push( { failure: true } );
+ change_log.push( { failure: true, message: e.toString() } );
} else {
throw e;
}
HandleScope scope;
ASSERT(args.length() == 0);
+ thread_local_.frames_are_dropped_ = false;
+
// Get the top-most JavaScript frame.
JavaScriptFrameIterator it;
JavaScriptFrame* frame = it.frame();
PrepareStep(step_action, step_count);
}
- // Install jump to the call address which was overwritten.
- SetAfterBreakTarget(frame);
+ if (thread_local_.frames_are_dropped_) {
+ // We must have been calling IC stub. Do not return there anymore.
+ Code* plain_return = Builtins::builtin(Builtins::PlainReturn_LiveEdit);
+ thread_local_.after_break_target_ = plain_return->entry();
+ } else {
+ SetAfterBreakTarget(frame);
+ }
return Heap::undefined_value();
}
}
+void Debug::FramesHaveBeenDropped(StackFrame::Id new_break_frame_id) {
+ thread_local_.frames_are_dropped_ = true;
+ thread_local_.break_frame_id_ = new_break_frame_id;
+}
+
+
bool Debug::IsDebugGlobal(GlobalObject* global) {
return IsLoaded() && global == Debug::debug_context()->global();
}
static void GenerateConstructCallDebugBreak(MacroAssembler* masm);
static void GenerateReturnDebugBreak(MacroAssembler* masm);
static void GenerateStubNoRegistersDebugBreak(MacroAssembler* masm);
+ static void GeneratePlainReturnLiveEdit(MacroAssembler* masm);
+ static void GenerateFrameDropperLiveEdit(MacroAssembler* masm);
// Called from stub-cache.cc.
static void GenerateCallICDebugBreak(MacroAssembler* masm);
+ static void FramesHaveBeenDropped(StackFrame::Id new_break_frame_id);
+
+ static void SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
+ Handle<Code> code);
+ static const int kFrameDropperFrameSize;
+
private:
static bool CompileDebuggerScript(int index);
static void ClearOneShot();
// Storage location for jump when exiting debug break calls.
Address after_break_target_;
+ // Indicates that LiveEdit has patched the stack.
+ bool frames_are_dropped_;
+
// Top debugger entry.
EnterDebugger* debugger_entry_;
}
+void EntryFrame::SetCallerFp(Address caller_fp) {
+ const int offset = EntryFrameConstants::kCallerFPOffset;
+ Memory::Address_at(this->fp() + offset) = caller_fp;
+}
+
+
StackFrame::Type EntryFrame::GetCallerState(State* state) const {
const int offset = EntryFrameConstants::kCallerFPOffset;
Address fp = Memory::Address_at(this->fp() + offset);
}
+void ExitFrame::SetCallerFp(Address caller_fp) {
+ Memory::Address_at(fp() + ExitFrameConstants::kCallerFPOffset) = caller_fp;
+}
+
+
Address ExitFrame::GetCallerStackPointer() const {
return fp() + ExitFrameConstants::kCallerSPDisplacement;
}
}
+void StandardFrame::SetCallerFp(Address caller_fp) {
+ Memory::Address_at(fp() + StandardFrameConstants::kCallerFPOffset) =
+ caller_fp;
+}
+
+
bool StandardFrame::IsExpressionInsideHandler(int n) const {
Address address = GetExpressionAddress(n);
for (StackHandlerIterator it(this, top_handler()); !it.done(); it.Advance()) {
}
+#define DEFINE_WRAPPER(type, field) \
+class field##_Wrapper : public ZoneObject { \
+ public: /* NOLINT */ \
+ field##_Wrapper(const field& original) : frame_(original) { \
+ } \
+ field frame_; \
+};
+STACK_FRAME_TYPE_LIST(DEFINE_WRAPPER)
+#undef DEFINE_WRAPPER
+
+static StackFrame* AllocateFrameCopy(StackFrame* frame) {
+#define FRAME_TYPE_CASE(type, field) \
+ case StackFrame::type: { \
+ field##_Wrapper* wrapper = \
+ new field##_Wrapper(*(reinterpret_cast<field*>(frame))); \
+ return &wrapper->frame_; \
+ }
+
+ switch (frame->type()) {
+ STACK_FRAME_TYPE_LIST(FRAME_TYPE_CASE)
+ default: UNREACHABLE();
+ }
+#undef FRAME_TYPE_CASE
+ return NULL;
+}
+
+Vector<StackFrame*> CreateStackMap() {
+ ZoneList<StackFrame*> list(10);
+ for (StackFrameIterator it; !it.done(); it.Advance()) {
+ StackFrame* frame = AllocateFrameCopy(it.frame());
+ list.Add(frame);
+ }
+ return list.ToVector();
+}
+
+
} } // namespace v8::internal
// by the debugger.
enum Id { NO_ID = 0 };
+ // Copy constructor; it breaks the connection to host iterator.
+ StackFrame(const StackFrame& original) {
+ this->state_ = original.state_;
+ this->iterator_ = NULL;
+ }
+
// Type testers.
bool is_entry() const { return type() == ENTRY; }
bool is_entry_construct() const { return type() == ENTRY_CONSTRUCT; }
Address pc() const { return *pc_address(); }
void set_pc(Address pc) { *pc_address() = pc; }
+ virtual void SetCallerFp(Address caller_fp) = 0;
+
Address* pc_address() const { return state_.pc_address; }
// Get the id of this stack frame.
friend class StackHandlerIterator;
friend class SafeStackFrameIterator;
- DISALLOW_IMPLICIT_CONSTRUCTORS(StackFrame);
+ private:
+ void operator=(const StackFrame& original);
};
ASSERT(frame->is_entry());
return static_cast<EntryFrame*>(frame);
}
+ virtual void SetCallerFp(Address caller_fp);
protected:
explicit EntryFrame(StackFrameIterator* iterator) : StackFrame(iterator) { }
// Garbage collection support.
virtual void Iterate(ObjectVisitor* v) const;
+ virtual void SetCallerFp(Address caller_fp);
+
static ExitFrame* cast(StackFrame* frame) {
ASSERT(frame->is_exit());
return static_cast<ExitFrame*>(frame);
inline void SetExpression(int index, Object* value);
int ComputeExpressionsCount() const;
+ virtual void SetCallerFp(Address caller_fp);
+
static StandardFrame* cast(StackFrame* frame) {
ASSERT(frame->is_standard());
return static_cast<StandardFrame*>(frame);
};
+// Reads all frames on the current stack and copies them into the current
+// zone memory.
+Vector<StackFrame*> CreateStackMap();
+
} } // namespace v8::internal
#endif // V8_FRAMES_H_
}
+void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
+ masm->ret(0);
+}
+
+// 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.
+// Frame structure (conforms InternalFrame structure):
+// -- JSFunction
+// -- code
+// -- SMI maker
+// -- context
+// -- frame base
+void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ // We do not know our frame height, but set esp based on ebp.
+ __ lea(esp, Operand(ebp, -4 * kPointerSize));
+
+ __ pop(edi); // function
+
+ // Skip code self-reference and marker.
+ __ add(Operand(esp), Immediate(2 * kPointerSize));
+
+ __ pop(esi); // Context.
+ __ pop(ebp);
+
+ // Get function code.
+ __ mov(edx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
+ __ mov(edx, FieldOperand(edx, SharedFunctionInfo::kCodeOffset));
+ __ lea(edx, FieldOperand(edx, Code::kHeaderSize));
+
+ // Re-run JSFunction, edi is function, esi is context.
+ __ jmp(Operand(edx));
+}
+
#undef __
+
+void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
+ Handle<Code> code) {
+ ASSERT(bottom_js_frame->is_java_script());
+
+ Address fp = bottom_js_frame->fp();
+ Memory::Object_at(fp - 4 * kPointerSize) =
+ Memory::Object_at(fp - 2 * kPointerSize); // Move edi (function).
+
+ Memory::Object_at(fp - 3 * kPointerSize) = *code;
+ Memory::Object_at(fp - 2 * kPointerSize) = Smi::FromInt(StackFrame::INTERNAL);
+}
+const int Debug::kFrameDropperFrameSize = 5;
+
+
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal
for (var i = 0; i < shared_wrapper_list.length; i++) {
shared_list[i] = shared_wrapper_list[i].info;
}
- var result = %LiveEditCheckStackActivations(shared_list);
+ var result = %LiveEditCheckAndDropActivations(shared_list, true);
+ if (result[shared_list.length]) {
+ // Extra array element may contain error message.
+ throw new liveedit.Failure(result[shared_list.length]);
+ }
+
var problems = new Array();
+ var dropped = new Array();
for (var i = 0; i < shared_list.length; i++) {
- if (result[i] == liveedit.FunctionPatchabilityStatus.FUNCTION_BLOCKED_ON_STACK) {
- var shared = shared_list[i];
+ var shared = shared_wrapper_list[i];
+ if (result[i] == liveedit.FunctionPatchabilityStatus.REPLACED_ON_ACTIVE_STACK) {
+ dropped.push({ name: shared.function_name } );
+ } else if (result[i] != liveedit.FunctionPatchabilityStatus.AVAILABLE_FOR_PATCH) {
var description = {
name: shared.function_name,
- start_pos: shared.start_position,
- end_pos: shared.end_position
+ start_pos: shared.start_position,
+ end_pos: shared.end_position,
+ replace_problem:
+ liveedit.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 liveedit.Failure("Blocked by functions on stack");
// A copy of the FunctionPatchabilityStatus enum from liveedit.h
Debug.LiveEditChangeScript.FunctionPatchabilityStatus = {
- FUNCTION_AVAILABLE_FOR_PATCH: 0,
- FUNCTION_BLOCKED_ON_STACK: 1
+ AVAILABLE_FOR_PATCH: 1,
+ BLOCKED_ON_ACTIVE_STACK: 2,
+ BLOCKED_ON_OTHER_STACK: 3,
+ BLOCKED_UNDER_NATIVE_CODE: 4,
+ REPLACED_ON_ACTIVE_STACK: 5
+}
+
+Debug.LiveEditChangeScript.FunctionPatchabilityStatus.SymbolName =
+ function(code) {
+ var enum = Debug.LiveEditChangeScript.FunctionPatchabilityStatus;
+ for (name in enum) {
+ if (enum[name] == code) {
+ return name;
+ }
+ }
}
#include "scopes.h"
#include "global-handles.h"
#include "debug.h"
+#include "memory.h"
namespace v8 {
namespace internal {
}
+// 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;
+ }
+ int len = Smi::cast(shared_info_array->length())->value();
+ for (int i = 0; i < len; i++) {
+ JSValue* wrapper = JSValue::cast(shared_info_array->GetElement(i));
+ Handle<SharedFunctionInfo> shared(
+ SharedFunctionInfo::cast(wrapper->value()));
+
+ if (frame->code() == shared->code()) {
+ SetElement(result, i, Handle<Smi>(Smi::FromInt(status)));
+ 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::get_address_from_id(Top::k_handler_address));
+
+ 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;
+}
+
+
+// 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) {
+ 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];
+
+ ASSERT(bottom_js_frame->is_java_script());
+
+ // Check the nature of the top frame.
+ if (pre_top_frame->code()->is_inline_cache_stub() &&
+ pre_top_frame->code()->ic_state() == DEBUG_BREAK) {
+ // OK, we can drop inline cache calls.
+ } else if (pre_top_frame->code() ==
+ Builtins::builtin(Builtins::FrameDropper_LiveEdit)) {
+ // OK, we can drop our own code.
+ } else if (pre_top_frame->code()->kind() == Code::STUB &&
+ pre_top_frame->code()->major_key()) {
+ // Unit Test entry, it's fine, we support this case.
+ } else {
+ return "Unknown structure of stack above changing function";
+ }
+
+ Address unused_stack_top = top_frame->sp();
+ Address unused_stack_bottom = bottom_js_frame->fp()
+ - Debug::kFrameDropperFrameSize * kPointerSize // Size of the new frame.
+ + kPointerSize; // Bigger address end is exclusive.
+
+ if (unused_stack_top > unused_stack_bottom) {
+ 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.
+ ASSERT(!FixTryCatchHandler(pre_top_frame, bottom_js_frame));
+
+ Handle<Code> code(Builtins::builtin(Builtins::FrameDropper_LiveEdit));
+ top_frame->set_pc(code->entry());
+ pre_top_frame->SetCallerFp(bottom_js_frame->fp());
+
+ Debug::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;
+}
+
+
+static bool IsDropableFrame(StackFrame* frame) {
+ return !frame->is_exit();
+}
+
+// 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> shared_info_array, Handle<JSArray> result, bool do_drop) {
+
+ ZoneScope scope(DELETE_ON_EXIT);
+ Vector<StackFrame*> frames = CreateStackMap();
+
+ int array_len = Smi::cast(shared_info_array->length())->value();
+
+ 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 (CheckActivation(shared_info_array, result, 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 NULL;
+ }
+
+ bool target_frame_found = false;
+ int bottom_js_frame_index = top_frame_index;
+ bool c_code_found = false;
+
+ for (; frame_index < frames.length(); frame_index++) {
+ StackFrame* frame = frames[frame_index];
+ if (!IsDropableFrame(frame)) {
+ c_code_found = true;
+ break;
+ }
+ if (CheckActivation(shared_info_array, result, frame,
+ LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
+ target_frame_found = true;
+ bottom_js_frame_index = frame_index;
+ }
+ }
+
+ if (c_code_found) {
+ // There is a C frames on stack. 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 (CheckActivation(shared_info_array, result, frame,
+ LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
+ // Cannot drop frame under C frames.
+ return NULL;
+ }
+ }
+ }
+ }
+
+ if (!do_drop) {
+ // We are in check-only mode.
+ return NULL;
+ }
+
+ if (!target_frame_found) {
+ // Nothing to drop.
+ return NULL;
+ }
+
+ const char* error_message = DropFrames(frames, top_frame_index,
+ bottom_js_frame_index);
+
+ 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);
+
+ // Replace "blocked on active" with "replaced on active" status.
+ for (int i = 0; i < array_len; i++) {
+ if (result->GetElement(i) ==
+ Smi::FromInt(LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
+ result->SetElement(i, Smi::FromInt(
+ LiveEdit::FUNCTION_REPLACED_ON_ACTIVE_STACK));
+ }
+ }
+ return NULL;
+}
+
+
+class InactiveThreadActivationsChecker : public ThreadVisitor {
+ public:
+ InactiveThreadActivationsChecker(Handle<JSArray> shared_info_array,
+ Handle<JSArray> result)
+ : shared_info_array_(shared_info_array), result_(result),
+ has_blocked_functions_(false) {
+ }
+ void VisitThread(ThreadLocalTop* top) {
+ for (StackFrameIterator it(top); !it.done(); it.Advance()) {
+ has_blocked_functions_ |= CheckActivation(
+ shared_info_array_, result_, it.frame(),
+ LiveEdit::FUNCTION_BLOCKED_ON_OTHER_STACK);
+ }
+ }
+ bool HasBlockedFunctions() {
+ return has_blocked_functions_;
+ }
+
+ private:
+ Handle<JSArray> shared_info_array_;
+ Handle<JSArray> result_;
+ bool has_blocked_functions_;
+};
+
+
+Handle<JSArray> LiveEdit::CheckAndDropActivations(
+ Handle<JSArray> shared_info_array, bool do_drop) {
+ int len = Smi::cast(shared_info_array->length())->value();
+
+ Handle<JSArray> result = Factory::NewJSArray(len);
+
+ // Fill the default values.
+ for (int i = 0; i < len; i++) {
+ SetElement(result, i,
+ Handle<Smi>(Smi::FromInt(FUNCTION_AVAILABLE_FOR_PATCH)));
+ }
+
+
+ // First check inactive threads. Fail if some functions are blocked there.
+ InactiveThreadActivationsChecker inactive_threads_checker(shared_info_array,
+ result);
+ ThreadManager::IterateThreads(&inactive_threads_checker);
+ if (inactive_threads_checker.HasBlockedFunctions()) {
+ return result;
+ }
+
+ // Try to drop activations from the current stack.
+ const char* error_message =
+ DropActivationsInActiveThread(shared_info_array, result, do_drop);
+ if (error_message != NULL) {
+ // Add error message as an array extra element.
+ Vector<const char> vector_message(error_message, strlen(error_message));
+ Handle<String> str = Factory::NewStringFromAscii(vector_message);
+ SetElement(result, len, str);
+ }
+ return result;
+}
+
+
LiveEditFunctionTracker::LiveEditFunctionTracker(FunctionLiteral* fun) {
if (active_function_info_listener != NULL) {
active_function_info_listener->FunctionStarted(fun);
static void PatchFunctionPositions(Handle<JSArray> shared_info_array,
Handle<JSArray> position_change_array);
+ // 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> shared_info_array, bool do_drop);
+
// A copy of this is in liveedit-debugger.js.
enum FunctionPatchabilityStatus {
- FUNCTION_AVAILABLE_FOR_PATCH = 0,
- FUNCTION_BLOCKED_ON_STACK = 1
+ 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
};
};
}
+void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
+ masm->Abort("LiveEdit frame dropping is not supported on mips");
+}
+
+void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ masm->Abort("LiveEdit frame dropping is not supported on mips");
+}
+
#undef __
+
+void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
+ Handle<Code> code) {
+ UNREACHABLE();
+}
+const int Debug::kFrameDropperFrameSize = -1;
+
+
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal
}
-static LiveEdit::FunctionPatchabilityStatus FindFunctionCodeOnStacks(
- Handle<SharedFunctionInfo> shared) {
- // TODO(635): check all threads, not only the current one.
- for (StackFrameIterator it; !it.done(); it.Advance()) {
- StackFrame* frame = it.frame();
- if (frame->code() == shared->code()) {
- return LiveEdit::FUNCTION_BLOCKED_ON_STACK;
- }
- }
- return LiveEdit::FUNCTION_AVAILABLE_FOR_PATCH;
-}
-
// For array of SharedFunctionInfo's (each wrapped in JSValue)
// checks that none of them have activations on stacks (of any thread).
// Returns array of the same length with corresponding results of
// LiveEdit::FunctionPatchabilityStatus type.
-static Object* Runtime_LiveEditCheckStackActivations(Arguments args) {
- ASSERT(args.length() == 1);
+static Object* Runtime_LiveEditCheckAndDropActivations(Arguments args) {
+ ASSERT(args.length() == 2);
HandleScope scope;
CONVERT_ARG_CHECKED(JSArray, shared_array, 0);
+ CONVERT_BOOLEAN_CHECKED(do_drop, args[1]);
- int len = Smi::cast(shared_array->length())->value();
- Handle<JSArray> result = Factory::NewJSArray(len);
-
- for (int i = 0; i < len; i++) {
- JSValue* wrapper = JSValue::cast(shared_array->GetElement(i));
- Handle<SharedFunctionInfo> shared(
- SharedFunctionInfo::cast(wrapper->value()));
- LiveEdit::FunctionPatchabilityStatus check_res =
- FindFunctionCodeOnStacks(shared);
- SetElement(result, i, Handle<Smi>(Smi::FromInt(check_res)));
- }
-
- return *result;
+ return *LiveEdit::CheckAndDropActivations(shared_array, do_drop);
}
}
+// Calls specified function with or without entering the debugger.
+// This is used in unit tests to run code as if debugger is entered or simply
+// to have a stack with C++ frame in the middle.
+static Object* Runtime_ExecuteInDebugContext(Arguments args) {
+ ASSERT(args.length() == 2);
+ HandleScope scope;
+ CONVERT_ARG_CHECKED(JSFunction, function, 0);
+ CONVERT_BOOLEAN_CHECKED(without_debugger, args[1]);
+
+ Handle<Object> result;
+ bool pending_exception;
+ {
+ if (without_debugger) {
+ result = Execution::Call(function, Top::global(), 0, NULL,
+ &pending_exception);
+ } else {
+ EnterDebugger enter_debugger;
+ result = Execution::Call(function, Top::global(), 0, NULL,
+ &pending_exception);
+ }
+ }
+ if (!pending_exception) {
+ return *result;
+ } else {
+ return Failure::Exception();
+ }
+}
+
+
#endif // ENABLE_DEBUGGER_SUPPORT
#ifdef ENABLE_LOGGING_AND_PROFILING
F(LiveEditReplaceFunctionCode, 2, 1) \
F(LiveEditRelinkFunctionToScript, 2, 1) \
F(LiveEditPatchFunctionPositions, 2, 1) \
- F(LiveEditCheckStackActivations, 1, 1) \
- F(GetFunctionCodePositionFromSource, 2, 1)
+ F(LiveEditCheckAndDropActivations, 2, 1) \
+ F(GetFunctionCodePositionFromSource, 2, 1) \
+ F(ExecuteInDebugContext, 2, 1)
#else
#define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F)
#endif
}
+void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
+ masm->Abort("LiveEdit frame dropping is not supported on x64");
+}
+
+void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
+ masm->Abort("LiveEdit frame dropping is not supported on x64");
+}
+
#undef __
+void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
+ Handle<Code> code) {
+ UNREACHABLE();
+}
+const int Debug::kFrameDropperFrameSize = -1;
+
+
void BreakLocationIterator::ClearDebugBreakAtReturn() {
rinfo()->PatchCode(original_rinfo()->pc(),
Assembler::kJSReturnSequenceLength);
Debug = debug.Debug
-eval(
- "function ChooseAnimal(callback) {\n " +
- " callback();\n" +
- " return 'Cat';\n" +
- "}\n"
-);
+unique_id = 1;
-function Noop() {}
-var res = ChooseAnimal(Noop);
+function TestBase(name) {
+ print("TestBase constructor: " + name);
-assertEquals("Cat", res);
+ this.ChooseAnimal = eval(
+ "/* " + unique_id + "*/\n" +
+ "(function ChooseAnimal(callback) {\n " +
+ " callback();\n" +
+ " return 'Cat';\n" +
+ "})\n"
+ );
+ // Prevents eval script caching.
+ unique_id++;
-var script = Debug.findScript(ChooseAnimal);
+ var script = Debug.findScript(this.ChooseAnimal);
-var orig_animal = "'Cat'";
-var patch_pos = script.source.indexOf(orig_animal);
-var new_animal_patch = "'Capybara'";
+ var orig_animal = "'Cat'";
+ var patch_pos = script.source.indexOf(orig_animal);
+ var new_animal_patch = "'Capybara'";
-var got_exception = false;
-var successfully_changed = false;
+ var got_exception = false;
+ var successfully_changed = false;
-function Changer() {
- // Never try the same patch again.
- assertEquals(false, successfully_changed);
- var change_log = new Array();
- try {
- Debug.LiveEditChangeScript(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
+ // Should be called from Debug context.
+ this.ScriptChanger = function() {
+ assertEquals(false, successfully_changed, "applying patch second time");
+ // Runs in debugger context.
+ var change_log = new Array();
+ try {
+ Debug.LiveEditChangeScript(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
+ } finally {
+ print("Change log: " + JSON.stringify(change_log) + "\n");
+ }
successfully_changed = true;
- } catch (e) {
- if (e instanceof Debug.LiveEditChangeScript.Failure) {
- got_exception = true;
- print(e);
- } else {
- throw e;
+ };
+}
+
+function Noop() {}
+
+function WrapInCatcher(f, holder) {
+ return function() {
+ delete holder[0];
+ try {
+ f();
+ } catch (e) {
+ if (e instanceof Debug.LiveEditChangeScript.Failure) {
+ holder[0] = e;
+ } else {
+ throw e;
+ }
+ }
+ };
+}
+
+function WrapInNativeCall(f) {
+ return function() {
+ return Debug.ExecuteInDebugContext(f, true);
+ };
+}
+
+function WrapInDebuggerCall(f) {
+ return function() {
+ return Debug.ExecuteInDebugContext(f, false);
+ };
+}
+
+function WrapInRestartProof(f) {
+ var already_called = false;
+ return function() {
+ if (already_called) {
+ return;
}
+ already_called = true;
+ f();
+ }
+}
+
+function WrapInConstructor(f) {
+ return function() {
+ return new function() {
+ f();
+ };
}
- print("Change log: " + JSON.stringify(change_log) + "\n");
}
-var new_res = ChooseAnimal(Changer);
-// Function must be not pached.
-assertEquals("Cat", new_res);
-assertEquals(true, got_exception);
+// A series of tests. In each test we call ChooseAnimal function that calls
+// a callback that attempts to modify the function on the fly.
+
+test = new TestBase("First test ChooseAnimal without edit");
+assertEquals("Cat", test.ChooseAnimal(Noop));
+
+test = new TestBase("Test without function on stack");
+test.ScriptChanger();
+assertEquals("Capybara", test.ChooseAnimal(Noop));
+
+test = new TestBase("Test with function on stack");
+assertEquals("Capybara", test.ChooseAnimal(WrapInDebuggerCall(WrapInRestartProof(test.ScriptChanger))));
+
-// This time it should succeed.
-Changer();
+test = new TestBase("Test with function on stack and with constructor frame");
+assertEquals("Capybara", test.ChooseAnimal(WrapInConstructor(WrapInDebuggerCall(WrapInRestartProof(test.ScriptChanger)))));
-new_res = ChooseAnimal(Noop);
-// Function must be not pached.
-assertEquals("Capybara", new_res);
+test = new TestBase("Test with C++ frame above ChooseAnimal frame");
+exception_holder = {};
+assertEquals("Cat", test.ChooseAnimal(WrapInNativeCall(WrapInDebuggerCall(WrapInCatcher(test.ScriptChanger, exception_holder)))));
+assertTrue(!!exception_holder[0]);
# Skip long running test in debug and allow it to timeout in release mode.
regress/regress-524: (PASS || TIMEOUT), SKIP if $mode == debug
+# Skip experimental liveedit drop frame on non-ia32 architectures.
+# debug-liveedit-check-stack: SKIP if $arch != ia32
+debug-liveedit-check-stack: SKIP
+
[ $arch == arm ]
# Slow tests which times out in debug mode.