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) {
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 = '';
......
......@@ -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) {
......
......@@ -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();
......
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