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