Added Error.captureStackTrace function.

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