Collect stack trace on stack overflow.
BUG=v8:2394 Review URL: https://chromiumcodereview.appspot.com/11275186 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12933 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
69846ce69d
commit
4c27298d27
115
src/isolate.cc
115
src/isolate.cc
@ -553,7 +553,97 @@ void Isolate::PushStackTraceAndDie(unsigned int magic,
|
||||
}
|
||||
|
||||
|
||||
void Isolate::CaptureAndSetCurrentStackTraceFor(Handle<JSObject> error_object) {
|
||||
// Determines whether the given stack frame should be displayed in
|
||||
// a stack trace. The caller is the error constructor that asked
|
||||
// for the stack trace to be collected. The first time a construct
|
||||
// call to this function is encountered it is skipped. The seen_caller
|
||||
// in/out parameter is used to remember if the caller has been seen
|
||||
// yet.
|
||||
static bool IsVisibleInStackTrace(StackFrame* raw_frame,
|
||||
Object* caller,
|
||||
bool* seen_caller) {
|
||||
// Only display JS frames.
|
||||
if (!raw_frame->is_java_script()) return false;
|
||||
JavaScriptFrame* frame = JavaScriptFrame::cast(raw_frame);
|
||||
Object* raw_fun = frame->function();
|
||||
// Not sure when this can happen but skip it just in case.
|
||||
if (!raw_fun->IsJSFunction()) return false;
|
||||
if ((raw_fun == caller) && !(*seen_caller)) {
|
||||
*seen_caller = true;
|
||||
return false;
|
||||
}
|
||||
// Skip all frames until we've seen the caller.
|
||||
if (!(*seen_caller)) return false;
|
||||
// Also, skip non-visible built-in functions and any call with the builtins
|
||||
// object as receiver, so as to not reveal either the builtins object or
|
||||
// an internal function.
|
||||
// The --builtins-in-stack-traces command line flag allows including
|
||||
// internal call sites in the stack trace for debugging purposes.
|
||||
if (!FLAG_builtins_in_stack_traces) {
|
||||
JSFunction* fun = JSFunction::cast(raw_fun);
|
||||
if (frame->receiver()->IsJSBuiltinsObject() ||
|
||||
(fun->IsBuiltin() && !fun->shared()->native())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
|
||||
Handle<Object> caller,
|
||||
int limit) {
|
||||
limit = Max(limit, 0); // Ensure that limit is not negative.
|
||||
int initial_size = Min(limit, 10);
|
||||
Handle<FixedArray> elements =
|
||||
factory()->NewFixedArrayWithHoles(initial_size * 4);
|
||||
|
||||
// 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;
|
||||
for (StackFrameIterator iter(this);
|
||||
!iter.done() && frames_seen < limit;
|
||||
iter.Advance()) {
|
||||
StackFrame* raw_frame = iter.frame();
|
||||
if (IsVisibleInStackTrace(raw_frame, *caller, &seen_caller)) {
|
||||
frames_seen++;
|
||||
JavaScriptFrame* frame = JavaScriptFrame::cast(raw_frame);
|
||||
// Set initial size to the maximum inlining level + 1 for the outermost
|
||||
// function.
|
||||
List<FrameSummary> frames(Compiler::kMaxInliningLevels + 1);
|
||||
frame->Summarize(&frames);
|
||||
for (int i = frames.length() - 1; i >= 0; i--) {
|
||||
if (cursor + 4 > elements->length()) {
|
||||
int new_capacity = JSObject::NewElementsCapacity(elements->length());
|
||||
Handle<FixedArray> new_elements =
|
||||
factory()->NewFixedArrayWithHoles(new_capacity);
|
||||
for (int i = 0; i < cursor; i++) {
|
||||
new_elements->set(i, elements->get(i));
|
||||
}
|
||||
elements = new_elements;
|
||||
}
|
||||
ASSERT(cursor + 4 <= elements->length());
|
||||
|
||||
Handle<Object> recv = frames[i].receiver();
|
||||
Handle<JSFunction> fun = frames[i].function();
|
||||
Handle<Code> code = frames[i].code();
|
||||
Handle<Smi> offset(Smi::FromInt(frames[i].offset()));
|
||||
elements->set(cursor++, *recv);
|
||||
elements->set(cursor++, *fun);
|
||||
elements->set(cursor++, *code);
|
||||
elements->set(cursor++, *offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
Handle<JSArray> result = factory()->NewJSArrayWithElements(elements);
|
||||
result->set_length(Smi::FromInt(cursor));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void Isolate::CaptureAndSetDetailedStackTrace(Handle<JSObject> error_object) {
|
||||
if (capture_stack_trace_for_uncaught_exceptions_) {
|
||||
// Capture stack trace for a detailed exception message.
|
||||
Handle<String> key = factory()->hidden_stack_trace_symbol();
|
||||
@ -910,15 +1000,28 @@ const char* const Isolate::kStackOverflowMessage =
|
||||
|
||||
Failure* Isolate::StackOverflow() {
|
||||
HandleScope scope;
|
||||
// At this point we cannot create an Error object using its javascript
|
||||
// constructor. Instead, we copy the pre-constructed boilerplate and
|
||||
// attach the stack trace as a hidden property.
|
||||
Handle<String> key = factory()->stack_overflow_symbol();
|
||||
Handle<JSObject> boilerplate =
|
||||
Handle<JSObject>::cast(GetProperty(js_builtins_object(), key));
|
||||
Handle<Object> exception = Copy(boilerplate);
|
||||
// TODO(1240995): To avoid having to call JavaScript code to compute
|
||||
// the message for stack overflow exceptions which is very likely to
|
||||
// double fault with another stack overflow exception, we use a
|
||||
// precomputed message.
|
||||
Handle<JSObject> exception = Copy(boilerplate);
|
||||
DoThrow(*exception, NULL);
|
||||
|
||||
// Get stack trace limit.
|
||||
Handle<Object> error = GetProperty(js_builtins_object(), "$Error");
|
||||
if (!error->IsJSObject()) return Failure::Exception();
|
||||
Handle<Object> stack_trace_limit =
|
||||
GetProperty(Handle<JSObject>::cast(error), "stackTraceLimit");
|
||||
if (!stack_trace_limit->IsNumber()) return Failure::Exception();
|
||||
int limit = static_cast<int>(stack_trace_limit->Number());
|
||||
|
||||
Handle<JSArray> stack_trace = CaptureSimpleStackTrace(
|
||||
exception, factory()->undefined_value(), limit);
|
||||
JSObject::SetHiddenProperty(exception,
|
||||
factory()->hidden_stack_trace_symbol(),
|
||||
stack_trace);
|
||||
return Failure::Exception();
|
||||
}
|
||||
|
||||
|
@ -716,7 +716,10 @@ class Isolate {
|
||||
int frame_limit,
|
||||
StackTrace::StackTraceOptions options);
|
||||
|
||||
void CaptureAndSetCurrentStackTraceFor(Handle<JSObject> error_object);
|
||||
Handle<JSArray> CaptureSimpleStackTrace(Handle<JSObject> error_object,
|
||||
Handle<Object> caller,
|
||||
int limit);
|
||||
void CaptureAndSetDetailedStackTrace(Handle<JSObject> error_object);
|
||||
|
||||
// Returns if the top context may access the given global object. If
|
||||
// the result is false, the pending exception is guaranteed to be
|
||||
|
@ -755,29 +755,6 @@ function GetStackTraceLine(recv, fun, pos, isGlobal) {
|
||||
// ----------------------------------------------------------------------------
|
||||
// Error implementation
|
||||
|
||||
// Defines accessors for a property that is calculated the first time
|
||||
// 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 value;
|
||||
var value_factory = fun;
|
||||
var getter = function() {
|
||||
if (value_factory == null) {
|
||||
return value;
|
||||
}
|
||||
value = value_factory(obj);
|
||||
value_factory = null;
|
||||
return value;
|
||||
};
|
||||
var setter = function(v) {
|
||||
value_factory = null;
|
||||
value = v;
|
||||
};
|
||||
%DefineOrRedefineAccessorProperty(obj, name, getter, setter, DONT_ENUM);
|
||||
}
|
||||
|
||||
function CallSite(receiver, fun, pos) {
|
||||
this.receiver = receiver;
|
||||
this.fun = fun;
|
||||
@ -1112,9 +1089,21 @@ function captureStackTrace(obj, cons_opt) {
|
||||
var raw_stack = %CollectStackTrace(obj,
|
||||
cons_opt ? cons_opt : captureStackTrace,
|
||||
stackTraceLimit);
|
||||
DefineOneShotAccessor(obj, 'stack', function (obj) {
|
||||
return FormatRawStackTrace(obj, raw_stack);
|
||||
});
|
||||
// Note that 'obj' and 'this' maybe different when called on objects that
|
||||
// have the error object on its prototype chain. The getter replaces itself
|
||||
// with a data property as soon as the stack trace has been formatted.
|
||||
var getter = function() {
|
||||
var value = FormatRawStackTrace(obj, raw_stack);
|
||||
%DefineOrRedefineDataProperty(obj, 'stack', value, NONE);
|
||||
return value;
|
||||
};
|
||||
// The 'stack' property of the receiver is set as data property. If
|
||||
// the receiver is the same as holder, this accessor pair is replaced.
|
||||
var setter = function(v) {
|
||||
%DefineOrRedefineDataProperty(this, 'stack', v, NONE);
|
||||
};
|
||||
|
||||
%DefineOrRedefineAccessorProperty(obj, 'stack', getter, setter, DONT_ENUM);
|
||||
}
|
||||
|
||||
|
||||
@ -1249,4 +1238,37 @@ InstallFunctions($Error.prototype, DONT_ENUM, ['toString', ErrorToString]);
|
||||
|
||||
// Boilerplate for exceptions for stack overflows. Used from
|
||||
// Isolate::StackOverflow().
|
||||
var kStackOverflowBoilerplate = MakeRangeError('stack_overflow', []);
|
||||
function SetUpStackOverflowBoilerplate() {
|
||||
var boilerplate = MakeRangeError('stack_overflow', []);
|
||||
|
||||
// The raw stack trace is stored as hidden property of the copy of this
|
||||
// boilerplate error object. Note that the receiver 'this' may not be that
|
||||
// error object copy, but can be found on the prototype chain of 'this'.
|
||||
// When the stack trace is formatted, this accessor property is replaced by
|
||||
// a data property.
|
||||
function getter() {
|
||||
var holder = this;
|
||||
while (!IS_ERROR(holder)) {
|
||||
holder = %GetPrototype(holder);
|
||||
if (holder == null) return MakeSyntaxError('illegal_access', []);
|
||||
}
|
||||
var raw_stack = %GetOverflowedRawStackTrace(holder);
|
||||
var result = IS_ARRAY(raw_stack) ? FormatRawStackTrace(holder, raw_stack)
|
||||
: void 0;
|
||||
%DefineOrRedefineDataProperty(holder, 'stack', result, NONE);
|
||||
return result;
|
||||
}
|
||||
|
||||
// The 'stack' property of the receiver is set as data property. If
|
||||
// the receiver is the same as holder, this accessor pair is replaced.
|
||||
function setter(v) {
|
||||
%DefineOrRedefineDataProperty(this, 'stack', v, NONE);
|
||||
}
|
||||
|
||||
%DefineOrRedefineAccessorProperty(
|
||||
boilerplate, 'stack', getter, setter, DONT_ENUM);
|
||||
|
||||
return boilerplate;
|
||||
}
|
||||
|
||||
var kStackOverflowBoilerplate = SetUpStackOverflowBoilerplate();
|
||||
|
105
src/runtime.cc
105
src/runtime.cc
@ -12903,47 +12903,6 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetScript) {
|
||||
}
|
||||
|
||||
|
||||
// Determines whether the given stack frame should be displayed in
|
||||
// a stack trace. The caller is the error constructor that asked
|
||||
// for the stack trace to be collected. The first time a construct
|
||||
// call to this function is encountered it is skipped. The seen_caller
|
||||
// in/out parameter is used to remember if the caller has been seen
|
||||
// yet.
|
||||
static bool ShowFrameInStackTrace(StackFrame* raw_frame,
|
||||
Object* caller,
|
||||
bool* seen_caller) {
|
||||
// Only display JS frames.
|
||||
if (!raw_frame->is_java_script()) {
|
||||
return false;
|
||||
}
|
||||
JavaScriptFrame* frame = JavaScriptFrame::cast(raw_frame);
|
||||
Object* raw_fun = frame->function();
|
||||
// Not sure when this can happen but skip it just in case.
|
||||
if (!raw_fun->IsJSFunction()) {
|
||||
return false;
|
||||
}
|
||||
if ((raw_fun == caller) && !(*seen_caller)) {
|
||||
*seen_caller = true;
|
||||
return false;
|
||||
}
|
||||
// Skip all frames until we've seen the caller.
|
||||
if (!(*seen_caller)) return false;
|
||||
// Also, skip non-visible built-in functions and any call with the builtins
|
||||
// object as receiver, so as to not reveal either the builtins object or
|
||||
// an internal function.
|
||||
// The --builtins-in-stack-traces command line flag allows including
|
||||
// internal call sites in the stack trace for debugging purposes.
|
||||
if (!FLAG_builtins_in_stack_traces) {
|
||||
JSFunction* fun = JSFunction::cast(raw_fun);
|
||||
if (frame->receiver()->IsJSBuiltinsObject() ||
|
||||
(fun->IsBuiltin() && !fun->shared()->native())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Collect the raw data for a stack trace. Returns an array of 4
|
||||
// element segments each containing a receiver, function, code and
|
||||
// native code offset.
|
||||
@ -12954,57 +12913,23 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CollectStackTrace) {
|
||||
CONVERT_NUMBER_CHECKED(int32_t, limit, Int32, args[2]);
|
||||
|
||||
HandleScope scope(isolate);
|
||||
Factory* factory = isolate->factory();
|
||||
// Optionally capture a more detailed stack trace for the message.
|
||||
isolate->CaptureAndSetDetailedStackTrace(error_object);
|
||||
// Capture a simple stack trace for the stack property.
|
||||
return *isolate->CaptureSimpleStackTrace(error_object, caller, limit);
|
||||
}
|
||||
|
||||
limit = Max(limit, 0); // Ensure that limit is not negative.
|
||||
int initial_size = Min(limit, 10);
|
||||
Handle<FixedArray> elements =
|
||||
factory->NewFixedArrayWithHoles(initial_size * 4);
|
||||
|
||||
StackFrameIterator iter(isolate);
|
||||
// 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) {
|
||||
StackFrame* raw_frame = iter.frame();
|
||||
if (ShowFrameInStackTrace(raw_frame, *caller, &seen_caller)) {
|
||||
frames_seen++;
|
||||
JavaScriptFrame* frame = JavaScriptFrame::cast(raw_frame);
|
||||
// Set initial size to the maximum inlining level + 1 for the outermost
|
||||
// function.
|
||||
List<FrameSummary> frames(Compiler::kMaxInliningLevels + 1);
|
||||
frame->Summarize(&frames);
|
||||
for (int i = frames.length() - 1; i >= 0; i--) {
|
||||
if (cursor + 4 > elements->length()) {
|
||||
int new_capacity = JSObject::NewElementsCapacity(elements->length());
|
||||
Handle<FixedArray> new_elements =
|
||||
factory->NewFixedArrayWithHoles(new_capacity);
|
||||
for (int i = 0; i < cursor; i++) {
|
||||
new_elements->set(i, elements->get(i));
|
||||
}
|
||||
elements = new_elements;
|
||||
}
|
||||
ASSERT(cursor + 4 <= elements->length());
|
||||
|
||||
Handle<Object> recv = frames[i].receiver();
|
||||
Handle<JSFunction> fun = frames[i].function();
|
||||
Handle<Code> code = frames[i].code();
|
||||
Handle<Smi> offset(Smi::FromInt(frames[i].offset()));
|
||||
elements->set(cursor++, *recv);
|
||||
elements->set(cursor++, *fun);
|
||||
elements->set(cursor++, *code);
|
||||
elements->set(cursor++, *offset);
|
||||
}
|
||||
}
|
||||
iter.Advance();
|
||||
}
|
||||
Handle<JSArray> result = factory->NewJSArrayWithElements(elements);
|
||||
// Capture and attach a more detailed stack trace if necessary.
|
||||
isolate->CaptureAndSetCurrentStackTraceFor(error_object);
|
||||
result->set_length(Smi::FromInt(cursor));
|
||||
return *result;
|
||||
// Retrieve the raw stack trace collected on stack overflow and delete
|
||||
// it since it is used only once to avoid keeping it alive.
|
||||
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetOverflowedRawStackTrace) {
|
||||
ASSERT_EQ(args.length(), 1);
|
||||
CONVERT_ARG_CHECKED(JSObject, error_object, 0);
|
||||
String* key = isolate->heap()->hidden_stack_trace_symbol();
|
||||
Object* result = error_object->GetHiddenProperty(key);
|
||||
RUNTIME_ASSERT(result->IsJSArray() || result->IsUndefined());
|
||||
error_object->DeleteHiddenProperty(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
@ -233,6 +233,7 @@ namespace internal {
|
||||
F(FunctionIsBuiltin, 1, 1) \
|
||||
F(GetScript, 1, 1) \
|
||||
F(CollectStackTrace, 3, 1) \
|
||||
F(GetOverflowedRawStackTrace, 1, 1) \
|
||||
F(GetV8Version, 0, 1) \
|
||||
\
|
||||
F(ClassOf, 1, 1) \
|
||||
|
@ -2410,19 +2410,13 @@ class SourceResource: public v8::String::ExternalAsciiStringResource {
|
||||
};
|
||||
|
||||
|
||||
TEST(ReleaseStackTraceData) {
|
||||
void ReleaseStackTraceDataTest(const char* source) {
|
||||
// Test that the data retained by the Error.stack accessor is released
|
||||
// after the first time the accessor is fired. We use external string
|
||||
// to check whether the data is being released since the external string
|
||||
// resource's callback is fired when the external string is GC'ed.
|
||||
InitializeVM();
|
||||
v8::HandleScope scope;
|
||||
static const char* source = "var error = 1; "
|
||||
"try { "
|
||||
" throw new Error(); "
|
||||
"} catch (e) { "
|
||||
" error = e; "
|
||||
"} ";
|
||||
SourceResource* resource = new SourceResource(i::StrDup(source));
|
||||
{
|
||||
v8::HandleScope scope;
|
||||
@ -2434,15 +2428,32 @@ TEST(ReleaseStackTraceData) {
|
||||
// External source is being retained by the stack trace.
|
||||
CHECK(!resource->IsDisposed());
|
||||
|
||||
CompileRun("error.stack; error.stack;");
|
||||
CompileRun("error.stack;");
|
||||
HEAP->CollectAllAvailableGarbage();
|
||||
// External source has been released.
|
||||
CHECK(resource->IsDisposed());
|
||||
|
||||
delete resource;
|
||||
}
|
||||
|
||||
|
||||
TEST(ReleaseStackTraceData) {
|
||||
static const char* source1 = "var error = null; "
|
||||
/* Normal Error */ "try { "
|
||||
" throw new Error(); "
|
||||
"} catch (e) { "
|
||||
" error = e; "
|
||||
"} ";
|
||||
static const char* source2 = "var error = null; "
|
||||
/* Stack overflow */ "try { "
|
||||
" (function f() { f(); })(); "
|
||||
"} catch (e) { "
|
||||
" error = e; "
|
||||
"} ";
|
||||
ReleaseStackTraceDataTest(source1);
|
||||
ReleaseStackTraceDataTest(source2);
|
||||
}
|
||||
|
||||
|
||||
TEST(Regression144230) {
|
||||
InitializeVM();
|
||||
v8::HandleScope scope;
|
||||
|
122
test/mjsunit/stack-traces-overflow.js
Normal file
122
test/mjsunit/stack-traces-overflow.js
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
function rec1(a) { rec1(a+1); }
|
||||
function rec2(a) { rec3(a+1); }
|
||||
function rec3(a) { rec2(a+1); }
|
||||
|
||||
// Test stack trace getter and setter.
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
assertTrue(e.stack.indexOf("rec1") > 0);
|
||||
e.stack = "123";
|
||||
assertEquals("123", e.stack);
|
||||
}
|
||||
|
||||
// Test setter w/o calling the getter.
|
||||
try {
|
||||
rec2(0);
|
||||
} catch (e) {
|
||||
assertTrue(e.stack.indexOf("rec2") > 0);
|
||||
assertTrue(e.stack.indexOf("rec3") > 0);
|
||||
e.stack = "123";
|
||||
assertEquals("123", e.stack);
|
||||
}
|
||||
|
||||
// Test getter to make sure setter does not affect the boilerplate.
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
assertTrue(e.stack.indexOf("rec1") > 0);
|
||||
assertInstanceof(e, RangeError);
|
||||
}
|
||||
|
||||
|
||||
// Check setting/getting stack property on the prototype chain.
|
||||
function testErrorPrototype(prototype) {
|
||||
var object = {};
|
||||
object.__proto__ = prototype;
|
||||
object.stack = "123";
|
||||
assertEquals("123", object.stack);
|
||||
assertTrue("123" != prototype.stack);
|
||||
}
|
||||
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
e.stack;
|
||||
testErrorPrototype(e);
|
||||
}
|
||||
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
testErrorPrototype(e);
|
||||
}
|
||||
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
testErrorPrototype(e);
|
||||
}
|
||||
|
||||
Error.stackTraceLimit = 3;
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
assertEquals(4, e.stack.split('\n').length);
|
||||
}
|
||||
|
||||
Error.stackTraceLimit = 25.9;
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
assertEquals(26, e.stack.split('\n').length);
|
||||
}
|
||||
|
||||
Error.stackTraceLimit = NaN;
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
assertEquals(1, e.stack.split('\n').length);
|
||||
}
|
||||
|
||||
Error.stackTraceLimit = "not a number";
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
assertEquals(undefined, e.stack);
|
||||
}
|
||||
|
||||
Error.stackTraceLimit = 3;
|
||||
Error = ""; // Overwrite Error in the global object.
|
||||
try {
|
||||
rec1(0);
|
||||
} catch (e) {
|
||||
assertEquals(4, e.stack.split('\n').length);
|
||||
}
|
Loading…
Reference in New Issue
Block a user