Fire 'stack' getter of error objects after GC.

BUG=v8:2340

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@13188 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
yangguo@chromium.org 2012-12-11 10:14:01 +00:00
parent 0b5359bf87
commit 72dfb27909
17 changed files with 411 additions and 44 deletions

View File

@ -1842,7 +1842,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()->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>();

View File

@ -644,6 +644,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());
}

View File

@ -550,6 +550,8 @@ void Heap::GarbageCollectionEpilogue() {
#ifdef ENABLE_DEBUGGER_SUPPORT
isolate_->debug()->AfterGarbageCollection();
#endif // ENABLE_DEBUGGER_SUPPORT
error_object_list_.DeferredFormatStackTrace(isolate());
}
@ -1354,6 +1356,8 @@ void Heap::Scavenge() {
UpdateNewSpaceReferencesInExternalStringTable(
&UpdateNewSpaceReferenceInExternalStringTableEntry);
error_object_list_.UpdateReferencesInNewSpace(this);
promotion_queue_.Destroy();
LiveObjectList::UpdateReferencesForScavengeGC();
@ -5870,6 +5874,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);
}
@ -6243,6 +6248,8 @@ void Heap::TearDown() {
external_string_table_.TearDown();
error_object_list_.TearDown();
new_space_.TearDown();
if (old_pointer_space_ != NULL) {
@ -7149,6 +7156,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()) {
@ -7158,6 +7167,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();
@ -7172,6 +7182,103 @@ 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_) 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) {
chunk->set_next_chunk(chunks_queued_for_free_);
chunks_queued_for_free_ = chunk;

View 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
@ -1573,6 +1609,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_;
@ -2149,6 +2189,8 @@ class Heap {
ExternalStringTable external_string_table_;
ErrorObjectList error_object_list_;
VisitorDispatchTable<ScavengingCallback> scavenging_visitors_table_;
MemoryChunk* chunks_queued_for_free_;

View File

@ -634,6 +634,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;
}

View 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]);

View 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;

View 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();
}
@ -2030,6 +2028,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;
@ -3069,6 +3068,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(

View 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;
@ -933,12 +932,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 +1017,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 +1063,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,6 +1081,7 @@ function GetTypeName(obj, requireConstructor) {
return constructorName;
}
function captureStackTrace(obj, cons_opt) {
var stackTraceLimit = $Error.stackTraceLimit;
if (!stackTraceLimit || !IS_NUMBER(stackTraceLimit)) return;
@ -1085,14 +1091,30 @@ function captureStackTrace(obj, cons_opt) {
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 @@ 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;
function getter() {
var holder = this;
while (!IS_ERROR(holder)) {
@ -1246,11 +1270,13 @@ function SetUpStackOverflowBoilerplate() {
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.

View File

@ -13098,6 +13098,17 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CollectStackTrace) {
}
// 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
// it since it is used only once to avoid keeping it alive.
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetOverflowedRawStackTrace) {

View File

@ -236,6 +236,7 @@ namespace internal {
F(FunctionIsBuiltin, 1, 1) \
F(GetScript, 1, 1) \
F(CollectStackTrace, 3, 1) \
F(MarkOneShotGetter, 1, 1) \
F(GetOverflowedRawStackTrace, 1, 1) \
F(GetV8Version, 0, 1) \
\

View File

@ -161,6 +161,7 @@ void DeclarationContext::Check(const char* source,
CHECK_EQ(value, catcher.Exception());
}
}
HEAP->CollectAllAvailableGarbage(); // Clean slate for the next test.
}

View File

@ -2429,11 +2429,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;

View 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

View File

@ -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;
}

View File

@ -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();

View File

@ -288,4 +288,36 @@ testOmittedBuiltin(function(){ [thrower, 2].sort(function (a,b) {
}, "QuickSort");
// Omitted because ADD from runtime.js is non-native builtin.
testOmittedBuiltin(function(){ thrower + 2; }, "ADD");
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);