// 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/v8.h" #include "test/cctest/cctest.h" #include "src/base/platform/platform.h" v8::base::Semaphore* semaphore = NULL; void Signal(const v8::FunctionCallbackInfo& args) { semaphore->Signal(); } void TerminateCurrentThread(const v8::FunctionCallbackInfo& args) { CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); v8::V8::TerminateExecution(args.GetIsolate()); } void Fail(const v8::FunctionCallbackInfo& args) { CHECK(false); } void Loop(const v8::FunctionCallbackInfo& args) { CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); v8::Handle source = v8::String::NewFromUtf8( args.GetIsolate(), "try { doloop(); fail(); } catch(e) { fail(); }"); v8::Handle result = v8::Script::Compile(source)->Run(); CHECK(result.IsEmpty()); CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate())); } void DoLoop(const v8::FunctionCallbackInfo& args) { v8::TryCatch try_catch(args.GetIsolate()); CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), "function f() {" " var term = true;" " try {" " while(true) {" " if (term) terminate();" " term = false;" " }" " fail();" " } catch(e) {" " fail();" " }" "}" "f()"))->Run(); CHECK(try_catch.HasCaught()); CHECK(try_catch.Exception()->IsNull()); CHECK(try_catch.Message().IsEmpty()); CHECK(!try_catch.CanContinue()); CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate())); } void DoLoopNoCall(const v8::FunctionCallbackInfo& args) { v8::TryCatch try_catch(args.GetIsolate()); CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), "var term = true;" "while(true) {" " if (term) terminate();" " term = false;" "}"))->Run(); CHECK(try_catch.HasCaught()); CHECK(try_catch.Exception()->IsNull()); CHECK(try_catch.Message().IsEmpty()); CHECK(!try_catch.CanContinue()); CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate())); } v8::Handle CreateGlobalTemplate( v8::Isolate* isolate, v8::FunctionCallback terminate, v8::FunctionCallback doloop) { v8::Handle global = v8::ObjectTemplate::New(isolate); global->Set(v8::String::NewFromUtf8(isolate, "terminate"), v8::FunctionTemplate::New(isolate, terminate)); global->Set(v8::String::NewFromUtf8(isolate, "fail"), v8::FunctionTemplate::New(isolate, Fail)); global->Set(v8::String::NewFromUtf8(isolate, "loop"), v8::FunctionTemplate::New(isolate, Loop)); global->Set(v8::String::NewFromUtf8(isolate, "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::Handle global = CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop); v8::Handle context = v8::Context::New(CcTest::isolate(), NULL, global); v8::Context::Scope context_scope(context); CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); // Run a loop that will be infinite if thread termination does not work. v8::Handle source = v8::String::NewFromUtf8( CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }"); v8::Script::Compile(source)->Run(); // Test that we can run the code again after thread termination. CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); v8::Script::Compile(source)->Run(); } // 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::Handle global = CreateGlobalTemplate( CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall); v8::Handle context = v8::Context::New(CcTest::isolate(), NULL, global); v8::Context::Scope context_scope(context); CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); // Run a loop that will be infinite if thread termination does not work. v8::Handle source = v8::String::NewFromUtf8( CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }"); v8::Script::Compile(source)->Run(); CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); // Test that we can run the code again after thread termination. v8::Script::Compile(source)->Run(); } class TerminatorThread : public v8::base::Thread { public: explicit TerminatorThread(i::Isolate* isolate) : Thread(Options("TerminatorThread")), isolate_(reinterpret_cast(isolate)) {} void Run() { semaphore->Wait(); CHECK(!v8::V8::IsExecutionTerminating(isolate_)); v8::V8::TerminateExecution(isolate_); } private: v8::Isolate* isolate_; }; // Test that a single thread of JavaScript execution can be terminated // from the side by another thread. TEST(TerminateOnlyV8ThreadFromOtherThread) { semaphore = new v8::base::Semaphore(0); TerminatorThread thread(CcTest::i_isolate()); thread.Start(); v8::HandleScope scope(CcTest::isolate()); v8::Handle global = CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop); v8::Handle context = v8::Context::New(CcTest::isolate(), NULL, global); v8::Context::Scope context_scope(context); CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); // Run a loop that will be infinite if thread termination does not work. v8::Handle source = v8::String::NewFromUtf8( CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }"); v8::Script::Compile(source)->Run(); thread.Join(); delete semaphore; semaphore = NULL; } int call_count = 0; void TerminateOrReturnObject(const v8::FunctionCallbackInfo& args) { if (++call_count == 10) { CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); v8::V8::TerminateExecution(args.GetIsolate()); return; } v8::Local result = v8::Object::New(args.GetIsolate()); result->Set(v8::String::NewFromUtf8(args.GetIsolate(), "x"), v8::Integer::New(args.GetIsolate(), 42)); args.GetReturnValue().Set(result); } void LoopGetProperty(const v8::FunctionCallbackInfo& args) { v8::TryCatch try_catch(args.GetIsolate()); CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); v8::Script::Compile( v8::String::NewFromUtf8(args.GetIsolate(), "function f() {" " try {" " while(true) {" " terminate_or_return_object().x;" " }" " fail();" " } catch(e) {" " (function() {})();" // trigger stack check. " fail();" " }" "}" "f()"))->Run(); CHECK(try_catch.HasCaught()); CHECK(try_catch.Exception()->IsNull()); CHECK(try_catch.Message().IsEmpty()); CHECK(!try_catch.CanContinue()); CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate())); } // 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::Handle global = v8::ObjectTemplate::New(isolate); global->Set( v8::String::NewFromUtf8(isolate, "terminate_or_return_object"), v8::FunctionTemplate::New(isolate, TerminateOrReturnObject)); global->Set(v8::String::NewFromUtf8(isolate, "fail"), v8::FunctionTemplate::New(isolate, Fail)); global->Set(v8::String::NewFromUtf8(isolate, "loop"), v8::FunctionTemplate::New(isolate, LoopGetProperty)); v8::Handle context = v8::Context::New(isolate, NULL, global); v8::Context::Scope context_scope(context); CHECK(!v8::V8::IsExecutionTerminating(isolate)); // Run a loop that will be infinite if thread termination does not work. v8::Handle source = v8::String::NewFromUtf8( isolate, "try { loop(); fail(); } catch(e) { fail(); }"); call_count = 0; v8::Script::Compile(source)->Run(); // Test that we can run the code again after thread termination. CHECK(!v8::V8::IsExecutionTerminating(isolate)); call_count = 0; v8::Script::Compile(source)->Run(); } v8::Persistent reenter_script_1; v8::Persistent reenter_script_2; void ReenterAfterTermination(const v8::FunctionCallbackInfo& args) { v8::TryCatch try_catch(args.GetIsolate()); v8::Isolate* isolate = args.GetIsolate(); CHECK(!v8::V8::IsExecutionTerminating(isolate)); v8::Local script = v8::Local::New(isolate, reenter_script_1); v8::Script::Compile(script)->Run(); CHECK(try_catch.HasCaught()); CHECK(try_catch.Exception()->IsNull()); CHECK(try_catch.Message().IsEmpty()); CHECK(!try_catch.CanContinue()); CHECK(v8::V8::IsExecutionTerminating(isolate)); script = v8::Local::New(isolate, reenter_script_2); v8::Script::Compile(script)->Run(); } // 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::Handle global = CreateGlobalTemplate( isolate, TerminateCurrentThread, ReenterAfterTermination); v8::Handle context = v8::Context::New(isolate, NULL, global); v8::Context::Scope context_scope(context); CHECK(!v8::V8::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(!v8::V8::IsExecutionTerminating(isolate)); // 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& args) { v8::TryCatch try_catch(args.GetIsolate()); CHECK(!v8::V8::IsExecutionTerminating()); v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), "var term = true;" "while(true) {" " if (term) terminate();" " term = false;" "}" "fail();"))->Run(); CHECK(try_catch.HasCaught()); CHECK(try_catch.Exception()->IsNull()); CHECK(try_catch.Message().IsEmpty()); CHECK(!try_catch.CanContinue()); CHECK(v8::V8::IsExecutionTerminating()); CHECK(try_catch.HasTerminated()); v8::V8::CancelTerminateExecution(CcTest::isolate()); CHECK(!v8::V8::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::Handle global = CreateGlobalTemplate( isolate, TerminateCurrentThread, DoLoopCancelTerminate); v8::Handle context = v8::Context::New(isolate, NULL, global); v8::Context::Scope context_scope(context); CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); v8::Handle source = v8::String::NewFromUtf8( isolate, "try { doloop(); } catch(e) { fail(); } 'completed';"); // Check that execution completed with correct return value. CHECK(v8::Script::Compile(source)->Run()->Equals(v8_str("completed"))); } void MicrotaskShouldNotRun(const v8::FunctionCallbackInfo& info) { CHECK(false); } void MicrotaskLoopForever(const v8::FunctionCallbackInfo& 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, MicrotaskShouldNotRun)); CompileRun("terminate(); while (true) { }"); CHECK(v8::V8::IsExecutionTerminating()); } TEST(TerminateFromOtherThreadWhileMicrotaskRunning) { semaphore = new v8::base::Semaphore(0); TerminatorThread thread(CcTest::i_isolate()); thread.Start(); v8::Isolate* isolate = CcTest::isolate(); isolate->SetAutorunMicrotasks(false); v8::HandleScope scope(isolate); v8::Handle global = CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop); v8::Handle context = v8::Context::New(CcTest::isolate(), NULL, global); v8::Context::Scope context_scope(context); isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskLoopForever)); // The second task should never be run because we bail out if we're // terminating. isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskShouldNotRun)); isolate->RunMicrotasks(); v8::V8::CancelTerminateExecution(isolate); isolate->RunMicrotasks(); // should not run MicrotaskShouldNotRun thread.Join(); delete semaphore; semaphore = NULL; } 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::Handle global = CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop); v8::Handle context = v8::Context::New(CcTest::isolate(), NULL, 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, NULL); 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, NULL); 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); } TEST(ErrorObjectAfterTermination) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Handle context = v8::Context::New(CcTest::isolate()); v8::Context::Scope context_scope(context); v8::V8::TerminateExecution(isolate); v8::Local error = v8::Exception::Error(v8_str("error")); // TODO(yangguo): crbug/403509. Check for empty handle instead. CHECK(error->IsUndefined()); } void InnerTryCallTerminate(const v8::FunctionCallbackInfo& args) { CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); v8::Handle global = CcTest::global(); v8::Handle loop = v8::Handle::Cast(global->Get(v8_str("loop"))); i::MaybeHandle result = i::Execution::TryCall(v8::Utils::OpenHandle((*loop)), v8::Utils::OpenHandle((*global)), 0, NULL, NULL); CHECK(result.is_null()); // TryCall ignores terminate execution, but rerequests the interrupt. CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); CHECK(CompileRun("1 + 1;").IsEmpty()); } TEST(TerminationInInnerTryCall) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Handle global_template = CreateGlobalTemplate( CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall); global_template->Set( v8_str("inner_try_call_terminate"), v8::FunctionTemplate::New(isolate, InnerTryCallTerminate)); v8::Handle context = v8::Context::New(CcTest::isolate(), NULL, global_template); v8::Context::Scope context_scope(context); { v8::TryCatch try_catch(isolate); CompileRun("inner_try_call_terminate()"); CHECK(try_catch.HasTerminated()); } CHECK_EQ(4, CompileRun("2 + 2")->ToInt32()->Int32Value()); CHECK(!v8::V8::IsExecutionTerminating()); } TEST(TerminateAndTryCall) { i::FLAG_allow_natives_syntax = true; v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Handle global = CreateGlobalTemplate( isolate, TerminateCurrentThread, DoLoopCancelTerminate); v8::Handle context = v8::Context::New(isolate, NULL, global); v8::Context::Scope context_scope(context); CHECK(!v8::V8::IsExecutionTerminating(isolate)); v8::TryCatch try_catch(isolate); CHECK(!v8::V8::IsExecutionTerminating(isolate)); // Terminate execution has been triggered inside TryCall, but re-requested // to trigger later. CHECK(CompileRun("terminate(); reference_error();").IsEmpty()); CHECK(try_catch.HasCaught()); CHECK(!v8::V8::IsExecutionTerminating(isolate)); CHECK(CcTest::global()->Get(v8_str("terminate"))->IsFunction()); // The first stack check after terminate has been re-requested fails. CHECK(CompileRun("1 + 1").IsEmpty()); CHECK(!v8::V8::IsExecutionTerminating(isolate)); // V8 then recovers. CHECK_EQ(4, CompileRun("2 + 2")->ToInt32()->Int32Value()); CHECK(!v8::V8::IsExecutionTerminating(isolate)); }