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 "v8.h"
29#include "platform.h"
30#include "cctest.h"
31
32
33v8::internal::Semaphore* semaphore = NULL;
34
35
36v8::Handle<v8::Value> Signal(const v8::Arguments& args) {
37  semaphore->Signal();
38  return v8::Undefined();
39}
40
41
42v8::Handle<v8::Value> TerminateCurrentThread(const v8::Arguments& args) {
43  CHECK(!v8::V8::IsExecutionTerminating());
44  v8::V8::TerminateExecution();
45  return v8::Undefined();
46}
47
48
49v8::Handle<v8::Value> Fail(const v8::Arguments& args) {
50  CHECK(false);
51  return v8::Undefined();
52}
53
54
55v8::Handle<v8::Value> Loop(const v8::Arguments& args) {
56  CHECK(!v8::V8::IsExecutionTerminating());
57  v8::Handle<v8::String> source =
58      v8::String::New("try { doloop(); fail(); } catch(e) { fail(); }");
59  v8::Handle<v8::Value> result = v8::Script::Compile(source)->Run();
60  CHECK(result.IsEmpty());
61  CHECK(v8::V8::IsExecutionTerminating());
62  return v8::Undefined();
63}
64
65
66v8::Handle<v8::Value> DoLoop(const v8::Arguments& args) {
67  v8::TryCatch try_catch;
68  CHECK(!v8::V8::IsExecutionTerminating());
69  v8::Script::Compile(v8::String::New("function f() {"
70                                      "  var term = true;"
71                                      "  try {"
72                                      "    while(true) {"
73                                      "      if (term) terminate();"
74                                      "      term = false;"
75                                      "    }"
76                                      "    fail();"
77                                      "  } catch(e) {"
78                                      "    fail();"
79                                      "  }"
80                                      "}"
81                                      "f()"))->Run();
82  CHECK(try_catch.HasCaught());
83  CHECK(try_catch.Exception()->IsNull());
84  CHECK(try_catch.Message().IsEmpty());
85  CHECK(!try_catch.CanContinue());
86  CHECK(v8::V8::IsExecutionTerminating());
87  return v8::Undefined();
88}
89
90
91v8::Handle<v8::Value> DoLoopNoCall(const v8::Arguments& args) {
92  v8::TryCatch try_catch;
93  CHECK(!v8::V8::IsExecutionTerminating());
94  v8::Script::Compile(v8::String::New("var term = true;"
95                                      "while(true) {"
96                                      "  if (term) terminate();"
97                                      "  term = false;"
98                                      "}"))->Run();
99  CHECK(try_catch.HasCaught());
100  CHECK(try_catch.Exception()->IsNull());
101  CHECK(try_catch.Message().IsEmpty());
102  CHECK(!try_catch.CanContinue());
103  CHECK(v8::V8::IsExecutionTerminating());
104  return v8::Undefined();
105}
106
107
108v8::Handle<v8::ObjectTemplate> CreateGlobalTemplate(
109    v8::InvocationCallback terminate,
110    v8::InvocationCallback doloop) {
111  v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
112  global->Set(v8::String::New("terminate"),
113              v8::FunctionTemplate::New(terminate));
114  global->Set(v8::String::New("fail"), v8::FunctionTemplate::New(Fail));
115  global->Set(v8::String::New("loop"), v8::FunctionTemplate::New(Loop));
116  global->Set(v8::String::New("doloop"), v8::FunctionTemplate::New(doloop));
117  return global;
118}
119
120
121// Test that a single thread of JavaScript execution can terminate
122// itself.
123TEST(TerminateOnlyV8ThreadFromThreadItself) {
124  v8::HandleScope scope;
125  v8::Handle<v8::ObjectTemplate> global =
126      CreateGlobalTemplate(TerminateCurrentThread, DoLoop);
127  v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
128  v8::Context::Scope context_scope(context);
129  CHECK(!v8::V8::IsExecutionTerminating());
130  // Run a loop that will be infinite if thread termination does not work.
131  v8::Handle<v8::String> source =
132      v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
133  v8::Script::Compile(source)->Run();
134  // Test that we can run the code again after thread termination.
135  CHECK(!v8::V8::IsExecutionTerminating());
136  v8::Script::Compile(source)->Run();
137  context.Dispose();
138}
139
140
141// Test that a single thread of JavaScript execution can terminate
142// itself in a loop that performs no calls.
143TEST(TerminateOnlyV8ThreadFromThreadItselfNoLoop) {
144  v8::HandleScope scope;
145  v8::Handle<v8::ObjectTemplate> global =
146      CreateGlobalTemplate(TerminateCurrentThread, DoLoopNoCall);
147  v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
148  v8::Context::Scope context_scope(context);
149  CHECK(!v8::V8::IsExecutionTerminating());
150  // Run a loop that will be infinite if thread termination does not work.
151  v8::Handle<v8::String> source =
152      v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
153  v8::Script::Compile(source)->Run();
154  CHECK(!v8::V8::IsExecutionTerminating());
155  // Test that we can run the code again after thread termination.
156  v8::Script::Compile(source)->Run();
157  context.Dispose();
158}
159
160
161class TerminatorThread : public v8::internal::Thread {
162 public:
163  explicit TerminatorThread(i::Isolate* isolate)
164      : Thread("TerminatorThread"),
165        isolate_(reinterpret_cast<v8::Isolate*>(isolate)) { }
166  void Run() {
167    semaphore->Wait();
168    CHECK(!v8::V8::IsExecutionTerminating(isolate_));
169    v8::V8::TerminateExecution(isolate_);
170  }
171
172 private:
173  v8::Isolate* isolate_;
174};
175
176
177// Test that a single thread of JavaScript execution can be terminated
178// from the side by another thread.
179TEST(TerminateOnlyV8ThreadFromOtherThread) {
180  semaphore = v8::internal::OS::CreateSemaphore(0);
181  TerminatorThread thread(i::Isolate::Current());
182  thread.Start();
183
184  v8::HandleScope scope;
185  v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(Signal, DoLoop);
186  v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
187  v8::Context::Scope context_scope(context);
188  CHECK(!v8::V8::IsExecutionTerminating());
189  // Run a loop that will be infinite if thread termination does not work.
190  v8::Handle<v8::String> source =
191      v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
192  v8::Script::Compile(source)->Run();
193
194  thread.Join();
195  delete semaphore;
196  semaphore = NULL;
197  context.Dispose();
198}
199
200
201class LoopingThread : public v8::internal::Thread {
202 public:
203  LoopingThread() : Thread("LoopingThread") { }
204  void Run() {
205    v8::Locker locker;
206    v8::HandleScope scope;
207    v8_thread_id_ = v8::V8::GetCurrentThreadId();
208    v8::Handle<v8::ObjectTemplate> global =
209        CreateGlobalTemplate(Signal, DoLoop);
210    v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
211    v8::Context::Scope context_scope(context);
212    CHECK(!v8::V8::IsExecutionTerminating());
213    // Run a loop that will be infinite if thread termination does not work.
214    v8::Handle<v8::String> source =
215        v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
216    v8::Script::Compile(source)->Run();
217    context.Dispose();
218  }
219
220  int GetV8ThreadId() { return v8_thread_id_; }
221
222 private:
223  int v8_thread_id_;
224};
225
226
227// Test that multiple threads using default isolate can be terminated
228// from another thread when using Lockers and preemption.
229TEST(TerminateMultipleV8ThreadsDefaultIsolate) {
230  {
231    v8::Locker locker;
232    v8::V8::Initialize();
233    v8::Locker::StartPreemption(1);
234    semaphore = v8::internal::OS::CreateSemaphore(0);
235  }
236  const int kThreads = 2;
237  i::List<LoopingThread*> threads(kThreads);
238  for (int i = 0; i < kThreads; i++) {
239    threads.Add(new LoopingThread());
240  }
241  for (int i = 0; i < kThreads; i++) {
242    threads[i]->Start();
243  }
244  // Wait until all threads have signaled the semaphore.
245  for (int i = 0; i < kThreads; i++) {
246    semaphore->Wait();
247  }
248  {
249    v8::Locker locker;
250    for (int i = 0; i < kThreads; i++) {
251      v8::V8::TerminateExecution(threads[i]->GetV8ThreadId());
252    }
253  }
254  for (int i = 0; i < kThreads; i++) {
255    threads[i]->Join();
256    delete threads[i];
257  }
258
259  delete semaphore;
260  semaphore = NULL;
261}
262
263
264int call_count = 0;
265
266
267v8::Handle<v8::Value> TerminateOrReturnObject(const v8::Arguments& args) {
268  if (++call_count == 10) {
269    CHECK(!v8::V8::IsExecutionTerminating());
270    v8::V8::TerminateExecution();
271    return v8::Undefined();
272  }
273  v8::Local<v8::Object> result = v8::Object::New();
274  result->Set(v8::String::New("x"), v8::Integer::New(42));
275  return result;
276}
277
278
279v8::Handle<v8::Value> LoopGetProperty(const v8::Arguments& args) {
280  v8::TryCatch try_catch;
281  CHECK(!v8::V8::IsExecutionTerminating());
282  v8::Script::Compile(v8::String::New("function f() {"
283                                      "  try {"
284                                      "    while(true) {"
285                                      "      terminate_or_return_object().x;"
286                                      "    }"
287                                      "    fail();"
288                                      "  } catch(e) {"
289                                      "    fail();"
290                                      "  }"
291                                      "}"
292                                      "f()"))->Run();
293  CHECK(try_catch.HasCaught());
294  CHECK(try_catch.Exception()->IsNull());
295  CHECK(try_catch.Message().IsEmpty());
296  CHECK(!try_catch.CanContinue());
297  CHECK(v8::V8::IsExecutionTerminating());
298  return v8::Undefined();
299}
300
301
302// Test that we correctly handle termination exceptions if they are
303// triggered by the creation of error objects in connection with ICs.
304TEST(TerminateLoadICException) {
305  v8::HandleScope scope;
306  v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
307  global->Set(v8::String::New("terminate_or_return_object"),
308              v8::FunctionTemplate::New(TerminateOrReturnObject));
309  global->Set(v8::String::New("fail"), v8::FunctionTemplate::New(Fail));
310  global->Set(v8::String::New("loop"),
311              v8::FunctionTemplate::New(LoopGetProperty));
312
313  v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
314  v8::Context::Scope context_scope(context);
315  CHECK(!v8::V8::IsExecutionTerminating());
316  // Run a loop that will be infinite if thread termination does not work.
317  v8::Handle<v8::String> source =
318      v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
319  call_count = 0;
320  v8::Script::Compile(source)->Run();
321  // Test that we can run the code again after thread termination.
322  CHECK(!v8::V8::IsExecutionTerminating());
323  call_count = 0;
324  v8::Script::Compile(source)->Run();
325  context.Dispose();
326}
327
328v8::Handle<v8::Value> ReenterAfterTermination(const v8::Arguments& args) {
329  v8::TryCatch try_catch;
330  CHECK(!v8::V8::IsExecutionTerminating());
331  v8::Script::Compile(v8::String::New("function f() {"
332                                      "  var term = true;"
333                                      "  try {"
334                                      "    while(true) {"
335                                      "      if (term) terminate();"
336                                      "      term = false;"
337                                      "    }"
338                                      "    fail();"
339                                      "  } catch(e) {"
340                                      "    fail();"
341                                      "  }"
342                                      "}"
343                                      "f()"))->Run();
344  CHECK(try_catch.HasCaught());
345  CHECK(try_catch.Exception()->IsNull());
346  CHECK(try_catch.Message().IsEmpty());
347  CHECK(!try_catch.CanContinue());
348  CHECK(v8::V8::IsExecutionTerminating());
349  v8::Script::Compile(v8::String::New("function f() { fail(); } f()"))->Run();
350  return v8::Undefined();
351}
352
353// Test that reentry into V8 while the termination exception is still pending
354// (has not yet unwound the 0-level JS frame) does not crash.
355TEST(TerminateAndReenterFromThreadItself) {
356  v8::HandleScope scope;
357  v8::Handle<v8::ObjectTemplate> global =
358      CreateGlobalTemplate(TerminateCurrentThread, ReenterAfterTermination);
359  v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
360  v8::Context::Scope context_scope(context);
361  CHECK(!v8::V8::IsExecutionTerminating());
362  v8::Handle<v8::String> source =
363      v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
364  v8::Script::Compile(source)->Run();
365  CHECK(!v8::V8::IsExecutionTerminating());
366  // Check we can run JS again after termination.
367  CHECK(v8::Script::Compile(v8::String::New("function f() { return true; }"
368                                            "f()"))->Run()->IsTrue());
369  context.Dispose();
370}
371
372