test-thread-termination.cc 18.7 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/v8.h"
#include "test/cctest/cctest.h"
30

31
#include "src/base/platform/platform.h"
32

33

34
v8::base::Semaphore* semaphore = NULL;
35 36


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


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


48
void Fail(const v8::FunctionCallbackInfo<v8::Value>& args) {
49 50 51 52
  CHECK(false);
}


53
void Loop(const v8::FunctionCallbackInfo<v8::Value>& args) {
54
  CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
55 56
  v8::Handle<v8::String> source = v8::String::NewFromUtf8(
      args.GetIsolate(), "try { doloop(); fail(); } catch(e) { fail(); }");
57 58
  v8::Handle<v8::Value> result = v8::Script::Compile(source)->Run();
  CHECK(result.IsEmpty());
59
  CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
60 61 62
}


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


88
void DoLoopNoCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
89
  v8::TryCatch try_catch;
90
  CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
91 92 93 94 95 96
  v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
                                              "var term = true;"
                                              "while(true) {"
                                              "  if (term) terminate();"
                                              "  term = false;"
                                              "}"))->Run();
97 98 99 100
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
101
  CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
102 103 104
}


105
v8::Handle<v8::ObjectTemplate> CreateGlobalTemplate(
106
    v8::Isolate* isolate,
107 108
    v8::FunctionCallback terminate,
    v8::FunctionCallback doloop) {
109
  v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
110
  global->Set(v8::String::NewFromUtf8(isolate, "terminate"),
111
              v8::FunctionTemplate::New(isolate, terminate));
112
  global->Set(v8::String::NewFromUtf8(isolate, "fail"),
113
              v8::FunctionTemplate::New(isolate, Fail));
114
  global->Set(v8::String::NewFromUtf8(isolate, "loop"),
115
              v8::FunctionTemplate::New(isolate, Loop));
116
  global->Set(v8::String::NewFromUtf8(isolate, "doloop"),
117
              v8::FunctionTemplate::New(isolate, doloop));
118 119 120 121 122 123 124
  return global;
}


// Test that a single thread of JavaScript execution can terminate
// itself.
TEST(TerminateOnlyV8ThreadFromThreadItself) {
125
  v8::HandleScope scope(CcTest::isolate());
126
  v8::Handle<v8::ObjectTemplate> global =
127
      CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
128
  v8::Handle<v8::Context> context =
129
      v8::Context::New(CcTest::isolate(), NULL, global);
130
  v8::Context::Scope context_scope(context);
131
  CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
132
  // Run a loop that will be infinite if thread termination does not work.
133 134
  v8::Handle<v8::String> source = v8::String::NewFromUtf8(
      CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }");
135 136
  v8::Script::Compile(source)->Run();
  // Test that we can run the code again after thread termination.
137
  CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
138 139 140 141 142 143 144
  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) {
145
  v8::HandleScope scope(CcTest::isolate());
146 147
  v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(
      CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall);
148
  v8::Handle<v8::Context> context =
149
      v8::Context::New(CcTest::isolate(), NULL, global);
150
  v8::Context::Scope context_scope(context);
151
  CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
152
  // Run a loop that will be infinite if thread termination does not work.
153 154
  v8::Handle<v8::String> source = v8::String::NewFromUtf8(
      CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }");
155
  v8::Script::Compile(source)->Run();
156
  CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
157 158 159 160 161
  // Test that we can run the code again after thread termination.
  v8::Script::Compile(source)->Run();
}


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

 private:
  v8::Isolate* isolate_;
175 176 177 178 179 180
};


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

185
  v8::HandleScope scope(CcTest::isolate());
186 187
  v8::Handle<v8::ObjectTemplate> global =
      CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
188
  v8::Handle<v8::Context> context =
189
      v8::Context::New(CcTest::isolate(), NULL, global);
190
  v8::Context::Scope context_scope(context);
191
  CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
192
  // Run a loop that will be infinite if thread termination does not work.
193 194
  v8::Handle<v8::String> source = v8::String::NewFromUtf8(
      CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }");
195 196 197 198 199 200 201 202
  v8::Script::Compile(source)->Run();

  thread.Join();
  delete semaphore;
  semaphore = NULL;
}


203 204 205
int call_count = 0;


206
void TerminateOrReturnObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
207
  if (++call_count == 10) {
208 209
    CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
    v8::V8::TerminateExecution(args.GetIsolate());
210
    return;
211
  }
212
  v8::Local<v8::Object> result = v8::Object::New(args.GetIsolate());
213
  result->Set(v8::String::NewFromUtf8(args.GetIsolate(), "x"),
214
              v8::Integer::New(args.GetIsolate(), 42));
215
  args.GetReturnValue().Set(result);
216 217 218
}


219
void LoopGetProperty(const v8::FunctionCallbackInfo<v8::Value>& args) {
220
  v8::TryCatch try_catch;
221
  CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
222 223 224 225 226 227 228 229 230 231 232 233 234
  v8::Script::Compile(
      v8::String::NewFromUtf8(args.GetIsolate(),
                              "function f() {"
                              "  try {"
                              "    while(true) {"
                              "      terminate_or_return_object().x;"
                              "    }"
                              "    fail();"
                              "  } catch(e) {"
                              "    fail();"
                              "  }"
                              "}"
                              "f()"))->Run();
235 236 237 238
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
239
  CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
240 241 242 243 244
}


// Test that we correctly handle termination exceptions if they are
// triggered by the creation of error objects in connection with ICs.
245
TEST(TerminateLoadICException) {
246 247
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
248
  v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
249
  global->Set(
250 251 252 253 254 255
      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));
256

257
  v8::Handle<v8::Context> context =
258
      v8::Context::New(isolate, NULL, global);
259
  v8::Context::Scope context_scope(context);
260
  CHECK(!v8::V8::IsExecutionTerminating(isolate));
261
  // Run a loop that will be infinite if thread termination does not work.
262
  v8::Handle<v8::String> source = v8::String::NewFromUtf8(
263
      isolate, "try { loop(); fail(); } catch(e) { fail(); }");
264 265 266
  call_count = 0;
  v8::Script::Compile(source)->Run();
  // Test that we can run the code again after thread termination.
267
  CHECK(!v8::V8::IsExecutionTerminating(isolate));
268 269 270
  call_count = 0;
  v8::Script::Compile(source)->Run();
}
271

272

273
void ReenterAfterTermination(const v8::FunctionCallbackInfo<v8::Value>& args) {
274
  v8::TryCatch try_catch;
275
  CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
276 277 278 279 280 281 282 283 284 285 286 287 288 289
  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();
290 291 292 293
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNull());
  CHECK(try_catch.Message().IsEmpty());
  CHECK(!try_catch.CanContinue());
294
  CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
295 296 297
  v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
                                              "function f() { fail(); } f()"))
      ->Run();
298 299
}

300

301 302 303
// 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) {
304 305
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
306
  v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(
307
      isolate, TerminateCurrentThread, ReenterAfterTermination);
308
  v8::Handle<v8::Context> context =
309
      v8::Context::New(isolate, NULL, global);
310 311
  v8::Context::Scope context_scope(context);
  CHECK(!v8::V8::IsExecutionTerminating());
312
  v8::Handle<v8::String> source = v8::String::NewFromUtf8(
313
      isolate, "try { loop(); fail(); } catch(e) { fail(); }");
314
  v8::Script::Compile(source)->Run();
315
  CHECK(!v8::V8::IsExecutionTerminating(isolate));
316
  // Check we can run JS again after termination.
317
  CHECK(v8::Script::Compile(
318
      v8::String::NewFromUtf8(isolate,
319 320 321 322
                              "function f() { return true; }"
                              "f()"))
            ->Run()
            ->IsTrue());
323
}
324

325

326
void DoLoopCancelTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
327 328
  v8::TryCatch try_catch;
  CHECK(!v8::V8::IsExecutionTerminating());
329 330 331 332 333 334 335
  v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
                                              "var term = true;"
                                              "while(true) {"
                                              "  if (term) terminate();"
                                              "  term = false;"
                                              "}"
                                              "fail();"))->Run();
336 337 338 339 340 341
  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());
342
  v8::V8::CancelTerminateExecution(CcTest::isolate());
343 344 345
  CHECK(!v8::V8::IsExecutionTerminating());
}

346

347 348 349
// Test that a single thread of JavaScript execution can terminate
// itself and then resume execution.
TEST(TerminateCancelTerminateFromThreadItself) {
350
  v8::Isolate* isolate = CcTest::isolate();
351
  v8::HandleScope scope(isolate);
352 353
  v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(
      isolate, TerminateCurrentThread, DoLoopCancelTerminate);
354
  v8::Handle<v8::Context> context = v8::Context::New(isolate, NULL, global);
355
  v8::Context::Scope context_scope(context);
356
  CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
357 358
  v8::Handle<v8::String> source = v8::String::NewFromUtf8(
      isolate, "try { doloop(); } catch(e) { fail(); } 'completed';");
359 360 361
  // Check that execution completed with correct return value.
  CHECK(v8::Script::Compile(source)->Run()->Equals(v8_str("completed")));
}
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380


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, MicrotaskShouldNotRun));
  CompileRun("terminate(); while (true) { }");
  CHECK(v8::V8::IsExecutionTerminating());
}


TEST(TerminateFromOtherThreadWhileMicrotaskRunning) {
381
  semaphore = new v8::base::Semaphore(0);
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
  TerminatorThread thread(CcTest::i_isolate());
  thread.Start();

  v8::Isolate* isolate = CcTest::isolate();
  isolate->SetAutorunMicrotasks(false);
  v8::HandleScope scope(isolate);
  v8::Handle<v8::ObjectTemplate> global =
      CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
  v8::Handle<v8::Context> 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;
}
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461


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<v8::ObjectTemplate> global =
      CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
  v8::Handle<v8::Context> context =
      v8::Context::New(CcTest::isolate(), NULL, global);
  v8::Context::Scope context_scope(context);

  v8::TryCatch try_catch;
  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);
}
462 463 464 465 466 467 468 469 470 471 472 473


TEST(ErrorObjectAfterTermination) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Handle<v8::Context> context = v8::Context::New(CcTest::isolate());
  v8::Context::Scope context_scope(context);
  v8::V8::TerminateExecution(isolate);
  v8::Local<v8::Value> error = v8::Exception::Error(v8_str("error"));
  // TODO(yangguo): crbug/403509. Check for empty handle instead.
  CHECK(error->IsUndefined());
}