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:
parent
0b5359bf87
commit
72dfb27909
@ -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>();
|
||||
|
@ -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());
|
||||
}
|
||||
|
107
src/heap.cc
107
src/heap.cc
@ -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;
|
||||
|
42
src/heap.h
42
src/heap.h
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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) \
|
||||
\
|
||||
|
@ -161,6 +161,7 @@ void DeclarationContext::Check(const char* source,
|
||||
CHECK_EQ(value, catcher.Exception());
|
||||
}
|
||||
}
|
||||
HEAP->CollectAllAvailableGarbage(); // Clean slate for the next test.
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
119
test/mjsunit/stack-traces-gc.js
Normal file
119
test/mjsunit/stack-traces-gc.js
Normal 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();
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user