Revision: 13188
Author:   [email protected]
Date:     Tue Dec 11 02:14:01 2012
Log:      Fire 'stack' getter of error objects after GC.

BUG=v8:2340

Review URL: https://chromiumcodereview.appspot.com/11377158
http://code.google.com/p/v8/source/detail?r=13188

Added:
 /branches/bleeding_edge/test/mjsunit/stack-traces-gc.js
Modified:
 /branches/bleeding_edge/src/api.cc
 /branches/bleeding_edge/src/heap-inl.h
 /branches/bleeding_edge/src/heap.cc
 /branches/bleeding_edge/src/heap.h
 /branches/bleeding_edge/src/isolate.cc
 /branches/bleeding_edge/src/list-inl.h
 /branches/bleeding_edge/src/list.h
 /branches/bleeding_edge/src/mark-compact.cc
 /branches/bleeding_edge/src/messages.js
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/src/runtime.h
 /branches/bleeding_edge/test/cctest/test-decls.cc
 /branches/bleeding_edge/test/cctest/test-heap.cc
 /branches/bleeding_edge/test/message/overwritten-builtins.out
 /branches/bleeding_edge/test/mjsunit/eval-stack-trace.js
 /branches/bleeding_edge/test/mjsunit/stack-traces.js

=======================================
--- /dev/null
+++ /branches/bleeding_edge/test/mjsunit/stack-traces-gc.js Tue Dec 11 02:14:01 2012
@@ -0,0 +1,119 @@
+// 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.
+
+// Flags: --expose-gc --allow-natives-syntax
+
+var fired = [];
+for (var i = 0; i < 100; i++) fired[i] = false;
+
+function getter_function(i) {
+  return %MarkOneShotGetter( function() { fired[i] = true; } );
+}
+
+// Error objects that die young.
+for (var i = 0; i < 100; i++) {
+  var error = new Error();
+  // Replace the getter to observe whether it has been fired,
+  // and disguise it as original getter.
+  var getter = getter_function(i);
+  error.__defineGetter__("stack", getter);
+
+  error = undefined;
+}
+
+gc();
+for (var i = 0; i < 100; i++) {
+  assertFalse(fired[i]);
+}
+
+// Error objects that are kept alive.
+var array = [];
+for (var i = 0; i < 100; i++) {
+  var error = new Error();
+  var getter = getter_function(i);
+  // Replace the getter to observe whether it has been fired,
+  // and disguise it as original getter.
+  error.__defineGetter__("stack", getter);
+
+  array.push(error);
+  error = undefined;
+}
+
+gc();
+// We don't expect all stack traces to be formatted after only one GC.
+assertTrue(fired[0]);
+
+for (var i = 0; i < 10; i++) gc();
+for (var i = 0; i < 100; i++) assertTrue(fired[i]);
+
+// Error objects with custom stack getter.
+var custom_error = new Error();
+var custom_getter_fired = false;
+custom_error.__defineGetter__("stack",
+                              function() { custom_getter_fired = true; });
+gc();
+assertFalse(custom_getter_fired);
+
+// Check that formatting caused by GC is not somehow observable.
+var error;
+
+var obj = { foo: function foo() { throw new Error(); } };
+
+try {
+  obj.foo();
+} catch (e) {
+  delete obj.foo;
+  Object.defineProperty(obj, 'foo', {
+    get: function() { assertUnreachable(); }
+  });
+  error = e;
+}
+
+gc();
+
+Object.defineProperty(Array.prototype, '0', {
+  get: function() { assertUnreachable(); }
+});
+
+try {
+  throw new Error();
+} catch (e) {
+  error = e;
+}
+
+gc();
+
+String.prototype.indexOf = function() { assertUnreachable(); };
+String.prototype.lastIndexOf = function() { assertUnreachable(); };
+var obj = { method: function() { throw Error(); } };
+try {
+  obj.method();
+} catch (e) {
+  error = e;
+}
+
+gc();
=======================================
--- /branches/bleeding_edge/src/api.cc  Fri Dec  7 05:47:42 2012
+++ /branches/bleeding_edge/src/api.cc  Tue Dec 11 02:14:01 2012
@@ -1842,7 +1842,7 @@
     if (!raw_obj->IsJSObject()) return v8::Local<Value>();
     i::HandleScope scope(isolate_);
     i::Handle<i::JSObject> obj(i::JSObject::cast(raw_obj), isolate_);
- i::Handle<i::String> name = isolate_->factory()->LookupAsciiSymbol("stack");
+    i::Handle<i::String> name = isolate_->factory()->stack_symbol();
     if (!obj->HasProperty(*name)) return v8::Local<Value>();
     i::Handle<i::Object> value = i::GetProperty(obj, name);
     if (value.is_null()) return v8::Local<Value>();
=======================================
--- /branches/bleeding_edge/src/heap-inl.h      Mon Dec  3 06:31:11 2012
+++ /branches/bleeding_edge/src/heap-inl.h      Tue Dec 11 02:14:01 2012
@@ -642,6 +642,19 @@
   }
 #endif
 }
+
+
+void ErrorObjectList::Add(JSObject* object) {
+  list_.Add(object);
+}
+
+
+void ErrorObjectList::Iterate(ObjectVisitor* v) {
+  if (!list_.is_empty()) {
+    Object** start = &list_[0];
+    v->VisitPointers(start, start + list_.length());
+  }
+}


 void Heap::ClearInstanceofCache() {
=======================================
--- /branches/bleeding_edge/src/heap.cc Fri Dec  7 01:44:10 2012
+++ /branches/bleeding_edge/src/heap.cc Tue Dec 11 02:14:01 2012
@@ -550,6 +550,8 @@
 #ifdef ENABLE_DEBUGGER_SUPPORT
   isolate_->debug()->AfterGarbageCollection();
 #endif  // ENABLE_DEBUGGER_SUPPORT
+
+  error_object_list_.DeferredFormatStackTrace(isolate());
 }


@@ -1354,6 +1356,8 @@
   UpdateNewSpaceReferencesInExternalStringTable(
       &UpdateNewSpaceReferenceInExternalStringTableEntry);

+  error_object_list_.UpdateReferencesInNewSpace(this);
+
   promotion_queue_.Destroy();

   LiveObjectList::UpdateReferencesForScavengeGC();
@@ -5870,6 +5874,7 @@
       mode != VISIT_ALL_IN_SWEEP_NEWSPACE) {
     // Scavenge collections have special processing for this.
     external_string_table_.Iterate(v);
+    error_object_list_.Iterate(v);
   }
   v->Synchronize(VisitorSynchronization::kExternalStringsTable);
 }
@@ -6243,6 +6248,8 @@

   external_string_table_.TearDown();

+  error_object_list_.TearDown();
+
   new_space_.TearDown();

   if (old_pointer_space_ != NULL) {
@@ -7149,6 +7156,8 @@
     }
   }
   new_space_strings_.Rewind(last);
+  new_space_strings_.Trim();
+
   last = 0;
   for (int i = 0; i < old_space_strings_.length(); ++i) {
     if (old_space_strings_[i] == heap_->the_hole_value()) {
@@ -7158,6 +7167,7 @@
     old_space_strings_[last++] = old_space_strings_[i];
   }
   old_space_strings_.Rewind(last);
+  old_space_strings_.Trim();
 #ifdef VERIFY_HEAP
   if (FLAG_verify_heap) {
     Verify();
@@ -7170,6 +7180,103 @@
   new_space_strings_.Free();
   old_space_strings_.Free();
 }
+
+
+// Update all references.
+void ErrorObjectList::UpdateReferences() {
+  for (int i = 0; i < list_.length(); i++) {
+    HeapObject* object = HeapObject::cast(list_[i]);
+    MapWord first_word = object->map_word();
+    if (first_word.IsForwardingAddress()) {
+      list_[i] = first_word.ToForwardingAddress();
+    }
+  }
+}
+
+
+// Unforwarded objects in new space are dead and removed from the list.
+void ErrorObjectList::UpdateReferencesInNewSpace(Heap* heap) {
+  if (!nested_) {
+    int write_index = 0;
+    for (int i = 0; i < list_.length(); i++) {
+      MapWord first_word = HeapObject::cast(list_[i])->map_word();
+      if (first_word.IsForwardingAddress()) {
+        list_[write_index++] = first_word.ToForwardingAddress();
+      }
+    }
+    list_.Rewind(write_index);
+  } else {
+    // If a GC is triggered during DeferredFormatStackTrace, we do not move
+    // objects in the list, just remove dead ones, as to not confuse the
+    // loop in DeferredFormatStackTrace.
+    for (int i = 0; i < list_.length(); i++) {
+      MapWord first_word = HeapObject::cast(list_[i])->map_word();
+      list_[i] = first_word.IsForwardingAddress()
+                     ? first_word.ToForwardingAddress()
+                     : heap->the_hole_value();
+    }
+  }
+}
+
+
+void ErrorObjectList::DeferredFormatStackTrace(Isolate* isolate) {
+  // If formatting the stack trace causes a GC, this method will be
+  // recursively called.  In that case, skip the recursive call, since
+  // the loop modifies the list while iterating over it.
+  if (nested_) return;
+  nested_ = true;
+  HandleScope scope(isolate);
+  Handle<String> stack_key = isolate->factory()->stack_symbol();
+  int write_index = 0;
+  int budget = kBudgetPerGC;
+  for (int i = 0; i < list_.length(); i++) {
+    Object* object = list_[i];
+    // Skip possible holes in the list.
+    if (object->IsTheHole()) continue;
+    if (isolate->heap()->InNewSpace(object) || budget == 0) {
+      list_[write_index++] = object;
+      continue;
+    }
+
+    // Fire the stack property getter, if it is the original marked getter.
+    LookupResult lookup(isolate);
+ JSObject::cast(object)->LocalLookupRealNamedProperty(*stack_key, &lookup);
+    if (!lookup.IsFound() || lookup.type() != CALLBACKS) continue;
+    Object* callback = lookup.GetCallbackObject();
+    if (!callback->IsAccessorPair()) continue;
+    Object* getter_obj = AccessorPair::cast(callback)->getter();
+    if (!getter_obj->IsJSFunction()) continue;
+    JSFunction* getter_fun = JSFunction::cast(getter_obj);
+    String* key = isolate->heap()->hidden_stack_trace_symbol();
+    if (key != getter_fun->GetHiddenProperty(key)) continue;
+    bool has_exception = false;
+    Execution::Call(Handle<Object>(getter_fun, isolate),
+                    Handle<Object>(object, isolate),
+                    0,
+                    NULL,
+                    &has_exception);
+    ASSERT(!has_exception);
+    budget--;
+  }
+  list_.Rewind(write_index);
+  list_.Trim();
+  nested_ = false;
+}
+
+
+void ErrorObjectList::RemoveUnmarked(Heap* heap) {
+  for (int i = 0; i < list_.length(); i++) {
+    HeapObject* object = HeapObject::cast(list_[i]);
+    if (!Marking::MarkBitFrom(object).Get()) {
+      list_[i] = heap->the_hole_value();
+    }
+  }
+}
+
+
+void ErrorObjectList::TearDown() {
+  list_.Free();
+}


 void Heap::QueueMemoryChunkForFree(MemoryChunk* chunk) {
=======================================
--- /branches/bleeding_edge/src/heap.h  Mon Dec 10 04:18:54 2012
+++ /branches/bleeding_edge/src/heap.h  Tue Dec 11 02:14:01 2012
@@ -209,6 +209,7 @@
   V(char_at_symbol, "CharAt")                                            \
   V(undefined_symbol, "undefined")                                       \
   V(value_of_symbol, "valueOf")                                          \
+  V(stack_symbol, "stack")                                               \
   V(InitializeVarGlobal_symbol, "InitializeVarGlobal")                   \
   V(InitializeConstGlobal_symbol, "InitializeConstGlobal")               \
   V(KeyedLoadElementMonomorphic_symbol,                                  \
@@ -427,6 +428,41 @@
 };


+// The stack property of an error object is implemented as a getter that
+// formats the attached raw stack trace into a string. This raw stack trace +// keeps code and function objects alive until the getter is called the first
+// time.  To release those objects, we call the getter after each GC for
+// newly tenured error objects that are kept in a list.
+class ErrorObjectList {
+ public:
+  inline void Add(JSObject* object);
+
+  inline void Iterate(ObjectVisitor* v);
+
+  void TearDown();
+
+  void RemoveUnmarked(Heap* heap);
+
+  void DeferredFormatStackTrace(Isolate* isolate);
+
+  void UpdateReferences();
+
+  void UpdateReferencesInNewSpace(Heap* heap);
+
+ private:
+  static const int kBudgetPerGC = 16;
+
+  ErrorObjectList() : nested_(false) { }
+
+  friend class Heap;
+
+  List<Object*> list_;
+  bool nested_;
+
+  DISALLOW_COPY_AND_ASSIGN(ErrorObjectList);
+};
+
+
 enum ArrayStorageAllocationMode {
   DONT_INITIALIZE_ARRAY_ELEMENTS,
   INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE
@@ -1572,6 +1608,10 @@
   ExternalStringTable* external_string_table() {
     return &external_string_table_;
   }
+
+  ErrorObjectList* error_object_list() {
+    return &error_object_list_;
+  }

   // Returns the current sweep generation.
   int sweep_generation() {
@@ -2149,6 +2189,8 @@

   ExternalStringTable external_string_table_;

+  ErrorObjectList error_object_list_;
+
   VisitorDispatchTable<ScavengingCallback> scavenging_visitors_table_;

   MemoryChunk* chunks_queued_for_free_;
=======================================
--- /branches/bleeding_edge/src/isolate.cc      Mon Dec 10 03:09:12 2012
+++ /branches/bleeding_edge/src/isolate.cc      Tue Dec 11 02:14:01 2012
@@ -634,6 +634,7 @@
   }
   Handle<JSArray> result = factory()->NewJSArrayWithElements(elements);
   result->set_length(Smi::FromInt(cursor));
+  heap()->error_object_list()->Add(*error_object);
   return result;
 }

=======================================
--- /branches/bleeding_edge/src/list-inl.h      Mon Jun 11 05:42:31 2012
+++ /branches/bleeding_edge/src/list-inl.h      Tue Dec 11 02:14:01 2012
@@ -85,8 +85,9 @@

 template<typename T, class P>
 void List<T, P>::Resize(int new_capacity, P alloc) {
+  ASSERT_LE(length_, new_capacity);
   T* new_data = NewData(new_capacity, alloc);
-  memcpy(new_data, data_, capacity_ * sizeof(T));
+  memcpy(new_data, data_, length_ * sizeof(T));
   List<T, P>::DeleteData(data_);
   data_ = new_data;
   capacity_ = new_capacity;
@@ -159,6 +160,14 @@
 void List<T, P>::Rewind(int pos) {
   length_ = pos;
 }
+
+
+template<typename T, class P>
+void List<T, P>::Trim(P alloc) {
+  if (length_ < capacity_ / 4) {
+    Resize(capacity_ / 2, alloc);
+  }
+}


 template<typename T, class P>
=======================================
--- /branches/bleeding_edge/src/list.h  Mon Jun 18 05:48:21 2012
+++ /branches/bleeding_edge/src/list.h  Tue Dec 11 02:14:01 2012
@@ -148,6 +148,9 @@

   // Drop the last 'count' elements from the list.
   INLINE(void RewindBy(int count)) { Rewind(length_ - count); }
+
+  // Halve the capacity if fill level is less than a quarter.
+  INLINE(void Trim(AllocationPolicy allocator = AllocationPolicy()));

   bool Contains(const T& elm) const;
   int CountOccurrences(const T& elm, int start, int end) const;
=======================================
--- /branches/bleeding_edge/src/mark-compact.cc Thu Dec  6 08:27:32 2012
+++ /branches/bleeding_edge/src/mark-compact.cc Tue Dec 11 02:14:01 2012
@@ -835,8 +835,6 @@
   // GC, because it relies on the new address of certain old space
   // objects (empty string, illegal builtin).
   heap()->isolate()->stub_cache()->Clear();
-
-  heap()->external_string_table_.CleanUp();
 }


@@ -2030,6 +2028,7 @@
   symbol_table->ElementsRemoved(v.PointersRemoved());
   heap()->external_string_table_.Iterate(&v);
   heap()->external_string_table_.CleanUp();
+  heap()->error_object_list_.RemoveUnmarked(heap());

   // Process the weak references.
   MarkCompactWeakObjectRetainer mark_compact_object_retainer;
@@ -3069,6 +3068,9 @@
   heap_->UpdateReferencesInExternalStringTable(
       &UpdateReferenceInExternalStringTableEntry);

+  // Update pointers in the new error object list.
+  heap_->error_object_list()->UpdateReferences();
+
   if (!FLAG_watch_ic_patching) {
     // Update JSFunction pointers from the runtime profiler.
     heap()->isolate()->runtime_profiler()->UpdateSamplesAfterCompact(
=======================================
--- /branches/bleeding_edge/src/messages.js     Wed Nov 14 01:14:47 2012
+++ /branches/bleeding_edge/src/messages.js     Tue Dec 11 02:14:01 2012
@@ -820,7 +820,7 @@
        %_CallFunction(this.receiver,
                       ownName,
                       ObjectLookupSetter) === this.fun ||
-       this.receiver[ownName] === this.fun)) {
+       %GetDataProperty(this.receiver, ownName) === this.fun)) {
     // To handle DontEnum properties we guess that the method has
     // the same name as the function.
     return ownName;
@@ -829,8 +829,7 @@
   for (var prop in this.receiver) {
if (%_CallFunction(this.receiver, prop, ObjectLookupGetter) === this.fun || %_CallFunction(this.receiver, prop, ObjectLookupSetter) === this.fun ||
-        (!%_CallFunction(this.receiver, prop, ObjectLookupGetter) &&
-         this.receiver[prop] === this.fun)) {
+        %GetDataProperty(this.receiver, prop) === this.fun) {
       // If we find more than one match bail out to avoid confusion.
       if (name) {
         return null;
@@ -933,12 +932,14 @@
     var typeName = GetTypeName(this, true);
     var methodName = this.getMethodName();
     if (functionName) {
-      if (typeName && functionName.indexOf(typeName) != 0) {
+      if (typeName &&
+          %_CallFunction(functionName, typeName, StringIndexOf) != 0) {
         line += typeName + ".";
       }
       line += functionName;
-      if (methodName && functionName.lastIndexOf("." + methodName) !=
-          functionName.length - methodName.length - 1) {
+      if (methodName &&
+          (%_CallFunction(functionName, "." + methodName, StringIndexOf) !=
+           functionName.length - methodName.length - 1)) {
         line += " [as " + methodName + "]";
       }
     } else {
@@ -1016,17 +1017,37 @@
   return eval_origin;
 }

-function FormatStackTrace(error, frames) {
-  var lines = [];
+
+function FormatErrorString(error) {
   try {
-    lines.push(error.toString());
+    return %_CallFunction(error, ErrorToString);
   } catch (e) {
     try {
-      lines.push("<error: " + e + ">");
+      return "<error: " + e + ">";
     } catch (ee) {
-      lines.push("<error>");
+      return "<error>";
     }
   }
+}
+
+
+function GetStackFrames(raw_stack) {
+  var frames = new InternalArray();
+  for (var i = 0; i < raw_stack.length; i += 4) {
+    var recv = raw_stack[i];
+    var fun = raw_stack[i + 1];
+    var code = raw_stack[i + 2];
+    var pc = raw_stack[i + 3];
+    var pos = %FunctionGetPositionForOffset(code, pc);
+    frames.push(new CallSite(recv, fun, pos));
+  }
+  return frames;
+}
+
+
+function FormatStackTrace(error_string, frames) {
+  var lines = new InternalArray();
+  lines.push(error_string);
   for (var i = 0; i < frames.length; i++) {
     var frame = frames[i];
     var line;
@@ -1042,25 +1063,9 @@
     }
     lines.push("    at " + line);
   }
-  return lines.join("\n");
+  return %_CallFunction(lines, "\n", ArrayJoin);
 }

-function FormatRawStackTrace(error, raw_stack) {
-  var frames = [ ];
-  for (var i = 0; i < raw_stack.length; i += 4) {
-    var recv = raw_stack[i];
-    var fun = raw_stack[i + 1];
-    var code = raw_stack[i + 2];
-    var pc = raw_stack[i + 3];
-    var pos = %FunctionGetPositionForOffset(code, pc);
-    frames.push(new CallSite(recv, fun, pos));
-  }
-  if (IS_FUNCTION($Error.prepareStackTrace)) {
-    return $Error.prepareStackTrace(error, frames);
-  } else {
-    return FormatStackTrace(error, frames);
-  }
-}

 function GetTypeName(obj, requireConstructor) {
   var constructor = obj.receiver.constructor;
@@ -1075,6 +1080,7 @@
   }
   return constructorName;
 }
+

 function captureStackTrace(obj, cons_opt) {
   var stackTraceLimit = $Error.stackTraceLimit;
@@ -1085,14 +1091,30 @@
   var raw_stack = %CollectStackTrace(obj,
cons_opt ? cons_opt : captureStackTrace,
                                      stackTraceLimit);
+
+  // Don't be lazy if the error stack formatting is custom (observable).
+  if (IS_FUNCTION($Error.prepareStackTrace)) {
+    var custom_stacktrace_fun = $Error.prepareStackTrace;
+ // Use default error formatting for the case that custom formatting throws.
+    $Error.prepareStackTrace = null;
+    var array = [];
+    %MoveArrayContents(GetStackFrames(raw_stack), array);
+    obj.stack = custom_stacktrace_fun(obj, array);
+    $Error.prepareStackTrace = custom_stacktrace_fun;
+    return;
+  }
+
+  var error_string = FormatErrorString(obj);
   // 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);
+    var value = FormatStackTrace(error_string, GetStackFrames(raw_stack));
     %DefineOrRedefineDataProperty(obj, 'stack', value, NONE);
     return value;
   };
+  %MarkOneShotGetter(getter);
+
   // 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) {
@@ -1239,6 +1261,8 @@
   // 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.
+  var error_string = boilerplate.name + ": " + boilerplate.message;
+
   function getter() {
     var holder = this;
     while (!IS_ERROR(holder)) {
@@ -1246,11 +1270,13 @@
       if (holder == null) return MakeSyntaxError('illegal_access', []);
     }
     var raw_stack = %GetOverflowedRawStackTrace(holder);
- var result = IS_ARRAY(raw_stack) ? FormatRawStackTrace(holder, raw_stack)
-                                     : void 0;
+    var result = IS_ARRAY(raw_stack)
+ ? FormatStackTrace(error_string, GetStackFrames(raw_stack))
+                     : void 0;
     %DefineOrRedefineDataProperty(holder, 'stack', result, NONE);
     return result;
   }
+  %MarkOneShotGetter(getter);

   // The 'stack' property of the receiver is set as data property.  If
   // the receiver is the same as holder, this accessor pair is replaced.
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Mon Dec 10 03:09:12 2012
+++ /branches/bleeding_edge/src/runtime.cc      Tue Dec 11 02:14:01 2012
@@ -13096,6 +13096,17 @@
   // Capture a simple stack trace for the stack property.
   return *isolate->CaptureSimpleStackTrace(error_object, caller, limit);
 }
+
+
+// Mark a function to recognize when called after GC to format the stack trace.
+RUNTIME_FUNCTION(MaybeObject*, Runtime_MarkOneShotGetter) {
+  ASSERT_EQ(args.length(), 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSFunction, fun, 0);
+  HandleScope scope(isolate);
+  Handle<String> key = isolate->factory()->hidden_stack_trace_symbol();
+  JSObject::SetHiddenProperty(fun, key, key);
+  return *fun;
+}


 // Retrieve the raw stack trace collected on stack overflow and delete
=======================================
--- /branches/bleeding_edge/src/runtime.h       Mon Dec 10 03:09:12 2012
+++ /branches/bleeding_edge/src/runtime.h       Tue Dec 11 02:14:01 2012
@@ -236,6 +236,7 @@
   F(FunctionIsBuiltin, 1, 1) \
   F(GetScript, 1, 1) \
   F(CollectStackTrace, 3, 1) \
+  F(MarkOneShotGetter, 1, 1) \
   F(GetOverflowedRawStackTrace, 1, 1) \
   F(GetV8Version, 0, 1) \
   \
=======================================
--- /branches/bleeding_edge/test/cctest/test-decls.cc Thu Nov 22 02:25:22 2012 +++ /branches/bleeding_edge/test/cctest/test-decls.cc Tue Dec 11 02:14:01 2012
@@ -161,6 +161,7 @@
       CHECK_EQ(value, catcher.Exception());
     }
   }
+  HEAP->CollectAllAvailableGarbage();  // Clean slate for the next test.
 }


=======================================
--- /branches/bleeding_edge/test/cctest/test-heap.cc Mon Dec 10 07:14:20 2012 +++ /branches/bleeding_edge/test/cctest/test-heap.cc Tue Dec 11 02:14:01 2012
@@ -2429,11 +2429,7 @@
     CHECK(!resource->IsDisposed());
   }
   HEAP->CollectAllAvailableGarbage();
-  // External source is being retained by the stack trace.
-  CHECK(!resource->IsDisposed());

-  CompileRun("error.stack;");
-  HEAP->CollectAllAvailableGarbage();
   // External source has been released.
   CHECK(resource->IsDisposed());
   delete resource;
=======================================
--- /branches/bleeding_edge/test/message/overwritten-builtins.out Tue Dec 7 03:01:02 2010 +++ /branches/bleeding_edge/test/message/overwritten-builtins.out Tue Dec 11 02:14:01 2012
@@ -28,3 +28,6 @@
 *%(basename)s:31: TypeError: Cannot read property 'x' of undefined
 undefined.x
          ^
+TypeError: Cannot read property 'x' of undefined
+    at *%(basename)s:31:10
+
=======================================
--- /branches/bleeding_edge/test/mjsunit/eval-stack-trace.js Mon Jun 18 06:39:24 2012 +++ /branches/bleeding_edge/test/mjsunit/eval-stack-trace.js Tue Dec 11 02:14:01 2012
@@ -26,12 +26,13 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 // Return the stack frames of an Error object.
+
+Error.prepareStackTrace = function(error, frames) {
+  return frames;
+}
+
 Error.prototype.getFrames = function() {
-  Error.prepareStackTrace = function(error, frames) {
-    return frames;
-  }
   var frames = this.stack;
-  Error.prepareStackTrace = undefined;
   return frames;
 }

=======================================
--- /branches/bleeding_edge/test/mjsunit/stack-traces.js Thu May 24 04:00:05 2012 +++ /branches/bleeding_edge/test/mjsunit/stack-traces.js Tue Dec 11 02:14:01 2012
@@ -289,3 +289,35 @@

 // Omitted because ADD from runtime.js is non-native builtin.
 testOmittedBuiltin(function(){ thrower + 2; }, "ADD");
+
+var error = new Error();
+error.toString = function() { assertUnreachable(); };
+error.stack;
+
+error = new Error();
+error.name = { toString: function() { assertUnreachable(); }};
+error.message = { toString: function() {  assertUnreachable(); }};
+error.stack;
+
+error = new Error();
+Array.prototype.push = function(x) { assertUnreachable(); };
+Array.prototype.join = function(x) { assertUnreachable(); };
+error.stack;
+
+var fired = false;
+error = new Error({ toString: function() { fired = true; } });
+assertTrue(fired);
+error.stack;
+assertTrue(fired);
+
+//Check that throwing exception in a custom stack trace formatting function
+//does not lead to recursion.
+Error.prepareStackTrace = function() { throw new Error("abc"); }
+var message;
+try {
+  throw new Error();
+} catch (e) {
+  message = e.message;
+}
+
+assertEquals("abc", message);

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to