Line data Source code
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/api-inl.h"
29 : #include "src/isolate.h"
30 : #include "src/objects-inl.h"
31 : #include "src/v8.h"
32 : #include "test/cctest/cctest.h"
33 :
34 : #include "src/base/platform/platform.h"
35 :
36 : v8::base::Semaphore* semaphore = nullptr;
37 :
38 30 : void Signal(const v8::FunctionCallbackInfo<v8::Value>& args) {
39 30 : semaphore->Signal();
40 30 : }
41 :
42 :
43 70 : void TerminateCurrentThread(const v8::FunctionCallbackInfo<v8::Value>& args) {
44 70 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
45 70 : args.GetIsolate()->TerminateExecution();
46 70 : }
47 :
48 0 : void Fail(const v8::FunctionCallbackInfo<v8::Value>& args) { UNREACHABLE(); }
49 :
50 45 : void Loop(const v8::FunctionCallbackInfo<v8::Value>& args) {
51 45 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
52 : v8::MaybeLocal<v8::Value> result =
53 : CompileRun(args.GetIsolate()->GetCurrentContext(),
54 45 : "try { doloop(); fail(); } catch(e) { fail(); }");
55 45 : CHECK(result.IsEmpty());
56 45 : CHECK(args.GetIsolate()->IsExecutionTerminating());
57 45 : }
58 :
59 :
60 15 : void DoLoop(const v8::FunctionCallbackInfo<v8::Value>& args) {
61 30 : v8::TryCatch try_catch(args.GetIsolate());
62 15 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
63 : v8::MaybeLocal<v8::Value> result =
64 : CompileRun(args.GetIsolate()->GetCurrentContext(),
65 : "function f() {"
66 : " var term = true;"
67 : " try {"
68 : " while(true) {"
69 : " if (term) terminate();"
70 : " term = false;"
71 : " }"
72 : " fail();"
73 : " } catch(e) {"
74 : " fail();"
75 : " }"
76 : "}"
77 15 : "f()");
78 15 : CHECK(result.IsEmpty());
79 15 : CHECK(try_catch.HasCaught());
80 30 : CHECK(try_catch.Exception()->IsNull());
81 30 : CHECK(try_catch.Message().IsEmpty());
82 15 : CHECK(!try_catch.CanContinue());
83 15 : CHECK(args.GetIsolate()->IsExecutionTerminating());
84 15 : }
85 :
86 :
87 15 : void DoLoopNoCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
88 30 : v8::TryCatch try_catch(args.GetIsolate());
89 15 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
90 : v8::MaybeLocal<v8::Value> result =
91 : CompileRun(args.GetIsolate()->GetCurrentContext(),
92 : "var term = true;"
93 : "while(true) {"
94 : " if (term) terminate();"
95 : " term = false;"
96 15 : "}");
97 15 : CHECK(result.IsEmpty());
98 15 : CHECK(try_catch.HasCaught());
99 30 : CHECK(try_catch.Exception()->IsNull());
100 30 : CHECK(try_catch.Message().IsEmpty());
101 15 : CHECK(!try_catch.CanContinue());
102 15 : CHECK(args.GetIsolate()->IsExecutionTerminating());
103 15 : }
104 :
105 :
106 90 : v8::Local<v8::ObjectTemplate> CreateGlobalTemplate(
107 : v8::Isolate* isolate, v8::FunctionCallback terminate,
108 : v8::FunctionCallback doloop) {
109 90 : v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
110 270 : global->Set(v8_str("terminate"),
111 90 : v8::FunctionTemplate::New(isolate, terminate));
112 270 : global->Set(v8_str("fail"), v8::FunctionTemplate::New(isolate, Fail));
113 270 : global->Set(v8_str("loop"), v8::FunctionTemplate::New(isolate, Loop));
114 270 : global->Set(v8_str("doloop"), v8::FunctionTemplate::New(isolate, doloop));
115 90 : return global;
116 : }
117 :
118 :
119 : // Test that a single thread of JavaScript execution can terminate
120 : // itself.
121 26644 : TEST(TerminateOnlyV8ThreadFromThreadItself) {
122 10 : v8::HandleScope scope(CcTest::isolate());
123 : v8::Local<v8::ObjectTemplate> global =
124 5 : CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
125 : v8::Local<v8::Context> context =
126 5 : v8::Context::New(CcTest::isolate(), nullptr, global);
127 : v8::Context::Scope context_scope(context);
128 5 : CHECK(!CcTest::isolate()->IsExecutionTerminating());
129 : // Run a loop that will be infinite if thread termination does not work.
130 : v8::MaybeLocal<v8::Value> result =
131 : CompileRun(CcTest::isolate()->GetCurrentContext(),
132 5 : "try { loop(); fail(); } catch(e) { fail(); }");
133 5 : CHECK(result.IsEmpty());
134 : // Test that we can run the code again after thread termination.
135 5 : CHECK(!CcTest::isolate()->IsExecutionTerminating());
136 : result = CompileRun(CcTest::isolate()->GetCurrentContext(),
137 5 : "try { loop(); fail(); } catch(e) { fail(); }");
138 5 : CHECK(result.IsEmpty());
139 5 : }
140 :
141 :
142 : // Test that a single thread of JavaScript execution can terminate
143 : // itself in a loop that performs no calls.
144 26644 : TEST(TerminateOnlyV8ThreadFromThreadItselfNoLoop) {
145 10 : v8::HandleScope scope(CcTest::isolate());
146 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
147 5 : CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall);
148 : v8::Local<v8::Context> context =
149 5 : v8::Context::New(CcTest::isolate(), nullptr, global);
150 : v8::Context::Scope context_scope(context);
151 5 : CHECK(!CcTest::isolate()->IsExecutionTerminating());
152 : // Run a loop that will be infinite if thread termination does not work.
153 : static const char* source = "try { loop(); fail(); } catch(e) { fail(); }";
154 : v8::MaybeLocal<v8::Value> result =
155 5 : CompileRun(CcTest::isolate()->GetCurrentContext(), source);
156 5 : CHECK(result.IsEmpty());
157 5 : CHECK(!CcTest::isolate()->IsExecutionTerminating());
158 : // Test that we can run the code again after thread termination.
159 5 : result = CompileRun(CcTest::isolate()->GetCurrentContext(), source);
160 5 : CHECK(result.IsEmpty());
161 5 : }
162 :
163 :
164 30 : class TerminatorThread : public v8::base::Thread {
165 : public:
166 : explicit TerminatorThread(i::Isolate* isolate)
167 : : Thread(Options("TerminatorThread")),
168 30 : isolate_(reinterpret_cast<v8::Isolate*>(isolate)) {}
169 30 : void Run() override {
170 30 : semaphore->Wait();
171 30 : CHECK(!isolate_->IsExecutionTerminating());
172 30 : isolate_->TerminateExecution();
173 30 : }
174 :
175 : private:
176 : v8::Isolate* isolate_;
177 : };
178 :
179 25 : void TestTerminatingSlowOperation(const char* source) {
180 25 : semaphore = new v8::base::Semaphore(0);
181 : TerminatorThread thread(CcTest::i_isolate());
182 25 : thread.Start();
183 :
184 50 : v8::HandleScope scope(CcTest::isolate());
185 : v8::Local<v8::ObjectTemplate> global =
186 25 : CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
187 : v8::Local<v8::Context> context =
188 25 : v8::Context::New(CcTest::isolate(), nullptr, global);
189 : v8::Context::Scope context_scope(context);
190 25 : CHECK(!CcTest::isolate()->IsExecutionTerminating());
191 : v8::MaybeLocal<v8::Value> result =
192 25 : CompileRun(CcTest::isolate()->GetCurrentContext(), source);
193 25 : CHECK(result.IsEmpty());
194 25 : thread.Join();
195 25 : delete semaphore;
196 25 : semaphore = nullptr;
197 25 : }
198 :
199 : // Test that a single thread of JavaScript execution can be terminated
200 : // from the side by another thread.
201 26644 : TEST(TerminateOnlyV8ThreadFromOtherThread) {
202 : // Run a loop that will be infinite if thread termination does not work.
203 5 : TestTerminatingSlowOperation("try { loop(); fail(); } catch(e) { fail(); }");
204 5 : }
205 :
206 : // Test that execution can be terminated from within JSON.stringify.
207 26644 : TEST(TerminateJsonStringify) {
208 : TestTerminatingSlowOperation(
209 : "var x = [];"
210 : "x[2**31]=1;"
211 : "terminate();"
212 : "JSON.stringify(x);"
213 5 : "fail();");
214 5 : }
215 :
216 26644 : TEST(TerminateBigIntMultiplication) {
217 : TestTerminatingSlowOperation(
218 : "terminate();"
219 : "var a = 5n ** 555555n;"
220 : "var b = 3n ** 3333333n;"
221 : "a * b;"
222 5 : "fail();");
223 5 : }
224 :
225 26644 : TEST(TerminateBigIntDivision) {
226 : TestTerminatingSlowOperation(
227 : "var a = 2n ** 2222222n;"
228 : "var b = 3n ** 333333n;"
229 : "terminate();"
230 : "a / b;"
231 5 : "fail();");
232 5 : }
233 :
234 26644 : TEST(TerminateBigIntToString) {
235 : TestTerminatingSlowOperation(
236 : "var a = 2n ** 2222222n;"
237 : "terminate();"
238 : "a.toString();"
239 5 : "fail();");
240 5 : }
241 :
242 : int call_count = 0;
243 :
244 :
245 100 : void TerminateOrReturnObject(const v8::FunctionCallbackInfo<v8::Value>& args) {
246 100 : if (++call_count == 10) {
247 10 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
248 10 : args.GetIsolate()->TerminateExecution();
249 10 : return;
250 : }
251 90 : v8::Local<v8::Object> result = v8::Object::New(args.GetIsolate());
252 : v8::Maybe<bool> val =
253 : result->Set(args.GetIsolate()->GetCurrentContext(), v8_str("x"),
254 360 : v8::Integer::New(args.GetIsolate(), 42));
255 90 : CHECK(val.FromJust());
256 : args.GetReturnValue().Set(result);
257 : }
258 :
259 :
260 10 : void LoopGetProperty(const v8::FunctionCallbackInfo<v8::Value>& args) {
261 20 : v8::TryCatch try_catch(args.GetIsolate());
262 10 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
263 : v8::MaybeLocal<v8::Value> result =
264 : CompileRun(args.GetIsolate()->GetCurrentContext(),
265 : "function f() {"
266 : " try {"
267 : " while(true) {"
268 : " terminate_or_return_object().x;"
269 : " }"
270 : " fail();"
271 : " } catch(e) {"
272 : " (function() {})();" // trigger stack check.
273 : " fail();"
274 : " }"
275 : "}"
276 10 : "f()");
277 10 : CHECK(result.IsEmpty());
278 10 : CHECK(try_catch.HasCaught());
279 20 : CHECK(try_catch.Exception()->IsNull());
280 20 : CHECK(try_catch.Message().IsEmpty());
281 10 : CHECK(!try_catch.CanContinue());
282 10 : CHECK(args.GetIsolate()->IsExecutionTerminating());
283 10 : }
284 :
285 :
286 : // Test that we correctly handle termination exceptions if they are
287 : // triggered by the creation of error objects in connection with ICs.
288 26644 : TEST(TerminateLoadICException) {
289 5 : v8::Isolate* isolate = CcTest::isolate();
290 10 : v8::HandleScope scope(isolate);
291 5 : v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
292 15 : global->Set(v8_str("terminate_or_return_object"),
293 5 : v8::FunctionTemplate::New(isolate, TerminateOrReturnObject));
294 15 : global->Set(v8_str("fail"), v8::FunctionTemplate::New(isolate, Fail));
295 15 : global->Set(v8_str("loop"),
296 5 : v8::FunctionTemplate::New(isolate, LoopGetProperty));
297 :
298 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
299 : v8::Context::Scope context_scope(context);
300 5 : CHECK(!isolate->IsExecutionTerminating());
301 : // Run a loop that will be infinite if thread termination does not work.
302 : static const char* source = "try { loop(); fail(); } catch(e) { fail(); }";
303 5 : call_count = 0;
304 : v8::MaybeLocal<v8::Value> result =
305 5 : CompileRun(isolate->GetCurrentContext(), source);
306 5 : CHECK(result.IsEmpty());
307 : // Test that we can run the code again after thread termination.
308 5 : CHECK(!isolate->IsExecutionTerminating());
309 5 : call_count = 0;
310 5 : result = CompileRun(isolate->GetCurrentContext(), source);
311 5 : CHECK(result.IsEmpty());
312 5 : }
313 :
314 :
315 26639 : v8::Persistent<v8::String> reenter_script_1;
316 26639 : v8::Persistent<v8::String> reenter_script_2;
317 :
318 10 : void ReenterAfterTermination(const v8::FunctionCallbackInfo<v8::Value>& args) {
319 20 : v8::TryCatch try_catch(args.GetIsolate());
320 : v8::Isolate* isolate = args.GetIsolate();
321 10 : CHECK(!isolate->IsExecutionTerminating());
322 : v8::Local<v8::String> script =
323 10 : v8::Local<v8::String>::New(isolate, reenter_script_1);
324 10 : v8::MaybeLocal<v8::Value> result = CompileRun(script);
325 10 : CHECK(result.IsEmpty());
326 10 : CHECK(try_catch.HasCaught());
327 20 : CHECK(try_catch.Exception()->IsNull());
328 20 : CHECK(try_catch.Message().IsEmpty());
329 10 : CHECK(!try_catch.CanContinue());
330 10 : CHECK(try_catch.HasTerminated());
331 10 : CHECK(isolate->IsExecutionTerminating());
332 10 : script = v8::Local<v8::String>::New(isolate, reenter_script_2);
333 : v8::MaybeLocal<v8::Script> compiled_script =
334 10 : v8::Script::Compile(isolate->GetCurrentContext(), script);
335 10 : CHECK(compiled_script.IsEmpty());
336 10 : }
337 :
338 :
339 : // Test that reentry into V8 while the termination exception is still pending
340 : // (has not yet unwound the 0-level JS frame) does not crash.
341 26644 : TEST(TerminateAndReenterFromThreadItself) {
342 5 : v8::Isolate* isolate = CcTest::isolate();
343 10 : v8::HandleScope scope(isolate);
344 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
345 5 : isolate, TerminateCurrentThread, ReenterAfterTermination);
346 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
347 : v8::Context::Scope context_scope(context);
348 5 : CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
349 : // Create script strings upfront as it won't work when terminating.
350 10 : reenter_script_1.Reset(isolate, v8_str(
351 : "function f() {"
352 : " var term = true;"
353 : " try {"
354 : " while(true) {"
355 : " if (term) terminate();"
356 : " term = false;"
357 : " }"
358 : " fail();"
359 : " } catch(e) {"
360 : " fail();"
361 : " }"
362 : "}"
363 : "f()"));
364 10 : reenter_script_2.Reset(isolate, v8_str("function f() { fail(); } f()"));
365 : CompileRun("try { loop(); fail(); } catch(e) { fail(); }");
366 5 : CHECK(!isolate->IsExecutionTerminating());
367 : // Check we can run JS again after termination.
368 5 : CHECK(CompileRun("function f() { return true; } f()")->IsTrue());
369 : reenter_script_1.Reset();
370 : reenter_script_2.Reset();
371 5 : }
372 :
373 26644 : TEST(TerminateAndReenterFromThreadItselfWithOuterTryCatch) {
374 5 : v8::Isolate* isolate = CcTest::isolate();
375 10 : v8::HandleScope scope(isolate);
376 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
377 5 : isolate, TerminateCurrentThread, ReenterAfterTermination);
378 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
379 : v8::Context::Scope context_scope(context);
380 5 : CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
381 : // Create script strings upfront as it won't work when terminating.
382 10 : reenter_script_1.Reset(isolate, v8_str("function f() {"
383 : " var term = true;"
384 : " try {"
385 : " while(true) {"
386 : " if (term) terminate();"
387 : " term = false;"
388 : " }"
389 : " fail();"
390 : " } catch(e) {"
391 : " fail();"
392 : " }"
393 : "}"
394 : "f()"));
395 10 : reenter_script_2.Reset(isolate, v8_str("function f() { fail(); } f()"));
396 : {
397 10 : v8::TryCatch try_catch(isolate);
398 : CompileRun("try { loop(); fail(); } catch(e) { fail(); }");
399 5 : CHECK(try_catch.HasCaught());
400 10 : CHECK(try_catch.Exception()->IsNull());
401 10 : CHECK(try_catch.Message().IsEmpty());
402 5 : CHECK(!try_catch.CanContinue());
403 5 : CHECK(try_catch.HasTerminated());
404 5 : CHECK(isolate->IsExecutionTerminating());
405 : }
406 5 : CHECK(!isolate->IsExecutionTerminating());
407 : // Check we can run JS again after termination.
408 5 : CHECK(CompileRun("function f() { return true; } f()")->IsTrue());
409 : reenter_script_1.Reset();
410 : reenter_script_2.Reset();
411 5 : }
412 :
413 5 : void DoLoopCancelTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
414 10 : v8::TryCatch try_catch(args.GetIsolate());
415 5 : CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
416 : v8::MaybeLocal<v8::Value> result =
417 : CompileRun(args.GetIsolate()->GetCurrentContext(),
418 : "var term = true;"
419 : "while(true) {"
420 : " if (term) terminate();"
421 : " term = false;"
422 : "}"
423 5 : "fail();");
424 5 : CHECK(result.IsEmpty());
425 5 : CHECK(try_catch.HasCaught());
426 10 : CHECK(try_catch.Exception()->IsNull());
427 10 : CHECK(try_catch.Message().IsEmpty());
428 5 : CHECK(!try_catch.CanContinue());
429 5 : CHECK(v8::Isolate::GetCurrent()->IsExecutionTerminating());
430 5 : CHECK(try_catch.HasTerminated());
431 5 : CcTest::isolate()->CancelTerminateExecution();
432 5 : CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
433 5 : }
434 :
435 :
436 : // Test that a single thread of JavaScript execution can terminate
437 : // itself and then resume execution.
438 26644 : TEST(TerminateCancelTerminateFromThreadItself) {
439 5 : v8::Isolate* isolate = CcTest::isolate();
440 10 : v8::HandleScope scope(isolate);
441 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
442 5 : isolate, TerminateCurrentThread, DoLoopCancelTerminate);
443 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
444 : v8::Context::Scope context_scope(context);
445 5 : CHECK(!CcTest::isolate()->IsExecutionTerminating());
446 : // Check that execution completed with correct return value.
447 : v8::Local<v8::Value> result =
448 5 : CompileRun(isolate->GetCurrentContext(),
449 5 : "try { doloop(); } catch(e) { fail(); } 'completed';")
450 : .ToLocalChecked();
451 15 : CHECK(result->Equals(isolate->GetCurrentContext(), v8_str("completed"))
452 : .FromJust());
453 5 : }
454 :
455 :
456 0 : void MicrotaskShouldNotRun(const v8::FunctionCallbackInfo<v8::Value>& info) {
457 0 : UNREACHABLE();
458 : }
459 :
460 :
461 5 : void MicrotaskLoopForever(const v8::FunctionCallbackInfo<v8::Value>& info) {
462 : v8::Isolate* isolate = info.GetIsolate();
463 10 : v8::HandleScope scope(isolate);
464 : // Enqueue another should-not-run task to ensure we clean out the queue
465 : // when we terminate.
466 : isolate->EnqueueMicrotask(
467 10 : v8::Function::New(isolate->GetCurrentContext(), MicrotaskShouldNotRun)
468 5 : .ToLocalChecked());
469 : CompileRun("terminate(); while (true) { }");
470 5 : CHECK(v8::Isolate::GetCurrent()->IsExecutionTerminating());
471 5 : }
472 :
473 :
474 26644 : TEST(TerminateFromOtherThreadWhileMicrotaskRunning) {
475 5 : semaphore = new v8::base::Semaphore(0);
476 : TerminatorThread thread(CcTest::i_isolate());
477 5 : thread.Start();
478 :
479 5 : v8::Isolate* isolate = CcTest::isolate();
480 5 : isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
481 10 : v8::HandleScope scope(isolate);
482 : v8::Local<v8::ObjectTemplate> global =
483 5 : CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);
484 : v8::Local<v8::Context> context =
485 5 : v8::Context::New(CcTest::isolate(), nullptr, global);
486 : v8::Context::Scope context_scope(context);
487 : isolate->EnqueueMicrotask(
488 10 : v8::Function::New(isolate->GetCurrentContext(), MicrotaskLoopForever)
489 5 : .ToLocalChecked());
490 : // The second task should never be run because we bail out if we're
491 : // terminating.
492 : isolate->EnqueueMicrotask(
493 10 : v8::Function::New(isolate->GetCurrentContext(), MicrotaskShouldNotRun)
494 5 : .ToLocalChecked());
495 5 : isolate->RunMicrotasks();
496 :
497 5 : isolate->CancelTerminateExecution();
498 5 : isolate->RunMicrotasks(); // should not run MicrotaskShouldNotRun
499 :
500 5 : thread.Join();
501 5 : delete semaphore;
502 5 : semaphore = nullptr;
503 5 : }
504 :
505 :
506 : static int callback_counter = 0;
507 :
508 :
509 10 : static void CounterCallback(v8::Isolate* isolate, void* data) {
510 10 : callback_counter++;
511 10 : }
512 :
513 :
514 26644 : TEST(PostponeTerminateException) {
515 5 : v8::Isolate* isolate = CcTest::isolate();
516 10 : v8::HandleScope scope(isolate);
517 : v8::Local<v8::ObjectTemplate> global =
518 5 : CreateGlobalTemplate(CcTest::isolate(), TerminateCurrentThread, DoLoop);
519 : v8::Local<v8::Context> context =
520 5 : v8::Context::New(CcTest::isolate(), nullptr, global);
521 : v8::Context::Scope context_scope(context);
522 :
523 10 : v8::TryCatch try_catch(isolate);
524 : static const char* terminate_and_loop =
525 : "terminate(); for (var i = 0; i < 10000; i++);";
526 :
527 : { // Postpone terminate execution interrupts.
528 : i::PostponeInterruptsScope p1(CcTest::i_isolate(),
529 : i::StackGuard::TERMINATE_EXECUTION);
530 :
531 : // API interrupts should still be triggered.
532 5 : CcTest::isolate()->RequestInterrupt(&CounterCallback, nullptr);
533 5 : CHECK_EQ(0, callback_counter);
534 5 : CompileRun(terminate_and_loop);
535 5 : CHECK(!try_catch.HasTerminated());
536 5 : CHECK_EQ(1, callback_counter);
537 :
538 : { // Postpone API interrupts as well.
539 : i::PostponeInterruptsScope p2(CcTest::i_isolate(),
540 : i::StackGuard::API_INTERRUPT);
541 :
542 : // None of the two interrupts should trigger.
543 5 : CcTest::isolate()->RequestInterrupt(&CounterCallback, nullptr);
544 5 : CompileRun(terminate_and_loop);
545 5 : CHECK(!try_catch.HasTerminated());
546 5 : CHECK_EQ(1, callback_counter);
547 : }
548 :
549 : // Now the previously requested API interrupt should trigger.
550 5 : CompileRun(terminate_and_loop);
551 5 : CHECK(!try_catch.HasTerminated());
552 5 : CHECK_EQ(2, callback_counter);
553 : }
554 :
555 : // Now the previously requested terminate execution interrupt should trigger.
556 : CompileRun("for (var i = 0; i < 10000; i++);");
557 5 : CHECK(try_catch.HasTerminated());
558 5 : CHECK_EQ(2, callback_counter);
559 5 : }
560 :
561 65 : static void AssertTerminatedCodeRun(v8::Isolate* isolate) {
562 130 : v8::TryCatch try_catch(isolate);
563 : CompileRun("for (var i = 0; i < 10000; i++);");
564 65 : CHECK(try_catch.HasTerminated());
565 65 : }
566 :
567 90 : static void AssertFinishedCodeRun(v8::Isolate* isolate) {
568 180 : v8::TryCatch try_catch(isolate);
569 : CompileRun("for (var i = 0; i < 10000; i++);");
570 90 : CHECK(!try_catch.HasTerminated());
571 90 : }
572 :
573 26644 : TEST(SafeForTerminateException) {
574 5 : v8::Isolate* isolate = CcTest::isolate();
575 10 : v8::HandleScope scope(isolate);
576 5 : v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
577 : v8::Context::Scope context_scope(context);
578 :
579 : { // Checks safe for termination scope.
580 : i::PostponeInterruptsScope p1(CcTest::i_isolate(),
581 : i::StackGuard::TERMINATE_EXECUTION);
582 5 : isolate->TerminateExecution();
583 5 : AssertFinishedCodeRun(isolate);
584 : {
585 : i::SafeForInterruptsScope p2(CcTest::i_isolate(),
586 : i::StackGuard::TERMINATE_EXECUTION);
587 5 : AssertTerminatedCodeRun(isolate);
588 5 : AssertFinishedCodeRun(isolate);
589 5 : isolate->TerminateExecution();
590 : }
591 5 : AssertFinishedCodeRun(isolate);
592 5 : isolate->CancelTerminateExecution();
593 : }
594 :
595 5 : isolate->TerminateExecution();
596 : { // no scope -> postpone
597 : i::PostponeInterruptsScope p1(CcTest::i_isolate(),
598 : i::StackGuard::TERMINATE_EXECUTION);
599 5 : AssertFinishedCodeRun(isolate);
600 : { // postpone -> postpone
601 : i::PostponeInterruptsScope p2(CcTest::i_isolate(),
602 : i::StackGuard::TERMINATE_EXECUTION);
603 5 : AssertFinishedCodeRun(isolate);
604 :
605 : { // postpone -> safe
606 : i::SafeForInterruptsScope p3(CcTest::i_isolate(),
607 : i::StackGuard::TERMINATE_EXECUTION);
608 5 : AssertTerminatedCodeRun(isolate);
609 5 : isolate->TerminateExecution();
610 :
611 : { // safe -> safe
612 : i::SafeForInterruptsScope p4(CcTest::i_isolate(),
613 : i::StackGuard::TERMINATE_EXECUTION);
614 5 : AssertTerminatedCodeRun(isolate);
615 5 : isolate->TerminateExecution();
616 :
617 : { // safe -> postpone
618 : i::PostponeInterruptsScope p5(CcTest::i_isolate(),
619 : i::StackGuard::TERMINATE_EXECUTION);
620 5 : AssertFinishedCodeRun(isolate);
621 : } // postpone -> safe
622 :
623 5 : AssertTerminatedCodeRun(isolate);
624 5 : isolate->TerminateExecution();
625 : } // safe -> safe
626 :
627 5 : AssertTerminatedCodeRun(isolate);
628 5 : isolate->TerminateExecution();
629 : } // safe -> postpone
630 :
631 5 : AssertFinishedCodeRun(isolate);
632 : } // postpone -> postpone
633 :
634 5 : AssertFinishedCodeRun(isolate);
635 : } // postpone -> no scope
636 5 : AssertTerminatedCodeRun(isolate);
637 :
638 5 : isolate->TerminateExecution();
639 : { // no scope -> safe
640 : i::SafeForInterruptsScope p1(CcTest::i_isolate(),
641 : i::StackGuard::TERMINATE_EXECUTION);
642 5 : AssertTerminatedCodeRun(isolate);
643 : } // safe -> no scope
644 5 : AssertFinishedCodeRun(isolate);
645 :
646 : { // no scope -> postpone
647 : i::PostponeInterruptsScope p1(CcTest::i_isolate(),
648 : i::StackGuard::TERMINATE_EXECUTION);
649 5 : isolate->TerminateExecution();
650 : { // postpone -> safe
651 : i::SafeForInterruptsScope p2(CcTest::i_isolate(),
652 : i::StackGuard::TERMINATE_EXECUTION);
653 5 : AssertTerminatedCodeRun(isolate);
654 : } // safe -> postpone
655 : } // postpone -> no scope
656 5 : AssertFinishedCodeRun(isolate);
657 :
658 : { // no scope -> postpone
659 : i::PostponeInterruptsScope p1(CcTest::i_isolate(),
660 : i::StackGuard::TERMINATE_EXECUTION);
661 : { // postpone -> safe
662 : i::SafeForInterruptsScope p2(CcTest::i_isolate(),
663 : i::StackGuard::TERMINATE_EXECUTION);
664 : { // safe -> postpone
665 : i::PostponeInterruptsScope p3(CcTest::i_isolate(),
666 : i::StackGuard::TERMINATE_EXECUTION);
667 5 : isolate->TerminateExecution();
668 : } // postpone -> safe
669 5 : AssertTerminatedCodeRun(isolate);
670 : } // safe -> postpone
671 : } // postpone -> no scope
672 5 : }
673 :
674 10 : void RequestTermianteAndCallAPI(
675 : const v8::FunctionCallbackInfo<v8::Value>& args) {
676 10 : args.GetIsolate()->TerminateExecution();
677 10 : AssertFinishedCodeRun(args.GetIsolate());
678 10 : }
679 :
680 26644 : UNINITIALIZED_TEST(IsolateSafeForTerminationMode) {
681 : v8::Isolate::CreateParams create_params;
682 5 : create_params.only_terminate_in_safe_scope = true;
683 5 : create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
684 5 : v8::Isolate* isolate = v8::Isolate::New(create_params);
685 : i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
686 : {
687 : v8::Isolate::Scope isolate_scope(isolate);
688 10 : v8::HandleScope handle_scope(isolate);
689 5 : v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
690 15 : global->Set(v8_str("terminateAndCallAPI"),
691 5 : v8::FunctionTemplate::New(isolate, RequestTermianteAndCallAPI));
692 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
693 : v8::Context::Scope context_scope(context);
694 :
695 : // Should postpone termination without safe scope.
696 5 : isolate->TerminateExecution();
697 5 : AssertFinishedCodeRun(isolate);
698 : {
699 10 : v8::Isolate::SafeForTerminationScope safe_scope(isolate);
700 5 : AssertTerminatedCodeRun(isolate);
701 : }
702 5 : AssertFinishedCodeRun(isolate);
703 :
704 : {
705 5 : isolate->TerminateExecution();
706 5 : AssertFinishedCodeRun(isolate);
707 : i::PostponeInterruptsScope p1(i_isolate,
708 : i::StackGuard::TERMINATE_EXECUTION);
709 : {
710 : // SafeForTermination overrides postpone.
711 10 : v8::Isolate::SafeForTerminationScope safe_scope(isolate);
712 5 : AssertTerminatedCodeRun(isolate);
713 : }
714 5 : AssertFinishedCodeRun(isolate);
715 : }
716 :
717 : {
718 10 : v8::Isolate::SafeForTerminationScope safe_scope(isolate);
719 : // Request terminate and call API recursively.
720 : CompileRun("terminateAndCallAPI()");
721 5 : AssertTerminatedCodeRun(isolate);
722 : }
723 :
724 : {
725 : i::PostponeInterruptsScope p1(i_isolate,
726 : i::StackGuard::TERMINATE_EXECUTION);
727 : // Request terminate and call API recursively.
728 : CompileRun("terminateAndCallAPI()");
729 5 : AssertFinishedCodeRun(isolate);
730 : }
731 5 : AssertFinishedCodeRun(isolate);
732 : {
733 10 : v8::Isolate::SafeForTerminationScope safe_scope(isolate);
734 5 : AssertTerminatedCodeRun(isolate);
735 : }
736 : }
737 5 : isolate->Dispose();
738 5 : }
739 :
740 26644 : TEST(ErrorObjectAfterTermination) {
741 5 : v8::Isolate* isolate = CcTest::isolate();
742 10 : v8::HandleScope scope(isolate);
743 5 : v8::Local<v8::Context> context = v8::Context::New(CcTest::isolate());
744 : v8::Context::Scope context_scope(context);
745 5 : isolate->TerminateExecution();
746 5 : v8::Local<v8::Value> error = v8::Exception::Error(v8_str("error"));
747 5 : CHECK(error->IsNativeError());
748 5 : }
749 :
750 :
751 5 : void InnerTryCallTerminate(const v8::FunctionCallbackInfo<v8::Value>& args) {
752 5 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
753 5 : v8::Local<v8::Object> global = CcTest::global();
754 : v8::Local<v8::Function> loop = v8::Local<v8::Function>::Cast(
755 15 : global->Get(CcTest::isolate()->GetCurrentContext(), v8_str("loop"))
756 : .ToLocalChecked());
757 5 : i::MaybeHandle<i::Object> exception;
758 : i::MaybeHandle<i::Object> result =
759 : i::Execution::TryCall(CcTest::i_isolate(), v8::Utils::OpenHandle((*loop)),
760 : v8::Utils::OpenHandle((*global)), 0, nullptr,
761 5 : i::Execution::MessageHandling::kReport, &exception);
762 5 : CHECK(result.is_null());
763 : // TryCall ignores terminate execution, but rerequests the interrupt.
764 5 : CHECK(!args.GetIsolate()->IsExecutionTerminating());
765 5 : CHECK(CompileRun("1 + 1;").IsEmpty());
766 5 : }
767 :
768 :
769 26644 : TEST(TerminationInInnerTryCall) {
770 5 : v8::Isolate* isolate = CcTest::isolate();
771 10 : v8::HandleScope scope(isolate);
772 : v8::Local<v8::ObjectTemplate> global_template = CreateGlobalTemplate(
773 5 : CcTest::isolate(), TerminateCurrentThread, DoLoopNoCall);
774 15 : global_template->Set(
775 : v8_str("inner_try_call_terminate"),
776 5 : v8::FunctionTemplate::New(isolate, InnerTryCallTerminate));
777 : v8::Local<v8::Context> context =
778 5 : v8::Context::New(CcTest::isolate(), nullptr, global_template);
779 : v8::Context::Scope context_scope(context);
780 : {
781 10 : v8::TryCatch try_catch(isolate);
782 : CompileRun("inner_try_call_terminate()");
783 5 : CHECK(try_catch.HasTerminated());
784 : }
785 : v8::Maybe<int32_t> result = CompileRun("2 + 2")->Int32Value(
786 10 : v8::Isolate::GetCurrent()->GetCurrentContext());
787 5 : CHECK_EQ(4, result.FromJust());
788 5 : CHECK(!v8::Isolate::GetCurrent()->IsExecutionTerminating());
789 5 : }
790 :
791 :
792 26644 : TEST(TerminateAndTryCall) {
793 5 : i::FLAG_allow_natives_syntax = true;
794 5 : v8::Isolate* isolate = CcTest::isolate();
795 10 : v8::HandleScope scope(isolate);
796 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
797 5 : isolate, TerminateCurrentThread, DoLoopCancelTerminate);
798 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
799 : v8::Context::Scope context_scope(context);
800 5 : CHECK(!isolate->IsExecutionTerminating());
801 : {
802 10 : v8::TryCatch try_catch(isolate);
803 5 : CHECK(!isolate->IsExecutionTerminating());
804 : // Terminate execution has been triggered inside TryCall, but re-requested
805 : // to trigger later.
806 5 : CHECK(CompileRun("terminate(); reference_error();").IsEmpty());
807 5 : CHECK(try_catch.HasCaught());
808 5 : CHECK(!isolate->IsExecutionTerminating());
809 : v8::Local<v8::Value> value =
810 10 : CcTest::global()
811 20 : ->Get(isolate->GetCurrentContext(), v8_str("terminate"))
812 : .ToLocalChecked();
813 5 : CHECK(value->IsFunction());
814 : // The first stack check after terminate has been re-requested fails.
815 5 : CHECK(CompileRun("1 + 1").IsEmpty());
816 5 : CHECK(isolate->IsExecutionTerminating());
817 : }
818 : // V8 then recovers.
819 : v8::Maybe<int32_t> result = CompileRun("2 + 2")->Int32Value(
820 10 : v8::Isolate::GetCurrent()->GetCurrentContext());
821 5 : CHECK_EQ(4, result.FromJust());
822 5 : CHECK(!isolate->IsExecutionTerminating());
823 5 : }
824 :
825 5 : class ConsoleImpl : public v8::debug::ConsoleDelegate {
826 : private:
827 5 : void Log(const v8::debug::ConsoleCallArguments& args,
828 : const v8::debug::ConsoleContext&) override {
829 : CompileRun("1 + 1");
830 5 : }
831 : };
832 :
833 26644 : TEST(TerminateConsole) {
834 5 : i::FLAG_allow_natives_syntax = true;
835 5 : v8::Isolate* isolate = CcTest::isolate();
836 5 : ConsoleImpl console;
837 5 : v8::debug::SetConsoleDelegate(isolate, &console);
838 10 : v8::HandleScope scope(isolate);
839 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
840 5 : isolate, TerminateCurrentThread, DoLoopCancelTerminate);
841 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
842 : v8::Context::Scope context_scope(context);
843 5 : CHECK(!isolate->IsExecutionTerminating());
844 10 : v8::TryCatch try_catch(isolate);
845 5 : CHECK(!isolate->IsExecutionTerminating());
846 5 : CHECK(CompileRun("terminate(); console.log(); fail();").IsEmpty());
847 5 : CHECK(try_catch.HasCaught());
848 5 : CHECK(isolate->IsExecutionTerminating());
849 5 : }
850 :
851 5 : class TerminatorSleeperThread : public v8::base::Thread {
852 : public:
853 : explicit TerminatorSleeperThread(v8::Isolate* isolate, int sleep_ms)
854 : : Thread(Options("TerminatorSlepperThread")),
855 : isolate_(isolate),
856 5 : sleep_ms_(sleep_ms) {}
857 5 : void Run() override {
858 10 : v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(sleep_ms_));
859 5 : CHECK(!isolate_->IsExecutionTerminating());
860 5 : isolate_->TerminateExecution();
861 5 : }
862 :
863 : private:
864 : v8::Isolate* isolate_;
865 : int sleep_ms_;
866 : };
867 :
868 26644 : TEST(TerminateRegExp) {
869 5 : i::FLAG_allow_natives_syntax = true;
870 5 : v8::Isolate* isolate = CcTest::isolate();
871 10 : v8::HandleScope scope(isolate);
872 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
873 5 : isolate, TerminateCurrentThread, DoLoopCancelTerminate);
874 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
875 : v8::Context::Scope context_scope(context);
876 5 : CHECK(!isolate->IsExecutionTerminating());
877 10 : v8::TryCatch try_catch(isolate);
878 5 : CHECK(!isolate->IsExecutionTerminating());
879 5 : CHECK(!CompileRun("var re = /(x+)+y$/; re.test('x');").IsEmpty());
880 : TerminatorSleeperThread terminator(isolate, 100);
881 5 : terminator.Start();
882 5 : CHECK(CompileRun("re.test('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); fail();")
883 : .IsEmpty());
884 5 : CHECK(try_catch.HasCaught());
885 5 : CHECK(isolate->IsExecutionTerminating());
886 5 : }
887 :
888 26644 : TEST(TerminateInMicrotask) {
889 5 : v8::Isolate* isolate = CcTest::isolate();
890 5 : v8::Locker locker(isolate);
891 5 : isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
892 10 : v8::HandleScope scope(isolate);
893 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
894 5 : isolate, TerminateCurrentThread, DoLoopCancelTerminate);
895 5 : v8::Local<v8::Context> context1 = v8::Context::New(isolate, nullptr, global);
896 5 : v8::Local<v8::Context> context2 = v8::Context::New(isolate, nullptr, global);
897 10 : v8::TryCatch try_catch(isolate);
898 : {
899 : v8::Context::Scope context_scope(context1);
900 5 : CHECK(!isolate->IsExecutionTerminating());
901 5 : CHECK(!CompileRun("Promise.resolve().then(function() {"
902 : "terminate(); loop(); fail();})")
903 : .IsEmpty());
904 5 : CHECK(!try_catch.HasCaught());
905 : }
906 : {
907 : v8::Context::Scope context_scope(context2);
908 10 : CHECK(context2 == isolate->GetCurrentContext());
909 10 : CHECK(context2 == isolate->GetEnteredOrMicrotaskContext());
910 5 : CHECK(!isolate->IsExecutionTerminating());
911 5 : isolate->RunMicrotasks();
912 10 : CHECK(context2 == isolate->GetCurrentContext());
913 10 : CHECK(context2 == isolate->GetEnteredOrMicrotaskContext());
914 5 : CHECK(try_catch.HasCaught());
915 5 : CHECK(try_catch.HasTerminated());
916 : }
917 5 : CHECK(!isolate->IsExecutionTerminating());
918 5 : }
919 :
920 5 : void TerminationMicrotask(void* data) {
921 5 : CcTest::isolate()->TerminateExecution();
922 : CompileRun("");
923 5 : }
924 :
925 0 : void UnreachableMicrotask(void* data) { UNREACHABLE(); }
926 :
927 26644 : TEST(TerminateInApiMicrotask) {
928 5 : v8::Isolate* isolate = CcTest::isolate();
929 5 : v8::Locker locker(isolate);
930 5 : isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
931 10 : v8::HandleScope scope(isolate);
932 : v8::Local<v8::ObjectTemplate> global = CreateGlobalTemplate(
933 5 : isolate, TerminateCurrentThread, DoLoopCancelTerminate);
934 5 : v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
935 10 : v8::TryCatch try_catch(isolate);
936 : {
937 : v8::Context::Scope context_scope(context);
938 5 : CHECK(!isolate->IsExecutionTerminating());
939 5 : isolate->EnqueueMicrotask(TerminationMicrotask);
940 5 : isolate->EnqueueMicrotask(UnreachableMicrotask);
941 5 : isolate->RunMicrotasks();
942 5 : CHECK(try_catch.HasCaught());
943 5 : CHECK(try_catch.HasTerminated());
944 : }
945 5 : CHECK(!isolate->IsExecutionTerminating());
946 79922 : }
|