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>();
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
}
+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());
}
#ifdef ENABLE_DEBUGGER_SUPPORT
isolate_->debug()->AfterGarbageCollection();
#endif // ENABLE_DEBUGGER_SUPPORT
+
+ error_object_list_.DeferredFormatStackTrace(isolate());
}
UpdateNewSpaceReferencesInExternalStringTable(
&UpdateNewSpaceReferenceInExternalStringTableEntry);
+ error_object_list_.UpdateReferencesInNewSpace(this);
+
promotion_queue_.Destroy();
LiveObjectList::UpdateReferencesForScavengeGC();
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);
}
external_string_table_.TearDown();
+ error_object_list_.TearDown();
+
new_space_.TearDown();
if (old_pointer_space_ != NULL) {
}
}
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()) {
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();
}
+// 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;
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, \
};
+// 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
return &external_string_table_;
}
+ ErrorObjectList* error_object_list() {
+ return &error_object_list_;
+ }
+
// Returns the current sweep generation.
int sweep_generation() {
return sweep_generation_;
ExternalStringTable external_string_table_;
+ ErrorObjectList error_object_list_;
+
VisitorDispatchTable<ScavengingCallback> scavenging_visitors_table_;
MemoryChunk* chunks_queued_for_free_;
}
Handle<JSArray> result = factory()->NewJSArrayWithElements(elements);
result->set_length(Smi::FromInt(cursor));
+ heap()->error_object_list()->Add(*error_object);
return result;
}
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;
}
+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]);
// 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;
// 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();
}
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;
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(
%_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;
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;
}
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;
}
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 {
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;
}
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;
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) {
// 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(
}
-// 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);
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) \
CHECK_EQ(value, catcher.Exception());
}
}
+ HEAP->CollectAllAvailableGarbage(); // Clean slate for the next test.
}
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;
*%(basename)s:31: TypeError: Cannot read property 'x' of undefined
undefined.x
^
+TypeError: Cannot read property 'x' of undefined
+ at *%(basename)s:31:10
+
// 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;
}
--- /dev/null
+// 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();
}, "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);