| // Copyright 2009 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. |
| |
| #include "src/api/api-inl.h" |
| #include "src/execution/isolate.h" |
| #include "src/init/v8.h" |
| #include "src/objects/objects-inl.h" |
| #include "test/cctest/cctest.h" |
| |
| #include "src/base/platform/platform.h" |
| |
| v8::base::Semaphore* semaphore = nullptr; |
| |
| void Signal(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| semaphore->Signal(); |
| } |
| |
| |
| void TerminateCurrentThread(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| CHECK(!args.GetIsolate()->IsExecutionTerminating()); |
| args.GetIsolate()->TerminateExecution(); |
| } |
| |
| void Fail(const v8::FunctionCallbackInfo<v8::Value>& args) { UNREACHABLE(); } |
| |
| void Loop(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| CHECK(!args.GetIsolate()->IsExecutionTerminating()); |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(args.GetIsolate()->GetCurrentContext(), |
| "try { doloop(); fail(); } catch(e) { fail(); }"); |
| CHECK(result.IsEmpty()); |
| CHECK(args.GetIsolate()->IsExecutionTerminating()); |
| } |
| |
| |
| void DoLoop(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::TryCatch try_catch(args.GetIsolate()); |
| CHECK(!args.GetIsolate()->IsExecutionTerminating()); |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(args.GetIsolate()->GetCurrentContext(), |
| "function f() {" |
| " var term = true;" |
| " try {" |
| " while(true) {" |
| " if (term) terminate();" |
| " term = false;" |
| " }" |
| " fail();" |
| " } catch(e) {" |
| " fail();" |
| " }" |
| "}" |
| "f()"); |
| CHECK(result.IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.Exception()->IsNull()); |
| CHECK(try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.CanContinue()); |
| CHECK(args.GetIsolate()->IsExecutionTerminating()); |
| } |
| |
| |
| void DoLoopNoCall(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::TryCatch try_catch(args.GetIsolate()); |
| CHECK(!args.GetIsolate()->IsExecutionTerminating()); |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(args.GetIsolate()->GetCurrentContext(), |
| "var term = true;" |
| "while(true) {" |
| " if (term) terminate();" |
| " term = false;" |
| "}"); |
| CHECK(result.IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.Exception()->IsNull()); |
| CHECK(try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.CanContinue()); |
| CHECK(args.GetIsolate()->IsExecutionTerminating()); |
| } |
| |
| |
| v8::Local<v8::ObjectTemplate> CreateGlobalTemplate( |
| v8::Isolate* isolate, v8::FunctionCallback terminate, |
| v8::FunctionCallback doloop) { |
| v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); |
| global->Set(v8_str("terminate"), |
| v8::FunctionTemplate::New(isolate, terminate)); |
| global->Set(v8_str("fail"), v8::FunctionTemplate::New(isolate, Fail)); |
| global->Set(v8_str("loop"), v8::FunctionTemplate::New(isolate, Loop)); |
| global->Set(v8_str("doloop"), v8::FunctionTemplate::New(isolate, doloop)); |
| return global; |
| } |
| |
| |
| // Test that a single thread of JavaScript execution can terminate |
| // itself. |
| TEST(TerminateOnlyV8ThreadFromThreadItself) { |
| v8::HandleScope scope(CcTest::isolate()); |
| v8::Local<v8::ObjectTemplate> global = |
| CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop); |
| v8::Local<v8::Context> context = |
| v8::Context::New(CcTest::isolate(), nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!CcTest::isolate()->IsExecutionTerminating()); |
| // Run a loop that will be infinite if thread termination does not work. |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(CcTest::isolate()->GetCurrentContext(), |
| "try { loop(); fail(); } catch(e) { fail(); }"); |
| CHECK(result.IsEmpty()); |
| // Test that we can run the code again after thread termination. |
| CHECK(!CcTest::isolate()->IsExecutionTerminating()); |
| result = CompileRun(CcTest::isolate()->GetCurrentContext(), |
| "try { loop(); fail(); } catch(e) { fail(); }"); |
| CHECK(result.IsEmpty()); |
| } |
| |
| |
| // Test that a single thread of JavaScript execution can terminate |
| // itself in a loop that performs no calls. |
| TEST(TerminateOnlyV8ThreadFromThreadItselfNoLoop) { |
| v8::HandleScope scope(CcTest::isolate()); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall); |
| v8::Local<v8::Context> context = |
| v8::Context::New(CcTest::isolate(), nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!CcTest::isolate()->IsExecutionTerminating()); |
| // Run a loop that will be infinite if thread termination does not work. |
| static const char* source = "try { loop(); fail(); } catch(e) { fail(); }"; |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(CcTest::isolate()->GetCurrentContext(), source); |
| CHECK(result.IsEmpty()); |
| CHECK(!CcTest::isolate()->IsExecutionTerminating()); |
| // Test that we can run the code again after thread termination. |
| result = CompileRun(CcTest::isolate()->GetCurrentContext(), source); |
| CHECK(result.IsEmpty()); |
| } |
| |
| |
| class TerminatorThread : public v8::base::Thread { |
| public: |
| explicit TerminatorThread(i::Isolate* isolate) |
| : Thread(Options("TerminatorThread")), |
| isolate_(reinterpret_cast<v8::Isolate*>(isolate)) {} |
| void Run() override { |
| semaphore->Wait(); |
| CHECK(!isolate_->IsExecutionTerminating()); |
| isolate_->TerminateExecution(); |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| }; |
| |
| void TestTerminatingSlowOperation(const char* source) { |
| semaphore = new v8::base::Semaphore(0); |
| TerminatorThread thread(CcTest::i_isolate()); |
| CHECK(thread.Start()); |
| |
| v8::HandleScope scope(CcTest::isolate()); |
| v8::Local<v8::ObjectTemplate> global = |
| CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop); |
| v8::Local<v8::Context> context = |
| v8::Context::New(CcTest::isolate(), nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!CcTest::isolate()->IsExecutionTerminating()); |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(CcTest::isolate()->GetCurrentContext(), source); |
| CHECK(result.IsEmpty()); |
| thread.Join(); |
| delete semaphore; |
| semaphore = nullptr; |
| } |
| |
| // Test that a single thread of JavaScript execution can be terminated |
| // from the side by another thread. |
| TEST(TerminateOnlyV8ThreadFromOtherThread) { |
| // Run a loop that will be infinite if thread termination does not work. |
| TestTerminatingSlowOperation("try { loop(); fail(); } catch(e) { fail(); }"); |
| } |
| |
| // Test that execution can be terminated from within JSON.stringify. |
| TEST(TerminateJsonStringify) { |
| TestTerminatingSlowOperation( |
| "var x = [];" |
| "x[2**31]=1;" |
| "terminate();" |
| "JSON.stringify(x);" |
| "fail();"); |
| } |
| |
| TEST(TerminateBigIntMultiplication) { |
| TestTerminatingSlowOperation( |
| "terminate();" |
| "var a = 5n ** 555555n;" |
| "var b = 3n ** 3333333n;" |
| "a * b;" |
| "fail();"); |
| } |
| |
| TEST(TerminateBigIntDivision) { |
| TestTerminatingSlowOperation( |
| "var a = 2n ** 2222222n;" |
| "var b = 3n ** 333333n;" |
| "terminate();" |
| "a / b;" |
| "fail();"); |
| } |
| |
| TEST(TerminateBigIntToString) { |
| TestTerminatingSlowOperation( |
| "var a = 2n ** 2222222n;" |
| "terminate();" |
| "a.toString();" |
| "fail();"); |
| } |
| |
| int call_count = 0; |
| |
| |
| void TerminateOrReturnObject(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| if (++call_count == 10) { |
| CHECK(!args.GetIsolate()->IsExecutionTerminating()); |
| args.GetIsolate()->TerminateExecution(); |
| return; |
| } |
| v8::Local<v8::Object> result = v8::Object::New(args.GetIsolate()); |
| v8::Maybe<bool> val = |
| result->Set(args.GetIsolate()->GetCurrentContext(), v8_str("x"), |
| v8::Integer::New(args.GetIsolate(), 42)); |
| CHECK(val.FromJust()); |
| args.GetReturnValue().Set(result); |
| } |
| |
| |
| void LoopGetProperty(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::TryCatch try_catch(args.GetIsolate()); |
| CHECK(!args.GetIsolate()->IsExecutionTerminating()); |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(args.GetIsolate()->GetCurrentContext(), |
| "function f() {" |
| " try {" |
| " while(true) {" |
| " terminate_or_return_object().x;" |
| " }" |
| " fail();" |
| " } catch(e) {" |
| " (function() {})();" // trigger stack check. |
| " fail();" |
| " }" |
| "}" |
| "f()"); |
| CHECK(result.IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.Exception()->IsNull()); |
| CHECK(try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.CanContinue()); |
| CHECK(args.GetIsolate()->IsExecutionTerminating()); |
| } |
| |
| |
| // Test that we correctly handle termination exceptions if they are |
| // triggered by the creation of error objects in connection with ICs. |
| TEST(TerminateLoadICException) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); |
| global->Set(v8_str("terminate_or_return_object"), |
| v8::FunctionTemplate::New(isolate, TerminateOrReturnObject)); |
| global->Set(v8_str("fail"), v8::FunctionTemplate::New(isolate, Fail)); |
| global->Set(v8_str("loop"), |
| v8::FunctionTemplate::New(isolate, LoopGetProperty)); |
| |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!isolate->IsExecutionTerminating()); |
| // Run a loop that will be infinite if thread termination does not work. |
| static const char* source = "try { loop(); fail(); } catch(e) { fail(); }"; |
| call_count = 0; |
| v8::MaybeLocal<v8::Value> result = |
| CompileRun(isolate->GetCurrentContext(), source); |
| CHECK(result.IsEmpty()); |
| // Test that we can run the code again after thread termination. |
| CHECK(!isolate->IsExecutionTerminating()); |
| call_count = 0; |
| result = CompileRun(isolate->GetCurrentContext(), source); |
| CHECK(result.IsEmpty()); |
| } |
| |
| |
| v8::Persistent<v8::String> reenter_script_1; |
| v8::Persistent<v8::String> reenter_script_2; |
| |
| void ReenterAfterTermination(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::TryCatch try_catch(args.GetIsolate()); |
| v8::Isolate* isolate = args.GetIsolate(); |
| CHECK(!isolate->IsExecutionTerminating()); |
| v8::Local<v8::String> script = |
| v8::Local<v8::String>::New(isolate, reenter_script_1); |
| v8::MaybeLocal<v8::Value> result = CompileRun(script); |
| CHECK(result.IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.Exception()->IsNull()); |
| CHECK(try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.CanContinue()); |
| CHECK(try_catch.HasTerminated()); |
| CHECK(isolate->IsExecutionTerminating()); |
| script = v8::Local<v8::String>::New(isolate, reenter_script_2); |
| v8::MaybeLocal<v8::Script> compiled_script = |
| v8::Script::Compile(isolate->GetCurrentContext(), script); |
| CHECK(compiled_script.IsEmpty()); |
| } |
| |
| |
| // Test that reentry into V8 while the termination exception is still pending |
| // (has not yet unwound the 0-level JS frame) does not crash. |
| TEST(TerminateAndReenterFromThreadItself) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, ReenterAfterTermination); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!isolate->IsExecutionTerminating()); |
| // Create script strings upfront as it won't work when terminating. |
| reenter_script_1.Reset(isolate, v8_str( |
| "function f() {" |
| " var term = true;" |
| " try {" |
| " while(true) {" |
| " if (term) terminate();" |
| " term = false;" |
| " }" |
| " fail();" |
| " } catch(e) {" |
| " fail();" |
| " }" |
| "}" |
| "f()")); |
| reenter_script_2.Reset(isolate, v8_str("function f() { fail(); } f()")); |
| CompileRun("try { loop(); fail(); } catch(e) { fail(); }"); |
| CHECK(!isolate->IsExecutionTerminating()); |
| // Check we can run JS again after termination. |
| CHECK(CompileRun("function f() { return true; } f()")->IsTrue()); |
| reenter_script_1.Reset(); |
| reenter_script_2.Reset(); |
| } |
| |
| TEST(TerminateAndReenterFromThreadItselfWithOuterTryCatch) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, ReenterAfterTermination); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!isolate->IsExecutionTerminating()); |
| // Create script strings upfront as it won't work when terminating. |
| reenter_script_1.Reset(isolate, v8_str("function f() {" |
| " var term = true;" |
| " try {" |
| " while(true) {" |
| " if (term) terminate();" |
| " term = false;" |
| " }" |
| " fail();" |
| " } catch(e) {" |
| " fail();" |
| " }" |
| "}" |
| "f()")); |
| reenter_script_2.Reset(isolate, v8_str("function f() { fail(); } f()")); |
| { |
| v8::TryCatch try_catch(isolate); |
| CompileRun("try { loop(); fail(); } catch(e) { fail(); }"); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.Exception()->IsNull()); |
| CHECK(try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.CanContinue()); |
| CHECK(try_catch.HasTerminated()); |
| CHECK(isolate->IsExecutionTerminating()); |
| } |
| CHECK(!isolate->IsExecutionTerminating()); |
| // Check we can run JS again after termination. |
| CHECK(CompileRun("function f() { return true; } f()")->IsTrue()); |
| reenter_script_1.Reset(); |
| reenter_script_2.Reset(); |
| } |
| |
| void DoLoopCancelTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| v8::Isolate* isolate = args.GetIsolate(); |
| v8::TryCatch try_catch(isolate); |
| CHECK(!isolate->IsExecutionTerminating()); |
| v8::MaybeLocal<v8::Value> result = CompileRun(isolate->GetCurrentContext(), |
| "var term = true;" |
| "while(true) {" |
| " if (term) terminate();" |
| " term = false;" |
| "}" |
| "fail();"); |
| CHECK(result.IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.Exception()->IsNull()); |
| CHECK(try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.CanContinue()); |
| CHECK(isolate->IsExecutionTerminating()); |
| CHECK(try_catch.HasTerminated()); |
| isolate->CancelTerminateExecution(); |
| CHECK(!isolate->IsExecutionTerminating()); |
| } |
| |
| |
| // Test that a single thread of JavaScript execution can terminate |
| // itself and then resume execution. |
| TEST(TerminateCancelTerminateFromThreadItself) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, DoLoopCancelTerminate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!CcTest::isolate()->IsExecutionTerminating()); |
| // Check that execution completed with correct return value. |
| v8::Local<v8::Value> result = |
| CompileRun(isolate->GetCurrentContext(), |
| "try { doloop(); } catch(e) { fail(); } 'completed';") |
| .ToLocalChecked(); |
| CHECK(result->Equals(isolate->GetCurrentContext(), v8_str("completed")) |
| .FromJust()); |
| } |
| |
| |
| void MicrotaskShouldNotRun(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| UNREACHABLE(); |
| } |
| |
| |
| void MicrotaskLoopForever(const v8::FunctionCallbackInfo<v8::Value>& info) { |
| v8::Isolate* isolate = info.GetIsolate(); |
| v8::HandleScope scope(isolate); |
| // Enqueue another should-not-run task to ensure we clean out the queue |
| // when we terminate. |
| isolate->EnqueueMicrotask( |
| v8::Function::New(isolate->GetCurrentContext(), MicrotaskShouldNotRun) |
| .ToLocalChecked()); |
| CompileRun("terminate(); while (true) { }"); |
| CHECK(isolate->IsExecutionTerminating()); |
| } |
| |
| |
| TEST(TerminateFromOtherThreadWhileMicrotaskRunning) { |
| semaphore = new v8::base::Semaphore(0); |
| TerminatorThread thread(CcTest::i_isolate()); |
| CHECK(thread.Start()); |
| |
| v8::Isolate* isolate = CcTest::isolate(); |
| isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = |
| CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop); |
| v8::Local<v8::Context> context = |
| v8::Context::New(CcTest::isolate(), nullptr, global); |
| v8::Context::Scope context_scope(context); |
| isolate->EnqueueMicrotask( |
| v8::Function::New(isolate->GetCurrentContext(), MicrotaskLoopForever) |
| .ToLocalChecked()); |
| // The second task should never be run because we bail out if we're |
| // terminating. |
| isolate->EnqueueMicrotask( |
| v8::Function::New(isolate->GetCurrentContext(), MicrotaskShouldNotRun) |
| .ToLocalChecked()); |
| isolate->PerformMicrotaskCheckpoint(); |
| |
| isolate->CancelTerminateExecution(); |
| // Should not run MicrotaskShouldNotRun. |
| isolate->PerformMicrotaskCheckpoint(); |
| |
| thread.Join(); |
| delete semaphore; |
| semaphore = nullptr; |
| } |
| |
| |
| static int callback_counter = 0; |
| |
| |
| static void CounterCallback(v8::Isolate* isolate, void* data) { |
| callback_counter++; |
| } |
| |
| |
| TEST(PostponeTerminateException) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = |
| CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop); |
| v8::Local<v8::Context> context = |
| v8::Context::New(CcTest::isolate(), nullptr, global); |
| v8::Context::Scope context_scope(context); |
| |
| v8::TryCatch try_catch(isolate); |
| static const char* terminate_and_loop = |
| "terminate(); for (var i = 0; i < 10000; i++);"; |
| |
| { // Postpone terminate execution interrupts. |
| i::PostponeInterruptsScope p1(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| |
| // API interrupts should still be triggered. |
| CcTest::isolate()->RequestInterrupt(&CounterCallback, nullptr); |
| CHECK_EQ(0, callback_counter); |
| CompileRun(terminate_and_loop); |
| CHECK(!try_catch.HasTerminated()); |
| CHECK_EQ(1, callback_counter); |
| |
| { // Postpone API interrupts as well. |
| i::PostponeInterruptsScope p2(CcTest::i_isolate(), |
| i::StackGuard::API_INTERRUPT); |
| |
| // None of the two interrupts should trigger. |
| CcTest::isolate()->RequestInterrupt(&CounterCallback, nullptr); |
| CompileRun(terminate_and_loop); |
| CHECK(!try_catch.HasTerminated()); |
| CHECK_EQ(1, callback_counter); |
| } |
| |
| // Now the previously requested API interrupt should trigger. |
| CompileRun(terminate_and_loop); |
| CHECK(!try_catch.HasTerminated()); |
| CHECK_EQ(2, callback_counter); |
| } |
| |
| // Now the previously requested terminate execution interrupt should trigger. |
| CompileRun("for (var i = 0; i < 10000; i++);"); |
| CHECK(try_catch.HasTerminated()); |
| CHECK_EQ(2, callback_counter); |
| } |
| |
| static void AssertTerminatedCodeRun(v8::Isolate* isolate) { |
| v8::TryCatch try_catch(isolate); |
| CompileRun("for (var i = 0; i < 10000; i++);"); |
| CHECK(try_catch.HasTerminated()); |
| } |
| |
| static void AssertFinishedCodeRun(v8::Isolate* isolate) { |
| v8::TryCatch try_catch(isolate); |
| CompileRun("for (var i = 0; i < 10000; i++);"); |
| CHECK(!try_catch.HasTerminated()); |
| } |
| |
| TEST(SafeForTerminateException) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| |
| { // Checks safe for termination scope. |
| i::PostponeInterruptsScope p1(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| isolate->TerminateExecution(); |
| AssertFinishedCodeRun(isolate); |
| { |
| i::SafeForInterruptsScope p2(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertTerminatedCodeRun(isolate); |
| AssertFinishedCodeRun(isolate); |
| isolate->TerminateExecution(); |
| } |
| AssertFinishedCodeRun(isolate); |
| isolate->CancelTerminateExecution(); |
| } |
| |
| isolate->TerminateExecution(); |
| { // no scope -> postpone |
| i::PostponeInterruptsScope p1(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertFinishedCodeRun(isolate); |
| { // postpone -> postpone |
| i::PostponeInterruptsScope p2(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertFinishedCodeRun(isolate); |
| |
| { // postpone -> safe |
| i::SafeForInterruptsScope p3(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertTerminatedCodeRun(isolate); |
| isolate->TerminateExecution(); |
| |
| { // safe -> safe |
| i::SafeForInterruptsScope p4(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertTerminatedCodeRun(isolate); |
| isolate->TerminateExecution(); |
| |
| { // safe -> postpone |
| i::PostponeInterruptsScope p5(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertFinishedCodeRun(isolate); |
| } // postpone -> safe |
| |
| AssertTerminatedCodeRun(isolate); |
| isolate->TerminateExecution(); |
| } // safe -> safe |
| |
| AssertTerminatedCodeRun(isolate); |
| isolate->TerminateExecution(); |
| } // safe -> postpone |
| |
| AssertFinishedCodeRun(isolate); |
| } // postpone -> postpone |
| |
| AssertFinishedCodeRun(isolate); |
| } // postpone -> no scope |
| AssertTerminatedCodeRun(isolate); |
| |
| isolate->TerminateExecution(); |
| { // no scope -> safe |
| i::SafeForInterruptsScope p1(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertTerminatedCodeRun(isolate); |
| } // safe -> no scope |
| AssertFinishedCodeRun(isolate); |
| |
| { // no scope -> postpone |
| i::PostponeInterruptsScope p1(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| isolate->TerminateExecution(); |
| { // postpone -> safe |
| i::SafeForInterruptsScope p2(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| AssertTerminatedCodeRun(isolate); |
| } // safe -> postpone |
| } // postpone -> no scope |
| AssertFinishedCodeRun(isolate); |
| |
| { // no scope -> postpone |
| i::PostponeInterruptsScope p1(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| { // postpone -> safe |
| i::SafeForInterruptsScope p2(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| { // safe -> postpone |
| i::PostponeInterruptsScope p3(CcTest::i_isolate(), |
| i::StackGuard::TERMINATE_EXECUTION); |
| isolate->TerminateExecution(); |
| } // postpone -> safe |
| AssertTerminatedCodeRun(isolate); |
| } // safe -> postpone |
| } // postpone -> no scope |
| } |
| |
| void RequestTermianteAndCallAPI( |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| args.GetIsolate()->TerminateExecution(); |
| AssertFinishedCodeRun(args.GetIsolate()); |
| } |
| |
| UNINITIALIZED_TEST(IsolateSafeForTerminationMode) { |
| v8::Isolate::CreateParams create_params; |
| create_params.only_terminate_in_safe_scope = true; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); |
| { |
| v8::Isolate::Scope isolate_scope(isolate); |
| v8::HandleScope handle_scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); |
| global->Set(v8_str("terminateAndCallAPI"), |
| v8::FunctionTemplate::New(isolate, RequestTermianteAndCallAPI)); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| |
| // Should postpone termination without safe scope. |
| isolate->TerminateExecution(); |
| AssertFinishedCodeRun(isolate); |
| { |
| v8::Isolate::SafeForTerminationScope safe_scope(isolate); |
| AssertTerminatedCodeRun(isolate); |
| } |
| AssertFinishedCodeRun(isolate); |
| |
| { |
| isolate->TerminateExecution(); |
| AssertFinishedCodeRun(isolate); |
| i::PostponeInterruptsScope p1(i_isolate, |
| i::StackGuard::TERMINATE_EXECUTION); |
| { |
| // SafeForTermination overrides postpone. |
| v8::Isolate::SafeForTerminationScope safe_scope(isolate); |
| AssertTerminatedCodeRun(isolate); |
| } |
| AssertFinishedCodeRun(isolate); |
| } |
| |
| { |
| v8::Isolate::SafeForTerminationScope safe_scope(isolate); |
| // Request terminate and call API recursively. |
| CompileRun("terminateAndCallAPI()"); |
| AssertTerminatedCodeRun(isolate); |
| } |
| |
| { |
| i::PostponeInterruptsScope p1(i_isolate, |
| i::StackGuard::TERMINATE_EXECUTION); |
| // Request terminate and call API recursively. |
| CompileRun("terminateAndCallAPI()"); |
| AssertFinishedCodeRun(isolate); |
| } |
| AssertFinishedCodeRun(isolate); |
| { |
| v8::Isolate::SafeForTerminationScope safe_scope(isolate); |
| AssertTerminatedCodeRun(isolate); |
| } |
| } |
| isolate->Dispose(); |
| } |
| |
| TEST(ErrorObjectAfterTermination) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate()); |
| v8::Context::Scope context_scope(context); |
| isolate->TerminateExecution(); |
| v8::Local<v8::Value> error = v8::Exception::Error(v8_str("error")); |
| CHECK(error->IsNativeError()); |
| } |
| |
| |
| void InnerTryCallTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| CHECK(!args.GetIsolate()->IsExecutionTerminating()); |
| v8::Local<v8::Object> global = CcTest::global(); |
| v8::Local<v8::Function> loop = v8::Local<v8::Function>::Cast( |
| global->Get(CcTest::isolate()->GetCurrentContext(), v8_str("loop")) |
| .ToLocalChecked()); |
| i::MaybeHandle<i::Object> exception; |
| i::MaybeHandle<i::Object> result = |
| i::Execution::TryCall(CcTest::i_isolate(), v8::Utils::OpenHandle((*loop)), |
| v8::Utils::OpenHandle((*global)), 0, nullptr, |
| i::Execution::MessageHandling::kReport, &exception); |
| CHECK(result.is_null()); |
| CHECK(exception.is_null()); |
| // TryCall reschedules the termination exception. |
| CHECK(args.GetIsolate()->IsExecutionTerminating()); |
| } |
| |
| |
| TEST(TerminationInInnerTryCall) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global_template = CreateGlobalTemplate( |
| CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall); |
| global_template->Set( |
| v8_str("inner_try_call_terminate"), |
| v8::FunctionTemplate::New(isolate, InnerTryCallTerminate)); |
| v8::Local<v8::Context> context = |
| v8::Context::New(CcTest::isolate(), nullptr, global_template); |
| v8::Context::Scope context_scope(context); |
| { |
| v8::TryCatch try_catch(isolate); |
| CompileRun("inner_try_call_terminate()"); |
| CHECK(try_catch.HasTerminated()); |
| // Any further exectutions in this TryCatch scope would fail. |
| CHECK(isolate->IsExecutionTerminating()); |
| } |
| // Leaving the TryCatch cleared the termination exception. |
| v8::Maybe<int32_t> result = |
| CompileRun("2 + 2")->Int32Value(isolate->GetCurrentContext()); |
| CHECK_EQ(4, result.FromJust()); |
| CHECK(!isolate->IsExecutionTerminating()); |
| } |
| |
| |
| TEST(TerminateAndTryCall) { |
| i::FLAG_allow_natives_syntax = true; |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, DoLoopCancelTerminate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!isolate->IsExecutionTerminating()); |
| { |
| v8::TryCatch try_catch(isolate); |
| CHECK(!isolate->IsExecutionTerminating()); |
| // Terminate execution has been triggered inside TryCall, but re-requested |
| // to trigger later. |
| CHECK(CompileRun("terminate(); reference_error();").IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(!isolate->IsExecutionTerminating()); |
| v8::Local<v8::Value> value = |
| CcTest::global() |
| ->Get(isolate->GetCurrentContext(), v8_str("terminate")) |
| .ToLocalChecked(); |
| CHECK(value->IsFunction()); |
| // Any further executions in this TryCatch scope fail. |
| CHECK(!isolate->IsExecutionTerminating()); |
| CHECK(CompileRun("1 + 1").IsEmpty()); |
| CHECK(isolate->IsExecutionTerminating()); |
| } |
| // Leaving the TryCatch cleared the termination exception. |
| v8::Maybe<int32_t> result = |
| CompileRun("2 + 2")->Int32Value(isolate->GetCurrentContext()); |
| CHECK_EQ(4, result.FromJust()); |
| CHECK(!isolate->IsExecutionTerminating()); |
| } |
| |
| class ConsoleImpl : public v8::debug::ConsoleDelegate { |
| private: |
| void Log(const v8::debug::ConsoleCallArguments& args, |
| const v8::debug::ConsoleContext&) override { |
| CompileRun("1 + 1"); |
| } |
| }; |
| |
| TEST(TerminateConsole) { |
| i::FLAG_allow_natives_syntax = true; |
| v8::Isolate* isolate = CcTest::isolate(); |
| ConsoleImpl console; |
| v8::debug::SetConsoleDelegate(isolate, &console); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, DoLoopCancelTerminate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!isolate->IsExecutionTerminating()); |
| v8::TryCatch try_catch(isolate); |
| CHECK(!isolate->IsExecutionTerminating()); |
| CHECK(CompileRun("terminate(); console.log(); fail();").IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(isolate->IsExecutionTerminating()); |
| } |
| |
| class TerminatorSleeperThread : public v8::base::Thread { |
| public: |
| explicit TerminatorSleeperThread(v8::Isolate* isolate, int sleep_ms) |
| : Thread(Options("TerminatorSlepperThread")), |
| isolate_(isolate), |
| sleep_ms_(sleep_ms) {} |
| void Run() override { |
| v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(sleep_ms_)); |
| CHECK(!isolate_->IsExecutionTerminating()); |
| isolate_->TerminateExecution(); |
| } |
| |
| private: |
| v8::Isolate* isolate_; |
| int sleep_ms_; |
| }; |
| |
| TEST(TerminateRegExp) { |
| i::FLAG_allow_natives_syntax = true; |
| // We want to be stuck regexp execution, so no fallback to linear-time |
| // engine. |
| // TODO(mbid,v8:10765): Find a way to test interrupt support of the |
| // experimental engine. |
| i::FLAG_enable_experimental_regexp_engine_on_excessive_backtracks = false; |
| |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, DoLoopCancelTerminate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| v8::Context::Scope context_scope(context); |
| CHECK(!isolate->IsExecutionTerminating()); |
| v8::TryCatch try_catch(isolate); |
| CHECK(!isolate->IsExecutionTerminating()); |
| CHECK(!CompileRun("var re = /(x+)+y$/; re.test('x');").IsEmpty()); |
| TerminatorSleeperThread terminator(isolate, 100); |
| CHECK(terminator.Start()); |
| CHECK(CompileRun("re.test('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); fail();") |
| .IsEmpty()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(isolate->IsExecutionTerminating()); |
| } |
| |
| TEST(TerminateInMicrotask) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::Locker locker(isolate); |
| isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, DoLoopCancelTerminate); |
| v8::Local<v8::Context> context1 = v8::Context::New(isolate, nullptr, global); |
| v8::Local<v8::Context> context2 = v8::Context::New(isolate, nullptr, global); |
| { |
| v8::TryCatch try_catch(isolate); |
| { |
| v8::Context::Scope context_scope(context1); |
| CHECK(!isolate->IsExecutionTerminating()); |
| CHECK(!CompileRun("Promise.resolve().then(function() {" |
| "terminate(); loop(); fail();})") |
| .IsEmpty()); |
| CHECK(!try_catch.HasCaught()); |
| } |
| { |
| v8::Context::Scope context_scope(context2); |
| CHECK(context2 == isolate->GetCurrentContext()); |
| CHECK(context2 == isolate->GetEnteredOrMicrotaskContext()); |
| CHECK(!isolate->IsExecutionTerminating()); |
| isolate->PerformMicrotaskCheckpoint(); |
| CHECK(context2 == isolate->GetCurrentContext()); |
| CHECK(context2 == isolate->GetEnteredOrMicrotaskContext()); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.HasTerminated()); |
| // Any further exectutions in this TryCatch scope would fail. |
| CHECK(isolate->IsExecutionTerminating()); |
| CHECK(!CcTest::i_isolate()->stack_guard()->CheckTerminateExecution()); |
| } |
| } |
| CHECK(!CcTest::i_isolate()->stack_guard()->CheckTerminateExecution()); |
| CHECK(!isolate->IsExecutionTerminating()); |
| } |
| |
| void TerminationMicrotask(void* data) { |
| CcTest::isolate()->TerminateExecution(); |
| CompileRun(""); |
| } |
| |
| void UnreachableMicrotask(void* data) { UNREACHABLE(); } |
| |
| TEST(TerminateInApiMicrotask) { |
| v8::Isolate* isolate = CcTest::isolate(); |
| v8::Locker locker(isolate); |
| isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit); |
| v8::HandleScope scope(isolate); |
| v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate( |
| isolate, TerminateCurrentThread, DoLoopCancelTerminate); |
| v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global); |
| { |
| v8::TryCatch try_catch(isolate); |
| v8::Context::Scope context_scope(context); |
| CHECK(!isolate->IsExecutionTerminating()); |
| isolate->EnqueueMicrotask(TerminationMicrotask); |
| isolate->EnqueueMicrotask(UnreachableMicrotask); |
| isolate->PerformMicrotaskCheckpoint(); |
| CHECK(try_catch.HasCaught()); |
| CHECK(try_catch.HasTerminated()); |
| CHECK(isolate->IsExecutionTerminating()); |
| } |
| CHECK(!isolate->IsExecutionTerminating()); |
| } |