Revision: 4069
Author: [email protected]
Date: Tue Mar  9 13:45:24 2010
Log: Check that function being patched has no activations on any thread stack

Review URL: http://codereview.chromium.org/668246
http://code.google.com/p/v8/source/detail?r=4069

Added:
 /branches/bleeding_edge/test/mjsunit/debug-liveedit-check-stack.js
Modified:
 /branches/bleeding_edge/src/debug-delay.js
 /branches/bleeding_edge/src/liveedit-delay.js
 /branches/bleeding_edge/src/liveedit.cc
 /branches/bleeding_edge/src/liveedit.h
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/src/runtime.h
 /branches/bleeding_edge/test/mjsunit/fuzz-natives.js

=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/debug-liveedit-check-stack.js Tue Mar 9 13:45:24 2010
@@ -0,0 +1,84 @@
+// Copyright 2010 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --expose-debug-as debug
+// Get the Debug object exposed from the debug context global object.
+
+Debug = debug.Debug
+
+eval(
+    "function ChooseAnimal(callback) {\n " +
+    "  callback();\n" +
+    "  return 'Cat';\n" +
+    "}\n"
+);
+
+function Noop() {}
+var res = ChooseAnimal(Noop);
+
+assertEquals("Cat", res);
+
+var script = Debug.findScript(ChooseAnimal);
+
+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;
+
+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);
+    successfully_changed = true;
+  } catch (e) {
+    if (e instanceof Debug.LiveEditChangeScript.Failure) {
+      got_exception = true;
+      print(e);
+    } else {
+      throw e;
+    }
+  }
+  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);
+
+// This time it should succeed.
+Changer();
+
+new_res = ChooseAnimal(Noop);
+// Function must be not pached.
+assertEquals("Capybara", new_res);
+
=======================================
--- /branches/bleeding_edge/src/debug-delay.js  Fri Mar  5 14:08:58 2010
+++ /branches/bleeding_edge/src/debug-delay.js  Tue Mar  9 13:45:24 2010
@@ -1986,9 +1986,18 @@
   }

   var change_log = new Array();
- Debug.LiveEditChangeScript(the_script, change_pos, change_len, new_string,
-                             change_log);
-
+  try {
+ Debug.LiveEditChangeScript(the_script, change_pos, change_len, new_string,
+                               change_log);
+  } catch (e) {
+    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 } );
+    } else {
+      throw e;
+    }
+  }
   response.body = {change_log: change_log};
 };

=======================================
--- /branches/bleeding_edge/src/liveedit-delay.js       Fri Mar  5 14:08:58 2010
+++ /branches/bleeding_edge/src/liveedit-delay.js       Tue Mar  9 13:45:24 2010
@@ -47,39 +47,9 @@
Debug.LiveEditChangeScript = function(script, change_pos, change_len, new_str,
     change_log) {

-  function Assert(condition, message) {
-    if (!condition) {
-      if (message) {
-        throw "Assert " + message;
-      } else {
-        throw "Assert";
-      }
-    }
-  }
-
-  // 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.scope_info = raw_array[5];
-    this.outer_index = raw_array[6];
-    this.next_sibling_index = null;
-    this.raw_array = raw_array;
-  }
-
-  // A structure describing SharedFunctionInfo.
-  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;
-  }
-
+  // So far the function works as namespace.
+  var liveedit = Debug.LiveEditChangeScript;
+  var Assert = liveedit.Assert;

   // Fully compiles source string as a script. Returns Array of
   // FunctionCompileInfo -- a descriptions of all functions of the script.
@@ -98,7 +68,7 @@
     var compile_info = new Array();
     var old_index_map = new Array();
     for (var i = 0; i < raw_compile_info.length; i++) {
-        compile_info.push(new FunctionCompileInfo(raw_compile_info[i]));
+ compile_info.push(new liveedit.FunctionCompileInfo(raw_compile_info[i]));
         old_index_map.push(i);
     }

@@ -169,30 +139,6 @@
     return index;
   }

-  // Compares a function interface old and new version, whether it
-  // changed or not.
-  function CompareFunctionExpectations(function_info1, function_info2) {
- // Check that function has the same number of parameters (there may exist
-    // an adapter, that won't survive function parameter number change).
-    if (function_info1.param_num != function_info2.param_num) {
-      return false;
-    }
-    var scope_info1 = function_info1.scope_info;
-    var scope_info2 = function_info2.scope_info;
-
-    if (!scope_info1) {
-      return !scope_info2;
-    }
-
-    if (scope_info1.length != scope_info2.length) {
-      return false;
-    }
-
- // Check that outer scope structure is not changed. Otherwise the function
-    // will not properly work with existing scopes.
-    return scope_info1.toString() == scope_info2.toString();
-  }
-
   // Variable forward declarations. Preprocessor "Minifier" needs them.
   var old_compile_info;
   var shared_infos;
@@ -265,6 +211,12 @@

   // Find all SharedFunctionInfo's that are compiled from this script.
   var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
+
+  var shared_infos = new Array();
+
+  for (var i = 0; i < shared_raw_list.length; i++) {
+    shared_infos.push(new liveedit.SharedInfoWrapper(shared_raw_list[i]));
+  }

   // Gather compile information about old version of script.
   var old_compile_info = DebugGatherCompileInfo(old_source);
@@ -274,12 +226,13 @@
   try {
     new_compile_info = DebugGatherCompileInfo(new_source);
   } catch (e) {
-    throw "Failed to compile new version of script: " + e;
+ throw new liveedit.Failure("Failed to compile new version of script: " + e);
   }

// An index of a single function, that is going to have its code replaced.
   var function_being_patched =
-      FindChangedFunction(old_compile_info, change_pos, change_len_old);
+      FindChangedFunction(old_compile_info, change_pos, change_len_old);
+
   // In old and new script versions function with a change should have the
   // same indexes.
   var function_being_patched2 =
@@ -290,7 +243,8 @@
   // Check that function being patched has the same expectations in a new
   // version. Otherwise we cannot safely patch its behavior and should
   // choose the outer function instead.
- while (!CompareFunctionExpectations(old_compile_info[function_being_patched],
+  while (!liveedit.CompareFunctionExpectations(
+      old_compile_info[function_being_patched],
       new_compile_info[function_being_patched])) {

     Assert(old_compile_info[function_being_patched].outer_index ==
@@ -300,21 +254,17 @@
     Assert(function_being_patched != -1);
   }

-  // TODO: Need to check here that there are no activations of the function
-  // being patched on stack.
+  // Check that function being patched is not currently on stack.
+  liveedit.CheckStackActivations(
+      [ FindFunctionInfo(function_being_patched) ], change_log );
+

   // Committing all changes.
-  var old_script_name = script.name + " (old)";
+  var old_script_name = liveedit.CreateNameForOldScript(script);

   // Update the script text and create a new script representing an old
   // version of the script.
var old_script = %LiveEditReplaceScript(script, new_source, old_script_name);
-
-  var shared_infos = new Array();
-
-  for (var i = 0; i < shared_raw_list.length; i++) {
-    shared_infos.push(new SharedInfoWrapper(shared_raw_list[i]));
-  }

   PatchCode(new_compile_info[function_being_patched],
       FindFunctionInfo(function_being_patched));
@@ -364,3 +314,113 @@
   }
 }

+Debug.LiveEditChangeScript.Assert = function(condition, message) {
+  if (!condition) {
+    if (message) {
+      throw "Assert " + message;
+    } else {
+      throw "Assert";
+    }
+  }
+}
+
+// An object describing function compilation details. Its index fields
+// apply to indexes inside array that stores these objects.
+Debug.LiveEditChangeScript.FunctionCompileInfo = function(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.scope_info = raw_array[5];
+  this.outer_index = raw_array[6];
+  this.next_sibling_index = null;
+  this.raw_array = raw_array;
+}
+
+// A structure describing SharedFunctionInfo.
+Debug.LiveEditChangeScript.SharedInfoWrapper = function(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;
+}
+
+// Adds a suffix to script name to mark that it is old version.
+Debug.LiveEditChangeScript.CreateNameForOldScript = function(script) {
+  // TODO(635): try better than this; support several changes.
+  return script.name + " (old)";
+}
+
+// Compares a function interface old and new version, whether it
+// changed or not.
+Debug.LiveEditChangeScript.CompareFunctionExpectations =
+    function(function_info1, function_info2) {
+  // Check that function has the same number of parameters (there may exist
+  // an adapter, that won't survive function parameter number change).
+  if (function_info1.param_num != function_info2.param_num) {
+    return false;
+  }
+  var scope_info1 = function_info1.scope_info;
+  var scope_info2 = function_info2.scope_info;
+
+  if (!scope_info1) {
+    return !scope_info2;
+  }
+
+  if (scope_info1.length != scope_info2.length) {
+    return false;
+  }
+
+ // Check that outer scope structure is not changed. Otherwise the function
+  // will not properly work with existing scopes.
+  return scope_info1.toString() == scope_info2.toString();
+}
+
+// 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.
+Debug.LiveEditChangeScript.CheckStackActivations = function(shared_wrapper_list,
+                                                            change_log) {
+  var liveedit = Debug.LiveEditChangeScript;
+
+  var shared_list = new Array();
+  for (var i = 0; i < shared_wrapper_list.length; i++) {
+    shared_list[i] = shared_wrapper_list[i].info;
+  }
+  var result = %LiveEditCheckStackActivations(shared_list);
+  var problems = 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 description = {
+          name: shared.function_name,
+          start_pos: shared.start_position,
+          end_pos: shared.end_position
+      };
+      problems.push(description);
+    }
+  }
+  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
+}
+
+
+// A logical failure in liveedit process. This means that change_log
+// is valid and consistent description of what happened.
+Debug.LiveEditChangeScript.Failure = function(message) {
+  this.message = message;
+}
+
+Debug.LiveEditChangeScript.Failure.prototype.toString = function() {
+  return "LiveEdit Failure: " + this.message;
+}
=======================================
--- /branches/bleeding_edge/src/liveedit.cc     Fri Mar  5 17:21:34 2010
+++ /branches/bleeding_edge/src/liveedit.cc     Tue Mar  9 13:45:24 2010
@@ -320,9 +320,9 @@
   CompilationZoneScope zone_scope(DELETE_ON_EXIT);

   FunctionInfoListener listener;
+  Handle<Object> original_source = Handle<Object>(script->source());
   script->set_source(*source);
   active_function_info_listener = &listener;
-  Handle<Object> original_source = Handle<Object>(script->source());
   CompileScriptForTracker(script);
   active_function_info_listener = NULL;
   script->set_source(*original_source);
=======================================
--- /branches/bleeding_edge/src/liveedit.h      Fri Mar  5 14:08:58 2010
+++ /branches/bleeding_edge/src/liveedit.h      Tue Mar  9 13:45:24 2010
@@ -90,6 +90,12 @@

   static void PatchFunctionPositions(Handle<JSArray> shared_info_array,
Handle<JSArray> position_change_array);
+
+  // A copy of this is in liveedit-delay.js.
+  enum FunctionPatchabilityStatus {
+    FUNCTION_AVAILABLE_FOR_PATCH = 0,
+    FUNCTION_BLOCKED_ON_STACK = 1
+  };
 };

 #endif  // ENABLE_DEBUGGER_SUPPORT
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Fri Mar  5 14:08:58 2010
+++ /branches/bleeding_edge/src/runtime.cc      Tue Mar  9 13:45:24 2010
@@ -8441,6 +8441,44 @@

   return Heap::undefined_value();
 }
+
+
+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);
+  HandleScope scope;
+  CONVERT_ARG_CHECKED(JSArray, shared_array, 0);
+
+
+  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;
+}


 #endif  // ENABLE_DEBUGGER_SUPPORT
=======================================
--- /branches/bleeding_edge/src/runtime.h       Fri Mar  5 14:08:58 2010
+++ /branches/bleeding_edge/src/runtime.h       Tue Mar  9 13:45:24 2010
@@ -331,7 +331,8 @@
   F(LiveEditReplaceScript, 3, 1) \
   F(LiveEditReplaceFunctionCode, 2, 1) \
   F(LiveEditRelinkFunctionToScript, 2, 1) \
-  F(LiveEditPatchFunctionPositions, 2, 1)
+  F(LiveEditPatchFunctionPositions, 2, 1) \
+  F(LiveEditCheckStackActivations, 1, 1)
 #else
 #define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F)
 #endif
=======================================
--- /branches/bleeding_edge/test/mjsunit/fuzz-natives.js Fri Mar 5 17:21:34 2010 +++ /branches/bleeding_edge/test/mjsunit/fuzz-natives.js Tue Mar 9 13:45:24 2010
@@ -158,7 +158,8 @@
   "LiveEditReplaceScript": true,
   "LiveEditReplaceFunctionCode": true,
   "LiveEditRelinkFunctionToScript": true,
-  "LiveEditPatchFunctionPositions": true
+  "LiveEditPatchFunctionPositions": true,
+  "LiveEditCheckStackActivations": true
 };

 var currentlyUncallable = {

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to