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 "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 semaphore->Signal(); 39 } 40 41 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 CHECK(false); 50 } 51 52 53 void Loop(const v8::FunctionCallbackInfo<v8::Value>& args) { 54 CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); 55 v8::Handle<v8::String> source = v8::String::NewFromUtf8( 56 args.GetIsolate(), "try { doloop(); fail(); } catch(e) { fail(); }"); 57 v8::Handle<v8::Value> result = v8::Script::Compile(source)->Run(); 58 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 v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), 67 "function f() {" 68 " var term = true;" 69 " try {" 70 " while(true) {" 71 " if (term) terminate();" 72 " term = false;" 73 " }" 74 " fail();" 75 " } catch(e) {" 76 " fail();" 77 " }" 78 "}" 79 "f()"))->Run(); 80 CHECK(try_catch.HasCaught()); 81 CHECK(try_catch.Exception()->IsNull()); 82 CHECK(try_catch.Message().IsEmpty()); 83 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 v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), 92 "var term = true;" 93 "while(true) {" 94 " if (term) terminate();" 95 " term = false;" 96 "}"))->Run(); 97 CHECK(try_catch.HasCaught()); 98 CHECK(try_catch.Exception()->IsNull()); 99 CHECK(try_catch.Message().IsEmpty()); 100 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 v8::FunctionCallback terminate, 108 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 return global; 119 } 120 121 122 // Test that a single thread of JavaScript execution can terminate 123 // itself. 124 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 v8::Handle<v8::String> source = v8::String::NewFromUtf8( 134 CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }"); 135 v8::Script::Compile(source)->Run(); 136 // Test that we can run the code again after thread termination. 137 CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); 138 v8::Script::Compile(source)->Run(); 139 } 140 141 142 // Test that a single thread of JavaScript execution can terminate 143 // itself in a loop that performs no calls. 144 TEST(TerminateOnlyV8ThreadFromThreadItselfNoLoop) { 145 v8::HandleScope scope(CcTest::isolate()); 146 v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate( 147 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 v8::Handle<v8::String> source = v8::String::NewFromUtf8( 154 CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }"); 155 v8::Script::Compile(source)->Run(); 156 CHECK(!v8::V8::IsExecutionTerminating(CcTest::isolate())); 157 // Test that we can run the code again after thread termination. 158 v8::Script::Compile(source)->Run(); 159 } 160 161 162 class TerminatorThread : public v8::base::Thread { 163 public: 164 explicit TerminatorThread(i::Isolate* isolate) 165 : Thread(Options("TerminatorThread")), 166 isolate_(reinterpret_cast<v8::Isolate*>(isolate)) {} 167 void Run() { 168 semaphore->Wait(); 169 CHECK(!v8::V8::IsExecutionTerminating(isolate_)); 170 v8::V8::TerminateExecution(isolate_); 171 } 172 173 private: 174 v8::Isolate* isolate_; 175 }; 176 177 178 // Test that a single thread of JavaScript execution can be terminated 179 // from the side by another thread. 180 TEST(TerminateOnlyV8ThreadFromOtherThread) { 181 semaphore = new v8::base::Semaphore(0); 182 TerminatorThread thread(CcTest::i_isolate()); 183 thread.Start(); 184 185 v8::HandleScope scope(CcTest::isolate()); 186 v8::Handle<v8::ObjectTemplate> global = 187 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 v8::Handle<v8::String> source = v8::String::NewFromUtf8( 194 CcTest::isolate(), "try { loop(); fail(); } catch(e) { fail(); }"); 195 v8::Script::Compile(source)->Run(); 196 197 thread.Join(); 198 delete semaphore; 199 semaphore = NULL; 200 } 201 202 203 int call_count = 0; 204 205 206 void TerminateOrReturnObject(const v8::FunctionCallbackInfo<v8::Value>& args) { 207 if (++call_count == 10) { 208 CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); 209 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 v8::Script::Compile( 223 v8::String::NewFromUtf8(args.GetIsolate(), 224 "function f() {" 225 " try {" 226 " while(true) {" 227 " terminate_or_return_object().x;" 228 " }" 229 " fail();" 230 " } catch(e) {" 231 " fail();" 232 " }" 233 "}" 234 "f()"))->Run(); 235 CHECK(try_catch.HasCaught()); 236 CHECK(try_catch.Exception()->IsNull()); 237 CHECK(try_catch.Message().IsEmpty()); 238 CHECK(!try_catch.CanContinue()); 239 CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate())); 240 } 241 242 243 // Test that we correctly handle termination exceptions if they are 244 // triggered by the creation of error objects in connection with ICs. 245 TEST(TerminateLoadICException) { 246 v8::Isolate* isolate = CcTest::isolate(); 247 v8::HandleScope scope(isolate); 248 v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); 249 global->Set( 250 v8::String::NewFromUtf8(isolate, "terminate_or_return_object"), 251 v8::FunctionTemplate::New(isolate, TerminateOrReturnObject)); 252 global->Set(v8::String::NewFromUtf8(isolate, "fail"), 253 v8::FunctionTemplate::New(isolate, Fail)); 254 global->Set(v8::String::NewFromUtf8(isolate, "loop"), 255 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 call_count = 0; 265 v8::Script::Compile(source)->Run(); 266 // Test that we can run the code again after thread termination. 267 CHECK(!v8::V8::IsExecutionTerminating(isolate)); 268 call_count = 0; 269 v8::Script::Compile(source)->Run(); 270 } 271 272 273 void ReenterAfterTermination(const v8::FunctionCallbackInfo<v8::Value>& args) { 274 v8::TryCatch try_catch; 275 CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate())); 276 v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), 277 "function f() {" 278 " var term = true;" 279 " try {" 280 " while(true) {" 281 " if (term) terminate();" 282 " term = false;" 283 " }" 284 " fail();" 285 " } catch(e) {" 286 " fail();" 287 " }" 288 "}" 289 "f()"))->Run(); 290 CHECK(try_catch.HasCaught()); 291 CHECK(try_catch.Exception()->IsNull()); 292 CHECK(try_catch.Message().IsEmpty()); 293 CHECK(!try_catch.CanContinue()); 294 CHECK(v8::V8::IsExecutionTerminating(args.GetIsolate())); 295 v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), 296 "function f() { fail(); } f()")) 297 ->Run(); 298 } 299 300 301 // Test that reentry into V8 while the termination exception is still pending 302 // (has not yet unwound the 0-level JS frame) does not crash. 303 TEST(TerminateAndReenterFromThreadItself) { 304 v8::Isolate* isolate = CcTest::isolate(); 305 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 v8::Context::Scope context_scope(context); 311 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 "function f() { return true; }" 320 "f()")) 321 ->Run() 322 ->IsTrue()); 323 } 324 325 326 void DoLoopCancelTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) { 327 v8::TryCatch try_catch; 328 CHECK(!v8::V8::IsExecutionTerminating()); 329 v8::Script::Compile(v8::String::NewFromUtf8(args.GetIsolate(), 330 "var term = true;" 331 "while(true) {" 332 " if (term) terminate();" 333 " term = false;" 334 "}" 335 "fail();"))->Run(); 336 CHECK(try_catch.HasCaught()); 337 CHECK(try_catch.Exception()->IsNull()); 338 CHECK(try_catch.Message().IsEmpty()); 339 CHECK(!try_catch.CanContinue()); 340 CHECK(v8::V8::IsExecutionTerminating()); 341 CHECK(try_catch.HasTerminated()); 342 v8::V8::CancelTerminateExecution(CcTest::isolate()); 343 CHECK(!v8::V8::IsExecutionTerminating()); 344 } 345 346 347 // Test that a single thread of JavaScript execution can terminate 348 // itself and then resume execution. 349 TEST(TerminateCancelTerminateFromThreadItself) { 350 v8::Isolate* isolate = CcTest::isolate(); 351 v8::HandleScope scope(isolate); 352 v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate( 353 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 v8::Handle<v8::String> source = v8::String::NewFromUtf8( 358 isolate, "try { doloop(); } catch(e) { fail(); } 'completed';"); 359 // Check that execution completed with correct return value. 360 CHECK(v8::Script::Compile(source)->Run()->Equals(v8_str("completed"))); 361 } 362 363 364 void MicrotaskShouldNotRun(const v8::FunctionCallbackInfo<v8::Value>& info) { 365 CHECK(false); 366 } 367 368 369 void MicrotaskLoopForever(const v8::FunctionCallbackInfo<v8::Value>& info) { 370 v8::Isolate* isolate = info.GetIsolate(); 371 v8::HandleScope scope(isolate); 372 // Enqueue another should-not-run task to ensure we clean out the queue 373 // when we terminate. 374 isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskShouldNotRun)); 375 CompileRun("terminate(); while (true) { }"); 376 CHECK(v8::V8::IsExecutionTerminating()); 377 } 378 379 380 TEST(TerminateFromOtherThreadWhileMicrotaskRunning) { 381 semaphore = new v8::base::Semaphore(0); 382 TerminatorThread thread(CcTest::i_isolate()); 383 thread.Start(); 384 385 v8::Isolate* isolate = CcTest::isolate(); 386 isolate->SetAutorunMicrotasks(false); 387 v8::HandleScope scope(isolate); 388 v8::Handle<v8::ObjectTemplate> global = 389 CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop); 390 v8::Handle<v8::Context> context = 391 v8::Context::New(CcTest::isolate(), NULL, global); 392 v8::Context::Scope context_scope(context); 393 isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskLoopForever)); 394 // The second task should never be run because we bail out if we're 395 // terminating. 396 isolate->EnqueueMicrotask(v8::Function::New(isolate, MicrotaskShouldNotRun)); 397 isolate->RunMicrotasks(); 398 399 v8::V8::CancelTerminateExecution(isolate); 400 isolate->RunMicrotasks(); // should not run MicrotaskShouldNotRun 401 402 thread.Join(); 403 delete semaphore; 404 semaphore = NULL; 405 } 406 407 408 static int callback_counter = 0; 409 410 411 static void CounterCallback(v8::Isolate* isolate, void* data) { 412 callback_counter++; 413 } 414 415 416 TEST(PostponeTerminateException) { 417 v8::Isolate* isolate = CcTest::isolate(); 418 v8::HandleScope scope(isolate); 419 v8::Handle<v8::ObjectTemplate> global = 420 CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop); 421 v8::Handle<v8::Context> context = 422 v8::Context::New(CcTest::isolate(), NULL, global); 423 v8::Context::Scope context_scope(context); 424 425 v8::TryCatch try_catch; 426 static const char* terminate_and_loop = 427 "terminate(); for (var i = 0; i < 10000; i++);"; 428 429 { // Postpone terminate execution interrupts. 430 i::PostponeInterruptsScope p1(CcTest::i_isolate(), 431 i::StackGuard::TERMINATE_EXECUTION) ; 432 433 // API interrupts should still be triggered. 434 CcTest::isolate()->RequestInterrupt(&CounterCallback, NULL); 435 CHECK_EQ(0, callback_counter); 436 CompileRun(terminate_and_loop); 437 CHECK(!try_catch.HasTerminated()); 438 CHECK_EQ(1, callback_counter); 439 440 { // Postpone API interrupts as well. 441 i::PostponeInterruptsScope p2(CcTest::i_isolate(), 442 i::StackGuard::API_INTERRUPT); 443 444 // None of the two interrupts should trigger. 445 CcTest::isolate()->RequestInterrupt(&CounterCallback, NULL); 446 CompileRun(terminate_and_loop); 447 CHECK(!try_catch.HasTerminated()); 448 CHECK_EQ(1, callback_counter); 449 } 450 451 // Now the previously requested API interrupt should trigger. 452 CompileRun(terminate_and_loop); 453 CHECK(!try_catch.HasTerminated()); 454 CHECK_EQ(2, callback_counter); 455 } 456 457 // Now the previously requested terminate execution interrupt should trigger. 458 CompileRun("for (var i = 0; i < 10000; i++);"); 459 CHECK(try_catch.HasTerminated()); 460 CHECK_EQ(2, callback_counter); 461 } 462 463 464 TEST(ErrorObjectAfterTermination) { 465 v8::Isolate* isolate = CcTest::isolate(); 466 v8::HandleScope scope(isolate); 467 v8::Handle<v8::Context> context = v8::Context::New(CcTest::isolate()); 468 v8::Context::Scope context_scope(context); 469 v8::V8::TerminateExecution(isolate); 470 v8::Local<v8::Value> error = v8::Exception::Error(v8_str("error")); 471 // TODO(yangguo): crbug/403509. Check for empty handle instead. 472 CHECK(error->IsUndefined()); 473 } 474