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
34v8::base::Semaphore* semaphore = NULL;
35
36
37void Signal(const v8::FunctionCallbackInfo<v8::Value>& args) {
38  semaphore->Signal();
39}
40
41
42void TerminateCurrentThread(const v8::FunctionCallbackInfo<v8::Value>& args) {
43  CHECK(!v8::V8::IsExecutionTerminating(args.GetIsolate()));
44  v8::V8::TerminateExecution(args.GetIsolate());
45}
46
47
48void Fail(const v8::FunctionCallbackInfo<v8::Value>& args) {
49  CHECK(false);
50}
51
52
53void 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
63void 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
88void 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
105v8::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.
124TEST(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.
144TEST(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
162class 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.
180TEST(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
203int call_count = 0;
204
205
206void 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
219void 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.
245TEST(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
273void 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.
303TEST(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
326void 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.
349TEST(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
364void MicrotaskShouldNotRun(const v8::FunctionCallbackInfo<v8::Value>& info) {
365  CHECK(false);
366}
367
368
369void 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
380TEST(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
408static int callback_counter = 0;
409
410
411static void CounterCallback(v8::Isolate* isolate, void* data) {
412  callback_counter++;
413}
414
415
416TEST(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
464TEST(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