Index: src/liveedit.cc |
diff --git a/src/liveedit.cc b/src/liveedit.cc |
index 55308ab67ce4d1043046618351b57295c929e640..0e836e5f500333b9944eb30fb7c4b83491ca2a58 100644 |
--- a/src/liveedit.cc |
+++ b/src/liveedit.cc |
@@ -34,6 +34,7 @@ |
#include "scopes.h" |
#include "global-handles.h" |
#include "debug.h" |
+#include "memory.h" |
namespace v8 { |
namespace internal { |
@@ -673,6 +674,272 @@ void LiveEdit::PatchFunctionPositions(Handle<JSArray> shared_info_array, |
} |
+// 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); |