test-thread-termination.cc 23.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// 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.

28 29
#include "src/api.h"
#include "src/isolate.h"
30
#include "src/objects-inl.h"
31 32
#include "src/v8.h"
#include "test/cctest/cctest.h"
33

34
#include "src/base/platform/platform.h"
35

36
v8::base::Semaphore* semaphore = nullptr;
37

38
void Signal(const v8::FunctionCallbackInfo<v8::Value>& args) {
39 40 41 42
  semaphore->Signal();
}


43
void TerminateCurrentThread(const v8::FunctionCallbackInfo<v8::Value>& args) {
44 45
  CHECK(!args.GetIsolate()->IsExecutionTerminating());
  args.GetIsolate()->TerminateExecution();
46 47
}

48
void Fail(const v8::FunctionCallbackInfo<v8::Value>& args) { UNREACHABLE(); }
49

50
void Loop(const v8::FunctionCallbackInfo<v8::Value>& args) {
51 52 53 54
  CHECK(!args.GetIsolate()->IsExecutionTerminating());
  v8::MaybeLocal<v8::Value> result =
      CompileRun(args.GetIsolate()->GetCurrentContext(),
                 "try { doloop(); fail(); } catch(e) { fail(); }");
55
  CHECK(result.IsEmpty());
56
  CHECK(args.GetIsolate()->IsExecutionTerminating());
57 58 59
}


60
void DoLoop(const v8::FunctionCallbackInfo<v8::Value>& args) {
61
  v8::TryCatch try_catch(args.GetIsolate());
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
  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());
79 80 81 82
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
83
  CHECK(args.GetIsolate()->IsExecutionTerminating());
84 85 86
}


87
void DoLoopNoCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
88
  v8::TryCatch try_catch(args.GetIsolate());
89 90 91 92 93 94 95 96 97
  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());
98 99 100 101
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
102
  CHECK(args.GetIsolate()->IsExecutionTerminating());
103 104 105
}


106 107
v8::Local<v8::ObjectTemplate> CreateGlobalTemplate(
    v8::Isolate* isolate, v8::FunctionCallback terminate,
108
    v8::FunctionCallback doloop) {
109 110
  v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
  global->Set(v8_str("terminate"),
111
              v8::FunctionTemplate::New(isolate, terminate));
112 113 114
  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));
115 116 117 118 119 120 121
  return global;
}


// Test that a single thread of JavaScript execution can terminate
// itself.
TEST(TerminateOnlyV8ThreadFromThreadItself) {
122
  v8::HandleScope scope(CcTest::isolate());
123
  v8::Local<v8::ObjectTemplate> global =
124
      CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
125
  v8::Local<v8::Context> context =
126
      v8::Context::New(CcTest::isolate(), nullptr, global);
127
  v8::Context::Scope context_scope(context);
128
  CHECK(!CcTest::isolate()->IsExecutionTerminating());
129
  // Run a loop that will be infinite if thread termination does not work.
130 131 132 133
  v8::MaybeLocal<v8::Value> result =
      CompileRun(CcTest::isolate()->GetCurrentContext(),
                 "try { loop(); fail(); } catch(e) { fail(); }");
  CHECK(result.IsEmpty());
134
  // Test that we can run the code again after thread termination.
135 136 137 138
  CHECK(!CcTest::isolate()->IsExecutionTerminating());
  result = CompileRun(CcTest::isolate()->GetCurrentContext(),
                      "try { loop(); fail(); } catch(e) { fail(); }");
  CHECK(result.IsEmpty());
139 140 141 142 143 144
}


// Test that a single thread of JavaScript execution can terminate
// itself in a loop that performs no calls.
TEST(TerminateOnlyV8ThreadFromThreadItselfNoLoop) {
145
  v8::HandleScope scope(CcTest::isolate());
146
  v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
147
      CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall);
148
  v8::Local<v8::Context> context =
149
      v8::Context::New(CcTest::isolate(), nullptr, global);
150
  v8::Context::Scope context_scope(context);
151
  CHECK(!CcTest::isolate()->IsExecutionTerminating());
152
  // Run a loop that will be infinite if thread termination does not work.
153 154 155 156 157
  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());
158
  // Test that we can run the code again after thread termination.
159 160
  result = CompileRun(CcTest::isolate()->GetCurrentContext(), source);
  CHECK(result.IsEmpty());
161 162 163
}


164
class TerminatorThread : public v8::base::Thread {
165
 public:
166
  explicit TerminatorThread(i::Isolate* isolate)
167 168
      : Thread(Options("TerminatorThread")),
        isolate_(reinterpret_cast<v8::Isolate*>(isolate)) {}
169 170
  void Run() {
    semaphore->Wait();
171 172
    CHECK(!isolate_->IsExecutionTerminating());
    isolate_->TerminateExecution();
173
  }
174 175 176

 private:
  v8::Isolate* isolate_;
177 178 179 180 181 182
};


// Test that a single thread of JavaScript execution can be terminated
// from the side by another thread.
TEST(TerminateOnlyV8ThreadFromOtherThread) {
183
  semaphore = new v8::base::Semaphore(0);
184
  TerminatorThread thread(CcTest::i_isolate());
185 186
  thread.Start();

187
  v8::HandleScope scope(CcTest::isolate());
188
  v8::Local<v8::ObjectTemplate> global =
189
      CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
190
  v8::Local<v8::Context> context =
191
      v8::Context::New(CcTest::isolate(), nullptr, global);
192
  v8::Context::Scope context_scope(context);
193
  CHECK(!CcTest::isolate()->IsExecutionTerminating());
194
  // Run a loop that will be infinite if thread termination does not work.
195 196 197 198
  v8::MaybeLocal<v8::Value> result =
      CompileRun(CcTest::isolate()->GetCurrentContext(),
                 "try { loop(); fail(); } catch(e) { fail(); }");
  CHECK(result.IsEmpty());
199 200
  thread.Join();
  delete semaphore;
201
  semaphore = nullptr;
202 203
}

204 205 206 207 208 209 210 211 212 213
// 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 =
214
      v8::Context::New(CcTest::isolate(), nullptr, global);
215 216 217 218 219 220 221 222 223 224 225 226
  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;
227
  semaphore = nullptr;
228
}
229

230 231 232
int call_count = 0;


233
void TerminateOrReturnObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
234
  if (++call_count == 10) {
235 236
    CHECK(!args.GetIsolate()->IsExecutionTerminating());
    args.GetIsolate()->TerminateExecution();
237
    return;
238
  }
239
  v8::Local<v8::Object> result = v8::Object::New(args.GetIsolate());
240 241 242 243
  v8::Maybe<bool> val =
      result->Set(args.GetIsolate()->GetCurrentContext(), v8_str("x"),
                  v8::Integer::New(args.GetIsolate(), 42));
  CHECK(val.FromJust());
244
  args.GetReturnValue().Set(result);
245 246 247
}


248
void LoopGetProperty(const v8::FunctionCallbackInfo<v8::Value>& args) {
249
  v8::TryCatch try_catch(args.GetIsolate());
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
  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());
266 267 268 269
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
270
  CHECK(args.GetIsolate()->IsExecutionTerminating());
271 272 273 274 275
}


// Test that we correctly handle termination exceptions if they are
// triggered by the creation of error objects in connection with ICs.
276
TEST(TerminateLoadICException) {
277 278
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
279 280 281 282 283
  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"),
284
              v8::FunctionTemplate::New(isolate, LoopGetProperty));
285

286
  v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
287
  v8::Context::Scope context_scope(context);
288
  CHECK(!isolate->IsExecutionTerminating());
289
  // Run a loop that will be infinite if thread termination does not work.
290
  static const char* source = "try { loop(); fail(); } catch(e) { fail(); }";
291
  call_count = 0;
292 293 294
  v8::MaybeLocal<v8::Value> result =
      CompileRun(isolate->GetCurrentContext(), source);
  CHECK(result.IsEmpty());
295
  // Test that we can run the code again after thread termination.
296
  CHECK(!isolate->IsExecutionTerminating());
297
  call_count = 0;
298 299
  result = CompileRun(isolate->GetCurrentContext(), source);
  CHECK(result.IsEmpty());
300
}
301

302

303 304 305
v8::Persistent<v8::String> reenter_script_1;
v8::Persistent<v8::String> reenter_script_2;

306
void ReenterAfterTermination(const v8::FunctionCallbackInfo<v8::Value>& args) {
307
  v8::TryCatch try_catch(args.GetIsolate());
308
  v8::Isolate* isolate = args.GetIsolate();
309
  CHECK(!isolate->IsExecutionTerminating());
310 311
  v8::Local<v8::String> script =
      v8::Local<v8::String>::New(isolate, reenter_script_1);
312 313
  v8::MaybeLocal<v8::Value> result = CompileRun(script);
  CHECK(result.IsEmpty());
314 315 316 317
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
318
  CHECK(isolate->IsExecutionTerminating());
319
  script = v8::Local<v8::String>::New(isolate, reenter_script_2);
320 321 322
  v8::MaybeLocal<v8::Script> compiled_script =
      v8::Script::Compile(isolate->GetCurrentContext(), script);
  CHECK(compiled_script.IsEmpty());
323 324
}

325

326 327 328
// 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) {
329 330
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
331
  v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
332
      isolate, TerminateCurrentThread, ReenterAfterTermination);
333
  v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
334
  v8::Context::Scope context_scope(context);
335
  CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
  // 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(); }");
353
  CHECK(!isolate->IsExecutionTerminating());
354
  // Check we can run JS again after termination.
355 356 357
  CHECK(CompileRun("function f() { return true; } f()")->IsTrue());
  reenter_script_1.Reset();
  reenter_script_2.Reset();
358
}
359

360

361
void DoLoopCancelTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
362
  v8::TryCatch try_catch(args.GetIsolate());
363 364 365 366 367 368 369 370 371 372
  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());
373 374 375 376
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
377
  CHECK(v8::Isolate::GetCurrent()->IsExecutionTerminating());
378
  CHECK(try_catch.HasTerminated());
379 380
  CcTest::isolate()->CancelTerminateExecution();
  CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
381 382
}

383

384 385 386
// Test that a single thread of JavaScript execution can terminate
// itself and then resume execution.
TEST(TerminateCancelTerminateFromThreadItself) {
387
  v8::Isolate* isolate = CcTest::isolate();
388
  v8::HandleScope scope(isolate);
389
  v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
390
      isolate, TerminateCurrentThread, DoLoopCancelTerminate);
391
  v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
392
  v8::Context::Scope context_scope(context);
393
  CHECK(!CcTest::isolate()->IsExecutionTerminating());
394
  // Check that execution completed with correct return value.
395 396 397 398 399 400
  v8::Local<v8::Value> result =
      CompileRun(isolate->GetCurrentContext(),
                 "try { doloop(); } catch(e) { fail(); } 'completed';")
          .ToLocalChecked();
  CHECK(result->Equals(isolate->GetCurrentContext(), v8_str("completed"))
            .FromJust());
401
}
402 403 404


void MicrotaskShouldNotRun(const v8::FunctionCallbackInfo<v8::Value>& info) {
405
  UNREACHABLE();
406 407 408 409 410 411 412 413
}


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.
414 415 416
  isolate->EnqueueMicrotask(
      v8::Function::New(isolate->GetCurrentContext(), MicrotaskShouldNotRun)
          .ToLocalChecked());
417
  CompileRun("terminate(); while (true) { }");
418
  CHECK(v8::Isolate::GetCurrent()->IsExecutionTerminating());
419 420 421 422
}


TEST(TerminateFromOtherThreadWhileMicrotaskRunning) {
423
  semaphore = new v8::base::Semaphore(0);
424 425 426 427
  TerminatorThread thread(CcTest::i_isolate());
  thread.Start();

  v8::Isolate* isolate = CcTest::isolate();
428
  isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
429
  v8::HandleScope scope(isolate);
430
  v8::Local<v8::ObjectTemplate> global =
431
      CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
432
  v8::Local<v8::Context> context =
433
      v8::Context::New(CcTest::isolate(), nullptr, global);
434
  v8::Context::Scope context_scope(context);
435 436 437
  isolate->EnqueueMicrotask(
      v8::Function::New(isolate->GetCurrentContext(), MicrotaskLoopForever)
          .ToLocalChecked());
438 439
  // The second task should never be run because we bail out if we're
  // terminating.
440 441 442
  isolate->EnqueueMicrotask(
      v8::Function::New(isolate->GetCurrentContext(), MicrotaskShouldNotRun)
          .ToLocalChecked());
443 444
  isolate->RunMicrotasks();

445
  isolate->CancelTerminateExecution();
446 447 448 449
  isolate->RunMicrotasks();  // should not run MicrotaskShouldNotRun

  thread.Join();
  delete semaphore;
450
  semaphore = nullptr;
451
}
452 453 454 455 456 457 458 459 460 461 462 463 464


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);
465
  v8::Local<v8::ObjectTemplate> global =
466
      CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
467
  v8::Local<v8::Context> context =
468
      v8::Context::New(CcTest::isolate(), nullptr, global);
469 470
  v8::Context::Scope context_scope(context);

471
  v8::TryCatch try_catch(isolate);
472 473 474 475 476
  static const char* terminate_and_loop =
      "terminate(); for (var i = 0; i < 10000; i++);";

  { // Postpone terminate execution interrupts.
    i::PostponeInterruptsScope p1(CcTest::i_isolate(),
477
                                  i::StackGuard::TERMINATE_EXECUTION);
478 479

    // API interrupts should still be triggered.
480
    CcTest::isolate()->RequestInterrupt(&CounterCallback, nullptr);
481 482 483 484 485 486 487 488 489 490
    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.
491
      CcTest::isolate()->RequestInterrupt(&CounterCallback, nullptr);
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
      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);
}
508 509 510 511 512


TEST(ErrorObjectAfterTermination) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
513
  v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
514
  v8::Context::Scope context_scope(context);
515
  isolate->TerminateExecution();
516
  v8::Local<v8::Value> error = v8::Exception::Error(v8_str("error"));
jgruber's avatar
jgruber committed
517
  CHECK(error->IsNativeError());
518
}
519 520 521


void InnerTryCallTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
522 523 524 525 526
  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());
527
  i::MaybeHandle<i::Object> exception;
528
  i::MaybeHandle<i::Object> result =
529
      i::Execution::TryCall(CcTest::i_isolate(), v8::Utils::OpenHandle((*loop)),
530 531
                            v8::Utils::OpenHandle((*global)), 0, nullptr,
                            i::Execution::MessageHandling::kReport, &exception);
532 533
  CHECK(result.is_null());
  // TryCall ignores terminate execution, but rerequests the interrupt.
534
  CHECK(!args.GetIsolate()->IsExecutionTerminating());
535 536 537 538 539 540 541
  CHECK(CompileRun("1 + 1;").IsEmpty());
}


TEST(TerminationInInnerTryCall) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
542
  v8::Local<v8::ObjectTemplate> global_template = CreateGlobalTemplate(
543 544 545 546
      CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall);
  global_template->Set(
      v8_str("inner_try_call_terminate"),
      v8::FunctionTemplate::New(isolate, InnerTryCallTerminate));
547
  v8::Local<v8::Context> context =
548
      v8::Context::New(CcTest::isolate(), nullptr, global_template);
549 550
  v8::Context::Scope context_scope(context);
  {
551
    v8::TryCatch try_catch(isolate);
552 553 554
    CompileRun("inner_try_call_terminate()");
    CHECK(try_catch.HasTerminated());
  }
555 556 557 558
  v8::Maybe<int32_t> result = CompileRun("2 + 2")->Int32Value(
      v8::Isolate::GetCurrent()->GetCurrentContext());
  CHECK_EQ(4, result.FromJust());
  CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
559 560 561 562 563 564 565
}


TEST(TerminateAndTryCall) {
  i::FLAG_allow_natives_syntax = true;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
566
  v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
567
      isolate, TerminateCurrentThread, DoLoopCancelTerminate);
568
  v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
569
  v8::Context::Scope context_scope(context);
570
  CHECK(!isolate->IsExecutionTerminating());
571
  v8::TryCatch try_catch(isolate);
572
  CHECK(!isolate->IsExecutionTerminating());
573 574 575 576
  // Terminate execution has been triggered inside TryCall, but re-requested
  // to trigger later.
  CHECK(CompileRun("terminate(); reference_error();").IsEmpty());
  CHECK(try_catch.HasCaught());
577 578 579 580 581 582
  CHECK(!isolate->IsExecutionTerminating());
  v8::Local<v8::Value> value =
      CcTest::global()
          ->Get(isolate->GetCurrentContext(), v8_str("terminate"))
          .ToLocalChecked();
  CHECK(value->IsFunction());
583 584
  // The first stack check after terminate has been re-requested fails.
  CHECK(CompileRun("1 + 1").IsEmpty());
585
  CHECK(!isolate->IsExecutionTerminating());
586
  // V8 then recovers.
587 588 589 590
  v8::Maybe<int32_t> result = CompileRun("2 + 2")->Int32Value(
      v8::Isolate::GetCurrent()->GetCurrentContext());
  CHECK_EQ(4, result.FromJust());
  CHECK(!isolate->IsExecutionTerminating());
591
}
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608

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);
609
  v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
610 611 612 613 614 615 616 617
  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());
}