1 // Copyright Joyent, Inc. and other Node contributors.
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:
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
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.
23 #include "node_script.h"
24 #include "node_watchdog.h"
33 using v8::HandleScope;
36 using v8::ThrowException;
45 using v8::FunctionTemplate;
49 class WrappedContext : ObjectWrap {
51 static void Initialize(Handle<Object> target);
52 static Handle<Value> New(const Arguments& args);
54 Persistent<Context> GetV8Context();
55 static Local<Object> NewInstance();
56 static bool InstanceOf(Handle<Value> value);
60 static Persistent<FunctionTemplate> constructor_template;
65 Persistent<Context> context_;
69 Persistent<FunctionTemplate> WrappedContext::constructor_template;
72 class WrappedScript : ObjectWrap {
74 static void Initialize(Handle<Object> target);
76 enum EvalInputFlags { compileCode, unwrapExternal };
77 enum EvalContextFlags { thisContext, newContext, userContext };
78 enum EvalOutputFlags { returnResult, wrapExternal };
79 enum EvalTimeoutFlags { noTimeout, useTimeout };
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);
88 static Persistent<FunctionTemplate> constructor_template;
90 WrappedScript() : ObjectWrap() {}
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);
102 Persistent<Script> script_;
106 Persistent<Function> cloneObjectMethod;
108 void CloneObject(Handle<Object> recv,
109 Handle<Value> source, Handle<Value> target) {
110 HandleScope scope(node_isolate);
112 Handle<Value> args[] = {source, target};
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\
121 var desc = Object.getOwnPropertyDescriptor(source, key);\n\
122 if (desc.value === source) desc.value = target;\n\
123 Object.defineProperty(target, key, desc);\n\
125 // Catch sealed properties errors\n\
129 ), String::New("binding:script"))->Run()
131 cloneObjectMethod = Persistent<Function>::New(node_isolate,
135 cloneObjectMethod->Call(recv, 2, args);
139 void WrappedContext::Initialize(Handle<Object> target) {
140 HandleScope scope(node_isolate);
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"));
147 target->Set(String::NewSymbol("Context"),
148 constructor_template->GetFunction());
152 bool WrappedContext::InstanceOf(Handle<Value> value) {
153 return !value.IsEmpty() && constructor_template->HasInstance(value);
157 Handle<Value> WrappedContext::New(const Arguments& args) {
158 HandleScope scope(node_isolate);
160 WrappedContext *t = new WrappedContext();
161 t->Wrap(args.This());
167 WrappedContext::WrappedContext() : ObjectWrap() {
168 context_ = Context::New();
172 WrappedContext::~WrappedContext() {
173 context_.Dispose(node_isolate);
177 Local<Object> WrappedContext::NewInstance() {
178 Local<Object> context = constructor_template->GetFunction()->NewInstance();
183 Persistent<Context> WrappedContext::GetV8Context() {
188 Persistent<FunctionTemplate> WrappedScript::constructor_template;
191 void WrappedScript::Initialize(Handle<Object> target) {
192 HandleScope scope(node_isolate);
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"));
202 NODE_SET_PROTOTYPE_METHOD(constructor_template,
204 WrappedScript::CreateContext);
206 NODE_SET_PROTOTYPE_METHOD(constructor_template,
208 WrappedScript::RunInContext);
210 NODE_SET_PROTOTYPE_METHOD(constructor_template,
212 WrappedScript::RunInThisContext);
214 NODE_SET_PROTOTYPE_METHOD(constructor_template,
216 WrappedScript::RunInNewContext);
218 NODE_SET_METHOD(constructor_template,
220 WrappedScript::CreateContext);
222 NODE_SET_METHOD(constructor_template,
224 WrappedScript::CompileRunInContext);
226 NODE_SET_METHOD(constructor_template,
228 WrappedScript::CompileRunInThisContext);
230 NODE_SET_METHOD(constructor_template,
232 WrappedScript::CompileRunInNewContext);
234 target->Set(String::NewSymbol("NodeScript"),
235 constructor_template->GetFunction());
239 Handle<Value> WrappedScript::New(const Arguments& args) {
240 if (!args.IsConstructCall()) {
241 return FromConstructorTemplate(constructor_template, args);
244 HandleScope scope(node_isolate);
246 WrappedScript *t = new WrappedScript();
247 t->Wrap(args.This());
250 WrappedScript::EvalMachine<
251 compileCode, thisContext, wrapExternal, noTimeout>(args);
255 WrappedScript::~WrappedScript() {
256 script_.Dispose(node_isolate);
260 Handle<Value> WrappedScript::CreateContext(const Arguments& args) {
261 HandleScope scope(node_isolate);
263 Local<Object> context = WrappedContext::NewInstance();
265 if (args.Length() > 0) {
266 if (args[0]->IsObject()) {
267 Local<Object> sandbox = args[0].As<Object>();
269 CloneObject(args.This(), sandbox, context);
271 return ThrowException(Exception::TypeError(String::New(
272 "createContext() accept only object as first argument.")));
277 return scope.Close(context);
281 Handle<Value> WrappedScript::RunInContext(const Arguments& args) {
283 WrappedScript::EvalMachine<
284 unwrapExternal, userContext, returnResult, useTimeout>(args);
288 Handle<Value> WrappedScript::RunInThisContext(const Arguments& args) {
290 WrappedScript::EvalMachine<
291 unwrapExternal, thisContext, returnResult, useTimeout>(args);
295 Handle<Value> WrappedScript::RunInNewContext(const Arguments& args) {
297 WrappedScript::EvalMachine<
298 unwrapExternal, newContext, returnResult, useTimeout>(args);
302 Handle<Value> WrappedScript::CompileRunInContext(const Arguments& args) {
304 WrappedScript::EvalMachine<
305 compileCode, userContext, returnResult, useTimeout>(args);
309 Handle<Value> WrappedScript::CompileRunInThisContext(const Arguments& args) {
311 WrappedScript::EvalMachine<
312 compileCode, thisContext, returnResult, useTimeout>(args);
316 Handle<Value> WrappedScript::CompileRunInNewContext(const Arguments& args) {
318 WrappedScript::EvalMachine<
319 compileCode, newContext, returnResult, useTimeout>(args);
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);
330 if (input_flag == compileCode && args.Length() < 1) {
331 return ThrowException(Exception::TypeError(
332 String::New("needs at least 'code' argument.")));
335 const int sandbox_index = input_flag == compileCode ? 1 : 0;
336 if (context_flag == userContext
337 && !WrappedContext::InstanceOf(args[sandbox_index]))
339 return ThrowException(Exception::TypeError(
340 String::New("needs a 'context' argument.")));
345 if (input_flag == compileCode) code = args[0]->ToString();
347 Local<Object> sandbox;
348 if (context_flag == newContext) {
349 sandbox = args[sandbox_index]->IsObject() ? args[sandbox_index]->ToObject()
351 } else if (context_flag == userContext) {
352 sandbox = args[sandbox_index]->ToObject();
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>");
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.")));
368 timeout = args[timeout_index]->Uint32Value();
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;
380 Handle<Context> context = Context::GetCurrent();
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);
393 } else if (context_flag == userContext) {
394 // Use the passed in context
395 WrappedContext *nContext = ObjectWrap::Unwrap<WrappedContext>(sandbox);
396 context = nContext->GetV8Context();
399 Context::Scope context_scope(context);
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());
411 Handle<Value> result;
412 Handle<Script> script;
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);
423 // Hack because I can't get a proper stacktrace on SyntaxError
424 return try_catch.ReThrow();
427 WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.This());
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.")));
437 script = n_script->script_;
441 if (output_flag == returnResult) {
443 Watchdog wd(timeout);
444 result = script->Run();
446 result = script->Run();
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.")));
453 if (result.IsEmpty()) {
454 if (display_error) DisplayExceptionLine(try_catch);
455 return try_catch.ReThrow();
458 WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.This());
460 return ThrowException(Exception::Error(
461 String::New("Must be called as a method of Script.")));
463 n_script->script_ = Persistent<Script>::New(node_isolate, script);
464 result = args.This();
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);
472 return result == args.This() ? result : scope.Close(result);
476 void InitEvals(Handle<Object> target) {
477 HandleScope scope(node_isolate);
479 WrappedContext::Initialize(target);
480 WrappedScript::Initialize(target);
487 NODE_MODULE(node_evals, node::InitEvals)