Added Error.captureStackTrace function.
authorchristian.plesner.hansen@gmail.com <christian.plesner.hansen@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 27 Jul 2009 12:01:32 +0000 (12:01 +0000)
committerchristian.plesner.hansen@gmail.com <christian.plesner.hansen@gmail.com@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 27 Jul 2009 12:01:32 +0000 (12:01 +0000)
Added utility function for capturing stack traces so that efficient
stack trace collection works for custom errors too, not just built-in
ones.

Review URL: http://codereview.chromium.org/159403

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

src/messages.js
src/runtime.cc
test/mjsunit/stack-traces.js

index 870c969..fd505ff 100644 (file)
@@ -561,20 +561,24 @@ function GetStackTraceLine(recv, fun, pos, isGlobal) {
 var kAddMessageAccessorsMarker = { };
 
 // Defines accessors for a property that is calculated the first time
-// the property is read and then replaces the accessor with the value.
-// Also, setting the property causes the accessors to be deleted.
+// the property is read.
 function DefineOneShotAccessor(obj, name, fun) {
   // Note that the accessors consistently operate on 'obj', not 'this'.
   // Since the object may occur in someone else's prototype chain we
   // can't rely on 'this' being the same as 'obj'.
+  var hasBeenSet = false;
+  var value;
   obj.__defineGetter__(name, function () {
-    var value = fun(obj);
-    obj[name] = value;
+    if (hasBeenSet) {
+      return value;
+    }
+    hasBeenSet = true;
+    value = fun(obj);
     return value;
   });
   obj.__defineSetter__(name, function (v) {
-    delete obj[name];
-    obj[name] = v;
+    hasBeenSet = true;
+    value = v;
   });
 }
 
@@ -833,22 +837,25 @@ function DefineError(f) {
       } else if (!IS_UNDEFINED(m)) {
         this.message = ToString(m);
       }
-      var stackTraceLimit = $Error.stackTraceLimit;
-      if (stackTraceLimit) {
-        // Cap the limit to avoid extremely big traces
-        if (stackTraceLimit < 0 || stackTraceLimit > 10000)
-          stackTraceLimit = 10000;
-        var raw_stack = %CollectStackTrace(f, stackTraceLimit);
-        DefineOneShotAccessor(this, 'stack', function (obj) {
-          return FormatRawStackTrace(obj, raw_stack);
-        });
-      }
+      captureStackTrace(this, f);
     } else {
       return new f(m);
     }
   });
 }
 
+function captureStackTrace(obj, cons_opt) {
+  var stackTraceLimit = $Error.stackTraceLimit;
+  if (!stackTraceLimit) return;
+  if (stackTraceLimit < 0 || stackTraceLimit > 10000)
+    stackTraceLimit = 10000;
+  var raw_stack = %CollectStackTrace(cons_opt ? cons_opt : captureStackTrace,
+      stackTraceLimit);
+  DefineOneShotAccessor(obj, 'stack', function (obj) {
+    return FormatRawStackTrace(obj, raw_stack);
+  });
+};
+
 $Math.__proto__ = global.Object.prototype;
 
 DefineError(function Error() { });
@@ -859,6 +866,8 @@ DefineError(function ReferenceError() { });
 DefineError(function EvalError() { });
 DefineError(function URIError() { });
 
+$Error.captureStackTrace = captureStackTrace;
+
 // Setup extra properties of the Error.prototype object.
 $Error.prototype.message = '';
 
index 350d391..7f55389 100644 (file)
@@ -7408,14 +7408,15 @@ static bool ShowFrameInStackTrace(StackFrame* raw_frame, Object* caller,
   // Not sure when this can happen but skip it just in case.
   if (!raw_fun->IsJSFunction())
     return false;
-  if ((raw_fun == caller) && !(*seen_caller) && frame->IsConstructor()) {
+  if ((raw_fun == caller) && !(*seen_caller)) {
     *seen_caller = true;
     return false;
   }
-  // Skip the most obvious builtin calls.  Some builtin calls (such as
-  // Number.ADD which is invoked using 'call') are very difficult to
-  // recognize so we're leaving them in for now.
-  return !frame->receiver()->IsJSBuiltinsObject();
+  // Skip all frames until we've seen the caller.  Also, skip the most
+  // obvious builtin calls.  Some builtin calls (such as Number.ADD
+  // which is invoked using 'call') are very difficult to recognize
+  // so we're leaving them in for now.
+  return *seen_caller && !frame->receiver()->IsJSBuiltinsObject();
 }
 
 
@@ -7433,7 +7434,9 @@ static Object* Runtime_CollectStackTrace(Arguments args) {
   Handle<JSArray> result = Factory::NewJSArray(initial_size * 3);
 
   StackFrameIterator iter;
-  bool seen_caller = false;
+  // If the caller parameter is a function we skip frames until we're
+  // under it before starting to collect.
+  bool seen_caller = !caller->IsJSFunction();
   int cursor = 0;
   int frames_seen = 0;
   while (!iter.done() && frames_seen < limit) {
index e457ece..3bb5755 100644 (file)
@@ -84,9 +84,26 @@ function testAnonymousMethod() {
   (function () { FAIL }).call([1, 2, 3]);
 }
 
+function CustomError(message, stripPoint) {
+  this.message = message;
+  Error.captureStackTrace(this, stripPoint);
+}
+
+CustomError.prototype.toString = function () {
+  return "CustomError: " + this.message;
+};
+
+function testDefaultCustomError() {
+  throw new CustomError("hep-hey", undefined);
+}
+
+function testStrippedCustomError() {
+  throw new CustomError("hep-hey", CustomError);
+}
+
 // Utility function for testing that the expected strings occur
 // in the stack trace produced when running the given function.
-function testTrace(fun, expected) {
+function testTrace(fun, expected, unexpected) {
   var threw = false;
   try {
     fun();
@@ -94,6 +111,11 @@ function testTrace(fun, expected) {
     for (var i = 0; i < expected.length; i++) {
       assertTrue(e.stack.indexOf(expected[i]) != -1);
     }
+    if (unexpected) {
+      for (var i = 0; i < unexpected.length; i++) {
+        assertEquals(e.stack.indexOf(unexpected[i]), -1);
+      }
+    }
     threw = true;
   }
   assertTrue(threw);
@@ -165,6 +187,10 @@ testTrace(testValue, ["at Number.causeError"]);
 testTrace(testConstructor, ["new Plonk"]);
 testTrace(testRenamedMethod, ["Wookie.a$b$c$d [as d]"]);
 testTrace(testAnonymousMethod, ["Array.<anonymous>"]);
+testTrace(testDefaultCustomError, ["hep-hey", "new CustomError"],
+    ["collectStackTrace"]);
+testTrace(testStrippedCustomError, ["hep-hey"], ["new CustomError",
+    "collectStackTrace"]);
 
 testCallerCensorship();
 testUnintendedCallerCensorship();