Home | History | Annotate | Download | only in cctest
      1 // Copyright 2009 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 #include "src/v8.h"
     29 #include "src/platform.h"
     30 #include "test/cctest/cctest.h"
     31 
     32 
     33 v8::internal::Semaphore* semaphore = NULL;
     34 
     35 
     36 void Signal(const v8::FunctionCallbackInfo<v8::Value>& args) {
     37   semaphore->Signal();
     38 }
     39 
     40 
     41 void TerminateCurrentThread(const v8::FunctionCallbackInfo<v8::Value>& args) {
     42   CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
     43   v8::V8::TerminateExecution(args.GetIsolate());
     44 }
     45 
     46 
     47 void Fail(const v8::FunctionCallbackInfo<v8::Value>& args) {
     48   CHECK(false);
     49 }
     50 
     51 
     52 void Loop(const v8::FunctionCallbackInfo<v8::Value>& args) {
     53   CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
     54   v8::Handle<v8::String> source = v8::String::NewFromUtf8(
     55       args.GetIsolate(), "try { doloop(); fail(); } catch(e) { fail(); }");
     56   v8::Handle<v8::Value> result = v8::Script::Compile(source)->Run();
     57   CHECK(result.IsEmpty());
     58   CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
     59 }
     60 
     61 
     62 void DoLoop(const v8::FunctionCallbackInfo<v8::Value>& args) {
     63   v8::TryCatch try_catch;
     64   CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
     65   v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
     66                                               "function f() {"
     67                                               "  var term = true;"
     68                                               "  try {"
     69                                               "    while(true) {"
     70                                               "      if (term) terminate();"
     71                                               "      term = false;"
     72                                               "    }"
     73                                               "    fail();"
     74                                               "  } catch(e) {"
     75                                               "    fail();"
     76                                               "  }"
     77                                               "}"
     78                                               "f()"))->Run();
     79   CHECK(try_catch.HasCaught());
     80   CHECK(try_catch.Exception()->IsNull());
     81   CHECK(try_catch.Message().IsEmpty());
     82   CHECK(!try_catch.CanContinue());
     83   CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
     84 }
     85 
     86 
     87 void DoLoopNoCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
     88   v8::TryCatch try_catch;
     89   CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
     90   v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
     91                                               "var term = true;"
     92                                               "while(true) {"
     93                                               "  if (term) terminate();"
     94                                               "  term = false;"
     95                                               "}"))->Run();
     96   CHECK(try_catch.HasCaught());
     97   CHECK(try_catch.Exception()->IsNull());
     98   CHECK(try_catch.Message().IsEmpty());
     99   CHECK(!try_catch.CanContinue());
    100   CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
    101 }
    102 
    103 
    104 v8::Handle<v8::ObjectTemplate> CreateGlobalTemplate(
    105     v8::Isolate* isolate,
    106     v8::FunctionCallback terminate,
    107     v8::FunctionCallback doloop) {
    108   v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
    109   global->Set(v8::String::NewFromUtf8(isolate, "terminate"),
    110               v8::FunctionTemplate::New(isolate, terminate));
    111   global->Set(v8::String::NewFromUtf8(isolate, "fail"),
    112               v8::FunctionTemplate::New(isolate, Fail));
    113   global->Set(v8::String::NewFromUtf8(isolate, "loop"),
    114               v8::FunctionTemplate::New(isolate, Loop));
    115   global->Set(v8::String::NewFromUtf8(isolate, "doloop"),
    116               v8::FunctionTemplate::New(isolate, doloop));
    117   return global;
    118 }
    119 
    120 
    121 // Test that a single thread of JavaScript execution can terminate
    122 // itself.
    123 TEST(TerminateOnlyV8ThreadFromThreadItself) {
    124   v8::HandleScope scope(CcTest::isolate());
    125   v8::Handle<v8::ObjectTemplate> global =
    126       CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
    127   v8::Handle<v8::Context> context =
    128       v8::Context::New(CcTest::isolate(), NULL, global);
    129   v8::Context::Scope context_scope(context);
    130   CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
    131   // Run a loop that will be infinite if thread termination does not work.
    132   v8::Handle<v8::String> source = v8::String::NewFromUtf8(
    133       CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }");
    134   v8::Script::Compile(source)->Run();
    135   // Test that we can run the code again after thread termination.
    136   CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
    137   v8::Script::Compile(source)->Run();
    138 }
    139 
    140 
    141 // Test that a single thread of JavaScript execution can terminate
    142 // itself in a loop that performs no calls.
    143 TEST(TerminateOnlyV8ThreadFromThreadItselfNoLoop) {
    144   v8::HandleScope scope(CcTest::isolate());
    145   v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(
    146       CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall);
    147   v8::Handle<v8::Context> context =
    148       v8::Context::New(CcTest::isolate(), NULL, global);
    149   v8::Context::Scope context_scope(context);
    150   CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
    151   // Run a loop that will be infinite if thread termination does not work.
    152   v8::Handle<v8::String> source = v8::String::NewFromUtf8(
    153       CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }");
    154   v8::Script::Compile(source)->Run();
    155   CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
    156   // Test that we can run the code again after thread termination.
    157   v8::Script::Compile(source)->Run();
    158 }
    159 
    160 
    161 class TerminatorThread : public v8::internal::Thread {
    162  public:
    163   explicit TerminatorThread(i::Isolate* isolate)
    164       : Thread("TerminatorThread"),
    165         isolate_(reinterpret_cast<v8::Isolate*>(isolate)) { }
    166   void Run() {
    167     semaphore->Wait();
    168     CHECK(!v8::V8::IsExecutionTerminating(isolate_));
    169     v8::V8::TerminateExecution(isolate_);
    170   }
    171 
    172  private:
    173   v8::Isolate* isolate_;
    174 };
    175 
    176 
    177 // Test that a single thread of JavaScript execution can be terminated
    178 // from the side by another thread.
    179 TEST(TerminateOnlyV8ThreadFromOtherThread) {
    180   semaphore = new v8::internal::Semaphore(0);
    181   TerminatorThread thread(CcTest::i_isolate());
    182   thread.Start();
    183 
    184   v8::HandleScope scope(CcTest::isolate());
    185   v8::Handle<v8::ObjectTemplate> global =
    186       CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
    187   v8::Handle<v8::Context> context =
    188       v8::Context::New(CcTest::isolate(), NULL, global);
    189   v8::Context::Scope context_scope(context);
    190   CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
    191   // Run a loop that will be infinite if thread termination does not work.
    192   v8::Handle<v8::String> source = v8::String::NewFromUtf8(
    193       CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }");
    194   v8::Script::Compile(source)->Run();
    195 
    196   thread.Join();
    197   delete semaphore;
    198   semaphore = NULL;
    199 }
    200 
    201 
    202 int call_count = 0;
    203 
    204 
    205 void TerminateOrReturnObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
    206   if (++call_count == 10) {
    207     CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
    208     v8::V8::TerminateExecution(args.GetIsolate());
    209     return;
    210   }
    211   v8::Local<v8::Object> result = v8::Object::New(args.GetIsolate());
    212   result->Set(v8::String::NewFromUtf8(args.GetIsolate(), "x"),
    213               v8::Integer::New(args.GetIsolate(), 42));
    214   args.GetReturnValue().Set(result);
    215 }
    216 
    217 
    218 void LoopGetProperty(const v8::FunctionCallbackInfo<v8::Value>& args) {
    219   v8::TryCatch try_catch;
    220   CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
    221   v8::Script::Compile(
    222       v8::String::NewFromUtf8(args.GetIsolate(),
    223                               "function f() {"
    224                               "  try {"
    225                               "    while(true) {"
    226                               "      terminate_or_return_object().x;"
    227                               "    }"
    228                               "    fail();"
    229                               "  } catch(e) {"
    230                               "    fail();"
    231                               "  }"
    232                               "}"
    233                               "f()"))->Run();
    234   CHECK(try_catch.HasCaught());
    235   CHECK(try_catch.Exception()->IsNull());
    236   CHECK(try_catch.Message().IsEmpty());
    237   CHECK(!try_catch.CanContinue());
    238   CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
    239 }
    240 
    241 
    242 // Test that we correctly handle termination exceptions if they are
    243 // triggered by the creation of error objects in connection with ICs.
    244 TEST(TerminateLoadICException) {
    245   v8::Isolate* isolate = CcTest::isolate();
    246   v8::HandleScope scope(isolate);
    247   v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
    248   global->Set(
    249       v8::String::NewFromUtf8(isolate, "terminate_or_return_object"),
    250       v8::FunctionTemplate::New(isolate, TerminateOrReturnObject));
    251   global->Set(v8::String::NewFromUtf8(isolate, "fail"),
    252               v8::FunctionTemplate::New(isolate, Fail));
    253   global->Set(v8::String::NewFromUtf8(isolate, "loop"),
    254               v8::FunctionTemplate::New(isolate, LoopGetProperty));
    255 
    256   v8::Handle<v8::Context> context =
    257       v8::Context::New(isolate, NULL, global);
    258   v8::Context::Scope context_scope(context);
    259   CHECK(!v8::V8::IsExecutionTerminating(isolate));
    260   // Run a loop that will be infinite if thread termination does not work.
    261   v8::Handle<v8::String> source = v8::String::NewFromUtf8(
    262       isolate, "try { loop(); fail(); } catch(e) { fail(); }");
    263   call_count = 0;
    264   v8::Script::Compile(source)->Run();
    265   // Test that we can run the code again after thread termination.
    266   CHECK(!v8::V8::IsExecutionTerminating(isolate));
    267   call_count = 0;
    268   v8::Script::Compile(source)->Run();
    269 }
    270 
    271 
    272 void ReenterAfterTermination(const v8::FunctionCallbackInfo<v8::Value>& args) {
    273   v8::TryCatch try_catch;
    274   CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
    275   v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
    276                                               "function f() {"
    277                                               "  var term = true;"
    278                                               "  try {"
    279                                               "    while(true) {"
    280                                               "      if (term) terminate();"
    281                                               "      term = false;"
    282                                               "    }"
    283                                               "    fail();"
    284                                               "  } catch(e) {"
    285                                               "    fail();"
    286                                               "  }"
    287                                               "}"
    288                                               "f()"))->Run();
    289   CHECK(try_catch.HasCaught());
    290   CHECK(try_catch.Exception()->IsNull());
    291   CHECK(try_catch.Message().IsEmpty());
    292   CHECK(!try_catch.CanContinue());
    293   CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate()));
    294   v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
    295                                               "function f() { fail(); } f()"))
    296       ->Run();
    297 }
    298 
    299 
    300 // Test that reentry into V8 while the termination exception is still pending
    301 // (has not yet unwound the 0-level JS frame) does not crash.
    302 TEST(TerminateAndReenterFromThreadItself) {
    303   v8::Isolate* isolate = CcTest::isolate();
    304   v8::HandleScope scope(isolate);
    305   v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(
    306       isolate, TerminateCurrentThread, ReenterAfterTermination);
    307   v8::Handle<v8::Context> context =
    308       v8::Context::New(isolate, NULL, global);
    309   v8::Context::Scope context_scope(context);
    310   CHECK(!v8::V8::IsExecutionTerminating());
    311   v8::Handle<v8::String> source = v8::String::NewFromUtf8(
    312       isolate, "try { loop(); fail(); } catch(e) { fail(); }");
    313   v8::Script::Compile(source)->Run();
    314   CHECK(!v8::V8::IsExecutionTerminating(isolate));
    315   // Check we can run JS again after termination.
    316   CHECK(v8::Script::Compile(
    317       v8::String::NewFromUtf8(isolate,
    318                               "function f() { return true; }"
    319                               "f()"))
    320             ->Run()
    321             ->IsTrue());
    322 }
    323 
    324 
    325 void DoLoopCancelTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
    326   v8::TryCatch try_catch;
    327   CHECK(!v8::V8::IsExecutionTerminating());
    328   v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(),
    329                                               "var term = true;"
    330                                               "while(true) {"
    331                                               "  if (term) terminate();"
    332                                               "  term = false;"
    333                                               "}"
    334                                               "fail();"))->Run();
    335   CHECK(try_catch.HasCaught());
    336   CHECK(try_catch.Exception()->IsNull());
    337   CHECK(try_catch.Message().IsEmpty());
    338   CHECK(!try_catch.CanContinue());
    339   CHECK(v8::V8::IsExecutionTerminating());
    340   CHECK(try_catch.HasTerminated());
    341   v8::V8::CancelTerminateExecution(CcTest::isolate());
    342   CHECK(!v8::V8::IsExecutionTerminating());
    343 }
    344 
    345 
    346 // Test that a single thread of JavaScript execution can terminate
    347 // itself and then resume execution.
    348 TEST(TerminateCancelTerminateFromThreadItself) {
    349   v8::Isolate* isolate = CcTest::isolate();
    350   v8::HandleScope scope(isolate);
    351   v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(
    352       isolate, TerminateCurrentThread, DoLoopCancelTerminate);
    353   v8::Handle<v8::Context> context = v8::Context::New(isolate, NULL, global);
    354   v8::Context::Scope context_scope(context);
    355   CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate()));
    356   v8::Handle<v8::String> source = v8::String::NewFromUtf8(
    357       isolate, "try { doloop(); } catch(e) { fail(); } 'completed';");
    358   // Check that execution completed with correct return value.
    359   CHECK(v8::Script::Compile(source)->Run()->Equals(v8_str("completed")));
    360 }
    361 
    362 
    363 void MicrotaskShouldNotRun(const v8::FunctionCallbackInfo<v8::Value>& info) {
    364   CHECK(false);
    365 }
    366 
    367 
    368 void MicrotaskLoopForever(const v8::FunctionCallbackInfo<v8::Value>& info) {
    369   v8::Isolate* isolate = info.GetIsolate();
    370   v8::HandleScope scope(isolate);
    371   // Enqueue another should-not-run task to ensure we clean out the queue
    372   // when we terminate.
    373   isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskShouldNotRun));
    374   CompileRun("terminate(); while (true) { }");
    375   CHECK(v8::V8::IsExecutionTerminating());
    376 }
    377 
    378 
    379 TEST(TerminateFromOtherThreadWhileMicrotaskRunning) {
    380   semaphore = new v8::internal::Semaphore(0);
    381   TerminatorThread thread(CcTest::i_isolate());
    382   thread.Start();
    383 
    384   v8::Isolate* isolate = CcTest::isolate();
    385   isolate->SetAutorunMicrotasks(false);
    386   v8::HandleScope scope(isolate);
    387   v8::Handle<v8::ObjectTemplate> global =
    388       CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
    389   v8::Handle<v8::Context> context =
    390       v8::Context::New(CcTest::isolate(), NULL, global);
    391   v8::Context::Scope context_scope(context);
    392   isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskLoopForever));
    393   // The second task should never be run because we bail out if we're
    394   // terminating.
    395   isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskShouldNotRun));
    396   isolate->RunMicrotasks();
    397 
    398   v8::V8::CancelTerminateExecution(isolate);
    399   isolate->RunMicrotasks();  // should not run MicrotaskShouldNotRun
    400 
    401   thread.Join();
    402   delete semaphore;
    403   semaphore = NULL;
    404 }
    405