LiveEdit: implement frame dropping
authorpeter.rybin@gmail.com <peter.rybin@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 6 Apr 2010 17:58:28 +0000 (17:58 +0000)
committerpeter.rybin@gmail.com <peter.rybin@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 6 Apr 2010 17:58:28 +0000 (17:58 +0000)
Review URL: http://codereview.chromium.org/1118007

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4351 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

18 files changed:
src/arm/debug-arm.cc
src/builtins.cc
src/builtins.h
src/debug-debugger.js
src/debug.cc
src/debug.h
src/frames.cc
src/frames.h
src/ia32/debug-ia32.cc
src/liveedit-debugger.js
src/liveedit.cc
src/liveedit.h
src/mips/debug-mips.cc
src/runtime.cc
src/runtime.h
src/x64/debug-x64.cc
test/mjsunit/debug-liveedit-check-stack.js
test/mjsunit/mjsunit.status

index bc81b19d21acbe82aed2300ca764f66feabb83dd..1aca7cee44c6a74774e9af24ada5777988f36512 100644 (file)
@@ -216,8 +216,23 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
 }
 
 
+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
index 69f6413108b8907d58b4bf818d2ef9f6a729a5b3..767820acb8cccba53a88ea01fe05ef90eed3a5c0 100644 (file)
@@ -1392,6 +1392,14 @@ static void Generate_Return_DebugBreak(MacroAssembler* masm) {
 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, };
index 1378c5455c6049cd78f216add18de5b26559da4a..ccb6c0c5338a73072a45660ed5bb617f39932ba2 100644 (file)
@@ -126,7 +126,9 @@ enum BuiltinExtraArguments {
   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
index cf949fc59a7f30193153ce75b852bd59932848d7..d9e0ecdd56b59f6d3a84057c84e64b8ad2bbe13d 100644 (file)
@@ -474,6 +474,11 @@ Debug.disassembleConstructor = function(f) {
   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);
@@ -2010,7 +2015,7 @@ DebugCommandProcessor.prototype.changeLiveRequest_ = function(request, response)
     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;
     }
index 4dce4cff053ff630fba8a2f9db2962fbe55e6608..bac05a06c4446aefbbc3a915a499e77fc7fc3d2b 100644 (file)
@@ -814,6 +814,8 @@ Object* Debug::Break(Arguments args) {
   HandleScope scope;
   ASSERT(args.length() == 0);
 
+  thread_local_.frames_are_dropped_ = false;
+
   // Get the top-most JavaScript frame.
   JavaScriptFrameIterator it;
   JavaScriptFrame* frame = it.frame();
@@ -890,8 +892,13 @@ Object* Debug::Break(Arguments args) {
     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();
 }
@@ -1655,6 +1662,12 @@ void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
 }
 
 
+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();
 }
index 546512b11306ace0f9c576e514c619f71eb5c960..a0e8b818e7a6cf1e88ab356eab28ba6065cd8c21 100644 (file)
@@ -377,10 +377,18 @@ class Debug {
   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();
@@ -446,6 +454,9 @@ class Debug {
     // 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_;
 
index 5e81a54d8dad81e9ccb4282ce17a6cb5a324130c..9cf83c91cfcb5fe8811cfe59509d8a0b1569edf4 100644 (file)
@@ -382,6 +382,12 @@ void EntryFrame::ComputeCallerState(State* state) const {
 }
 
 
+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);
@@ -414,6 +420,11 @@ void ExitFrame::ComputeCallerState(State* state) const {
 }
 
 
+void ExitFrame::SetCallerFp(Address caller_fp) {
+  Memory::Address_at(fp() + ExitFrameConstants::kCallerFPOffset) = caller_fp;
+}
+
+
 Address ExitFrame::GetCallerStackPointer() const {
   return fp() + ExitFrameConstants::kCallerSPDisplacement;
 }
@@ -443,6 +454,12 @@ void StandardFrame::ComputeCallerState(State* state) const {
 }
 
 
+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()) {
@@ -767,4 +784,40 @@ int JSCallerSavedCode(int n) {
 }
 
 
+#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
index 8cbbc6267978b5929277e97787a684ee3167dcb0..98aaead28bd16a19dbb14fc4c5435008c31b85ca 100644 (file)
@@ -114,6 +114,12 @@ class StackFrame BASE_EMBEDDED {
   // 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; }
@@ -132,6 +138,8 @@ class StackFrame BASE_EMBEDDED {
   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.
@@ -200,7 +208,8 @@ class StackFrame BASE_EMBEDDED {
   friend class StackHandlerIterator;
   friend class SafeStackFrameIterator;
 
-  DISALLOW_IMPLICIT_CONSTRUCTORS(StackFrame);
+ private:
+  void operator=(const StackFrame& original);
 };
 
 
@@ -218,6 +227,7 @@ class EntryFrame: public StackFrame {
     ASSERT(frame->is_entry());
     return static_cast<EntryFrame*>(frame);
   }
+  virtual void SetCallerFp(Address caller_fp);
 
  protected:
   explicit EntryFrame(StackFrameIterator* iterator) : StackFrame(iterator) { }
@@ -268,6 +278,8 @@ class ExitFrame: public StackFrame {
   // 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);
@@ -303,6 +315,8 @@ class StandardFrame: public StackFrame {
   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);
@@ -658,6 +672,10 @@ class StackFrameLocator BASE_EMBEDDED {
 };
 
 
+// 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_
index 5d18a0354e81639b39ff9c0ed394b03828eb5452..d142b11cf7b715d1d0039031e5a86ed3fd5ac181 100644 (file)
@@ -206,8 +206,58 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
 }
 
 
+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
index 2cbf0d5f259364c5ef7d593bf84e2eca24ae9a48..75effb227ff933cff1373f8c2c7e4ea36cff5102 100644 (file)
@@ -389,19 +389,32 @@ Debug.LiveEditChangeScript.CheckStackActivations = function(shared_wrapper_list,
   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");
@@ -410,8 +423,21 @@ Debug.LiveEditChangeScript.CheckStackActivations = function(shared_wrapper_list,
 
 // 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;
+    }
+  }      
 }
 
 
index 55308ab67ce4d1043046618351b57295c929e640..0e836e5f500333b9944eb30fb7c4b83491ca2a58 100644 (file)
@@ -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);
index 2a9cc628a1760405bbe950220388e1fab3c9e356..bd4c1ee85a3f09139b7d3e8917baaea1ea35f94e 100644 (file)
@@ -91,10 +91,21 @@ class LiveEdit : AllStatic {
   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
   };
 };
 
index 772bcc0138ee5d0f42878074f938d47e3fd293a1..cdb35ae3d0d117ed5a0999cc8d0484710142c1c2 100644 (file)
@@ -104,8 +104,24 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
 }
 
 
+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
index 40b37d96e1dcb4405cb0bf0b94ad5b1997e53686..fe4ad9518d3f58bc36cbbd4aac5f87df8b5b996f 100644 (file)
@@ -9693,41 +9693,18 @@ static Object* Runtime_LiveEditPatchFunctionPositions(Arguments args) {
 }
 
 
-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);
 }
 
 
@@ -9761,6 +9738,35 @@ static Object* Runtime_GetFunctionCodePositionFromSource(Arguments args) {
 }
 
 
+// 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
index c079345ef91fb8a22f63b1d2da059a0f067a7686..896a8f763982f076f6a5e8b26a2c1fd47d8430f4 100644 (file)
@@ -334,8 +334,9 @@ namespace internal {
   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
index 261b16c0196054eee6624538cb961b068aeda844..5470912a3def49b00105f41006a5fbd5759d7d55 100644 (file)
@@ -177,9 +177,24 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
 }
 
 
+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);
index 1d788be86e0762e162d0ea2d3002c3563e7ca79e..6b16490d7121b58aae524443475aeac9f7ce6c14 100644 (file)
 
 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]);
 
index 7cb2416f0cba8d0314e97307b9e702f5f203d750..47963fe6412311f19f70099184da8c520c26f77c 100644 (file)
@@ -48,6 +48,10 @@ unicode-case-overoptimization: PASS, TIMEOUT if ($arch == arm)
 # 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.