vm, core, module: re-do vm to fix known issues
authorDomenic Denicola <domenic@domenicdenicola.com>
Sat, 27 Jul 2013 04:34:12 +0000 (00:34 -0400)
committerisaacs <i@izs.me>
Wed, 21 Aug 2013 22:52:23 +0000 (15:52 -0700)
As documented in #3042 and in [1], the existing vm implementation has
many problems. All of these are solved by @brianmcd's [contextify][2]
package. This commit uses contextify as a conceptual base and its code
core to overhaul the vm module and fix its many edge cases and caveats.

Functionally, this fixes #3042. In particular:

- A context is now indistinguishable from the object it is based on
  (the "sandbox"). A context is simply a sandbox that has been marked
  by the vm module, via `vm.createContext`, with special internal
  information that allows scripts to be run inside of it.
- Consequently, items added to the context from anywhere are
  immediately visible to all code that can access that context, both
  inside and outside the virtual machine.

This commit also smooths over the API very slightly:

- Parameter defaults are now uniformly triggered via `undefined`, per
  ES6 semantics and previous discussion at [3].
- Several undocumented and problematic features have been removed, e.g.
  the conflation of `vm.Script` with `vm` itself, and the fact that
  `Script` instances also had all static `vm` methods. The API is now
  exactly as documented (although arguably the existence of the
  `vm.Script` export is not yet documented, just the `Script` class
  itself).

In terms of implementation, this replaces node_script.cc with
node_contextify.cc, which is derived originally from [4] (see [5]) but
has since undergone extensive modifications and iterations to expose
the most useful C++ API and use the coding conventions and utilities of
Node core.

The bindings exposed by `process.binding('contextify')`
(node_contextify.cc) replace those formerly exposed by
`process.binding('evals')` (node_script.cc). They are:

- ContextifyScript(code, [filename]), with methods:
  - runInThisContext()
  - runInContext(sandbox, [timeout])
- makeContext(sandbox)

From this, the vm.js file builds the entire documented vm module API.

node.js and module.js were modified to use this new native binding, or
the vm module itself where possible. This introduces an extra line or
two into the stack traces of module compilation (and thus into most
stack traces), explaining the changed tests.

The tests were also updated slightly, with all vm-related simple tests
consolidated as test/simple/test-vm-* (some of them were formerly
test/simple/test-script-*). At the same time they switched from
`common.debug` to `console.error` and were updated to use
`assert.throws` instead of rolling their own error-testing methods.

New tests were also added, of course, demonstrating the new
capabilities and fixes.

[1]: http://nodejs.org/docs/v0.10.16/api/vm.html#vm_caveats
[2]: https://github.com/brianmcd/contextify
[3]: https://github.com/joyent/node/issues/5323#issuecomment-20250726
[4]: https://github.com/kkoopa/contextify/blob/bf123f3ef960f0943d1e30bda02e3163a004e964/src/contextify.cc
[5]: https://gist.github.com/domenic/6068120

26 files changed:
lib/module.js
lib/vm.js
node.gyp
src/node.cc
src/node.js
src/node_contextify.cc [new file with mode: 0644]
src/node_contextify.h [moved from src/node_script.h with 89% similarity]
src/node_extensions.h
src/node_script.cc [deleted file]
test/message/eval_messages.out
test/message/stdin_messages.out
test/message/undefined_reference_in_new_context.js
test/message/undefined_reference_in_new_context.out
test/simple/test-debug-break-on-uncaught.js
test/simple/test-querystring.js
test/simple/test-vm-basic.js [new file with mode: 0644]
test/simple/test-vm-context-async-script.js [new file with mode: 0644]
test/simple/test-vm-context-property-forwarding.js [new file with mode: 0644]
test/simple/test-vm-context.js [moved from test/simple/test-script-context.js with 75% similarity]
test/simple/test-vm-create-and-run-in-context.js [moved from test/simple/test-script-static-context.js with 78% similarity]
test/simple/test-vm-global-identity.js [new file with mode: 0644]
test/simple/test-vm-new-script-new-context.js [moved from test/simple/test-script-new.js with 83% similarity]
test/simple/test-vm-new-script-this-context.js [moved from test/simple/test-script-this.js with 94% similarity]
test/simple/test-vm-run-in-new-context.js [moved from test/simple/test-script-static-new.js with 76% similarity]
test/simple/test-vm-static-this.js [moved from test/simple/test-script-static-this.js with 80% similarity]
test/simple/test-vm-timeout.js [moved from test/simple/test-vm-run-timeout.js with 100% similarity]

index 88e0c5a..d9e9744 100644 (file)
@@ -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.
index f06d5ab..5dd1dde 100644 (file)
--- a/lib/vm.js
+++ b/lib/vm.js
 // 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.
+
+Script.prototype.runInNewContext = function(initSandbox, timeout) {
+  var context = exports.createContext(initSandbox);
+  return this.runInContext(context, timeout);
+};
+
+exports.Script = Script;
+
+exports.createScript = function(code, filename) {
+  return new Script(code, filename);
+};
+
+exports.createContext = function(initSandbox) {
+  if (util.isUndefined(initSandbox)) {
+    initSandbox = {};
   }
 
-  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);
+  binding.makeContext(initSandbox);
+
+  return initSandbox;
+};
+
+exports.runInContext = function(code, sandbox, filename, timeout) {
+  var script = exports.createScript(code, filename);
+  return script.runInContext(sandbox, timeout);
 };
 
-Script.createContext = binding.NodeScript.createContext;
-Script.runInContext = binding.NodeScript.runInContext;
-Script.runInThisContext = binding.NodeScript.runInThisContext;
-Script.runInNewContext = binding.NodeScript.runInNewContext;
+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);
+};
index d6aefae..f42ba46 100644 (file)
--- a/node.gyp
+++ b/node.gyp
         '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',
         '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',
index 1d2f905..4507477 100644 (file)
@@ -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
index 549f580..d827152 100644 (file)
                '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);
   // 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';
     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 (file)
index 0000000..002cffe
--- /dev/null
@@ -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<Object> sandbox_;
+  Persistent<Object> proxy_global_;
+  static Persistent<FunctionTemplate> data_wrapper_tmpl;
+  static Persistent<Function> data_wrapper_ctor;
+
+ public:
+  Persistent<Context> context_;
+  static Persistent<FunctionTemplate> js_tmpl;
+
+  explicit ContextifyContext(Local<Object> 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<Object> handle) {
+    HandleScope scope(node_isolate);
+    ObjectWrap::Wrap(handle);
+    Local<Context> 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<Value> CreateDataWrapper() {
+    HandleScope scope(node_isolate);
+    Local<Function> ctor = PersistentToLocal(node_isolate, data_wrapper_ctor);
+    Local<Object> wrapper = ctor->NewInstance();
+    NODE_WRAP(wrapper, this);
+    return scope.Close(wrapper);
+  }
+
+
+  Local<Context> CreateV8Context() {
+    HandleScope scope(node_isolate);
+    Local<FunctionTemplate> function_template = FunctionTemplate::New();
+    function_template->SetHiddenPrototype(true);
+
+    Local<Object> sandbox = PersistentToLocal(node_isolate, sandbox_);
+    function_template->SetClassName(sandbox->GetConstructorName());
+
+    Local<ObjectTemplate> 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<Object> target) {
+    HandleScope scope(node_isolate);
+
+    Local<FunctionTemplate> function_template = FunctionTemplate::New();
+    function_template->InstanceTemplate()->SetInternalFieldCount(1);
+    data_wrapper_tmpl.Reset(node_isolate, function_template);
+
+    Local<FunctionTemplate> 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<FunctionTemplate> ljs_tmpl = PersistentToLocal(node_isolate, js_tmpl);
+    ljs_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
+
+    Local<String> 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<Value>& args) {
+    HandleScope scope(node_isolate);
+    if (!args[0]->IsObject()) {
+      return ThrowTypeError("sandbox argument must be an object.");
+    }
+    ContextifyContext* ctx = new ContextifyContext(args[0].As<Object>());
+    ctx->Wrap(args.This());
+  }
+
+
+  static void MakeContext(const FunctionCallbackInfo<Value>& args) {
+    Local<Object> sandbox = args[0].As<Object>();
+
+    Local<FunctionTemplate> ljs_tmpl = PersistentToLocal(node_isolate, js_tmpl);
+    Local<Value> constructor_args[] = { sandbox };
+    Local<Object> contextify_context_object =
+        ljs_tmpl->GetFunction()->NewInstance(1, constructor_args);
+
+    Local<String> hidden_name =
+        FIXED_ONE_BYTE_STRING(node_isolate, "_contextifyHidden");
+    sandbox->SetHiddenValue(hidden_name, contextify_context_object);
+  }
+
+
+  static const Local<Context> ContextFromContextifiedSandbox(
+      const Local<Object>& sandbox) {
+    Local<String> hidden_name =
+        FIXED_ONE_BYTE_STRING(node_isolate, "_contextifyHidden");
+    Local<Object> hidden_context =
+        sandbox->GetHiddenValue(hidden_name).As<Object>();
+
+    if (hidden_context.IsEmpty()) {
+      ThrowTypeError("sandbox argument must have been converted to a context.");
+      return Local<Context>();
+    }
+
+    ContextifyContext* ctx =
+        ObjectWrap::Unwrap<ContextifyContext>(hidden_context);
+    Persistent<Context> context;
+    context.Reset(node_isolate, ctx->context_);
+    return PersistentToLocal(node_isolate, context);
+  }
+
+
+  static bool GlobalPropertyNamedAccessCheck(Local<Object> host,
+                                             Local<Value> key,
+                                             AccessType type,
+                                             Local<Value> data) {
+    return true;
+  }
+
+
+  static bool GlobalPropertyIndexedAccessCheck(Local<Object> host,
+                                               uint32_t key,
+                                               AccessType type,
+                                               Local<Value> data) {
+    return true;
+  }
+
+
+  static void GlobalPropertyGetterCallback(
+      Local<String> property,
+      const PropertyCallbackInfo<Value>& args) {
+    HandleScope scope(node_isolate);
+
+    Local<Object> data = args.Data()->ToObject();
+    ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
+
+    Local<Object> sandbox = PersistentToLocal(node_isolate, ctx->sandbox_);
+    Local<Value> rv = sandbox->GetRealNamedProperty(property);
+    if (rv.IsEmpty()) {
+      Local<Object> 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<String> property,
+      Local<Value> value,
+      const PropertyCallbackInfo<Value>& args) {
+    HandleScope scope(node_isolate);
+
+    Local<Object> data = args.Data()->ToObject();
+    ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
+
+    PersistentToLocal(node_isolate, ctx->sandbox_)->Set(property, value);
+  }
+
+
+  static void GlobalPropertyQueryCallback(
+      Local<String> property,
+      const PropertyCallbackInfo<Integer>& args) {
+    HandleScope scope(node_isolate);
+
+    Local<Object> data = args.Data()->ToObject();
+    ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
+
+    Local<Object> sandbox = PersistentToLocal(node_isolate, ctx->sandbox_);
+    Local<Object> 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<String> property,
+      const PropertyCallbackInfo<Boolean>& args) {
+    HandleScope scope(node_isolate);
+
+    Local<Object> data = args.Data()->ToObject();
+    ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(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<Array>& args) {
+    HandleScope scope(node_isolate);
+
+    Local<Object> data = args.Data()->ToObject();
+    ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
+
+    Local<Object> sandbox = PersistentToLocal(node_isolate, ctx->sandbox_);
+    args.GetReturnValue().Set(sandbox->GetPropertyNames());
+  }
+};
+
+class ContextifyScript : ObjectWrap {
+ private:
+  Persistent<Script> script_;
+
+ public:
+  static Persistent<FunctionTemplate> script_tmpl;
+
+  static void Init(Local<Object> target) {
+    HandleScope scope(node_isolate);
+    Local<String> class_name =
+        FIXED_ONE_BYTE_STRING(node_isolate, "ContextifyScript");
+
+    script_tmpl.Reset(node_isolate, FunctionTemplate::New(New));
+    Local<FunctionTemplate> lscript_tmpl =
+        PersistentToLocal(node_isolate, script_tmpl);
+    lscript_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
+    lscript_tmpl->SetClassName(class_name);
+    NODE_SET_PROTOTYPE_METHOD(lscript_tmpl, "runInContext", RunInContext);
+    NODE_SET_PROTOTYPE_METHOD(lscript_tmpl,
+                              "runInThisContext",
+                              RunInThisContext);
+
+    target->Set(class_name, lscript_tmpl->GetFunction());
+  }
+
+
+  // args: code, [filename]
+  static void New(const FunctionCallbackInfo<Value>& args) {
+    HandleScope scope(node_isolate);
+
+    if (!args.IsConstructCall()) {
+      return ThrowError("Must call vm.Script as a constructor.");
+    }
+
+    ContextifyScript *contextify_script = new ContextifyScript();
+    contextify_script->Wrap(args.Holder());
+    Local<String> code = args[0]->ToString();
+    Local<String> filename = GetFilenameArg(args, 1);
+
+    Local<Context> context = Context::GetCurrent();
+    Context::Scope context_scope(context);
+
+    TryCatch try_catch;
+
+    Local<Script> v8_script = Script::New(code, filename);
+
+    if (v8_script.IsEmpty()) {
+      DisplayExceptionLine(try_catch.Message());
+      try_catch.ReThrow();
+      return;
+    }
+    contextify_script->script_.Reset(node_isolate, v8_script);
+  }
+
+
+  static bool InstanceOf(const Local<Value>& value) {
+    return !value.IsEmpty() &&
+        PersistentToLocal(node_isolate, script_tmpl)->HasInstance(value);
+  }
+
+
+  // args: [timeout]
+  static void RunInThisContext(const FunctionCallbackInfo<Value>& args) {
+    HandleScope scope(node_isolate);
+
+    // Assemble arguments
+    TryCatch try_catch;
+    uint64_t timeout = GetTimeoutArg(args, 0);
+    if (try_catch.HasCaught()) {
+      try_catch.ReThrow();
+      return;
+    }
+
+    // Do the eval within this context
+    EvalMachine(timeout, args, try_catch);
+  }
+
+  // args: sandbox, [timeout]
+  static void RunInContext(const FunctionCallbackInfo<Value>& args) {
+    HandleScope scope(node_isolate);
+
+    // Assemble arguments
+    TryCatch try_catch;
+    if (!args[0]->IsObject()) {
+      return ThrowTypeError("sandbox argument must be an object.");
+    }
+    Local<Object> sandbox = args[0].As<Object>();
+    uint64_t timeout = GetTimeoutArg(args, 1);
+    if (try_catch.HasCaught()) {
+      try_catch.ReThrow();
+      return;
+    }
+
+    // Get the context from the sandbox
+    Local<Context> context =
+        ContextifyContext::ContextFromContextifiedSandbox(sandbox);
+    if (try_catch.HasCaught()) {
+      try_catch.ReThrow();
+      return;
+    }
+
+    // Do the eval within the context
+    Context::Scope context_scope(context);
+    EvalMachine(timeout, args, try_catch);
+  }
+
+  static int64_t GetTimeoutArg(const FunctionCallbackInfo<Value>& args,
+                               const int i) {
+    if (args[i]->IsUndefined()) {
+      return 0;
+    }
+
+    int64_t timeout = args[i]->IntegerValue();
+    if (timeout < 0) {
+      ThrowRangeError("timeout must be a positive number");
+    }
+    return timeout;
+  }
+
+
+  static Local<String> GetFilenameArg(const FunctionCallbackInfo<Value>& args,
+                                      const int i) {
+    return !args[i]->IsUndefined()
+        ? args[i]->ToString()
+        : FIXED_ONE_BYTE_STRING(node_isolate, "evalmachine.<anonymous>");
+  }
+
+
+  static void EvalMachine(const int64_t timeout,
+                          const FunctionCallbackInfo<Value>& args,
+                          TryCatch& try_catch) {
+    if (!ContextifyScript::InstanceOf(args.This())) {
+      return ThrowTypeError(
+          "Script methods can only be called on script instances.");
+    }
+
+    ContextifyScript* wrapped_script =
+        ObjectWrap::Unwrap<ContextifyScript>(args.This());
+    Local<Script> script = PersistentToLocal(node_isolate,
+                                             wrapped_script->script_);
+    if (script.IsEmpty()) {
+      DisplayExceptionLine(try_catch.Message());
+      try_catch.ReThrow();
+      return;
+    }
+
+    Local<Value> result;
+    if (timeout) {
+      Watchdog wd(timeout);
+      result = script->Run();
+    } else {
+      result = script->Run();
+    }
+
+    if (try_catch.HasCaught() && try_catch.HasTerminated()) {
+      V8::CancelTerminateExecution(args.GetIsolate());
+      return ThrowError("Script execution timed out.");
+    }
+
+    if (result.IsEmpty()) {
+      // Error occurred during execution of the script.
+      DisplayExceptionLine(try_catch.Message());
+      try_catch.ReThrow();
+      return;
+    }
+
+    args.GetReturnValue().Set(result);
+  }
+
+
+  ~ContextifyScript() {
+    script_.Dispose();
+  }
+};
+
+
+Persistent<FunctionTemplate> ContextifyContext::js_tmpl;
+Persistent<FunctionTemplate> ContextifyContext::data_wrapper_tmpl;
+Persistent<Function> ContextifyContext::data_wrapper_ctor;
+
+Persistent<FunctionTemplate> ContextifyScript::script_tmpl;
+
+void InitContextify(Local<Object> target) {
+  HandleScope scope(node_isolate);
+  ContextifyContext::Init(target);
+  ContextifyScript::Init(target);
+}
+
+}  // namespace node
+
+NODE_MODULE(node_contextify, node::InitContextify);
similarity index 89%
rename from src/node_script.h
rename to src/node_contextify.h
index 5914f56..2b45bb0 100644 (file)
@@ -19,8 +19,8 @@
 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 // USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-#ifndef SRC_NODE_SCRIPT_H_
-#define SRC_NODE_SCRIPT_H_
+#ifndef SRC_NODE_CONTEXTIFY_H_
+#define SRC_NODE_CONTEXTIFY_H_
 
 #include "node.h"
 #include "node_object_wrap.h"
@@ -29,8 +29,8 @@
 
 namespace node {
 
-void InitEvals(v8::Handle<v8::Object> target);
+void InitContextify(v8::Handle<v8::Object> target);
 
 }  // namespace node
 
-#endif  // SRC_NODE_SCRIPT_H_
+#endif  // SRC_NODE_CONTEXTIFY_H_
index 1d65a41..662596f 100644 (file)
@@ -34,7 +34,7 @@
     START                                                                     \
     ITEM(node_buffer)                                                         \
     NODE_EXT_LIST_SSL(ITEM)                                                   \
-    ITEM(node_evals)                                                          \
+    ITEM(node_contextify)                                                     \
     ITEM(node_fs)                                                             \
     ITEM(node_http_parser)                                                    \
     ITEM(node_os)                                                             \
diff --git a/src/node_script.cc b/src/node_script.cc
deleted file mode 100644 (file)
index 2795cef..0000000
+++ /dev/null
@@ -1,458 +0,0 @@
-// 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_script.h"
-#include "node_watchdog.h"
-#include <assert.h>
-
-namespace node {
-
-using v8::Array;
-using v8::Context;
-using v8::Exception;
-using v8::Function;
-using v8::FunctionCallbackInfo;
-using v8::FunctionTemplate;
-using v8::Handle;
-using v8::HandleScope;
-using v8::Local;
-using v8::Object;
-using v8::Persistent;
-using v8::Script;
-using v8::String;
-using v8::TryCatch;
-using v8::V8;
-using v8::Value;
-
-
-class WrappedContext : ObjectWrap {
- public:
-  static void Initialize(Handle<Object> target);
-  static void New(const FunctionCallbackInfo<Value>& args);
-
-  Local<Context> GetV8Context();
-  static Local<Object> NewInstance();
-  static bool InstanceOf(Handle<Value> value);
-
- protected:
-
-  static Persistent<FunctionTemplate> constructor_template;
-
-  WrappedContext();
-  ~WrappedContext();
-
-  Persistent<Context> context_;
-};
-
-
-Persistent<FunctionTemplate> WrappedContext::constructor_template;
-
-
-class WrappedScript : ObjectWrap {
- public:
-  static void Initialize(Handle<Object> target);
-
-  enum EvalInputFlags { compileCode, unwrapExternal };
-  enum EvalContextFlags { thisContext, newContext, userContext };
-  enum EvalOutputFlags { returnResult, wrapExternal };
-  enum EvalTimeoutFlags { noTimeout, useTimeout };
-
-  template <EvalInputFlags input_flag,
-            EvalContextFlags context_flag,
-            EvalOutputFlags output_flag,
-            EvalTimeoutFlags timeout_flag>
-  static void EvalMachine(const FunctionCallbackInfo<Value>& args);
-
- protected:
-  WrappedScript() : ObjectWrap() {}
-  ~WrappedScript();
-
-  static void New(const FunctionCallbackInfo<Value>& args);
-  static void CreateContext(const FunctionCallbackInfo<Value>& args);
-  static void RunInContext(const FunctionCallbackInfo<Value>& args);
-  static void RunInThisContext(const FunctionCallbackInfo<Value>& args);
-  static void RunInNewContext(const FunctionCallbackInfo<Value>& args);
-  static void CompileRunInContext(const FunctionCallbackInfo<Value>& args);
-  static void CompileRunInThisContext(const FunctionCallbackInfo<Value>& args);
-  static void CompileRunInNewContext(const FunctionCallbackInfo<Value>& args);
-
-  Persistent<Script> script_;
-};
-
-
-void CloneObject(Handle<Object> recv,
-                 Handle<Value> source,
-                 Handle<Value> target) {
-  HandleScope scope(node_isolate);
-
-  const char raw_script_source[] =
-      "(function(source, target) {                                    \n"
-      "  Object.getOwnPropertyNames(source).forEach(function(key) {   \n"
-      "    try {                                                      \n"
-      "      var desc = Object.getOwnPropertyDescriptor(source, key); \n"
-      "      if (desc.value === source) desc.value = target;          \n"
-      "      Object.defineProperty(target, key, desc);                \n"
-      "    } catch (e) {                                              \n"
-      "     // Catch sealed properties errors                         \n"
-      "    }                                                          \n"
-      "  });                                                          \n"
-      "});                                                            \n";
-
-  Local<String> script_source =
-      FIXED_ONE_BYTE_STRING(node_isolate, raw_script_source);
-  Local<String> script_name =
-      FIXED_ONE_BYTE_STRING(node_isolate, "binding:script");
-  Local<Script> script = Script::Compile(script_source, script_name);
-
-  Local<Function> fun = script->Run().As<Function>();
-  assert(fun.IsEmpty() == false);
-  assert(fun->IsFunction() == true);
-
-  Handle<Value> argv[] = { source, target };
-  Handle<Value> rc = fun->Call(recv, ARRAY_SIZE(argv), argv);
-  assert(rc.IsEmpty() == false);
-}
-
-
-void WrappedContext::Initialize(Handle<Object> target) {
-  HandleScope scope(node_isolate);
-
-  Local<FunctionTemplate> t = FunctionTemplate::New(WrappedContext::New);
-  t->InstanceTemplate()->SetInternalFieldCount(1);
-  t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "Context"));
-
-  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Context"), t->GetFunction());
-  constructor_template.Reset(node_isolate, t);
-}
-
-
-bool WrappedContext::InstanceOf(Handle<Value> value) {
-  return !value.IsEmpty() && HasInstance(constructor_template, value);
-}
-
-
-void WrappedContext::New(const FunctionCallbackInfo<Value>& args) {
-  HandleScope scope(node_isolate);
-  WrappedContext *t = new WrappedContext();
-  t->Wrap(args.This());
-}
-
-
-WrappedContext::WrappedContext() : ObjectWrap() {
-  context_.Reset(node_isolate, Context::New(node_isolate));
-}
-
-
-WrappedContext::~WrappedContext() {
-  context_.Dispose();
-}
-
-
-Local<Object> WrappedContext::NewInstance() {
-  Local<FunctionTemplate> constructor_template_handle =
-      PersistentToLocal(node_isolate, constructor_template);
-  return constructor_template_handle->GetFunction()->NewInstance();
-}
-
-
-Local<Context> WrappedContext::GetV8Context() {
-  return PersistentToLocal(node_isolate, context_);
-}
-
-
-void WrappedScript::Initialize(Handle<Object> target) {
-  HandleScope scope(node_isolate);
-
-  Local<FunctionTemplate> t = FunctionTemplate::New(WrappedScript::New);
-  t->InstanceTemplate()->SetInternalFieldCount(1);
-  // Note: We use 'NodeScript' instead of 'Script' so that we do not
-  // conflict with V8's Script class defined in v8/src/messages.js
-  // See GH-203 https://github.com/joyent/node/issues/203
-  t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "NodeScript"));
-
-  NODE_SET_PROTOTYPE_METHOD(t,
-                            "createContext",
-                            WrappedScript::CreateContext);
-
-  NODE_SET_PROTOTYPE_METHOD(t,
-                            "runInContext",
-                            WrappedScript::RunInContext);
-
-  NODE_SET_PROTOTYPE_METHOD(t,
-                            "runInThisContext",
-                            WrappedScript::RunInThisContext);
-
-  NODE_SET_PROTOTYPE_METHOD(t,
-                            "runInNewContext",
-                            WrappedScript::RunInNewContext);
-
-  NODE_SET_METHOD(t,
-                  "createContext",
-                  WrappedScript::CreateContext);
-
-  NODE_SET_METHOD(t,
-                  "runInContext",
-                  WrappedScript::CompileRunInContext);
-
-  NODE_SET_METHOD(t,
-                  "runInThisContext",
-                  WrappedScript::CompileRunInThisContext);
-
-  NODE_SET_METHOD(t,
-                  "runInNewContext",
-                  WrappedScript::CompileRunInNewContext);
-
-  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "NodeScript"),
-              t->GetFunction());
-}
-
-
-void WrappedScript::New(const FunctionCallbackInfo<Value>& args) {
-  assert(args.IsConstructCall() == true);
-  HandleScope scope(node_isolate);
-  WrappedScript *t = new WrappedScript();
-  t->Wrap(args.This());
-  WrappedScript::EvalMachine<
-      compileCode, thisContext, wrapExternal, noTimeout>(args);
-}
-
-
-WrappedScript::~WrappedScript() {
-  script_.Dispose();
-}
-
-
-void WrappedScript::CreateContext(const FunctionCallbackInfo<Value>& args) {
-  HandleScope scope(node_isolate);
-
-  Local<Object> context = WrappedContext::NewInstance();
-
-  if (args.Length() > 0) {
-    if (args[0]->IsObject()) {
-      Local<Object> sandbox = args[0].As<Object>();
-
-      CloneObject(args.This(), sandbox, context);
-    } else {
-      return ThrowTypeError(
-          "createContext() accept only object as first argument.");
-    }
-  }
-
-  args.GetReturnValue().Set(context);
-}
-
-
-void WrappedScript::RunInContext(const FunctionCallbackInfo<Value>& args) {
-  WrappedScript::EvalMachine<
-      unwrapExternal, userContext, returnResult, useTimeout>(args);
-}
-
-
-void WrappedScript::RunInThisContext(const FunctionCallbackInfo<Value>& args) {
-  WrappedScript::EvalMachine<
-      unwrapExternal, thisContext, returnResult, useTimeout>(args);
-}
-
-
-void WrappedScript::RunInNewContext(const FunctionCallbackInfo<Value>& args) {
-  WrappedScript::EvalMachine<
-      unwrapExternal, newContext, returnResult, useTimeout>(args);
-}
-
-
-void WrappedScript::CompileRunInContext(
-    const FunctionCallbackInfo<Value>& args) {
-  WrappedScript::EvalMachine<
-      compileCode, userContext, returnResult, useTimeout>(args);
-}
-
-
-void WrappedScript::CompileRunInThisContext(
-    const FunctionCallbackInfo<Value>& args) {
-  WrappedScript::EvalMachine<
-      compileCode, thisContext, returnResult, useTimeout>(args);
-}
-
-
-void WrappedScript::CompileRunInNewContext(
-    const FunctionCallbackInfo<Value>& args) {
-  WrappedScript::EvalMachine<
-      compileCode, newContext, returnResult, useTimeout>(args);
-}
-
-
-template <WrappedScript::EvalInputFlags input_flag,
-          WrappedScript::EvalContextFlags context_flag,
-          WrappedScript::EvalOutputFlags output_flag,
-          WrappedScript::EvalTimeoutFlags timeout_flag>
-void WrappedScript::EvalMachine(const FunctionCallbackInfo<Value>& args) {
-  HandleScope scope(node_isolate);
-
-  if (input_flag == compileCode && args.Length() < 1) {
-    return ThrowTypeError("needs at least 'code' argument.");
-  }
-
-  const int sandbox_index = input_flag == compileCode ? 1 : 0;
-  if (context_flag == userContext &&
-      !WrappedContext::InstanceOf(args[sandbox_index])) {
-    return ThrowTypeError("needs a 'context' argument.");
-  }
-
-  Local<String> code;
-  if (input_flag == compileCode) code = args[0]->ToString();
-
-  Local<Object> sandbox;
-  if (context_flag == newContext) {
-    sandbox = args[sandbox_index]->IsObject() ? args[sandbox_index]->ToObject()
-                                              : Object::New();
-  } else if (context_flag == userContext) {
-    sandbox = args[sandbox_index]->ToObject();
-  }
-
-  const int filename_index = sandbox_index +
-                             (context_flag == thisContext? 0 : 1);
-  Local<String> filename;
-  if (args.Length() > filename_index) {
-    filename = args[filename_index]->ToString();
-  } else {
-    filename = FIXED_ONE_BYTE_STRING(node_isolate, "evalmachine.<anonymous>");
-  }
-
-  uint64_t timeout = 0;
-  const int timeout_index = filename_index + 1;
-  if (timeout_flag == useTimeout && args.Length() > timeout_index) {
-    if (!args[timeout_index]->IsUint32()) {
-      return ThrowTypeError("needs an unsigned integer 'ms' argument.");
-    }
-    timeout = args[timeout_index]->Uint32Value();
-  }
-
-  const int display_error_index = timeout_index +
-                                  (timeout_flag == noTimeout ? 0 : 1);
-  bool display_error = false;
-  if (args.Length() > display_error_index &&
-      args[display_error_index]->IsBoolean() &&
-      args[display_error_index]->BooleanValue() == true) {
-    display_error = true;
-  }
-
-  Local<Context> context = Context::GetCurrent();
-
-  Local<Array> keys;
-  if (context_flag == newContext) {
-    // Create the new context
-    context = Context::New(node_isolate);
-
-  } else if (context_flag == userContext) {
-    // Use the passed in context
-    WrappedContext *nContext = ObjectWrap::Unwrap<WrappedContext>(sandbox);
-    context = nContext->GetV8Context();
-  }
-
-  Context::Scope context_scope(context);
-
-  // New and user context share code. DRY it up.
-  if (context_flag == userContext || context_flag == newContext) {
-    // Copy everything from the passed in sandbox (either the persistent
-    // context for runInContext(), or the sandbox arg to runInNewContext()).
-    CloneObject(args.This(), sandbox, context->Global()->GetPrototype());
-  }
-
-  // Catch errors
-  TryCatch try_catch;
-
-  // TryCatch must not be verbose to prevent duplicate logging
-  // of uncaught exceptions (we are rethrowing them)
-  try_catch.SetVerbose(false);
-
-  Handle<Value> result;
-  Local<Script> script;
-
-  if (input_flag == compileCode) {
-    // well, here WrappedScript::New would suffice in all cases, but maybe
-    // Compile has a little better performance where possible
-    script = output_flag == returnResult ? Script::Compile(code, filename)
-                                         : Script::New(code, filename);
-    if (script.IsEmpty()) {
-      // FIXME UGLY HACK TO DISPLAY SYNTAX ERRORS.
-      if (display_error) DisplayExceptionLine(try_catch.Message());
-
-      // Hack because I can't get a proper stacktrace on SyntaxError
-      try_catch.ReThrow();
-      return;
-    }
-  } else {
-    WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.This());
-    if (!n_script) {
-      return ThrowError("Must be called as a method of Script.");
-    } else if (n_script->script_.IsEmpty()) {
-      return ThrowError(
-          "'this' must be a result of previous new Script(code) call.");
-    }
-
-    script = PersistentToLocal(node_isolate, n_script->script_);
-  }
-
-  if (output_flag == returnResult) {
-    if (timeout) {
-      Watchdog wd(timeout);
-      result = script->Run();
-    } else {
-      result = script->Run();
-    }
-    if (try_catch.HasCaught() && try_catch.HasTerminated()) {
-      V8::CancelTerminateExecution(args.GetIsolate());
-      return ThrowError("Script execution timed out.");
-    }
-    if (result.IsEmpty()) {
-      if (display_error) DisplayExceptionLine(try_catch.Message());
-      try_catch.ReThrow();
-      return;
-    }
-  } else {
-    WrappedScript *n_script = ObjectWrap::Unwrap<WrappedScript>(args.This());
-    if (!n_script) {
-      return ThrowError("Must be called as a method of Script.");
-    }
-    n_script->script_.Reset(node_isolate, script);
-    result = args.This();
-  }
-
-  if (context_flag == userContext || context_flag == newContext) {
-    // success! copy changes back onto the sandbox object.
-    CloneObject(args.This(), context->Global()->GetPrototype(), sandbox);
-  }
-
-  args.GetReturnValue().Set(result);
-}
-
-
-void InitEvals(Handle<Object> target) {
-  HandleScope scope(node_isolate);
-  WrappedContext::Initialize(target);
-  WrappedScript::Initialize(target);
-}
-
-
-}  // namespace node
-
-NODE_MODULE(node_evals, node::InitEvals)
index 378fe94..b20f403 100644 (file)
@@ -4,6 +4,8 @@
 with(this){__filename}
 ^^^^
 SyntaxError: Strict mode code may not include a with statement
+    at Object.exports.createScript (vm.js:*)
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([eval]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
@@ -17,6 +19,7 @@ throw new Error("hello")
       ^
 Error: hello
     at [eval]:1:7
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([eval]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
@@ -28,6 +31,7 @@ throw new Error("hello")
       ^
 Error: hello
     at [eval]:1:7
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([eval]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
@@ -40,6 +44,7 @@ var x = 100; y = x;
                ^
 ReferenceError: y is not defined
     at [eval]:1:16
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([eval]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
index cd7c12a..3851aa7 100644 (file)
@@ -4,6 +4,8 @@
 with(this){__filename}
 ^^^^
 SyntaxError: Strict mode code may not include a with statement
+    at Object.exports.createScript (vm.js:*)
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([stdin]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
@@ -19,6 +21,7 @@ throw new Error("hello")
       ^
 Error: hello
     at [stdin]:1:*
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([stdin]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
@@ -32,6 +35,7 @@ throw new Error("hello")
       ^
 Error: hello
     at [stdin]:1:*
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([stdin]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
@@ -46,6 +50,7 @@ var x = 100; y = x;
                ^
 ReferenceError: y is not defined
     at [stdin]:1:16
+    at Object.exports.runInThisContext (vm.js:*)
     at Object.<anonymous> ([stdin]-wrapper:*:*)
     at Module._compile (module.js:*:*)
     at evalScript (node.js:*:*)
index 3da10b0..9ee6ef9 100644 (file)
 
 var common = require('../common');
 var assert = require('assert');
+var vm = require('vm');
 
 console.error('before');
 
-var Script = process.binding('evals').NodeScript;
-
 // undefined reference
-var script = new Script('foo.bar = 5;');
-script.runInNewContext();
+vm.runInNewContext('foo.bar = 5;');
 
 console.error('after');
index f8095ce..3364c9b 100644 (file)
@@ -1,10 +1,12 @@
 before
 
-*test*message*undefined_reference_in_new_context.js:34
-script.runInNewContext();
-       ^
+evalmachine.<anonymous>:1
+foo.bar = 5;
+^
 ReferenceError: foo is not defined
-    at evalmachine.<anonymous>:*
+    at evalmachine.<anonymous>:1:1
+    at ContextifyScript.Script.runInNewContext (vm.js:*)
+    at Object.exports.runInNewContext (vm.js:*)
     at Object.<anonymous> (*test*message*undefined_reference_in_new_context.js:*)
     at Module._compile (module.js:*)
     at *..js (module.js:*)
@@ -12,4 +14,3 @@ ReferenceError: foo is not defined
     at Function.Module._load (module.js:*:*)
     at Function.Module.runMain (module.js:*:*)
     at startup (node.js:*:*)
-    at node.js:*:*
index 5bb5b06..c41d940 100644 (file)
@@ -29,9 +29,9 @@ addScenario('global.js', null, 2);
 addScenario('timeout.js', null, 2);
 addScenario('domain.js', null, 10);
 
-// Exception is thrown from module.js (internal file)
+// Exception is thrown from vm.js via module.js (internal file)
 //   var compiledWrapper = runInThisContext(wrapper, filename, 0, true);
-addScenario('parse-error.js', 'module.js', null);
+addScenario('parse-error.js', 'vm.js', null);
 
 run();
 
index 2d86625..483982c 100644 (file)
@@ -91,9 +91,8 @@ var qsWeirdObjects = [
 ];
 // }}}
 
-var Script = require('vm').Script;
-var foreignObject = Script.runInContext('({"foo": ["bar", "baz"]})',
-                                        Script.createContext());
+var vm = require('vm');
+var foreignObject = vm.runInNewContext('({"foo": ["bar", "baz"]})');
 
 var qsNoMungeTestCases = [
   ['', {}],
diff --git a/test/simple/test-vm-basic.js b/test/simple/test-vm-basic.js
new file mode 100644 (file)
index 0000000..680a81a
--- /dev/null
@@ -0,0 +1,70 @@
+// 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.
+
+var common = require('../common');
+var assert = require('assert');
+var vm = require('vm');
+
+// Test 1: vm.runInNewContext
+var sandbox = {};
+var result = vm.runInNewContext(
+  'foo = "bar"; this.typeofProcess = typeof process; typeof Object;',
+  sandbox
+);
+assert.deepEqual(sandbox, {
+  foo: 'bar',
+  typeofProcess: 'undefined',
+});
+assert.strictEqual(result, 'function');
+
+// Test 2: vm.runInContext
+var sandbox2 = { foo: 'bar' };
+var context = vm.createContext(sandbox2);
+var result = vm.runInContext(
+  'baz = foo; this.typeofProcess = typeof process; typeof Object;',
+  context
+);
+assert.deepEqual(sandbox2, {
+  foo: 'bar',
+  baz: 'bar',
+  typeofProcess: 'undefined'
+});
+assert.strictEqual(result, 'function');
+
+// Test 3: vm.runInThisContext
+var result = vm.runInThisContext(
+  'vmResult = "foo"; Object.prototype.toString.call(process);'
+);
+assert.strictEqual(global.vmResult, 'foo');
+assert.strictEqual(result, '[object process]');
+delete global.vmResult;
+
+// Test 4: vm.runInNewContext
+var result = vm.runInNewContext(
+  'vmResult = "foo"; typeof process;'
+);
+assert.strictEqual(global.vmResult, undefined);
+assert.strictEqual(result, 'undefined');
+
+// Test 5: vm.createContext
+var sandbox3 = {};
+var context2 = vm.createContext(sandbox3);
+assert.strictEqual(sandbox3, context2);
diff --git a/test/simple/test-vm-context-async-script.js b/test/simple/test-vm-context-async-script.js
new file mode 100644 (file)
index 0000000..d201e98
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.
+
+var common = require('../common');
+var assert = require('assert');
+var vm = require('vm');
+
+var sandbox = { setTimeout: setTimeout };
+
+var ctx = vm.createContext(sandbox);
+
+vm.runInContext('setTimeout(function() { x = 3; }, 0);', ctx);
+setTimeout(function () {
+  assert.strictEqual(sandbox.x, 3);
+  assert.strictEqual(ctx.x, 3);
+}, 1);
diff --git a/test/simple/test-vm-context-property-forwarding.js b/test/simple/test-vm-context-property-forwarding.js
new file mode 100644 (file)
index 0000000..6d51463
--- /dev/null
@@ -0,0 +1,33 @@
+// 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.
+
+var common = require('../common');
+var assert = require('assert');
+var vm = require('vm');
+
+var sandbox = { x: 3 };
+
+var ctx = vm.createContext(sandbox);
+
+assert.strictEqual(vm.runInContext('x;', ctx), 3);
+vm.runInContext('y = 4;', ctx);
+assert.strictEqual(sandbox.y, 4);
+assert.strictEqual(ctx.y, 4);
similarity index 75%
rename from test/simple/test-script-context.js
rename to test/simple/test-vm-context.js
index 8095a71..b3127d1 100644 (file)
@@ -26,30 +26,32 @@ var vm = require('vm');
 var Script = vm.Script;
 var script = new Script('"passed";');
 
-common.debug('run in a new empty context');
-var context = script.createContext();
+console.error('run in a new empty context');
+var context = vm.createContext();
 var result = script.runInContext(context);
 assert.equal('passed', result);
 
-common.debug('create a new pre-populated context');
-context = script.createContext({'foo': 'bar', 'thing': 'lala'});
+console.error('create a new pre-populated context');
+context = vm.createContext({'foo': 'bar', 'thing': 'lala'});
 assert.equal('bar', context.foo);
 assert.equal('lala', context.thing);
 
-common.debug('test updating context');
+console.error('test updating context');
 script = new Script('foo = 3;');
 result = script.runInContext(context);
 assert.equal(3, context.foo);
 assert.equal('lala', context.thing);
 
 // Issue GH-227:
-Script.runInNewContext('', null, 'some.js');
+assert.throws(function () {
+  vm.runInNewContext('', null, 'some.js');
+}, TypeError);
 
 // Issue GH-1140:
-common.debug('test runInContext signature');
+console.error('test runInContext signature');
 var gh1140Exception;
 try {
-  Script.runInContext('throw new Error()', context, 'expected-filename.js');
+  vm.runInContext('throw new Error()', context, 'expected-filename.js');
 }
 catch (e) {
   gh1140Exception = e;
@@ -60,17 +62,13 @@ assert.ok(gh1140Exception,
           'expected exception from runInContext signature test');
 
 // GH-558, non-context argument segfaults / raises assertion
-function isTypeError(o) {
-  return o instanceof TypeError;
-}
-
-([undefined, null, 0, 0.0, '', {}, []].forEach(function(e) {
-  assert.throws(function() { script.runInContext(e); }, isTypeError);
-  assert.throws(function() { vm.runInContext('', e); }, isTypeError);
-}));
+[undefined, null, 0, 0.0, '', {}, []].forEach(function(e) {
+  assert.throws(function() { script.runInContext(e); }, TypeError);
+  assert.throws(function() { vm.runInContext('', e); }, TypeError);
+});
 
 // Issue GH-693:
-common.debug('test RegExp as argument to assert.throws');
+console.error('test RegExp as argument to assert.throws');
 script = vm.createScript('var assert = require(\'assert\'); assert.throws(' +
                          'function() { throw "hello world"; }, /hello/);',
                          'some.js');
similarity index 78%
rename from test/simple/test-script-static-context.js
rename to test/simple/test-vm-create-and-run-in-context.js
index d2cb701..863569c 100644 (file)
 var common = require('../common');
 var assert = require('assert');
 
-var Script = require('vm').Script;
+var vm = require('vm');
 
-common.debug('run in a new empty context');
-var context = Script.createContext();
-var result = Script.runInContext('"passed";', context);
+console.error('run in a new empty context');
+var context = vm.createContext();
+var result = vm.runInContext('"passed";', context);
 assert.equal('passed', result);
 
-common.debug('create a new pre-populated context');
-context = Script.createContext({'foo': 'bar', 'thing': 'lala'});
+console.error('create a new pre-populated context');
+context = vm.createContext({'foo': 'bar', 'thing': 'lala'});
 assert.equal('bar', context.foo);
 assert.equal('lala', context.thing);
 
-common.debug('test updating context');
-result = Script.runInContext('var foo = 3;', context);
+console.error('test updating context');
+result = vm.runInContext('var foo = 3;', context);
 assert.equal(3, context.foo);
 assert.equal('lala', context.thing);
diff --git a/test/simple/test-vm-global-identity.js b/test/simple/test-vm-global-identity.js
new file mode 100644 (file)
index 0000000..adfb323
--- /dev/null
@@ -0,0 +1,31 @@
+// 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.
+
+var common = require('../common');
+var assert = require('assert');
+var vm = require('vm');
+
+var ctx = vm.createContext();
+ctx.window = ctx;
+
+var thisVal = vm.runInContext('this;', ctx);
+var windowVal = vm.runInContext('window;', ctx);
+assert.strictEqual(thisVal, windowVal);
similarity index 83%
rename from test/simple/test-script-new.js
rename to test/simple/test-vm-new-script-new-context.js
index 7894bb9..139d854 100644 (file)
@@ -25,34 +25,28 @@ var Script = require('vm').Script;
 
 common.globalCheck = false;
 
-common.debug('run a string');
+console.error('run a string');
 var script = new Script('\'passed\';');
-common.debug('script created');
+console.error('script created');
 var result1 = script.runInNewContext();
 var result2 = script.runInNewContext();
 assert.equal('passed', result1);
 assert.equal('passed', result2);
 
-common.debug('thrown error');
+console.error('thrown error');
 script = new Script('throw new Error(\'test\');');
 assert.throws(function() {
   script.runInNewContext();
-});
+}, /test/);
 
 
 
-common.debug('undefined reference');
+console.error('undefined reference');
 var error;
 script = new Script('foo.bar = 5;');
-try {
+assert.throws(function () {
   script.runInNewContext();
-} catch (e) {
-  error = e;
-}
-assert.ok(error);
-assert.ok(error.message.indexOf('not defined') >= 0);
-
-common.debug('error.message: ' + error.message);
+}, /not defined/);
 
 
 hello = 5;
@@ -61,7 +55,7 @@ script.runInNewContext();
 assert.equal(5, hello);
 
 
-common.debug('pass values in and out');
+console.error('pass values in and out');
 code = 'foo = 1;' +
        'bar = 2;' +
        'if (baz !== 3) throw new Error(\'test fail\');';
@@ -73,21 +67,25 @@ assert.equal(1, obj.foo);
 assert.equal(2, obj.bar);
 assert.equal(2, foo);
 
-common.debug('call a function by reference');
+console.error('call a function by reference');
 script = new Script('f()');
 function changeFoo() { foo = 100 }
 script.runInNewContext({ f: changeFoo });
 assert.equal(foo, 100);
 
-common.debug('modify an object by reference');
+console.error('modify an object by reference');
 script = new Script('f.a = 2');
 var f = { a: 1 };
 script.runInNewContext({ f: f });
 assert.equal(f.a, 2);
 
-common.debug('invalid this');
+assert.throws(function() {
+  script.runInNewContext();
+}, /f is not defined/);
+
+console.error('invalid this');
 assert.throws(function() {
   script.runInNewContext.call('\'hello\';');
-});
+}, TypeError);
 
 
similarity index 94%
rename from test/simple/test-script-this.js
rename to test/simple/test-vm-new-script-this-context.js
index a55a490..ff0b5b3 100644 (file)
@@ -25,12 +25,12 @@ var Script = require('vm').Script;
 
 common.globalCheck = false;
 
-common.debug('run a string');
+console.error('run a string');
 var script = new Script('\'passed\';');
 var result = script.runInThisContext(script);
 assert.equal('passed', result);
 
-common.debug('thrown error');
+console.error('thrown error');
 script = new Script('throw new Error(\'test\');');
 assert.throws(function() {
   script.runInThisContext(script);
@@ -42,7 +42,7 @@ script.runInThisContext(script);
 assert.equal(2, hello);
 
 
-common.debug('pass values');
+console.error('pass values');
 code = 'foo = 1;' +
        'bar = 2;' +
        'if (typeof baz !== \'undefined\') throw new Error(\'test fail\');';
@@ -54,7 +54,7 @@ assert.equal(0, obj.foo);
 assert.equal(2, bar);
 assert.equal(1, foo);
 
-common.debug('call a function');
+console.error('call a function');
 f = function() { foo = 100 };
 script = new Script('f()');
 script.runInThisContext(script);
similarity index 76%
rename from test/simple/test-script-static-new.js
rename to test/simple/test-vm-run-in-new-context.js
index eaac2d9..77b74d5 100644 (file)
 
 var common = require('../common');
 var assert = require('assert');
-var Script = require('vm').Script;
+var vm = require('vm');
 
 common.globalCheck = false;
 
-common.debug('run a string');
-var result = Script.runInNewContext('\'passed\';');
+console.error('run a string');
+var result = vm.runInNewContext('\'passed\';');
 assert.equal('passed', result);
 
-common.debug('thrown error');
+console.error('thrown error');
 assert.throws(function() {
-  Script.runInNewContext('throw new Error(\'test\');');
+  vm.runInNewContext('throw new Error(\'test\');');
 });
 
 hello = 5;
-Script.runInNewContext('hello = 2');
+vm.runInNewContext('hello = 2');
 assert.equal(5, hello);
 
 
-common.debug('pass values in and out');
+console.error('pass values in and out');
 code = 'foo = 1;' +
        'bar = 2;' +
        'if (baz !== 3) throw new Error(\'test fail\');';
 foo = 2;
 obj = { foo: 0, baz: 3 };
-var baz = Script.runInNewContext(code, obj);
+var baz = vm.runInNewContext(code, obj);
 assert.equal(1, obj.foo);
 assert.equal(2, obj.bar);
 assert.equal(2, foo);
 
-common.debug('call a function by reference');
+console.error('call a function by reference');
 function changeFoo() { foo = 100 }
-Script.runInNewContext('f()', { f: changeFoo });
+vm.runInNewContext('f()', { f: changeFoo });
 assert.equal(foo, 100);
 
-common.debug('modify an object by reference');
+console.error('modify an object by reference');
 var f = { a: 1 };
-Script.runInNewContext('f.a = 2', { f: f });
+vm.runInNewContext('f.a = 2', { f: f });
 assert.equal(f.a, 2);
 
similarity index 80%
rename from test/simple/test-script-static-this.js
rename to test/simple/test-vm-static-this.js
index 63c1133..860fbf0 100644 (file)
 
 var common = require('../common');
 var assert = require('assert');
-var Script = require('vm').Script;
+var vm = require('vm');
 
 common.globalCheck = false;
 
-common.debug('run a string');
-var result = Script.runInThisContext('\'passed\';');
+console.error('run a string');
+var result = vm.runInThisContext('\'passed\';');
 assert.equal('passed', result);
 
-common.debug('thrown error');
+console.error('thrown error');
 assert.throws(function() {
-  Script.runInThisContext('throw new Error(\'test\');');
-});
+  vm.runInThisContext('throw new Error(\'test\');');
+}, /test/);
 
 hello = 5;
-Script.runInThisContext('hello = 2');
+vm.runInThisContext('hello = 2');
 assert.equal(2, hello);
 
 
-common.debug('pass values');
+console.error('pass values');
 code = 'foo = 1;' +
        'bar = 2;' +
        'if (typeof baz !== \'undefined\') throw new Error(\'test fail\');';
 foo = 2;
 obj = { foo: 0, baz: 3 };
-var baz = Script.runInThisContext(code);
+var baz = vm.runInThisContext(code);
 assert.equal(0, obj.foo);
 assert.equal(2, bar);
 assert.equal(1, foo);
 
-common.debug('call a function');
+console.error('call a function');
 f = function() { foo = 100 };
-Script.runInThisContext('f()');
+vm.runInThisContext('f()');
 assert.equal(100, foo);