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