diff --git a/lib/module.js b/lib/module.js index 88e0c5a710b..d9e9744c0ad 100644 --- a/lib/module.js +++ b/lib/module.js @@ -21,9 +21,8 @@ var NativeModule = require('native_module'); var util = NativeModule.require('util'); -var Script = process.binding('evals').NodeScript; -var runInThisContext = Script.runInThisContext; -var runInNewContext = Script.runInNewContext; +var runInThisContext = require('vm').runInThisContext; +var runInNewContext = require('vm').runInNewContext; var assert = require('assert').ok; @@ -413,7 +412,7 @@ Module.prototype._compile = function(content, filename) { sandbox.global = sandbox; sandbox.root = root; - return runInNewContext(content, sandbox, filename, 0, true); + return runInNewContext(content, sandbox, filename); } debug('load root module'); @@ -424,13 +423,13 @@ Module.prototype._compile = function(content, filename) { global.__dirname = dirname; global.module = self; - return runInThisContext(content, filename, 0, true); + return runInThisContext(content, filename); } // create wrapper function var wrapper = Module.wrap(content); - var compiledWrapper = runInThisContext(wrapper, filename, 0, true); + var compiledWrapper = runInThisContext(wrapper, filename); if (global.v8debug) { if (!resolvedArgv) { // we enter the repl if we're not given a filename argument. diff --git a/lib/vm.js b/lib/vm.js index f06d5abe989..5dd1ddea709 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -19,37 +19,49 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -var binding = process.binding('evals'); - -module.exports = Script; -Script.Script = Script; +var binding = process.binding('contextify'); +var Script = binding.ContextifyScript; var util = require('util'); -function Script(code, ctx, filename) { - if (!(this instanceof Script)) { - return new Script(code, ctx, filename); - } +// The binding provides a few useful primitives: +// - ContextifyScript(code, [filename]), with methods: +// - runInThisContext() +// - runInContext(sandbox, [timeout]) +// - makeContext(sandbox) +// From this we build the entire documented API. - var ns = new binding.NodeScript(code, ctx, filename); - - // bind all methods to this Script object - Object.keys(binding.NodeScript.prototype).forEach(function(f) { - if (util.isFunction(binding.NodeScript.prototype[f])) { - this[f] = function() { - if (!(this instanceof Script)) { - throw new TypeError('invalid call to ' + f); - } - return ns[f].apply(ns, arguments); - }; - } - }, this); -} - -Script.createScript = function(code, ctx, name) { - return new Script(code, ctx, name); +Script.prototype.runInNewContext = function(initSandbox, timeout) { + var context = exports.createContext(initSandbox); + return this.runInContext(context, timeout); }; -Script.createContext = binding.NodeScript.createContext; -Script.runInContext = binding.NodeScript.runInContext; -Script.runInThisContext = binding.NodeScript.runInThisContext; -Script.runInNewContext = binding.NodeScript.runInNewContext; +exports.Script = Script; + +exports.createScript = function(code, filename) { + return new Script(code, filename); +}; + +exports.createContext = function(initSandbox) { + if (util.isUndefined(initSandbox)) { + initSandbox = {}; + } + + binding.makeContext(initSandbox); + + return initSandbox; +}; + +exports.runInContext = function(code, sandbox, filename, timeout) { + var script = exports.createScript(code, filename); + return script.runInContext(sandbox, timeout); +}; + +exports.runInNewContext = function(code, sandbox, filename, timeout) { + var script = exports.createScript(code, filename); + return script.runInNewContext(sandbox, timeout); +}; + +exports.runInThisContext = function(code, filename, timeout) { + var script = exports.createScript(code, filename); + return script.runInThisContext(timeout); +}; diff --git a/node.gyp b/node.gyp index d6aefae26dd..f42ba46e652 100644 --- a/node.gyp +++ b/node.gyp @@ -94,13 +94,13 @@ 'src/node.cc', 'src/node_buffer.cc', 'src/node_constants.cc', + 'src/node_contextify.cc', 'src/node_extensions.cc', 'src/node_file.cc', 'src/node_http_parser.cc', 'src/node_javascript.cc', 'src/node_main.cc', 'src/node_os.cc', - 'src/node_script.cc', 'src/node_stat_watcher.cc', 'src/node_watchdog.cc', 'src/node_zlib.cc', @@ -120,13 +120,13 @@ 'src/node.h', 'src/node_buffer.h', 'src/node_constants.h', + 'src/node_contextify.h', 'src/node_extensions.h', 'src/node_file.h', 'src/node_http_parser.h', 'src/node_javascript.h', 'src/node_os.h', 'src/node_root_certs.h', - 'src/node_script.h', 'src/node_version.h', 'src/node_watchdog.h', 'src/node_wrap.h', diff --git a/src/node.cc b/src/node.cc index 1d2f9055668..450747729ba 100644 --- a/src/node.cc +++ b/src/node.cc @@ -25,7 +25,6 @@ #include "node_file.h" #include "node_http_parser.h" #include "node_javascript.h" -#include "node_script.h" #include "node_version.h" #if defined HAVE_PERFCTR diff --git a/src/node.js b/src/node.js index 549f5806c1e..d827152002e 100644 --- a/src/node.js +++ b/src/node.js @@ -426,7 +426,7 @@ 'global.require = require;\n' + 'return require("vm").runInThisContext(' + JSON.stringify(body) + ', ' + - JSON.stringify(name) + ', 0, true);\n'; + JSON.stringify(name) + ');\n'; } var result = module._compile(script, name + '-wrapper'); if (process._print_eval) console.log(result); @@ -717,8 +717,11 @@ // core modules found in lib/*.js. All core modules are compiled into the // node binary, so they can be loaded faster. - var Script = process.binding('evals').NodeScript; - var runInThisContext = Script.runInThisContext; + var ContextifyScript = process.binding('contextify').ContextifyScript; + function runInThisContext(code, filename) { + var script = new ContextifyScript(code, filename); + return script.runInThisContext(); + } function NativeModule(id) { this.filename = id + '.js'; @@ -779,7 +782,7 @@ var source = NativeModule.getSource(this.id); source = NativeModule.wrap(source); - var fn = runInThisContext(source, this.filename, 0, true); + var fn = runInThisContext(source, this.filename); fn(this.exports, NativeModule.require, this, this.filename); this.loaded = true; diff --git a/src/node_contextify.cc b/src/node_contextify.cc new file mode 100644 index 00000000000..002cffe1468 --- /dev/null +++ b/src/node_contextify.cc @@ -0,0 +1,484 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "node.h" +#include "node_internals.h" +#include "node_watchdog.h" + +namespace node { + +using v8::AccessType; +using v8::Array; +using v8::Boolean; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::HandleScope; +using v8::Integer; +using v8::Local; +using v8::None; +using v8::Object; +using v8::ObjectTemplate; +using v8::Persistent; +using v8::PropertyCallbackInfo; +using v8::Script; +using v8::String; +using v8::TryCatch; +using v8::Value; +using v8::V8; + + +class ContextifyContext : ObjectWrap { + private: + Persistent sandbox_; + Persistent proxy_global_; + static Persistent data_wrapper_tmpl; + static Persistent data_wrapper_ctor; + + public: + Persistent context_; + static Persistent js_tmpl; + + explicit ContextifyContext(Local sandbox) : + sandbox_(node_isolate, sandbox) { + } + + + ~ContextifyContext() { + context_.Dispose(); + proxy_global_.Dispose(); + sandbox_.Dispose(); + } + + + // We override ObjectWrap::Wrap so that we can create our context after + // we have a reference to our "host" JavaScript object. If we try to use + // handle_ in the ContextifyContext constructor, it will be empty since it's + // set in ObjectWrap::Wrap. + inline void Wrap(Local handle) { + HandleScope scope(node_isolate); + ObjectWrap::Wrap(handle); + Local v8_context = CreateV8Context(); + context_.Reset(node_isolate, v8_context); + proxy_global_.Reset(node_isolate, v8_context->Global()); + } + + + // This is an object that just keeps an internal pointer to this + // ContextifyContext. It's passed to the NamedPropertyHandler. If we + // pass the main JavaScript context object we're embedded in, then the + // NamedPropertyHandler will store a reference to it forever and keep it + // from getting gc'd. + Local CreateDataWrapper() { + HandleScope scope(node_isolate); + Local ctor = PersistentToLocal(node_isolate, data_wrapper_ctor); + Local wrapper = ctor->NewInstance(); + NODE_WRAP(wrapper, this); + return scope.Close(wrapper); + } + + + Local CreateV8Context() { + HandleScope scope(node_isolate); + Local function_template = FunctionTemplate::New(); + function_template->SetHiddenPrototype(true); + + Local sandbox = PersistentToLocal(node_isolate, sandbox_); + function_template->SetClassName(sandbox->GetConstructorName()); + + Local object_template = + function_template->InstanceTemplate(); + object_template->SetNamedPropertyHandler(GlobalPropertyGetterCallback, + GlobalPropertySetterCallback, + GlobalPropertyQueryCallback, + GlobalPropertyDeleterCallback, + GlobalPropertyEnumeratorCallback, + CreateDataWrapper()); + object_template->SetAccessCheckCallbacks(GlobalPropertyNamedAccessCheck, + GlobalPropertyIndexedAccessCheck); + return scope.Close(Context::New(node_isolate, NULL, object_template)); + } + + + static void Init(Local target) { + HandleScope scope(node_isolate); + + Local function_template = FunctionTemplate::New(); + function_template->InstanceTemplate()->SetInternalFieldCount(1); + data_wrapper_tmpl.Reset(node_isolate, function_template); + + Local lwrapper_tmpl = + PersistentToLocal(node_isolate, data_wrapper_tmpl); + data_wrapper_ctor.Reset(node_isolate, lwrapper_tmpl->GetFunction()); + + js_tmpl.Reset(node_isolate, FunctionTemplate::New(New)); + Local ljs_tmpl = PersistentToLocal(node_isolate, js_tmpl); + ljs_tmpl->InstanceTemplate()->SetInternalFieldCount(1); + + Local class_name + = FIXED_ONE_BYTE_STRING(node_isolate, "ContextifyContext"); + ljs_tmpl->SetClassName(class_name); + target->Set(class_name, ljs_tmpl->GetFunction()); + + NODE_SET_METHOD(target, "makeContext", MakeContext); + } + + + // args[0] = the sandbox object + static void New(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + if (!args[0]->IsObject()) { + return ThrowTypeError("sandbox argument must be an object."); + } + ContextifyContext* ctx = new ContextifyContext(args[0].As()); + ctx->Wrap(args.This()); + } + + + static void MakeContext(const FunctionCallbackInfo& args) { + Local sandbox = args[0].As(); + + Local ljs_tmpl = PersistentToLocal(node_isolate, js_tmpl); + Local constructor_args[] = { sandbox }; + Local contextify_context_object = + ljs_tmpl->GetFunction()->NewInstance(1, constructor_args); + + Local hidden_name = + FIXED_ONE_BYTE_STRING(node_isolate, "_contextifyHidden"); + sandbox->SetHiddenValue(hidden_name, contextify_context_object); + } + + + static const Local ContextFromContextifiedSandbox( + const Local& sandbox) { + Local hidden_name = + FIXED_ONE_BYTE_STRING(node_isolate, "_contextifyHidden"); + Local hidden_context = + sandbox->GetHiddenValue(hidden_name).As(); + + if (hidden_context.IsEmpty()) { + ThrowTypeError("sandbox argument must have been converted to a context."); + return Local(); + } + + ContextifyContext* ctx = + ObjectWrap::Unwrap(hidden_context); + Persistent context; + context.Reset(node_isolate, ctx->context_); + return PersistentToLocal(node_isolate, context); + } + + + static bool GlobalPropertyNamedAccessCheck(Local host, + Local key, + AccessType type, + Local data) { + return true; + } + + + static bool GlobalPropertyIndexedAccessCheck(Local host, + uint32_t key, + AccessType type, + Local data) { + return true; + } + + + static void GlobalPropertyGetterCallback( + Local property, + const PropertyCallbackInfo& args) { + HandleScope scope(node_isolate); + + Local data = args.Data()->ToObject(); + ContextifyContext* ctx = ObjectWrap::Unwrap(data); + + Local sandbox = PersistentToLocal(node_isolate, ctx->sandbox_); + Local rv = sandbox->GetRealNamedProperty(property); + if (rv.IsEmpty()) { + Local proxy_global = PersistentToLocal(node_isolate, + ctx->proxy_global_); + rv = proxy_global->GetRealNamedProperty(property); + } + if (!rv.IsEmpty() && rv == ctx->sandbox_) { + rv = PersistentToLocal(node_isolate, ctx->proxy_global_); + } + + args.GetReturnValue().Set(rv); + } + + + static void GlobalPropertySetterCallback( + Local property, + Local value, + const PropertyCallbackInfo& args) { + HandleScope scope(node_isolate); + + Local data = args.Data()->ToObject(); + ContextifyContext* ctx = ObjectWrap::Unwrap(data); + + PersistentToLocal(node_isolate, ctx->sandbox_)->Set(property, value); + } + + + static void GlobalPropertyQueryCallback( + Local property, + const PropertyCallbackInfo& args) { + HandleScope scope(node_isolate); + + Local data = args.Data()->ToObject(); + ContextifyContext* ctx = ObjectWrap::Unwrap(data); + + Local sandbox = PersistentToLocal(node_isolate, ctx->sandbox_); + Local proxy_global = PersistentToLocal(node_isolate, + ctx->proxy_global_); + + bool in_sandbox = sandbox->GetRealNamedProperty(property).IsEmpty(); + bool in_proxy_global = + proxy_global->GetRealNamedProperty(property).IsEmpty(); + if (!in_sandbox || !in_proxy_global) { + args.GetReturnValue().Set(None); + } + } + + + static void GlobalPropertyDeleterCallback( + Local property, + const PropertyCallbackInfo& args) { + HandleScope scope(node_isolate); + + Local data = args.Data()->ToObject(); + ContextifyContext* ctx = ObjectWrap::Unwrap(data); + + bool success = PersistentToLocal(node_isolate, + ctx->sandbox_)->Delete(property); + if (!success) { + success = PersistentToLocal(node_isolate, + ctx->proxy_global_)->Delete(property); + } + args.GetReturnValue().Set(success); + } + + + static void GlobalPropertyEnumeratorCallback( + const PropertyCallbackInfo& args) { + HandleScope scope(node_isolate); + + Local data = args.Data()->ToObject(); + ContextifyContext* ctx = ObjectWrap::Unwrap(data); + + Local sandbox = PersistentToLocal(node_isolate, ctx->sandbox_); + args.GetReturnValue().Set(sandbox->GetPropertyNames()); + } +}; + +class ContextifyScript : ObjectWrap { + private: + Persistent