Revision: 12933
Author: [email protected]
Date: Mon Nov 12 06:54:29 2012
Log: Collect stack trace on stack overflow.
BUG=v8:2394
Review URL: https://chromiumcodereview.appspot.com/11275186
http://code.google.com/p/v8/source/detail?r=12933
Added:
/branches/bleeding_edge/test/mjsunit/stack-traces-overflow.js
Modified:
/branches/bleeding_edge/src/isolate.cc
/branches/bleeding_edge/src/isolate.h
/branches/bleeding_edge/src/messages.js
/branches/bleeding_edge/src/runtime.cc
/branches/bleeding_edge/src/runtime.h
/branches/bleeding_edge/test/cctest/test-heap.cc
=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/stack-traces-overflow.js Mon Nov
12 06:54:29 2012
@@ -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);
+}
=======================================
--- /branches/bleeding_edge/src/isolate.cc Mon Nov 12 04:34:18 2012
+++ /branches/bleeding_edge/src/isolate.cc Mon Nov 12 06:54:29 2012
@@ -553,7 +553,97 @@
}
-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 @@
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();
}
=======================================
--- /branches/bleeding_edge/src/isolate.h Thu Nov 8 05:44:59 2012
+++ /branches/bleeding_edge/src/isolate.h Mon Nov 12 06:54:29 2012
@@ -716,7 +716,10 @@
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
=======================================
--- /branches/bleeding_edge/src/messages.js Mon Nov 12 02:33:20 2012
+++ /branches/bleeding_edge/src/messages.js Mon Nov 12 06:54:29 2012
@@ -754,29 +754,6 @@
//
----------------------------------------------------------------------------
// 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;
@@ -1112,9 +1089,21 @@
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 @@
// 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();
=======================================
--- /branches/bleeding_edge/src/runtime.cc Mon Nov 12 02:33:20 2012
+++ /branches/bleeding_edge/src/runtime.cc Mon Nov 12 06:54:29 2012
@@ -12901,47 +12901,6 @@
Runtime_GetScriptFromScriptName(Handle<String>(script_name));
return *result;
}
-
-
-// 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
@@ -12954,57 +12913,23 @@
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;
}
=======================================
--- /branches/bleeding_edge/src/runtime.h Mon Nov 12 02:33:20 2012
+++ /branches/bleeding_edge/src/runtime.h Mon Nov 12 06:54:29 2012
@@ -233,6 +233,7 @@
F(FunctionIsBuiltin, 1, 1) \
F(GetScript, 1, 1) \
F(CollectStackTrace, 3, 1) \
+ F(GetOverflowedRawStackTrace, 1, 1) \
F(GetV8Version, 0, 1) \
\
F(ClassOf, 1, 1) \
=======================================
--- /branches/bleeding_edge/test/cctest/test-heap.cc Thu Nov 8 02:26:50
2012
+++ /branches/bleeding_edge/test/cctest/test-heap.cc Mon Nov 12 06:54:29
2012
@@ -2410,19 +2410,13 @@
};
-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,13 +2428,30 @@
// 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) {
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev