21e169d9d37b2864114bb5aba57c73ba9bebe02c
[platform/upstream/nodejs.git] / src / node_script.cc
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 #include "node.h"
23 #include "node_script.h"
24 #include "node_watchdog.h"
25 #include <assert.h>
26
27 namespace node {
28
29 using v8::Context;
30 using v8::Script;
31 using v8::Value;
32 using v8::Handle;
33 using v8::HandleScope;
34 using v8::Object;
35 using v8::Arguments;
36 using v8::ThrowException;
37 using v8::TryCatch;
38 using v8::String;
39 using v8::Exception;
40 using v8::Local;
41 using v8::Array;
42 using v8::Persistent;
43 using v8::Integer;
44 using v8::Function;
45 using v8::FunctionTemplate;
46 using v8::V8;
47
48
49 class WrappedContext : ObjectWrap {
50  public:
51   static void Initialize(Handle<Object> target);
52   static Handle<Value> New(const Arguments& args);
53
54   Persistent<Context> GetV8Context();
55   static Local<Object> NewInstance();
56   static bool InstanceOf(Handle<Value> value);
57
58  protected:
59
60   static Persistent<FunctionTemplate> constructor_template;
61
62   WrappedContext();
63   ~WrappedContext();
64
65   Persistent<Context> context_;
66 };
67
68
69 Persistent<FunctionTemplate> WrappedContext::constructor_template;
70
71
72 class WrappedScript : ObjectWrap {
73  public:
74   static void Initialize(Handle<Object> target);
75
76   enum EvalInputFlags { compileCode, unwrapExternal };
77   enum EvalContextFlags { thisContext, newContext, userContext };
78   enum EvalOutputFlags { returnResult, wrapExternal };
79   enum EvalTimeoutFlags { noTimeout, useTimeout };
80
81   template <EvalInputFlags input_flag,
82             EvalContextFlags context_flag,
83             EvalOutputFlags output_flag,
84             EvalTimeoutFlags timeout_flag>
85   static Handle<Value> EvalMachine(const Arguments& args);
86
87  protected:
88   static Persistent<FunctionTemplate> constructor_template;
89
90   WrappedScript() : ObjectWrap() {}
91   ~WrappedScript();
92
93   static Handle<Value> New(const Arguments& args);
94   static Handle<Value> CreateContext(const Arguments& arg);
95   static Handle<Value> RunInContext(const Arguments& args);
96   static Handle<Value> RunInThisContext(const Arguments& args);
97   static Handle<Value> RunInNewContext(const Arguments& args);
98   static Handle<Value> CompileRunInContext(const Arguments& args);
99   static Handle<Value> CompileRunInThisContext(const Arguments& args);
100   static Handle<Value> CompileRunInNewContext(const Arguments& args);
101
102   Persistent<Script> script_;
103 };
104
105
106 Persistent<Function> cloneObjectMethod;
107
108 void CloneObject(Handle<Object> recv,
109                  Handle<Value> source, Handle<Value> target) {
110   HandleScope scope(node_isolate);
111
112   Handle<Value> args[] = {source, target};
113
114   // Init
115   if (cloneObjectMethod.IsEmpty()) {
116     Local<Function> cloneObjectMethod_ = Local<Function>::Cast(
117       Script::Compile(String::New(
118         "(function(source, target) {\n\
119            Object.getOwnPropertyNames(source).forEach(function(key) {\n\
120            try {\n\
121              var desc = Object.getOwnPropertyDescriptor(source, key);\n\
122              if (desc.value === source) desc.value = target;\n\
123              Object.defineProperty(target, key, desc);\n\
124            } catch (e) {\n\
125             // Catch sealed properties errors\n\
126            }\n\
127          });\n\
128         })"
129       ), String::New("binding:script"))->Run()
130     );
131     cloneObjectMethod = Persistent<Function>::New(node_isolate,
132                                                   cloneObjectMethod_);
133   }
134
135   cloneObjectMethod->Call(recv, 2, args);
136 }
137
138
139 void WrappedContext::Initialize(Handle<Object> target) {
140   HandleScope scope(node_isolate);
141
142   Local<FunctionTemplate> t = FunctionTemplate::New(WrappedContext::New);
143   constructor_template = Persistent<FunctionTemplate>::New(node_isolate, t);
144   constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
145   constructor_template->SetClassName(String::NewSymbol("Context"));
146
147   target->Set(String::NewSymbol("Context"),
148               constructor_template->GetFunction());
149 }
150
151
152 bool WrappedContext::InstanceOf(Handle<Value> value) {
153   return !value.IsEmpty() && constructor_template->HasInstance(value);
154 }
155
156
157 Handle<Value> WrappedContext::New(const Arguments& args) {
158   HandleScope scope(node_isolate);
159
160   WrappedContext *t = new WrappedContext();
161   t->Wrap(args.This());
162
163   return args.This();
164 }
165
166
167 WrappedContext::WrappedContext() : ObjectWrap() {
168   context_ = Context::New();
169 }
170
171
172 WrappedContext::~WrappedContext() {
173   context_.Dispose(node_isolate);
174 }
175
176
177 Local<Object> WrappedContext::NewInstance() {
178   Local<Object> context = constructor_template->GetFunction()->NewInstance();
179   return context;
180 }
181
182
183 Persistent<Context> WrappedContext::GetV8Context() {
184   return context_;
185 }
186
187
188 Persistent<FunctionTemplate> WrappedScript::constructor_template;
189
190
191 void WrappedScript::Initialize(Handle<Object> target) {
192   HandleScope scope(node_isolate);
193
194   Local<FunctionTemplate> t = FunctionTemplate::New(WrappedScript::New);
195   constructor_template = Persistent<FunctionTemplate>::New(node_isolate, t);
196   constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
197   // Note: We use 'NodeScript' instead of 'Script' so that we do not
198   // conflict with V8's Script class defined in v8/src/messages.js
199   // See GH-203 https://github.com/joyent/node/issues/203
200   constructor_template->SetClassName(String::NewSymbol("NodeScript"));
201
202   NODE_SET_PROTOTYPE_METHOD(constructor_template,
203                             "createContext",
204                             WrappedScript::CreateContext);
205
206   NODE_SET_PROTOTYPE_METHOD(constructor_template,
207                             "runInContext",
208                             WrappedScript::RunInContext);
209
210   NODE_SET_PROTOTYPE_METHOD(constructor_template,
211                             "runInThisContext",
212                             WrappedScript::RunInThisContext);
213
214   NODE_SET_PROTOTYPE_METHOD(constructor_template,
215                             "runInNewContext",
216                             WrappedScript::RunInNewContext);
217
218   NODE_SET_METHOD(constructor_template,
219                   "createContext",
220                   WrappedScript::CreateContext);
221
222   NODE_SET_METHOD(constructor_template,
223                   "runInContext",
224                   WrappedScript::CompileRunInContext);
225
226   NODE_SET_METHOD(constructor_template,
227                   "runInThisContext",
228                   WrappedScript::CompileRunInThisContext);
229
230   NODE_SET_METHOD(constructor_template,
231                   "runInNewContext",
232                   WrappedScript::CompileRunInNewContext);
233
234   target->Set(String::NewSymbol("NodeScript"),
235               constructor_template->GetFunction());
236 }
237
238
239 Handle<Value> WrappedScript::New(const Arguments& args) {
240   if (!args.IsConstructCall()) {
241     return FromConstructorTemplate(constructor_template, args);
242   }
243
244   HandleScope scope(node_isolate);
245
246   WrappedScript *t = new WrappedScript();
247   t->Wrap(args.This());
248
249   return
250     WrappedScript::EvalMachine<
251       compileCode, thisContext, wrapExternal, noTimeout>(args);
252 }
253
254
255 WrappedScript::~WrappedScript() {
256   script_.Dispose(node_isolate);
257 }
258
259
260 Handle<Value> WrappedScript::CreateContext(const Arguments& args) {
261   HandleScope scope(node_isolate);
262
263   Local<Object> context = WrappedContext::NewInstance();
264
265   if (args.Length() > 0) {
266     if (args[0]->IsObject()) {
267       Local<Object> sandbox = args[0].As<Object>();
268
269       CloneObject(args.This(), sandbox, context);
270     } else {
271       return ThrowException(Exception::TypeError(String::New(
272           "createContext() accept only object as first argument.")));
273     }
274   }
275
276
277   return scope.Close(context);
278 }
279
280
281 Handle<Value> WrappedScript::RunInContext(const Arguments& args) {
282   return
283     WrappedScript::EvalMachine<
284       unwrapExternal, userContext, returnResult, useTimeout>(args);
285 }
286
287
288 Handle<Value> WrappedScript::RunInThisContext(const Arguments& args) {
289   return
290     WrappedScript::EvalMachine<
291       unwrapExternal, thisContext, returnResult, useTimeout>(args);
292 }
293
294
295 Handle<Value> WrappedScript::RunInNewContext(const Arguments& args) {
296   return
297     WrappedScript::EvalMachine<
298       unwrapExternal, newContext, returnResult, useTimeout>(args);
299 }
300
301
302 Handle<Value> WrappedScript::CompileRunInContext(const Arguments& args) {
303   return
304     WrappedScript::EvalMachine<
305       compileCode, userContext, returnResult, useTimeout>(args);
306 }
307
308
309 Handle<Value> WrappedScript::CompileRunInThisContext(const Arguments& args) {
310   return
311     WrappedScript::EvalMachine<
312       compileCode, thisContext, returnResult, useTimeout>(args);
313 }
314
315
316 Handle<Value> WrappedScript::CompileRunInNewContext(const Arguments& args) {
317   return
318     WrappedScript::EvalMachine<
319       compileCode, newContext, returnResult, useTimeout>(args);
320 }
321
322
323 template <WrappedScript::EvalInputFlags input_flag,
324           WrappedScript::EvalContextFlags context_flag,
325           WrappedScript::EvalOutputFlags output_flag,
326           WrappedScript::EvalTimeoutFlags timeout_flag>
327 Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
328   HandleScope scope(node_isolate);
329
330   if (input_flag == compileCode && args.Length() < 1) {
331     return ThrowException(Exception::TypeError(
332           String::New("needs at least 'code' argument.")));
333   }
334
335   const int sandbox_index = input_flag == compileCode ? 1 : 0;
336   if (context_flag == userContext
337     && !WrappedContext::InstanceOf(args[sandbox_index]))
338   {
339     return ThrowException(Exception::TypeError(
340           String::New("needs a 'context' argument.")));
341   }
342
343
344   Local<String> code;
345   if (input_flag == compileCode) code = args[0]->ToString();
346
347   Local<Object> sandbox;
348   if (context_flag == newContext) {
349     sandbox = args[sandbox_index]->IsObject() ? args[sandbox_index]->ToObject()
350                                               : Object::New();
351   } else if (context_flag == userContext) {
352     sandbox = args[sandbox_index]->ToObject();
353   }
354
355   const int filename_index = sandbox_index +
356                              (context_flag == thisContext? 0 : 1);
357   Local<String> filename = args.Length() > filename_index
358                            ? args[filename_index]->ToString()
359                            : String::New("evalmachine.<anonymous>");
360
361   uint64_t timeout = 0;
362   const int timeout_index = filename_index + 1;
363   if (timeout_flag == useTimeout && args.Length() > timeout_index) {
364     if (!args[timeout_index]->IsUint32()) {
365       return ThrowException(Exception::TypeError(
366             String::New("needs an unsigned integer 'ms' argument.")));
367     }
368     timeout = args[timeout_index]->Uint32Value();
369   }
370
371   const int display_error_index = timeout_index +
372                                   (timeout_flag == noTimeout ? 0 : 1);
373   bool display_error = false;
374   if (args.Length() > display_error_index &&
375       args[display_error_index]->IsBoolean() &&
376       args[display_error_index]->BooleanValue() == true) {
377     display_error = true;
378   }
379
380   Handle<Context> context = Context::GetCurrent();
381
382   Local<Array> keys;
383   if (context_flag == newContext) {
384     // Create the new context
385     // Context::New returns a Persistent<Context>, but we only need it for this
386     // function. Here we grab a temporary handle to the new context, assign it
387     // to a local handle, and then dispose the persistent handle. This ensures
388     // that when this function exits the context will be disposed.
389     Persistent<Context> tmp = Context::New();
390     context = Local<Context>::New(node_isolate, tmp);
391     tmp.Dispose(node_isolate);
392
393   } else if (context_flag == userContext) {
394     // Use the passed in context
395     WrappedContext *nContext = ObjectWrap::Unwrap<WrappedContext>(sandbox);
396     context = nContext->GetV8Context();
397   }
398
399   Context::Scope context_scope(context);
400
401   // New and user context share code. DRY it up.
402   if (context_flag == userContext || context_flag == newContext) {
403     // Copy everything from the passed in sandbox (either the persistent
404     // context for runInContext(), or the sandbox arg to runInNewContext()).
405     CloneObject(args.This(), sandbox, context->Global()->GetPrototype());
406   }
407
408   // Catch errors
409   TryCatch try_catch;
410
411   Handle<Value> result;
412   Handle<Script> script;
413
414   if (input_flag == compileCode) {
415     // well, here WrappedScript::New would suffice in all cases, but maybe
416     // Compile has a little better performance where possible
417     script = output_flag == returnResult ? Script::Compile(code, filename)
418                                          : Script::New(code, filename);
419     if (script.IsEmpty()) {
420       // FIXME UGLY HACK TO DISPLAY SYNTAX ERRORS.
421       if (display_error) DisplayExceptionLine(try_catch);
422
423       // Hack because I can't get a proper stacktrace on SyntaxError
424       return try_catch.ReThrow();
425     }
426   } else {
427     WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.This());
428     if (!n_script) {
429       return ThrowException(Exception::Error(
430             String::New("Must be called as a method of Script.")));
431     } else if (n_script->script_.IsEmpty()) {
432       return ThrowException(Exception::Error(
433             String::New("'this' must be a result of previous "
434                         "new Script(code) call.")));
435     }
436
437     script = n_script->script_;
438   }
439
440
441   if (output_flag == returnResult) {
442     if (timeout) {
443       Watchdog wd(timeout);
444       result = script->Run();
445     } else {
446       result = script->Run();
447     }
448     if (try_catch.HasCaught() && try_catch.HasTerminated()) {
449       V8::CancelTerminateExecution(args.GetIsolate());
450       return ThrowException(Exception::Error(
451             String::New("Script execution timed out.")));
452     }
453     if (result.IsEmpty()) {
454       if (display_error) DisplayExceptionLine(try_catch);
455       return try_catch.ReThrow();
456     }
457   } else {
458     WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.This());
459     if (!n_script) {
460       return ThrowException(Exception::Error(
461             String::New("Must be called as a method of Script.")));
462     }
463     n_script->script_ = Persistent<Script>::New(node_isolate, script);
464     result = args.This();
465   }
466
467   if (context_flag == userContext || context_flag == newContext) {
468     // success! copy changes back onto the sandbox object.
469     CloneObject(args.This(), context->Global()->GetPrototype(), sandbox);
470   }
471
472   return result == args.This() ? result : scope.Close(result);
473 }
474
475
476 void InitEvals(Handle<Object> target) {
477   HandleScope scope(node_isolate);
478
479   WrappedContext::Initialize(target);
480   WrappedScript::Initialize(target);
481 }
482
483
484 }  // namespace node
485
486
487 NODE_MODULE(node_evals, node::InitEvals)
488