From 03866841aabae66709d15fcb341c7614839f8b68 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Mon, 24 Mar 2014 09:17:18 +0000 Subject: [PATCH] Revert "Remove Failure::OutOfMemory propagation and V8::IgnoreOutOfMemoryException." This reverts r20179. TBR=svenpanne@chromium.org Review URL: https://codereview.chromium.org/201573007 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@20183 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 17 +++++++++ src/api.cc | 16 ++++++++ src/arm/code-stubs-arm.cc | 43 +++++++++++++++++++++ src/arm64/code-stubs-arm64.cc | 45 ++++++++++++++++++++++ src/bootstrapper.cc | 3 ++ src/code-stubs.h | 1 + src/contexts.h | 7 ++++ src/elements.cc | 2 +- src/execution.cc | 8 ++++ src/heap-inl.h | 19 ++++++++-- src/heap.cc | 18 +++++---- src/ia32/code-stubs-ia32.cc | 41 ++++++++++++++++++++ src/isolate.cc | 87 +++++++++++++++++++++++++++++-------------- src/isolate.h | 23 +++++++++++- src/mips/code-stubs-mips.cc | 42 +++++++++++++++++++++ src/objects-inl.h | 16 ++++++++ src/objects.cc | 2 +- src/objects.h | 5 +++ src/runtime.cc | 4 +- src/x64/code-stubs-x64.cc | 42 +++++++++++++++++++++ test/cctest/test-api.cc | 58 +++++++++++++++++------------ test/cctest/test-strings.cc | 17 +++++++++ 22 files changed, 447 insertions(+), 69 deletions(-) diff --git a/include/v8.h b/include/v8.h index ef09994..5e54267 100644 --- a/include/v8.h +++ b/include/v8.h @@ -4616,6 +4616,20 @@ class V8_EXPORT V8 { static void SetArrayBufferAllocator(ArrayBuffer::Allocator* allocator); /** + * Ignore out-of-memory exceptions. + * + * V8 running out of memory is treated as a fatal error by default. + * This means that the fatal error handler is called and that V8 is + * terminated. + * + * IgnoreOutOfMemoryException can be used to not treat an + * out-of-memory situation as a fatal error. This way, the contexts + * that did not cause the out of memory problem might be able to + * continue execution. + */ + static void IgnoreOutOfMemoryException(); + + /** * Check if V8 is dead and therefore unusable. This is the case after * fatal errors such as out-of-memory situations. */ @@ -5219,6 +5233,9 @@ class V8_EXPORT Context { */ void Exit(); + /** Returns true if the context has experienced an out of memory situation. */ + bool HasOutOfMemoryException(); + /** Returns an isolate associated with a current context. */ v8::Isolate* GetIsolate(); diff --git a/src/api.cc b/src/api.cc index b1d133a..1c349b5 100644 --- a/src/api.cc +++ b/src/api.cc @@ -95,6 +95,11 @@ namespace v8 { (isolate)->handle_scope_implementer(); \ handle_scope_implementer->DecrementCallDepth(); \ if (has_pending_exception) { \ + if (handle_scope_implementer->CallDepthIsZero() && \ + (isolate)->is_out_of_memory()) { \ + if (!(isolate)->ignore_out_of_memory()) \ + i::V8::FatalProcessOutOfMemory(NULL); \ + } \ bool call_depth_is_zero = handle_scope_implementer->CallDepthIsZero(); \ (isolate)->OptionalRescheduleException(call_depth_is_zero); \ do_callback \ @@ -5256,6 +5261,12 @@ Handle v8::Context::GetSecurityToken() { } +bool Context::HasOutOfMemoryException() { + i::Handle env = Utils::OpenHandle(this); + return env->has_out_of_memory(); +} + + v8::Isolate* Context::GetIsolate() { i::Handle env = Utils::OpenHandle(this); return reinterpret_cast(env->GetIsolate()); @@ -6214,6 +6225,11 @@ Local v8::Integer::NewFromUnsigned(Isolate* isolate, uint32_t value) { } +void V8::IgnoreOutOfMemoryException() { + EnterIsolateIfNeeded()->set_ignore_out_of_memory(true); +} + + bool V8::AddMessageListener(MessageCallback that, Handle data) { i::Isolate* isolate = i::Isolate::Current(); EnsureInitializedForIsolate(isolate, "v8::V8::AddMessageListener()"); diff --git a/src/arm/code-stubs-arm.cc b/src/arm/code-stubs-arm.cc index b3df94e..5609cd0 100644 --- a/src/arm/code-stubs-arm.cc +++ b/src/arm/code-stubs-arm.cc @@ -1501,9 +1501,22 @@ void CEntryStub::GenerateAheadOfTime(Isolate* isolate) { } +static void JumpIfOOM(MacroAssembler* masm, + Register value, + Register scratch, + Label* oom_label) { + STATIC_ASSERT(Failure::OUT_OF_MEMORY_EXCEPTION == 3); + STATIC_ASSERT(kFailureTag == 3); + __ and_(scratch, value, Operand(0xf)); + __ cmp(scratch, Operand(0xf)); + __ b(eq, oom_label); +} + + void CEntryStub::GenerateCore(MacroAssembler* masm, Label* throw_normal_exception, Label* throw_termination_exception, + Label* throw_out_of_memory_exception, bool do_gc, bool always_allocate) { // r0: result parameter for PerformGC, if any @@ -1601,11 +1614,17 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, __ tst(r0, Operand(((1 << kFailureTypeTagSize) - 1) << kFailureTagSize)); __ b(eq, &retry); + // Special handling of out of memory exceptions. + JumpIfOOM(masm, r0, ip, throw_out_of_memory_exception); + // Retrieve the pending exception. __ mov(ip, Operand(ExternalReference(Isolate::kPendingExceptionAddress, isolate))); __ ldr(r0, MemOperand(ip)); + // See if we just retrieved an OOM exception. + JumpIfOOM(masm, r0, ip, throw_out_of_memory_exception); + // Clear the pending exception. __ LoadRoot(r3, Heap::kTheHoleValueRootIndex); __ mov(ip, Operand(ExternalReference(Isolate::kPendingExceptionAddress, @@ -1660,11 +1679,13 @@ void CEntryStub::Generate(MacroAssembler* masm) { Label throw_normal_exception; Label throw_termination_exception; + Label throw_out_of_memory_exception; // Call into the runtime system. GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, false, false); @@ -1672,6 +1693,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, false); @@ -1681,9 +1703,30 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, true); + __ bind(&throw_out_of_memory_exception); + // Set external caught exception to false. + Isolate* isolate = masm->isolate(); + ExternalReference external_caught(Isolate::kExternalCaughtExceptionAddress, + isolate); + __ mov(r0, Operand(false, RelocInfo::NONE32)); + __ mov(r2, Operand(external_caught)); + __ str(r0, MemOperand(r2)); + + // Set pending exception and r0 to out of memory exception. + Label already_have_failure; + JumpIfOOM(masm, r0, ip, &already_have_failure); + Failure* out_of_memory = Failure::OutOfMemoryException(0x1); + __ mov(r0, Operand(reinterpret_cast(out_of_memory))); + __ bind(&already_have_failure); + __ mov(r2, Operand(ExternalReference(Isolate::kPendingExceptionAddress, + isolate))); + __ str(r0, MemOperand(r2)); + // Fall through to the next label. + __ bind(&throw_termination_exception); __ ThrowUncatchable(r0); diff --git a/src/arm64/code-stubs-arm64.cc b/src/arm64/code-stubs-arm64.cc index 61b395f..f06f6bc 100644 --- a/src/arm64/code-stubs-arm64.cc +++ b/src/arm64/code-stubs-arm64.cc @@ -1405,6 +1405,18 @@ void CodeStub::GenerateFPStubs(Isolate* isolate) { } +static void JumpIfOOM(MacroAssembler* masm, + Register value, + Register scratch, + Label* oom_label) { + STATIC_ASSERT(Failure::OUT_OF_MEMORY_EXCEPTION == 3); + STATIC_ASSERT(kFailureTag == 3); + __ And(scratch, value, 0xf); + __ Cmp(scratch, 0xf); + __ B(eq, oom_label); +} + + bool CEntryStub::NeedsImmovableCode() { // CEntryStub stores the return address on the stack before calling into // C++ code. In some cases, the VM accesses this address, but it is not used @@ -1429,6 +1441,7 @@ void CEntryStub::GenerateAheadOfTime(Isolate* isolate) { void CEntryStub::GenerateCore(MacroAssembler* masm, Label* throw_normal, Label* throw_termination, + Label* throw_out_of_memory, bool do_gc, bool always_allocate) { // x0 : Result parameter for PerformGC, if do_gc is true. @@ -1576,6 +1589,10 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, __ Tst(result, kFailureTypeTagMask << kFailureTagSize); __ B(eq, &retry); // RETRY_AFTER_GC + // Special handling of out-of-memory exceptions: Pass the failure result, + // rather than the exception descriptor. + JumpIfOOM(masm, result, x10, throw_out_of_memory); + // Retrieve the pending exception. const Register& exception = result; const Register& exception_address = x11; @@ -1584,6 +1601,9 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, isolate))); __ Ldr(exception, MemOperand(exception_address)); + // See if we just retrieved an OOM exception. + JumpIfOOM(masm, exception, x10, throw_out_of_memory); + // Clear the pending exception. __ Mov(x10, Operand(isolate->factory()->the_hole_value())); __ Str(x10, MemOperand(exception_address)); @@ -1677,11 +1697,13 @@ void CEntryStub::Generate(MacroAssembler* masm) { Label throw_normal; Label throw_termination; + Label throw_out_of_memory; // Call the runtime function. GenerateCore(masm, &throw_normal, &throw_termination, + &throw_out_of_memory, false, false); @@ -1692,6 +1714,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal, &throw_termination, + &throw_out_of_memory, true, false); @@ -1700,6 +1723,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal, &throw_termination, + &throw_out_of_memory, true, true); @@ -1716,6 +1740,27 @@ void CEntryStub::Generate(MacroAssembler* masm) { // If we throw an exception, we can end up re-entering CEntryStub before we // pop the exit frame, so need to ensure that x21-x23 contain GC-safe values // here. + __ Bind(&throw_out_of_memory); + ASM_LOCATION("Throw out of memory"); + __ Mov(argv, 0); + __ Mov(argc, 0); + __ Mov(target, 0); + // Set external caught exception to false. + Isolate* isolate = masm->isolate(); + __ Mov(x2, Operand(ExternalReference(Isolate::kExternalCaughtExceptionAddress, + isolate))); + __ Str(xzr, MemOperand(x2)); + + // Set pending exception and x0 to out of memory exception. + Label already_have_failure; + JumpIfOOM(masm, x0, x10, &already_have_failure); + Failure* out_of_memory = Failure::OutOfMemoryException(0x1); + __ Mov(x0, Operand(reinterpret_cast(out_of_memory))); + __ Bind(&already_have_failure); + __ Mov(x2, Operand(ExternalReference(Isolate::kPendingExceptionAddress, + isolate))); + __ Str(x0, MemOperand(x2)); + // Fall through to the next label. __ Bind(&throw_termination); ASM_LOCATION("Throw termination"); diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 7942c7f..7edc5d5 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1310,6 +1310,9 @@ void Genesis::InitializeGlobal(Handle inner_global, delegate->shared()->DontAdaptArguments(); } + // Initialize the out of memory slot. + native_context()->set_out_of_memory(heap->false_value()); + // Initialize the embedder data slot. Handle embedder_data = factory->NewFixedArray(3); native_context()->set_embedder_data(*embedder_data); diff --git a/src/code-stubs.h b/src/code-stubs.h index 25328fe..3367c6d 100644 --- a/src/code-stubs.h +++ b/src/code-stubs.h @@ -1496,6 +1496,7 @@ class CEntryStub : public PlatformCodeStub { void GenerateCore(MacroAssembler* masm, Label* throw_normal_exception, Label* throw_termination_exception, + Label* throw_out_of_memory_exception, bool do_gc, bool always_allocate_scope); diff --git a/src/contexts.h b/src/contexts.h index 6ba9b3e..479a692 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -162,6 +162,7 @@ enum BindingFlags { V(SCRIPT_FUNCTION_INDEX, JSFunction, script_function) \ V(OPAQUE_REFERENCE_FUNCTION_INDEX, JSFunction, opaque_reference_function) \ V(CONTEXT_EXTENSION_FUNCTION_INDEX, JSFunction, context_extension_function) \ + V(OUT_OF_MEMORY_INDEX, Object, out_of_memory) \ V(MAP_CACHE_INDEX, Object, map_cache) \ V(EMBEDDER_DATA_INDEX, FixedArray, embedder_data) \ V(ALLOW_CODE_GEN_FROM_STRINGS_INDEX, Object, allow_code_gen_from_strings) \ @@ -439,6 +440,12 @@ class Context: public FixedArray { return map == map->GetHeap()->global_context_map(); } + // Tells whether the native context is marked with out of memory. + inline bool has_out_of_memory(); + + // Mark the native context with out of memory. + inline void mark_out_of_memory(); + // A native context holds a list of all functions with optimized code. void AddOptimizedFunction(JSFunction* function); void RemoveOptimizedFunction(JSFunction* function); diff --git a/src/elements.cc b/src/elements.cc index 527df6e..1ddc69d 100644 --- a/src/elements.cc +++ b/src/elements.cc @@ -318,7 +318,7 @@ MUST_USE_RESULT static MaybeObject* CopyDoubleToObjectElements( // that no GC is triggered, allocate HeapNumbers from old space if they // can't be taken from new space. if (!maybe_value->ToObject(&value)) { - ASSERT(maybe_value->IsRetryAfterGC()); + ASSERT(maybe_value->IsRetryAfterGC() || maybe_value->IsOutOfMemory()); Heap* heap = from->GetHeap(); MaybeObject* maybe_value_object = heap->AllocateHeapNumber(from->get_scalar(i + from_start), diff --git a/src/execution.cc b/src/execution.cc index 7442d17..1e0a6a8 100644 --- a/src/execution.cc +++ b/src/execution.cc @@ -135,6 +135,11 @@ static Handle Invoke(bool is_construct, ASSERT(*has_pending_exception == isolate->has_pending_exception()); if (*has_pending_exception) { isolate->ReportPendingMessages(); + if (isolate->pending_exception()->IsOutOfMemory()) { + if (!isolate->ignore_out_of_memory()) { + V8::FatalProcessOutOfMemory("JS", true); + } + } #ifdef ENABLE_DEBUGGER_SUPPORT // Reset stepping state when script exits with uncaught exception. if (isolate->debugger()->IsDebuggerActive()) { @@ -220,6 +225,9 @@ Handle Execution::TryCall(Handle func, ASSERT(catcher.HasCaught()); ASSERT(isolate->has_pending_exception()); ASSERT(isolate->external_caught_exception()); + if (isolate->is_out_of_memory() && !isolate->ignore_out_of_memory()) { + V8::FatalProcessOutOfMemory("OOM during Execution::TryCall"); + } if (isolate->pending_exception() == isolate->heap()->termination_exception()) { result = isolate->factory()->termination_exception(); diff --git a/src/heap-inl.h b/src/heap-inl.h index 7e465d5..efad2fb 100644 --- a/src/heap-inl.h +++ b/src/heap-inl.h @@ -138,7 +138,7 @@ MaybeObject* Heap::AllocateInternalizedStringImpl( MaybeObject* Heap::AllocateOneByteInternalizedString(Vector str, uint32_t hash_field) { if (str.length() > String::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); + return Failure::OutOfMemoryException(0x2); } // Compute map and object size. Map* map = ascii_internalized_string_map(); @@ -171,7 +171,7 @@ MaybeObject* Heap::AllocateOneByteInternalizedString(Vector str, MaybeObject* Heap::AllocateTwoByteInternalizedString(Vector str, uint32_t hash_field) { if (str.length() > String::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); + return Failure::OutOfMemoryException(0x3); } // Compute map and object size. Map* map = internalized_string_map(); @@ -641,18 +641,24 @@ Isolate* Heap::isolate() { // Warning: Do not use the identifiers __object__, __maybe_object__ or // __scope__ in a call to this macro. -#define CALL_AND_RETRY(ISOLATE, FUNCTION_CALL, RETURN_VALUE, RETURN_EMPTY) \ +#define CALL_AND_RETRY(ISOLATE, FUNCTION_CALL, RETURN_VALUE, RETURN_EMPTY, OOM)\ do { \ GC_GREEDY_CHECK(ISOLATE); \ MaybeObject* __maybe_object__ = FUNCTION_CALL; \ Object* __object__ = NULL; \ if (__maybe_object__->ToObject(&__object__)) RETURN_VALUE; \ + if (__maybe_object__->IsOutOfMemory()) { \ + OOM; \ + } \ if (!__maybe_object__->IsRetryAfterGC()) RETURN_EMPTY; \ (ISOLATE)->heap()->CollectGarbage(Failure::cast(__maybe_object__)-> \ allocation_space(), \ "allocation failure"); \ __maybe_object__ = FUNCTION_CALL; \ if (__maybe_object__->ToObject(&__object__)) RETURN_VALUE; \ + if (__maybe_object__->IsOutOfMemory()) { \ + OOM; \ + } \ if (!__maybe_object__->IsRetryAfterGC()) RETURN_EMPTY; \ (ISOLATE)->counters()->gc_last_resort_from_handles()->Increment(); \ (ISOLATE)->heap()->CollectAllAvailableGarbage("last resort gc"); \ @@ -661,6 +667,9 @@ Isolate* Heap::isolate() { __maybe_object__ = FUNCTION_CALL; \ } \ if (__maybe_object__->ToObject(&__object__)) RETURN_VALUE; \ + if (__maybe_object__->IsOutOfMemory()) { \ + OOM; \ + } \ if (__maybe_object__->IsRetryAfterGC()) { \ /* TODO(1181417): Fix this. */ \ v8::internal::Heap::FatalProcessOutOfMemory("CALL_AND_RETRY_LAST", true);\ @@ -674,7 +683,8 @@ Isolate* Heap::isolate() { ISOLATE, \ FUNCTION_CALL, \ RETURN_VALUE, \ - RETURN_EMPTY) + RETURN_EMPTY, \ + v8::internal::Heap::FatalProcessOutOfMemory("CALL_AND_RETRY", true)) #define CALL_HEAP_FUNCTION(ISOLATE, FUNCTION_CALL, TYPE) \ CALL_AND_RETRY_OR_DIE(ISOLATE, \ @@ -691,6 +701,7 @@ Isolate* Heap::isolate() { CALL_AND_RETRY(ISOLATE, \ FUNCTION_CALL, \ return __object__, \ + return __maybe_object__, \ return __maybe_object__) diff --git a/src/heap.cc b/src/heap.cc index 6b2f8f7..0691947 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -3871,7 +3871,8 @@ MaybeObject* Heap::AllocateExternalStringFromAscii( const ExternalAsciiString::Resource* resource) { size_t length = resource->length(); if (length > static_cast(String::kMaxLength)) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); + isolate()->context()->mark_out_of_memory(); + return Failure::OutOfMemoryException(0x5); } Map* map = external_ascii_string_map(); @@ -3893,7 +3894,8 @@ MaybeObject* Heap::AllocateExternalStringFromTwoByte( const ExternalTwoByteString::Resource* resource) { size_t length = resource->length(); if (length > static_cast(String::kMaxLength)) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); + isolate()->context()->mark_out_of_memory(); + return Failure::OutOfMemoryException(0x6); } // For small strings we check whether the resource contains only @@ -3944,7 +3946,7 @@ MaybeObject* Heap::LookupSingleCharacterStringFromCode(uint16_t code) { MaybeObject* Heap::AllocateByteArray(int length, PretenureFlag pretenure) { if (length < 0 || length > ByteArray::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid array length", true); + return Failure::OutOfMemoryException(0x7); } int size = ByteArray::SizeFor(length); AllocationSpace space = SelectSpace(size, OLD_DATA_SPACE, pretenure); @@ -4979,7 +4981,7 @@ MaybeObject* Heap::AllocateInternalizedStringImpl( Map* map; if (chars > String::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); + return Failure::OutOfMemoryException(0x9); } if (is_one_byte) { map = ascii_internalized_string_map(); @@ -5027,7 +5029,7 @@ MaybeObject* Heap::AllocateInternalizedStringImpl( MaybeObject* Heap::AllocateRawOneByteString(int length, PretenureFlag pretenure) { if (length < 0 || length > String::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); + return Failure::OutOfMemoryException(0xb); } int size = SeqOneByteString::SizeFor(length); ASSERT(size <= SeqOneByteString::kMaxSize); @@ -5051,7 +5053,7 @@ MaybeObject* Heap::AllocateRawOneByteString(int length, MaybeObject* Heap::AllocateRawTwoByteString(int length, PretenureFlag pretenure) { if (length < 0 || length > String::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); + return Failure::OutOfMemoryException(0xc); } int size = SeqTwoByteString::SizeFor(length); ASSERT(size <= SeqTwoByteString::kMaxSize); @@ -5199,7 +5201,7 @@ MaybeObject* Heap::CopyConstantPoolArrayWithMap(ConstantPoolArray* src, MaybeObject* Heap::AllocateRawFixedArray(int length, PretenureFlag pretenure) { if (length < 0 || length > FixedArray::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid array length", true); + return Failure::OutOfMemoryException(0xe); } int size = FixedArray::SizeFor(length); AllocationSpace space = SelectSpace(size, OLD_POINTER_SPACE, pretenure); @@ -5311,7 +5313,7 @@ MaybeObject* Heap::AllocateFixedDoubleArrayWithHoles( MaybeObject* Heap::AllocateRawFixedDoubleArray(int length, PretenureFlag pretenure) { if (length < 0 || length > FixedDoubleArray::kMaxLength) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid array length", true); + return Failure::OutOfMemoryException(0xf); } int size = FixedDoubleArray::SizeFor(length); #ifndef V8_HOST_ARCH_64_BIT diff --git a/src/ia32/code-stubs-ia32.cc b/src/ia32/code-stubs-ia32.cc index ce9bd21..fa67502 100644 --- a/src/ia32/code-stubs-ia32.cc +++ b/src/ia32/code-stubs-ia32.cc @@ -2583,9 +2583,23 @@ void CEntryStub::GenerateAheadOfTime(Isolate* isolate) { } +static void JumpIfOOM(MacroAssembler* masm, + Register value, + Register scratch, + Label* oom_label) { + __ mov(scratch, value); + STATIC_ASSERT(Failure::OUT_OF_MEMORY_EXCEPTION == 3); + STATIC_ASSERT(kFailureTag == 3); + __ and_(scratch, 0xf); + __ cmp(scratch, 0xf); + __ j(equal, oom_label); +} + + void CEntryStub::GenerateCore(MacroAssembler* masm, Label* throw_normal_exception, Label* throw_termination_exception, + Label* throw_out_of_memory_exception, bool do_gc, bool always_allocate_scope) { // eax: result parameter for PerformGC, if any @@ -2680,9 +2694,15 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, __ test(eax, Immediate(((1 << kFailureTypeTagSize) - 1) << kFailureTagSize)); __ j(zero, &retry, Label::kNear); + // Special handling of out of memory exceptions. + JumpIfOOM(masm, eax, ecx, throw_out_of_memory_exception); + // Retrieve the pending exception. __ mov(eax, Operand::StaticVariable(pending_exception_address)); + // See if we just retrieved an OOM exception. + JumpIfOOM(masm, eax, ecx, throw_out_of_memory_exception); + // Clear the pending exception. __ mov(edx, Immediate(masm->isolate()->factory()->the_hole_value())); __ mov(Operand::StaticVariable(pending_exception_address), edx); @@ -2726,11 +2746,13 @@ void CEntryStub::Generate(MacroAssembler* masm) { Label throw_normal_exception; Label throw_termination_exception; + Label throw_out_of_memory_exception; // Call into the runtime system. GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, false, false); @@ -2738,6 +2760,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, false); @@ -2747,9 +2770,27 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, true); + __ bind(&throw_out_of_memory_exception); + // Set external caught exception to false. + Isolate* isolate = masm->isolate(); + ExternalReference external_caught(Isolate::kExternalCaughtExceptionAddress, + isolate); + __ mov(Operand::StaticVariable(external_caught), Immediate(false)); + + // Set pending exception and eax to out of memory exception. + ExternalReference pending_exception(Isolate::kPendingExceptionAddress, + isolate); + Label already_have_failure; + JumpIfOOM(masm, eax, ecx, &already_have_failure); + __ mov(eax, reinterpret_cast(Failure::OutOfMemoryException(0x1))); + __ bind(&already_have_failure); + __ mov(Operand::StaticVariable(pending_exception), eax); + // Fall through to the next label. + __ bind(&throw_termination_exception); __ ThrowUncatchable(eax); diff --git a/src/isolate.cc b/src/isolate.cc index 2e94828..5518f00 100644 --- a/src/isolate.cc +++ b/src/isolate.cc @@ -80,6 +80,10 @@ int ThreadId::GetCurrentThreadId() { ThreadLocalTop::ThreadLocalTop() { InitializeInternal(); + // This flag may be set using v8::V8::IgnoreOutOfMemoryException() + // before an isolate is initialized. The initialize methods below do + // not touch it to preserve its value. + ignore_out_of_memory_ = false; } @@ -1269,8 +1273,14 @@ void Isolate::ReportPendingMessages() { ASSERT(has_pending_exception()); PropagatePendingExceptionToExternalTryCatch(); + // If the pending exception is OutOfMemoryException set out_of_memory in + // the native context. Note: We have to mark the native context here + // since the GenerateThrowOutOfMemory stub cannot make a RuntimeCall to + // set it. HandleScope scope(this); - if (thread_local_top_.pending_exception_ == + if (thread_local_top_.pending_exception_->IsOutOfMemory()) { + context()->mark_out_of_memory(); + } else if (thread_local_top_.pending_exception_ == heap()->termination_exception()) { // Do nothing: if needed, the exception has been already propagated to // v8::TryCatch. @@ -1301,7 +1311,8 @@ void Isolate::ReportPendingMessages() { MessageLocation Isolate::GetMessageLocation() { ASSERT(has_pending_exception()); - if (thread_local_top_.pending_exception_ != heap()->termination_exception() && + if (!thread_local_top_.pending_exception_->IsOutOfMemory() && + thread_local_top_.pending_exception_ != heap()->termination_exception() && thread_local_top_.has_pending_message_ && !thread_local_top_.pending_message_obj_->IsTheHole() && !thread_local_top_.pending_message_obj_->IsTheHole()) { @@ -1320,36 +1331,39 @@ bool Isolate::OptionalRescheduleException(bool is_bottom_call) { ASSERT(has_pending_exception()); PropagatePendingExceptionToExternalTryCatch(); - bool is_termination_exception = - pending_exception() == heap_.termination_exception(); + // Always reschedule out of memory exceptions. + if (!is_out_of_memory()) { + bool is_termination_exception = + pending_exception() == heap_.termination_exception(); - // Do not reschedule the exception if this is the bottom call. - bool clear_exception = is_bottom_call; + // Do not reschedule the exception if this is the bottom call. + bool clear_exception = is_bottom_call; - if (is_termination_exception) { - if (is_bottom_call) { + if (is_termination_exception) { + if (is_bottom_call) { + thread_local_top()->external_caught_exception_ = false; + clear_pending_exception(); + return false; + } + } else if (thread_local_top()->external_caught_exception_) { + // If the exception is externally caught, clear it if there are no + // JavaScript frames on the way to the C++ frame that has the + // external handler. + ASSERT(thread_local_top()->try_catch_handler_address() != NULL); + Address external_handler_address = + thread_local_top()->try_catch_handler_address(); + JavaScriptFrameIterator it(this); + if (it.done() || (it.frame()->sp() > external_handler_address)) { + clear_exception = true; + } + } + + // Clear the exception if needed. + if (clear_exception) { thread_local_top()->external_caught_exception_ = false; clear_pending_exception(); return false; } - } else if (thread_local_top()->external_caught_exception_) { - // If the exception is externally caught, clear it if there are no - // JavaScript frames on the way to the C++ frame that has the - // external handler. - ASSERT(thread_local_top()->try_catch_handler_address() != NULL); - Address external_handler_address = - thread_local_top()->try_catch_handler_address(); - JavaScriptFrameIterator it(this); - if (it.done() || (it.frame()->sp() > external_handler_address)) { - clear_exception = true; - } - } - - // Clear the exception if needed. - if (clear_exception) { - thread_local_top()->external_caught_exception_ = false; - clear_pending_exception(); - return false; } // Reschedule the exception. @@ -1369,6 +1383,23 @@ void Isolate::SetCaptureStackTraceForUncaughtExceptions( } +bool Isolate::is_out_of_memory() { + if (has_pending_exception()) { + MaybeObject* e = pending_exception(); + if (e->IsFailure() && Failure::cast(e)->IsOutOfMemoryException()) { + return true; + } + } + if (has_scheduled_exception()) { + MaybeObject* e = scheduled_exception(); + if (e->IsFailure() && Failure::cast(e)->IsOutOfMemoryException()) { + return true; + } + } + return false; +} + + Handle Isolate::native_context() { return Handle(context()->global_object()->native_context()); } @@ -1820,7 +1851,9 @@ void Isolate::PropagatePendingExceptionToExternalTryCatch() { if (!external_caught) return; - if (thread_local_top_.pending_exception_ == + if (thread_local_top_.pending_exception_->IsOutOfMemory()) { + // Do not propagate OOM exception: we should kill VM asap. + } else if (thread_local_top_.pending_exception_ == heap()->termination_exception()) { try_catch_handler()->can_continue_ = false; try_catch_handler()->has_terminated_ = true; diff --git a/src/isolate.h b/src/isolate.h index 9111a1d..1f3361c 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -288,6 +288,9 @@ class ThreadLocalTop BASE_EMBEDDED { // Head of the list of live LookupResults. LookupResult* top_lookup_result_; + // Whether out of memory exceptions should be ignored. + bool ignore_out_of_memory_; + private: void InitializeInternal(); @@ -638,7 +641,8 @@ class Isolate { bool IsExternallyCaught(); bool is_catchable_by_javascript(MaybeObject* exception) { - return exception != heap()->termination_exception(); + return (!exception->IsOutOfMemory()) && + (exception != heap()->termination_exception()); } // Serializer. @@ -717,6 +721,12 @@ class Isolate { int frame_limit, StackTrace::StackTraceOptions options); + // Tells whether the current context has experienced an out of memory + // exception. + bool is_out_of_memory(); + + THREAD_LOCAL_TOP_ACCESSOR(bool, ignore_out_of_memory) + void PrintCurrentStackTrace(FILE* out); void PrintStack(StringStream* accumulator); void PrintStack(FILE* out); @@ -1465,6 +1475,17 @@ class PostponeInterruptsScope BASE_EMBEDDED { }; +// Tells whether the native context is marked with out of memory. +inline bool Context::has_out_of_memory() { + return native_context()->out_of_memory()->IsTrue(); +} + + +// Mark the native context with out of memory. +inline void Context::mark_out_of_memory() { + native_context()->set_out_of_memory(GetIsolate()->heap()->true_value()); +} + class CodeTracer V8_FINAL : public Malloced { public: explicit CodeTracer(int isolate_id) diff --git a/src/mips/code-stubs-mips.cc b/src/mips/code-stubs-mips.cc index 3b8e223..6d06bd9 100644 --- a/src/mips/code-stubs-mips.cc +++ b/src/mips/code-stubs-mips.cc @@ -1606,9 +1606,21 @@ void CEntryStub::GenerateAheadOfTime(Isolate* isolate) { } +static void JumpIfOOM(MacroAssembler* masm, + Register value, + Register scratch, + Label* oom_label) { + STATIC_ASSERT(Failure::OUT_OF_MEMORY_EXCEPTION == 3); + STATIC_ASSERT(kFailureTag == 3); + __ andi(scratch, value, 0xf); + __ Branch(oom_label, eq, scratch, Operand(0xf)); +} + + void CEntryStub::GenerateCore(MacroAssembler* masm, Label* throw_normal_exception, Label* throw_termination_exception, + Label* throw_out_of_memory_exception, bool do_gc, bool always_allocate) { // v0: result parameter for PerformGC, if any @@ -1711,11 +1723,17 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, __ andi(t0, v0, ((1 << kFailureTypeTagSize) - 1) << kFailureTagSize); __ Branch(&retry, eq, t0, Operand(zero_reg)); + // Special handling of out of memory exceptions. + JumpIfOOM(masm, v0, t0, throw_out_of_memory_exception); + // Retrieve the pending exception. __ li(t0, Operand(ExternalReference(Isolate::kPendingExceptionAddress, isolate))); __ lw(v0, MemOperand(t0)); + // See if we just retrieved an OOM exception. + JumpIfOOM(masm, v0, t0, throw_out_of_memory_exception); + // Clear the pending exception. __ li(a3, Operand(isolate->factory()->the_hole_value())); __ li(t0, Operand(ExternalReference(Isolate::kPendingExceptionAddress, @@ -1769,11 +1787,13 @@ void CEntryStub::Generate(MacroAssembler* masm) { Label throw_normal_exception; Label throw_termination_exception; + Label throw_out_of_memory_exception; // Call into the runtime system. GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, false, false); @@ -1781,6 +1801,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, false); @@ -1790,9 +1811,30 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, true); + __ bind(&throw_out_of_memory_exception); + // Set external caught exception to false. + Isolate* isolate = masm->isolate(); + ExternalReference external_caught(Isolate::kExternalCaughtExceptionAddress, + isolate); + __ li(a0, Operand(false, RelocInfo::NONE32)); + __ li(a2, Operand(external_caught)); + __ sw(a0, MemOperand(a2)); + + // Set pending exception and v0 to out of memory exception. + Label already_have_failure; + JumpIfOOM(masm, v0, t0, &already_have_failure); + Failure* out_of_memory = Failure::OutOfMemoryException(0x1); + __ li(v0, Operand(reinterpret_cast(out_of_memory))); + __ bind(&already_have_failure); + __ li(a2, Operand(ExternalReference(Isolate::kPendingExceptionAddress, + isolate))); + __ sw(v0, MemOperand(a2)); + // Fall through to the next label. + __ bind(&throw_termination_exception); __ ThrowUncatchable(v0); diff --git a/src/objects-inl.h b/src/objects-inl.h index b3f23e6..2e60a44 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -649,6 +649,12 @@ bool MaybeObject::IsRetryAfterGC() { } +bool MaybeObject::IsOutOfMemory() { + return HAS_FAILURE_TAG(this) + && Failure::cast(this)->IsOutOfMemoryException(); +} + + bool MaybeObject::IsException() { return this == Failure::Exception(); } @@ -1239,6 +1245,11 @@ bool Failure::IsInternalError() const { } +bool Failure::IsOutOfMemoryException() const { + return type() == OUT_OF_MEMORY_EXCEPTION; +} + + AllocationSpace Failure::allocation_space() const { ASSERT_EQ(RETRY_AFTER_GC, type()); return static_cast((value() >> kFailureTypeTagSize) @@ -1256,6 +1267,11 @@ Failure* Failure::Exception() { } +Failure* Failure::OutOfMemoryException(intptr_t value) { + return Construct(OUT_OF_MEMORY_EXCEPTION, value); +} + + intptr_t Failure::value() const { return static_cast( reinterpret_cast(this) >> kFailureTagSize); diff --git a/src/objects.cc b/src/objects.cc index c764b33..dae3223 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -13921,7 +13921,7 @@ MaybeObject* HashTable::Allocate(Heap* heap, ? at_least_space_for : ComputeCapacity(at_least_space_for); if (capacity > HashTable::kMaxCapacity) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid table size", true); + return Failure::OutOfMemoryException(0x10); } Object* obj; diff --git a/src/objects.h b/src/objects.h index c4d3f25..34edd0a 100644 --- a/src/objects.h +++ b/src/objects.h @@ -934,6 +934,7 @@ class MaybeObject BASE_EMBEDDED { public: inline bool IsFailure(); inline bool IsRetryAfterGC(); + inline bool IsOutOfMemory(); inline bool IsException(); INLINE(bool IsTheHole()); INLINE(bool IsUninitialized()); @@ -1727,11 +1728,15 @@ class Failure: public MaybeObject { inline AllocationSpace allocation_space() const; inline bool IsInternalError() const; + inline bool IsOutOfMemoryException() const; static inline Failure* RetryAfterGC(AllocationSpace space); static inline Failure* RetryAfterGC(); // NEW_SPACE static inline Failure* Exception(); static inline Failure* InternalError(); + // TODO(jkummerow): The value is temporary instrumentation. Remove it + // when it has served its purpose. + static inline Failure* OutOfMemoryException(intptr_t value); // Casting. static inline Failure* cast(MaybeObject* object); diff --git a/src/runtime.cc b/src/runtime.cc index 8b18e55..b4c34ef 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -3913,9 +3913,7 @@ MUST_USE_RESULT static MaybeObject* StringReplaceGlobalAtomRegExpWithString( static_cast(pattern_len)) * static_cast(matches) + static_cast(subject_len); - if (result_len_64 > INT_MAX) { - v8::internal::Heap::FatalProcessOutOfMemory("invalid string length", true); - } + if (result_len_64 > INT_MAX) return Failure::OutOfMemoryException(0x11); int result_len = static_cast(result_len_64); int subject_pos = 0; diff --git a/src/x64/code-stubs-x64.cc b/src/x64/code-stubs-x64.cc index dc5496c..ce820b1 100644 --- a/src/x64/code-stubs-x64.cc +++ b/src/x64/code-stubs-x64.cc @@ -2407,9 +2407,23 @@ void CEntryStub::GenerateAheadOfTime(Isolate* isolate) { } +static void JumpIfOOM(MacroAssembler* masm, + Register value, + Register scratch, + Label* oom_label) { + __ movp(scratch, value); + STATIC_ASSERT(Failure::OUT_OF_MEMORY_EXCEPTION == 3); + STATIC_ASSERT(kFailureTag == 3); + __ and_(scratch, Immediate(0xf)); + __ cmpq(scratch, Immediate(0xf)); + __ j(equal, oom_label); +} + + void CEntryStub::GenerateCore(MacroAssembler* masm, Label* throw_normal_exception, Label* throw_termination_exception, + Label* throw_out_of_memory_exception, bool do_gc, bool always_allocate_scope) { // rax: result parameter for PerformGC, if any. @@ -2516,6 +2530,9 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, __ testl(rax, Immediate(((1 << kFailureTypeTagSize) - 1) << kFailureTagSize)); __ j(zero, &retry, Label::kNear); + // Special handling of out of memory exceptions. + JumpIfOOM(masm, rax, kScratchRegister, throw_out_of_memory_exception); + // Retrieve the pending exception. ExternalReference pending_exception_address( Isolate::kPendingExceptionAddress, masm->isolate()); @@ -2523,6 +2540,9 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, masm->ExternalOperand(pending_exception_address); __ movp(rax, pending_exception_operand); + // See if we just retrieved an OOM exception. + JumpIfOOM(masm, rax, kScratchRegister, throw_out_of_memory_exception); + // Clear the pending exception. pending_exception_operand = masm->ExternalOperand(pending_exception_address); @@ -2578,11 +2598,13 @@ void CEntryStub::Generate(MacroAssembler* masm) { Label throw_normal_exception; Label throw_termination_exception; + Label throw_out_of_memory_exception; // Call into the runtime system. GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, false, false); @@ -2590,6 +2612,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, false); @@ -2599,9 +2622,28 @@ void CEntryStub::Generate(MacroAssembler* masm) { GenerateCore(masm, &throw_normal_exception, &throw_termination_exception, + &throw_out_of_memory_exception, true, true); + __ bind(&throw_out_of_memory_exception); + // Set external caught exception to false. + Isolate* isolate = masm->isolate(); + ExternalReference external_caught(Isolate::kExternalCaughtExceptionAddress, + isolate); + __ Set(rax, static_cast(false)); + __ Store(external_caught, rax); + + // Set pending exception and rax to out of memory exception. + ExternalReference pending_exception(Isolate::kPendingExceptionAddress, + isolate); + Label already_have_failure; + JumpIfOOM(masm, rax, kScratchRegister, &already_have_failure); + __ Move(rax, Failure::OutOfMemoryException(0x1), Assembler::RelocInfoNone()); + __ bind(&already_have_failure); + __ Store(pending_exception, rax); + // Fall through to the next label. + __ bind(&throw_termination_exception); __ ThrowUncatchable(rax); diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 0818f33..a159522 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -19332,6 +19332,7 @@ TEST(IsolateDifferentContexts) { class InitDefaultIsolateThread : public v8::internal::Thread { public: enum TestCase { + IgnoreOOM, SetResourceConstraints, SetFatalHandler, SetCounterFunction, @@ -19348,30 +19349,34 @@ class InitDefaultIsolateThread : public v8::internal::Thread { v8::Isolate* isolate = v8::Isolate::New(); isolate->Enter(); switch (testCase_) { - case SetResourceConstraints: { - static const int K = 1024; - v8::ResourceConstraints constraints; - constraints.set_max_young_space_size(256 * K); - constraints.set_max_old_space_size(4 * K * K); - v8::SetResourceConstraints(CcTest::isolate(), &constraints); - break; - } + case IgnoreOOM: + v8::V8::IgnoreOutOfMemoryException(); + break; - case SetFatalHandler: - v8::V8::SetFatalErrorHandler(NULL); - break; + case SetResourceConstraints: { + static const int K = 1024; + v8::ResourceConstraints constraints; + constraints.set_max_young_space_size(256 * K); + constraints.set_max_old_space_size(4 * K * K); + v8::SetResourceConstraints(CcTest::isolate(), &constraints); + break; + } - case SetCounterFunction: - v8::V8::SetCounterFunction(NULL); - break; + case SetFatalHandler: + v8::V8::SetFatalErrorHandler(NULL); + break; - case SetCreateHistogramFunction: - v8::V8::SetCreateHistogramFunction(NULL); - break; + case SetCounterFunction: + v8::V8::SetCounterFunction(NULL); + break; - case SetAddHistogramSampleFunction: - v8::V8::SetAddHistogramSampleFunction(NULL); - break; + case SetCreateHistogramFunction: + v8::V8::SetCreateHistogramFunction(NULL); + break; + + case SetAddHistogramSampleFunction: + v8::V8::SetAddHistogramSampleFunction(NULL); + break; } isolate->Exit(); isolate->Dispose(); @@ -19395,26 +19400,31 @@ static void InitializeTestHelper(InitDefaultIsolateThread::TestCase testCase) { TEST(InitializeDefaultIsolateOnSecondaryThread1) { - InitializeTestHelper(InitDefaultIsolateThread::SetResourceConstraints); + InitializeTestHelper(InitDefaultIsolateThread::IgnoreOOM); } TEST(InitializeDefaultIsolateOnSecondaryThread2) { - InitializeTestHelper(InitDefaultIsolateThread::SetFatalHandler); + InitializeTestHelper(InitDefaultIsolateThread::SetResourceConstraints); } TEST(InitializeDefaultIsolateOnSecondaryThread3) { - InitializeTestHelper(InitDefaultIsolateThread::SetCounterFunction); + InitializeTestHelper(InitDefaultIsolateThread::SetFatalHandler); } TEST(InitializeDefaultIsolateOnSecondaryThread4) { - InitializeTestHelper(InitDefaultIsolateThread::SetCreateHistogramFunction); + InitializeTestHelper(InitDefaultIsolateThread::SetCounterFunction); } TEST(InitializeDefaultIsolateOnSecondaryThread5) { + InitializeTestHelper(InitDefaultIsolateThread::SetCreateHistogramFunction); +} + + +TEST(InitializeDefaultIsolateOnSecondaryThread6) { InitializeTestHelper(InitDefaultIsolateThread::SetAddHistogramSampleFunction); } diff --git a/test/cctest/test-strings.cc b/test/cctest/test-strings.cc index 0f37c3e..8e63cb0 100644 --- a/test/cctest/test-strings.cc +++ b/test/cctest/test-strings.cc @@ -1275,6 +1275,23 @@ TEST(RobustSubStringStub) { } +TEST(RegExpOverflow) { + // Result string has the length 2^32, causing a 32-bit integer overflow. + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + LocalContext context; + v8::V8::IgnoreOutOfMemoryException(); + v8::Local result = CompileRun( + "var a = 'a'; " + "for (var i = 0; i < 16; i++) { " + " a += a; " + "} " + "a.replace(/a/g, a); "); + CHECK(result.IsEmpty()); + CHECK(context->HasOutOfMemoryException()); +} + + TEST(StringReplaceAtomTwoByteResult) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); -- 2.7.4