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

Reply via email to