text: // 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.h"
  #include "src/isolate.h"
  #include "src/objects-inl.h"
  #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<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) {
    CHECK(false);
  }
  
  
  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(), NULL, 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(), NULL, 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() {
      semaphore->Wait();
      CHECK(!isolate_->IsExecutionTerminating());
      isolate_->TerminateExecution();
    }
  
   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::Local<v8::ObjectTemplate> global =
        CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
    v8::Local<v8::Context> context =
        v8::Context::New(CcTest::isolate(), NULL, 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());
    thread.Join();
    delete semaphore;
    semaphore = NULL;
  }
  
  // Test that execution can be terminated from within JSON.stringify.
  TEST(TerminateJsonStringify) {
    semaphore = new v8::base::Semaphore(0);
    TerminatorThread thread(CcTest::i_isolate());
    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(), NULL, global);
    v8::Context::Scope context_scope(context);
    CHECK(!CcTest::isolate()->IsExecutionTerminating());
    v8::MaybeLocal<v8::Value> result =
        CompileRun(CcTest::isolate()->GetCurrentContext(),
                   "var x = [];"
                   "x[2**31]=1;"
                   "terminate();"
                   "JSON.stringify(x);"
                   "fail();");
    CHECK(result.IsEmpty());
    thread.Join();
    delete semaphore;
    semaphore = NULL;
  }
  
  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, NULL, 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(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, NULL, global);
    v8::Context::Scope context_scope(context);
    CHECK(!v8::Isolate::GetCurrent()->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();
  }
  
  
  void DoLoopCancelTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
    v8::TryCatch try_catch(args.GetIsolate());
    CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
    v8::MaybeLocal<v8::Value> result =
        CompileRun(args.GetIsolate()->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(v8::Isolate::GetCurrent()->IsExecutionTerminating());
    CHECK(try_catch.HasTerminated());
    CcTest::isolate()->CancelTerminateExecution();
    CHECK(!v8::Isolate::GetCurrent()->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, NULL, 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) {
    CHECK(false);
  }
  
  
  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(v8::Isolate::GetCurrent()->IsExecutionTerminating());
  }
  
  
  TEST(TerminateFromOtherThreadWhileMicrotaskRunning) {
    semaphore = new v8::base::Semaphore(0);
    TerminatorThread thread(CcTest::i_isolate());
    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(), NULL, 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->RunMicrotasks();
  
    isolate->CancelTerminateExecution();
    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::Local<v8::ObjectTemplate> global =
        CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
    v8::Local<v8::Context> 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::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());
    // TryCall ignores terminate execution, but rerequests the interrupt.
    CHECK(!args.GetIsolate()->IsExecutionTerminating());
    CHECK(CompileRun("1 + 1;").IsEmpty());
  }
  
  
  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(), NULL, global_template);
    v8::Context::Scope context_scope(context);
    {
      v8::TryCatch try_catch(isolate);
      CompileRun("inner_try_call_terminate()");
      CHECK(try_catch.HasTerminated());
    }
    v8::Maybe<int32_t> result = CompileRun("2 + 2")->Int32Value(
        v8::Isolate::GetCurrent()->GetCurrentContext());
    CHECK_EQ(4, result.FromJust());
    CHECK(!v8::Isolate::GetCurrent()->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, NULL, 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());
    // The first stack check after terminate has been re-requested fails.
    CHECK(CompileRun("1 + 1").IsEmpty());
    CHECK(!isolate->IsExecutionTerminating());
    // V8 then recovers.
    v8::Maybe<int32_t> result = CompileRun("2 + 2")->Int32Value(
        v8::Isolate::GetCurrent()->GetCurrentContext());
    CHECK_EQ(4, result.FromJust());
    CHECK(!isolate->IsExecutionTerminating());
  }
  