Revision: 13371
Author: [email protected]
Date: Mon Jan 14 05:19:27 2013
Log: Reland r13188, r13194, r13256 (Deferred formatting of error stack
trace during GC).
BUG=
Review URL: https://chromiumcodereview.appspot.com/11880018
http://code.google.com/p/v8/source/detail?r=13371
Added:
/branches/bleeding_edge/test/mjsunit/stack-traces-gc.js
Modified:
/branches/bleeding_edge/src/api.cc
/branches/bleeding_edge/src/handles.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 Mon Jan 14
05:19:27 2013
@@ -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 Mon Jan 14 03:22:05 2013
+++ /branches/bleeding_edge/src/api.cc Mon Jan 14 05:19:27 2013
@@ -1862,8 +1862,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()->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>();
=======================================
--- /branches/bleeding_edge/src/handles.cc Wed Jan 9 07:47:53 2013
+++ /branches/bleeding_edge/src/handles.cc Mon Jan 14 05:19:27 2013
@@ -375,6 +375,15 @@
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
=======================================
--- /branches/bleeding_edge/src/heap-inl.h Wed Jan 9 04:29:06 2013
+++ /branches/bleeding_edge/src/heap-inl.h Mon Jan 14 05:19:27 2013
@@ -667,6 +667,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 Mon Jan 14 04:59:41 2013
+++ /branches/bleeding_edge/src/heap.cc Mon Jan 14 05:19:27 2013
@@ -550,6 +550,8 @@
#ifdef ENABLE_DEBUGGER_SUPPORT
isolate_->debug()->AfterGarbageCollection();
#endif // ENABLE_DEBUGGER_SUPPORT
+
+ error_object_list_.DeferredFormatStackTrace(isolate());
}
@@ -1383,6 +1385,8 @@
UpdateNewSpaceReferencesInExternalStringTable(
&UpdateNewSpaceReferenceInExternalStringTableEntry);
+ error_object_list_.UpdateReferencesInNewSpace(this);
+
promotion_queue_.Destroy();
LiveObjectList::UpdateReferencesForScavengeGC();
@@ -5965,6 +5969,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);
}
@@ -6338,6 +6343,8 @@
external_string_table_.TearDown();
+ error_object_list_.TearDown();
+
new_space_.TearDown();
if (old_pointer_space_ != NULL) {
@@ -7244,6 +7251,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()) {
@@ -7253,6 +7262,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();
@@ -7265,6 +7275,118 @@
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_ || 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) {
=======================================
--- /branches/bleeding_edge/src/heap.h Wed Jan 9 06:01:39 2013
+++ /branches/bleeding_edge/src/heap.h Mon Jan 14 05:19:27 2013
@@ -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
@@ -1588,6 +1624,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() {
@@ -2165,6 +2205,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 Thu Jan 10 07:53:11 2013
+++ /branches/bleeding_edge/src/isolate.cc Mon Jan 14 05:19:27 2013
@@ -635,6 +635,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 Thu Dec 27 05:12:27 2012
+++ /branches/bleeding_edge/src/list-inl.h Mon Jan 14 05:19:27 2013
@@ -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 Thu Dec 27 05:12:27 2012
+++ /branches/bleeding_edge/src/list.h Mon Jan 14 05:19:27 2013
@@ -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 Fri Jan 11 05:13:11 2013
+++ /branches/bleeding_edge/src/mark-compact.cc Mon Jan 14 05:19:27 2013
@@ -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();
}
@@ -2059,6 +2057,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;
@@ -3098,6 +3097,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 Thu Dec 27 05:12:27 2012
+++ /branches/bleeding_edge/src/messages.js Mon Jan 14 05:19:27 2013
@@ -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;
@@ -883,7 +882,8 @@
}
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 @@
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 @@
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 @@
}
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 +1081,11 @@
}
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;
@@ -1082,17 +1093,40 @@
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 @@
// 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(
=======================================
--- /branches/bleeding_edge/src/runtime.cc Mon Jan 14 02:59:00 2013
+++ /branches/bleeding_edge/src/runtime.cc Mon Jan 14 05:19:27 2013
@@ -13220,17 +13220,47 @@
}
-// 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.
=======================================
--- /branches/bleeding_edge/src/runtime.h Wed Jan 9 01:32:12 2013
+++ /branches/bleeding_edge/src/runtime.h Mon Jan 14 05:19:27 2013
@@ -238,7 +238,9 @@
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) \
=======================================
--- /branches/bleeding_edge/test/cctest/test-decls.cc Thu Dec 27 05:12:27
2012
+++ /branches/bleeding_edge/test/cctest/test-decls.cc Mon Jan 14 05:19:27
2013
@@ -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 Fri Jan 11 05:13:11
2013
+++ /branches/bleeding_edge/test/cctest/test-heap.cc Mon Jan 14 05:19:27
2013
@@ -2436,11 +2436,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 Thu Dec
27 05:12:27 2012
+++ /branches/bleeding_edge/test/message/overwritten-builtins.out Mon Jan
14 05:19:27 2013
@@ -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 Thu Dec 27
05:12:27 2012
+++ /branches/bleeding_edge/test/mjsunit/eval-stack-trace.js Mon Jan 14
05:19:27 2013
@@ -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 Dec 27
05:12:27 2012
+++ /branches/bleeding_edge/test/mjsunit/stack-traces.js Mon Jan 14
05:19:27 2013
@@ -289,3 +289,41 @@
// 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);
+
+// Test that modifying Error.prepareStackTrace by itself works.
+Error.prepareStackTrace = function() { Error.prepareStackTrace = "custom";
};
+new Error();
+
+assertEquals("custom", Error.prepareStackTrace);
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev