Reland r13188, r13194, r13256 (Deferred formatting of error stack trace during GC).
authoryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 14 Jan 2013 13:19:27 +0000 (13:19 +0000)
committeryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 14 Jan 2013 13:19:27 +0000 (13:19 +0000)
BUG=

Review URL: https://chromiumcodereview.appspot.com/11880018

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@13371 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

18 files changed:
src/api.cc
src/handles.cc
src/heap-inl.h
src/heap.cc
src/heap.h
src/isolate.cc
src/list-inl.h
src/list.h
src/mark-compact.cc
src/messages.js
src/runtime.cc
src/runtime.h
test/cctest/test-decls.cc
test/cctest/test-heap.cc
test/message/overwritten-builtins.out
test/mjsunit/eval-stack-trace.js
test/mjsunit/stack-traces-gc.js [new file with mode: 0644]
test/mjsunit/stack-traces.js

index d14096bb971d0db7ac4b5a7ec944544f00afff0b..26ac66aea10befa8106ee3d8bda5bab956502055 100644 (file)
@@ -1862,8 +1862,7 @@ v8::Local<Value> v8::TryCatch::StackTrace() const {
     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()->LookupOneByteSymbol(STATIC_ASCII_VECTOR("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>();
index ed92c414e37c1e8f3f423525dce17b603e4f1fe5..c0dea20aae3c09b6a98d3b4bd851e8fc15da140b 100644 (file)
@@ -375,6 +375,15 @@ Handle<JSValue> GetScriptWrapper(Handle<Script> script) {
   Handle<JSFunction> constructor = isolate->script_function();
   Handle<JSValue> result =
       Handle<JSValue>::cast(isolate->factory()->NewJSObject(constructor));
+
+  // The allocation might have triggered a GC, which could have called this
+  // function recursively, and a wrapper has already been created and cached.
+  // In that case, simply return the cached wrapper.
+  if (script->wrapper()->foreign_address() != NULL) {
+    return Handle<JSValue>(
+        reinterpret_cast<JSValue**>(script->wrapper()->foreign_address()));
+  }
+
   result->set_value(*script);
 
   // Create a new weak global handle and use it to cache the wrapper
index 6f4b69c0a4b6864d5ab209dfe3172e8a46a5b8e6..08ca47b6fbd4765e92f37a10d7872af155e62f32 100644 (file)
@@ -669,6 +669,19 @@ void ExternalStringTable::ShrinkNewStrings(int position) {
 }
 
 
+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() {
   set_instanceof_cache_function(the_hole_value());
 }
index 6fc374e0934d57ae1494ef7d45af40a469932000..c10c43081251de80763f9af2e15c9a6aa2c92fac 100644 (file)
@@ -550,6 +550,8 @@ void Heap::GarbageCollectionEpilogue() {
 #ifdef ENABLE_DEBUGGER_SUPPORT
   isolate_->debug()->AfterGarbageCollection();
 #endif  // ENABLE_DEBUGGER_SUPPORT
+
+  error_object_list_.DeferredFormatStackTrace(isolate());
 }
 
 
@@ -1383,6 +1385,8 @@ void Heap::Scavenge() {
   UpdateNewSpaceReferencesInExternalStringTable(
       &UpdateNewSpaceReferenceInExternalStringTableEntry);
 
+  error_object_list_.UpdateReferencesInNewSpace(this);
+
   promotion_queue_.Destroy();
 
   LiveObjectList::UpdateReferencesForScavengeGC();
@@ -5965,6 +5969,7 @@ void Heap::IterateWeakRoots(ObjectVisitor* v, VisitMode mode) {
       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);
 }
@@ -6338,6 +6343,8 @@ void Heap::TearDown() {
 
   external_string_table_.TearDown();
 
+  error_object_list_.TearDown();
+
   new_space_.TearDown();
 
   if (old_pointer_space_ != NULL) {
@@ -7244,6 +7251,8 @@ void ExternalStringTable::CleanUp() {
     }
   }
   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()) {
@@ -7253,6 +7262,7 @@ void ExternalStringTable::CleanUp() {
     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();
@@ -7267,6 +7277,118 @@ void ExternalStringTable::TearDown() {
 }
 
 
+// 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_ || isolate->has_pending_exception()) 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];
+    JSFunction* getter_fun;
+
+    { AssertNoAllocation assert;
+      // Skip possible holes in the list.
+      if (object->IsTheHole()) continue;
+      if (isolate->heap()->InNewSpace(object) || budget == 0) {
+        list_[write_index++] = object;
+        continue;
+      }
+
+      // Check whether the stack property is backed by the original 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;
+      getter_fun = JSFunction::cast(getter_obj);
+      String* key = isolate->heap()->hidden_stack_trace_symbol();
+      if (key != getter_fun->GetHiddenProperty(key)) continue;
+    }
+
+    budget--;
+    HandleScope scope(isolate);
+    bool has_exception = false;
+#ifdef DEBUG
+    Handle<Map> map(HeapObject::cast(object)->map(), isolate);
+#endif
+    Handle<Object> object_handle(object, isolate);
+    Handle<Object> getter_handle(getter_fun, isolate);
+    Execution::Call(getter_handle, object_handle, 0, NULL, &has_exception);
+    ASSERT(*map == HeapObject::cast(*object_handle)->map());
+    if (has_exception) {
+      // Hit an exception (most likely a stack overflow).
+      // Wrap up this pass and retry after another GC.
+      isolate->clear_pending_exception();
+      // We use the handle since calling the getter might have caused a GC.
+      list_[write_index++] = *object_handle;
+      budget = 0;
+    }
+  }
+  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) {
   chunk->set_next_chunk(chunks_queued_for_free_);
   chunks_queued_for_free_ = chunk;
index a2564a5a4f9b3b6f2565aa552837ff00ac504146..1615db5527dbcb8fc036c1a2f9a895051b303910 100644 (file)
@@ -209,6 +209,7 @@ namespace internal {
   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 @@ class ExternalStringTable {
 };
 
 
+// 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
@@ -1589,6 +1625,10 @@ class Heap {
     return &external_string_table_;
   }
 
+  ErrorObjectList* error_object_list() {
+    return &error_object_list_;
+  }
+
   // Returns the current sweep generation.
   int sweep_generation() {
     return sweep_generation_;
@@ -2165,6 +2205,8 @@ class Heap {
 
   ExternalStringTable external_string_table_;
 
+  ErrorObjectList error_object_list_;
+
   VisitorDispatchTable<ScavengingCallback> scavenging_visitors_table_;
 
   MemoryChunk* chunks_queued_for_free_;
index 2044805ca90f384e425265378bec18d2e621fd59..80138c0aab962c8d6569b37ee864cc9732f9849e 100644 (file)
@@ -635,6 +635,7 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
   }
   Handle<JSArray> result = factory()->NewJSArrayWithElements(elements);
   result->set_length(Smi::FromInt(cursor));
+  heap()->error_object_list()->Add(*error_object);
   return result;
 }
 
index 60a033df04527ca2678cf46931a8bc86d32163dc..7a84313cd6ad5ed5235b97c5442ef3f520a1cc1a 100644 (file)
@@ -85,8 +85,9 @@ void List<T, P>::ResizeAddInternal(const T& element, P alloc) {
 
 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;
@@ -161,6 +162,14 @@ void List<T, P>::Rewind(int 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>
 void List<T, P>::Iterate(void (*callback)(T* x)) {
   for (int i = 0; i < length_; i++) callback(&data_[i]);
index 7fd4f5cd2d6f6af7b8e2675252347b560c95dbdd..43d982f687a7b37bfe55a128f47e9e8ee8c12eea 100644 (file)
@@ -149,6 +149,9 @@ class List {
   // 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;
 
index d867197d7c563dad3957ca593cb8208bbbe510c6..2c6d9d1c93687b96bfa4144638374982289c199d 100644 (file)
@@ -835,8 +835,6 @@ void MarkCompactCollector::Finish() {
   // 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();
 }
 
 
@@ -2059,6 +2057,7 @@ void MarkCompactCollector::AfterMarking() {
   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;
@@ -3098,6 +3097,9 @@ void MarkCompactCollector::EvacuateNewSpaceAndCandidates() {
   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(
index 5d065d098434f39743d7d35a20524e22d973f22a..4a8143e611162a447436ecb64aefb670e10c14fb 100644 (file)
@@ -820,7 +820,7 @@ function CallSiteGetMethodName() {
        %_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 @@ function CallSiteGetMethodName() {
   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;
@@ -883,7 +882,8 @@ function CallSiteGetPosition() {
 }
 
 function CallSiteIsConstructor() {
-  var constructor = this.receiver ? this.receiver.constructor : null;
+  var receiver = this.receiver;
+  var constructor = receiver ? %GetDataProperty(receiver, "constructor") : null;
   if (!constructor) {
     return false;
   }
@@ -933,12 +933,14 @@ function CallSiteToString() {
     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 +1018,37 @@ function FormatEvalOrigin(script) {
   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 +1064,9 @@ function FormatStackTrace(error, frames) {
     }
     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;
@@ -1076,23 +1082,51 @@ function GetTypeName(obj, requireConstructor) {
   return constructorName;
 }
 
+
+// Flag to prevent recursive call of Error.prepareStackTrace.
+var formatting_custom_stack_trace = false;
+
+
 function captureStackTrace(obj, cons_opt) {
   var stackTraceLimit = $Error.stackTraceLimit;
   if (!stackTraceLimit || !IS_NUMBER(stackTraceLimit)) return;
   if (stackTraceLimit < 0 || stackTraceLimit > 10000) {
     stackTraceLimit = 10000;
   }
-  var raw_stack = %CollectStackTrace(obj,
-                                     cons_opt ? cons_opt : captureStackTrace,
-                                     stackTraceLimit);
+  var 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) && !formatting_custom_stack_trace) {
+    var array = [];
+    %MoveArrayContents(GetStackFrames(stack), array);
+    formatting_custom_stack_trace = true;
+    try {
+      obj.stack = $Error.prepareStackTrace(obj, array);
+    } catch (e) {
+      throw e;  // The custom formatting function threw.  Rethrow.
+    } finally {
+      formatting_custom_stack_trace = false;
+    }
+    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.
+  // The getter must not change the object layout as it may be called after GC.
   var getter = function() {
-    var value = FormatRawStackTrace(obj, raw_stack);
-    %DefineOrRedefineDataProperty(obj, 'stack', value, NONE);
-    return value;
+    if (IS_STRING(stack)) return stack;
+    // Stack is still a raw array awaiting to be formatted.
+    stack = FormatStackTrace(error_string, GetStackFrames(stack));
+    // Release context value.
+    error_string = void 0;
+    return stack;
   };
+  %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,23 +1273,32 @@ function SetUpStackOverflowBoilerplate() {
   // 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;
+
+  // The getter must not change the object layout as it may be called after GC.
   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;
+    var stack = %GetOverflowedStackTrace(holder);
+    if (IS_STRING(stack)) return stack;
+    if (IS_ARRAY(stack)) {
+      var result = FormatStackTrace(error_string, GetStackFrames(stack));
+      %SetOverflowedStackTrace(holder, result);
+      return result;
+    }
+    return void 0;
   }
+  %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.
   function setter(v) {
     %DefineOrRedefineDataProperty(this, 'stack', v, NONE);
+    // Release the stack trace that is stored as hidden property, if exists.
+    %SetOverflowedStackTrace(this, void 0);
   }
 
   %DefineOrRedefineAccessorProperty(
index dbd777e22b84beea961331258f7738f721523eaf..3b31f1d00009534b4c08a50cff9ac41b99a4589b 100644 (file)
@@ -13220,19 +13220,49 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CollectStackTrace) {
 }
 
 
-// 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) {
+// 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 stack trace.  This could be the raw stack trace collected
+// on stack overflow or the already formatted stack trace string.
+RUNTIME_FUNCTION(MaybeObject*, Runtime_GetOverflowedStackTrace) {
+  HandleScope scope(isolate);
   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);
+  RUNTIME_ASSERT(result->IsJSArray() ||
+                 result->IsString() ||
+                 result->IsUndefined());
   return result;
 }
 
 
+// Set or clear the stack trace attached to an stack overflow error object.
+RUNTIME_FUNCTION(MaybeObject*, Runtime_SetOverflowedStackTrace) {
+  HandleScope scope(isolate);
+  ASSERT_EQ(args.length(), 2);
+  CONVERT_ARG_HANDLE_CHECKED(JSObject, error_object, 0);
+  CONVERT_ARG_HANDLE_CHECKED(HeapObject, value, 1);
+  Handle<String> key = isolate->factory()->hidden_stack_trace_symbol();
+  if (value->IsUndefined()) {
+    error_object->DeleteHiddenProperty(*key);
+  } else {
+    RUNTIME_ASSERT(value->IsString());
+    JSObject::SetHiddenProperty(error_object, key, value);
+  }
+  return *error_object;
+}
+
+
 // Returns V8 version as a string.
 RUNTIME_FUNCTION(MaybeObject*, Runtime_GetV8Version) {
   ASSERT_EQ(args.length(), 0);
index b72813dd3d11f24cc34d9641b47839170a33d103..dfbb4bd25a18d70e0887c0d3a877980ad2f7e1bb 100644 (file)
@@ -238,7 +238,9 @@ namespace internal {
   F(FunctionIsBuiltin, 1, 1) \
   F(GetScript, 1, 1) \
   F(CollectStackTrace, 3, 1) \
-  F(GetOverflowedRawStackTrace, 1, 1) \
+  F(MarkOneShotGetter, 1, 1) \
+  F(GetOverflowedStackTrace, 1, 1) \
+  F(SetOverflowedStackTrace, 2, 1) \
   F(GetV8Version, 0, 1) \
   \
   F(ClassOf, 1, 1) \
index 824c4e764a2b8ebf9f5b8cee64d30d357ddc4869..d379fbbc7faa8bf1b80fdcbbcb3600055dbf38b5 100644 (file)
@@ -161,6 +161,7 @@ void DeclarationContext::Check(const char* source,
       CHECK_EQ(value, catcher.Exception());
     }
   }
+  HEAP->CollectAllAvailableGarbage();  // Clean slate for the next test.
 }
 
 
index 834e75b1c348ee17ff28dee737ab44474aa4de1b..634deefbfb2190e5d94c31d08ac9ace20eef6ef5 100644 (file)
@@ -2436,11 +2436,7 @@ void ReleaseStackTraceDataTest(const char* source) {
     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;
index ccf292463b5403268e13624fbe51743cd522cc51..db31bbf08fdb7b2824e17aac0b1e7cc76d8f90a9 100644 (file)
@@ -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
+
index 723d522c788616696ea1221addcccd35c187df4b..d83b84c16f283cf6710be54960be13848d957e71 100644 (file)
 // 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;
 }
 
diff --git a/test/mjsunit/stack-traces-gc.js b/test/mjsunit/stack-traces-gc.js
new file mode 100644 (file)
index 0000000..dd878f2
--- /dev/null
@@ -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();
index 438eec979d1766ed831a45c594bc29b8e9beab7a..b5d58fa0759f3086bce84d139824114d0b80d505 100644 (file)
@@ -288,4 +288,42 @@ testOmittedBuiltin(function(){ [thrower, 2].sort(function (a,b) {
                    }, "QuickSort");
 
 // Omitted because ADD from runtime.js is non-native builtin.
-testOmittedBuiltin(function(){ thrower + 2; }, "ADD");
\ No newline at end of file
+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);
+
+// Test that modifying Error.prepareStackTrace by itself works.
+Error.prepareStackTrace = function() { Error.prepareStackTrace = "custom"; };
+new Error();
+
+assertEquals("custom", Error.prepareStackTrace);