Commit eadcc1c1 authored by yangguo@chromium.org's avatar yangguo@chromium.org

Reland r13188, r13194, r13256 (Deferred formatting of error stack trace during GC).

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
parent 17a73739
...@@ -1862,8 +1862,7 @@ v8::Local<Value> v8::TryCatch::StackTrace() const { ...@@ -1862,8 +1862,7 @@ v8::Local<Value> v8::TryCatch::StackTrace() const {
if (!raw_obj->IsJSObject()) return v8::Local<Value>(); if (!raw_obj->IsJSObject()) return v8::Local<Value>();
i::HandleScope scope(isolate_); i::HandleScope scope(isolate_);
i::Handle<i::JSObject> obj(i::JSObject::cast(raw_obj), isolate_); i::Handle<i::JSObject> obj(i::JSObject::cast(raw_obj), isolate_);
i::Handle<i::String> name = i::Handle<i::String> name = isolate_->factory()->stack_symbol();
isolate_->factory()->LookupOneByteSymbol(STATIC_ASCII_VECTOR("stack"));
if (!obj->HasProperty(*name)) return v8::Local<Value>(); if (!obj->HasProperty(*name)) return v8::Local<Value>();
i::Handle<i::Object> value = i::GetProperty(obj, name); i::Handle<i::Object> value = i::GetProperty(obj, name);
if (value.is_null()) return v8::Local<Value>(); if (value.is_null()) return v8::Local<Value>();
......
...@@ -375,6 +375,15 @@ Handle<JSValue> GetScriptWrapper(Handle<Script> script) { ...@@ -375,6 +375,15 @@ Handle<JSValue> GetScriptWrapper(Handle<Script> script) {
Handle<JSFunction> constructor = isolate->script_function(); Handle<JSFunction> constructor = isolate->script_function();
Handle<JSValue> result = Handle<JSValue> result =
Handle<JSValue>::cast(isolate->factory()->NewJSObject(constructor)); 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); result->set_value(*script);
// Create a new weak global handle and use it to cache the wrapper // Create a new weak global handle and use it to cache the wrapper
......
...@@ -669,6 +669,19 @@ void ExternalStringTable::ShrinkNewStrings(int position) { ...@@ -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() { void Heap::ClearInstanceofCache() {
set_instanceof_cache_function(the_hole_value()); set_instanceof_cache_function(the_hole_value());
} }
......
...@@ -550,6 +550,8 @@ void Heap::GarbageCollectionEpilogue() { ...@@ -550,6 +550,8 @@ void Heap::GarbageCollectionEpilogue() {
#ifdef ENABLE_DEBUGGER_SUPPORT #ifdef ENABLE_DEBUGGER_SUPPORT
isolate_->debug()->AfterGarbageCollection(); isolate_->debug()->AfterGarbageCollection();
#endif // ENABLE_DEBUGGER_SUPPORT #endif // ENABLE_DEBUGGER_SUPPORT
error_object_list_.DeferredFormatStackTrace(isolate());
} }
...@@ -1383,6 +1385,8 @@ void Heap::Scavenge() { ...@@ -1383,6 +1385,8 @@ void Heap::Scavenge() {
UpdateNewSpaceReferencesInExternalStringTable( UpdateNewSpaceReferencesInExternalStringTable(
&UpdateNewSpaceReferenceInExternalStringTableEntry); &UpdateNewSpaceReferenceInExternalStringTableEntry);
error_object_list_.UpdateReferencesInNewSpace(this);
promotion_queue_.Destroy(); promotion_queue_.Destroy();
LiveObjectList::UpdateReferencesForScavengeGC(); LiveObjectList::UpdateReferencesForScavengeGC();
...@@ -5965,6 +5969,7 @@ void Heap::IterateWeakRoots(ObjectVisitor* v, VisitMode mode) { ...@@ -5965,6 +5969,7 @@ void Heap::IterateWeakRoots(ObjectVisitor* v, VisitMode mode) {
mode != VISIT_ALL_IN_SWEEP_NEWSPACE) { mode != VISIT_ALL_IN_SWEEP_NEWSPACE) {
// Scavenge collections have special processing for this. // Scavenge collections have special processing for this.
external_string_table_.Iterate(v); external_string_table_.Iterate(v);
error_object_list_.Iterate(v);
} }
v->Synchronize(VisitorSynchronization::kExternalStringsTable); v->Synchronize(VisitorSynchronization::kExternalStringsTable);
} }
...@@ -6338,6 +6343,8 @@ void Heap::TearDown() { ...@@ -6338,6 +6343,8 @@ void Heap::TearDown() {
external_string_table_.TearDown(); external_string_table_.TearDown();
error_object_list_.TearDown();
new_space_.TearDown(); new_space_.TearDown();
if (old_pointer_space_ != NULL) { if (old_pointer_space_ != NULL) {
...@@ -7244,6 +7251,8 @@ void ExternalStringTable::CleanUp() { ...@@ -7244,6 +7251,8 @@ void ExternalStringTable::CleanUp() {
} }
} }
new_space_strings_.Rewind(last); new_space_strings_.Rewind(last);
new_space_strings_.Trim();
last = 0; last = 0;
for (int i = 0; i < old_space_strings_.length(); ++i) { for (int i = 0; i < old_space_strings_.length(); ++i) {
if (old_space_strings_[i] == heap_->the_hole_value()) { if (old_space_strings_[i] == heap_->the_hole_value()) {
...@@ -7253,6 +7262,7 @@ void ExternalStringTable::CleanUp() { ...@@ -7253,6 +7262,7 @@ void ExternalStringTable::CleanUp() {
old_space_strings_[last++] = old_space_strings_[i]; old_space_strings_[last++] = old_space_strings_[i];
} }
old_space_strings_.Rewind(last); old_space_strings_.Rewind(last);
old_space_strings_.Trim();
#ifdef VERIFY_HEAP #ifdef VERIFY_HEAP
if (FLAG_verify_heap) { if (FLAG_verify_heap) {
Verify(); Verify();
...@@ -7267,6 +7277,118 @@ void ExternalStringTable::TearDown() { ...@@ -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) { void Heap::QueueMemoryChunkForFree(MemoryChunk* chunk) {
chunk->set_next_chunk(chunks_queued_for_free_); chunk->set_next_chunk(chunks_queued_for_free_);
chunks_queued_for_free_ = chunk; chunks_queued_for_free_ = chunk;
......
...@@ -209,6 +209,7 @@ namespace internal { ...@@ -209,6 +209,7 @@ namespace internal {
V(char_at_symbol, "CharAt") \ V(char_at_symbol, "CharAt") \
V(undefined_symbol, "undefined") \ V(undefined_symbol, "undefined") \
V(value_of_symbol, "valueOf") \ V(value_of_symbol, "valueOf") \
V(stack_symbol, "stack") \
V(InitializeVarGlobal_symbol, "InitializeVarGlobal") \ V(InitializeVarGlobal_symbol, "InitializeVarGlobal") \
V(InitializeConstGlobal_symbol, "InitializeConstGlobal") \ V(InitializeConstGlobal_symbol, "InitializeConstGlobal") \
V(KeyedLoadElementMonomorphic_symbol, \ V(KeyedLoadElementMonomorphic_symbol, \
...@@ -427,6 +428,41 @@ class ExternalStringTable { ...@@ -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 { enum ArrayStorageAllocationMode {
DONT_INITIALIZE_ARRAY_ELEMENTS, DONT_INITIALIZE_ARRAY_ELEMENTS,
INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE
...@@ -1589,6 +1625,10 @@ class Heap { ...@@ -1589,6 +1625,10 @@ class Heap {
return &external_string_table_; return &external_string_table_;
} }
ErrorObjectList* error_object_list() {
return &error_object_list_;
}
// Returns the current sweep generation. // Returns the current sweep generation.
int sweep_generation() { int sweep_generation() {
return sweep_generation_; return sweep_generation_;
...@@ -2165,6 +2205,8 @@ class Heap { ...@@ -2165,6 +2205,8 @@ class Heap {
ExternalStringTable external_string_table_; ExternalStringTable external_string_table_;
ErrorObjectList error_object_list_;
VisitorDispatchTable<ScavengingCallback> scavenging_visitors_table_; VisitorDispatchTable<ScavengingCallback> scavenging_visitors_table_;
MemoryChunk* chunks_queued_for_free_; MemoryChunk* chunks_queued_for_free_;
......
...@@ -635,6 +635,7 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object, ...@@ -635,6 +635,7 @@ Handle<JSArray> Isolate::CaptureSimpleStackTrace(Handle<JSObject> error_object,
} }
Handle<JSArray> result = factory()->NewJSArrayWithElements(elements); Handle<JSArray> result = factory()->NewJSArrayWithElements(elements);
result->set_length(Smi::FromInt(cursor)); result->set_length(Smi::FromInt(cursor));
heap()->error_object_list()->Add(*error_object);
return result; return result;
} }
......
...@@ -85,8 +85,9 @@ void List<T, P>::ResizeAddInternal(const T& element, P alloc) { ...@@ -85,8 +85,9 @@ void List<T, P>::ResizeAddInternal(const T& element, P alloc) {
template<typename T, class P> template<typename T, class P>
void List<T, P>::Resize(int new_capacity, P alloc) { void List<T, P>::Resize(int new_capacity, P alloc) {
ASSERT_LE(length_, new_capacity);
T* new_data = NewData(new_capacity, alloc); 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_); List<T, P>::DeleteData(data_);
data_ = new_data; data_ = new_data;
capacity_ = new_capacity; capacity_ = new_capacity;
...@@ -161,6 +162,14 @@ void List<T, P>::Rewind(int pos) { ...@@ -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> template<typename T, class P>
void List<T, P>::Iterate(void (*callback)(T* x)) { void List<T, P>::Iterate(void (*callback)(T* x)) {
for (int i = 0; i < length_; i++) callback(&data_[i]); for (int i = 0; i < length_; i++) callback(&data_[i]);
......
...@@ -149,6 +149,9 @@ class List { ...@@ -149,6 +149,9 @@ class List {
// Drop the last 'count' elements from the list. // Drop the last 'count' elements from the list.
INLINE(void RewindBy(int count)) { Rewind(length_ - count); } 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; bool Contains(const T& elm) const;
int CountOccurrences(const T& elm, int start, int end) const; int CountOccurrences(const T& elm, int start, int end) const;
......
...@@ -835,8 +835,6 @@ void MarkCompactCollector::Finish() { ...@@ -835,8 +835,6 @@ void MarkCompactCollector::Finish() {
// GC, because it relies on the new address of certain old space // GC, because it relies on the new address of certain old space
// objects (empty string, illegal builtin). // objects (empty string, illegal builtin).
heap()->isolate()->stub_cache()->Clear(); heap()->isolate()->stub_cache()->Clear();
heap()->external_string_table_.CleanUp();
} }
...@@ -2059,6 +2057,7 @@ void MarkCompactCollector::AfterMarking() { ...@@ -2059,6 +2057,7 @@ void MarkCompactCollector::AfterMarking() {
symbol_table->ElementsRemoved(v.PointersRemoved()); symbol_table->ElementsRemoved(v.PointersRemoved());
heap()->external_string_table_.Iterate(&v); heap()->external_string_table_.Iterate(&v);
heap()->external_string_table_.CleanUp(); heap()->external_string_table_.CleanUp();
heap()->error_object_list_.RemoveUnmarked(heap());
// Process the weak references. // Process the weak references.
MarkCompactWeakObjectRetainer mark_compact_object_retainer; MarkCompactWeakObjectRetainer mark_compact_object_retainer;
...@@ -3098,6 +3097,9 @@ void MarkCompactCollector::EvacuateNewSpaceAndCandidates() { ...@@ -3098,6 +3097,9 @@ void MarkCompactCollector::EvacuateNewSpaceAndCandidates() {
heap_->UpdateReferencesInExternalStringTable( heap_->UpdateReferencesInExternalStringTable(
&UpdateReferenceInExternalStringTableEntry); &UpdateReferenceInExternalStringTableEntry);
// Update pointers in the new error object list.
heap_->error_object_list()->UpdateReferences();
if (!FLAG_watch_ic_patching) { if (!FLAG_watch_ic_patching) {
// Update JSFunction pointers from the runtime profiler. // Update JSFunction pointers from the runtime profiler.
heap()->isolate()->runtime_profiler()->UpdateSamplesAfterCompact( heap()->isolate()->runtime_profiler()->UpdateSamplesAfterCompact(
......
...@@ -820,7 +820,7 @@ function CallSiteGetMethodName() { ...@@ -820,7 +820,7 @@ function CallSiteGetMethodName() {
%_CallFunction(this.receiver, %_CallFunction(this.receiver,
ownName, ownName,
ObjectLookupSetter) === this.fun || ObjectLookupSetter) === this.fun ||
this.receiver[ownName] === this.fun)) { %GetDataProperty(this.receiver, ownName) === this.fun)) {
// To handle DontEnum properties we guess that the method has // To handle DontEnum properties we guess that the method has
// the same name as the function. // the same name as the function.
return ownName; return ownName;
...@@ -829,8 +829,7 @@ function CallSiteGetMethodName() { ...@@ -829,8 +829,7 @@ function CallSiteGetMethodName() {
for (var prop in this.receiver) { for (var prop in this.receiver) {
if (%_CallFunction(this.receiver, prop, ObjectLookupGetter) === this.fun || if (%_CallFunction(this.receiver, prop, ObjectLookupGetter) === this.fun ||
%_CallFunction(this.receiver, prop, ObjectLookupSetter) === this.fun || %_CallFunction(this.receiver, prop, ObjectLookupSetter) === this.fun ||
(!%_CallFunction(this.receiver, prop, ObjectLookupGetter) && %GetDataProperty(this.receiver, prop) === this.fun) {
this.receiver[prop] === this.fun)) {
// If we find more than one match bail out to avoid confusion. // If we find more than one match bail out to avoid confusion.
if (name) { if (name) {
return null; return null;
...@@ -883,7 +882,8 @@ function CallSiteGetPosition() { ...@@ -883,7 +882,8 @@ function CallSiteGetPosition() {
} }
function CallSiteIsConstructor() { function CallSiteIsConstructor() {
var constructor = this.receiver ? this.receiver.constructor : null; var receiver = this.receiver;
var constructor = receiver ? %GetDataProperty(receiver, "constructor") : null;
if (!constructor) { if (!constructor) {
return false; return false;
} }
...@@ -933,12 +933,14 @@ function CallSiteToString() { ...@@ -933,12 +933,14 @@ function CallSiteToString() {
var typeName = GetTypeName(this, true); var typeName = GetTypeName(this, true);
var methodName = this.getMethodName(); var methodName = this.getMethodName();
if (functionName) { if (functionName) {
if (typeName && functionName.indexOf(typeName) != 0) { if (typeName &&
%_CallFunction(functionName, typeName, StringIndexOf) != 0) {
line += typeName + "."; line += typeName + ".";
} }
line += functionName; line += functionName;
if (methodName && functionName.lastIndexOf("." + methodName) != if (methodName &&
functionName.length - methodName.length - 1) { (%_CallFunction(functionName, "." + methodName, StringIndexOf) !=
functionName.length - methodName.length - 1)) {
line += " [as " + methodName + "]"; line += " [as " + methodName + "]";
} }
} else { } else {
...@@ -1016,17 +1018,37 @@ function FormatEvalOrigin(script) { ...@@ -1016,17 +1018,37 @@ function FormatEvalOrigin(script) {
return eval_origin; return eval_origin;
} }
function FormatStackTrace(error, frames) {
var lines = []; function FormatErrorString(error) {
try { try {
lines.push(error.toString()); return %_CallFunction(error, ErrorToString);
} catch (e) { } catch (e) {
try { try {
lines.push("<error: " + e + ">"); return "<error: " + e + ">";
} catch (ee) { } 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++) { for (var i = 0; i < frames.length; i++) {
var frame = frames[i]; var frame = frames[i];
var line; var line;
...@@ -1042,25 +1064,9 @@ function FormatStackTrace(error, frames) { ...@@ -1042,25 +1064,9 @@ function FormatStackTrace(error, frames) {
} }
lines.push(" at " + 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) { function GetTypeName(obj, requireConstructor) {
var constructor = obj.receiver.constructor; var constructor = obj.receiver.constructor;
...@@ -1076,23 +1082,51 @@ function GetTypeName(obj, requireConstructor) { ...@@ -1076,23 +1082,51 @@ function GetTypeName(obj, requireConstructor) {
return constructorName; return constructorName;
} }
// Flag to prevent recursive call of Error.prepareStackTrace.
var formatting_custom_stack_trace = false;
function captureStackTrace(obj, cons_opt) { function captureStackTrace(obj, cons_opt) {
var stackTraceLimit = $Error.stackTraceLimit; var stackTraceLimit = $Error.stackTraceLimit;
if (!stackTraceLimit || !IS_NUMBER(stackTraceLimit)) return; if (!stackTraceLimit || !IS_NUMBER(stackTraceLimit)) return;
if (stackTraceLimit < 0 || stackTraceLimit > 10000) { if (stackTraceLimit < 0 || stackTraceLimit > 10000) {
stackTraceLimit = 10000; stackTraceLimit = 10000;
} }
var raw_stack = %CollectStackTrace(obj, var stack = %CollectStackTrace(obj,
cons_opt ? cons_opt : captureStackTrace, cons_opt ? cons_opt : captureStackTrace,
stackTraceLimit); 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 // Note that 'obj' and 'this' maybe different when called on objects that
// have the error object on its prototype chain. The getter replaces itself // 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. // 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 getter = function() {
var value = FormatRawStackTrace(obj, raw_stack); if (IS_STRING(stack)) return stack;
%DefineOrRedefineDataProperty(obj, 'stack', value, NONE); // Stack is still a raw array awaiting to be formatted.
return value; 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 'stack' property of the receiver is set as data property. If
// the receiver is the same as holder, this accessor pair is replaced. // the receiver is the same as holder, this accessor pair is replaced.
var setter = function(v) { var setter = function(v) {
...@@ -1239,23 +1273,32 @@ function SetUpStackOverflowBoilerplate() { ...@@ -1239,23 +1273,32 @@ function SetUpStackOverflowBoilerplate() {
// error object copy, but can be found on the prototype chain of 'this'. // 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 // When the stack trace is formatted, this accessor property is replaced by
// a data property. // 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() { function getter() {
var holder = this; var holder = this;
while (!IS_ERROR(holder)) { while (!IS_ERROR(holder)) {
holder = %GetPrototype(holder); holder = %GetPrototype(holder);
if (holder == null) return MakeSyntaxError('illegal_access', []); if (holder == null) return MakeSyntaxError('illegal_access', []);
} }
var raw_stack = %GetOverflowedRawStackTrace(holder); var stack = %GetOverflowedStackTrace(holder);
var result = IS_ARRAY(raw_stack) ? FormatRawStackTrace(holder, raw_stack) if (IS_STRING(stack)) return stack;
: void 0; if (IS_ARRAY(stack)) {
%DefineOrRedefineDataProperty(holder, 'stack', result, NONE); var result = FormatStackTrace(error_string, GetStackFrames(stack));
return result; %SetOverflowedStackTrace(holder, result);
return result;
}
return void 0;
} }
%MarkOneShotGetter(getter);
// The 'stack' property of the receiver is set as data property. If // The 'stack' property of the receiver is set as data property. If
// the receiver is the same as holder, this accessor pair is replaced. // the receiver is the same as holder, this accessor pair is replaced.
function setter(v) { function setter(v) {
%DefineOrRedefineDataProperty(this, 'stack', v, NONE); %DefineOrRedefineDataProperty(this, 'stack', v, NONE);
// Release the stack trace that is stored as hidden property, if exists.
%SetOverflowedStackTrace(this, void 0);
} }
%DefineOrRedefineAccessorProperty( %DefineOrRedefineAccessorProperty(
......
...@@ -13220,19 +13220,49 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CollectStackTrace) { ...@@ -13220,19 +13220,49 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_CollectStackTrace) {
} }
// Retrieve the raw stack trace collected on stack overflow and delete // Mark a function to recognize when called after GC to format the stack trace.
// it since it is used only once to avoid keeping it alive. RUNTIME_FUNCTION(MaybeObject*, Runtime_MarkOneShotGetter) {
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetOverflowedRawStackTrace) { 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); ASSERT_EQ(args.length(), 1);
CONVERT_ARG_CHECKED(JSObject, error_object, 0); CONVERT_ARG_CHECKED(JSObject, error_object, 0);
String* key = isolate->heap()->hidden_stack_trace_symbol(); String* key = isolate->heap()->hidden_stack_trace_symbol();
Object* result = error_object->GetHiddenProperty(key); Object* result = error_object->GetHiddenProperty(key);
RUNTIME_ASSERT(result->IsJSArray() || result->IsUndefined()); RUNTIME_ASSERT(result->IsJSArray() ||
error_object->DeleteHiddenProperty(key); result->IsString() ||
result->IsUndefined());
return result; 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. // Returns V8 version as a string.
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetV8Version) { RUNTIME_FUNCTION(MaybeObject*, Runtime_GetV8Version) {
ASSERT_EQ(args.length(), 0); ASSERT_EQ(args.length(), 0);
......
...@@ -238,7 +238,9 @@ namespace internal { ...@@ -238,7 +238,9 @@ namespace internal {
F(FunctionIsBuiltin, 1, 1) \ F(FunctionIsBuiltin, 1, 1) \
F(GetScript, 1, 1) \ F(GetScript, 1, 1) \
F(CollectStackTrace, 3, 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(GetV8Version, 0, 1) \
\ \
F(ClassOf, 1, 1) \ F(ClassOf, 1, 1) \
......
...@@ -161,6 +161,7 @@ void DeclarationContext::Check(const char* source, ...@@ -161,6 +161,7 @@ void DeclarationContext::Check(const char* source,
CHECK_EQ(value, catcher.Exception()); CHECK_EQ(value, catcher.Exception());
} }
} }
HEAP->CollectAllAvailableGarbage(); // Clean slate for the next test.
} }
......
...@@ -2436,11 +2436,7 @@ void ReleaseStackTraceDataTest(const char* source) { ...@@ -2436,11 +2436,7 @@ void ReleaseStackTraceDataTest(const char* source) {
CHECK(!resource->IsDisposed()); CHECK(!resource->IsDisposed());
} }
HEAP->CollectAllAvailableGarbage(); HEAP->CollectAllAvailableGarbage();
// External source is being retained by the stack trace.
CHECK(!resource->IsDisposed());
CompileRun("error.stack;");
HEAP->CollectAllAvailableGarbage();
// External source has been released. // External source has been released.
CHECK(resource->IsDisposed()); CHECK(resource->IsDisposed());
delete resource; delete resource;
......
...@@ -28,3 +28,6 @@ ...@@ -28,3 +28,6 @@
*%(basename)s:31: TypeError: Cannot read property 'x' of undefined *%(basename)s:31: TypeError: Cannot read property 'x' of undefined
undefined.x undefined.x
^ ^
TypeError: Cannot read property 'x' of undefined
at *%(basename)s:31:10
...@@ -26,12 +26,13 @@ ...@@ -26,12 +26,13 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Return the stack frames of an Error object. // Return the stack frames of an Error object.
Error.prepareStackTrace = function(error, frames) {
return frames;
}
Error.prototype.getFrames = function() { Error.prototype.getFrames = function() {
Error.prepareStackTrace = function(error, frames) {
return frames;
}
var frames = this.stack; var frames = this.stack;
Error.prepareStackTrace = undefined;
return frames; return frames;
} }
......
// 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();
...@@ -288,4 +288,42 @@ testOmittedBuiltin(function(){ [thrower, 2].sort(function (a,b) { ...@@ -288,4 +288,42 @@ testOmittedBuiltin(function(){ [thrower, 2].sort(function (a,b) {
}, "QuickSort"); }, "QuickSort");
// Omitted because ADD from runtime.js is non-native builtin. // Omitted because ADD from runtime.js is non-native builtin.
testOmittedBuiltin(function(){ thrower + 2; }, "ADD"); testOmittedBuiltin(function(){ thrower + 2; }, "ADD");
\ No newline at end of file
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);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment