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_internals.h"
24 #include "node_watchdog.h"
25 #include "base-object.h"
26 #include "base-object-inl.h"
38 using v8::EscapableHandleScope;
41 using v8::FunctionCallbackInfo;
42 using v8::FunctionTemplate;
44 using v8::HandleScope;
50 using v8::ObjectTemplate;
52 using v8::PropertyCallbackInfo;
58 using v8::WeakCallbackData;
61 class ContextifyContext {
69 Environment* const env_;
70 Persistent<Object> sandbox_;
71 Persistent<Context> context_;
72 Persistent<Object> proxy_global_;
76 explicit ContextifyContext(Environment* env, Local<Object> sandbox)
78 sandbox_(env->isolate(), sandbox),
79 context_(env->isolate(), CreateV8Context(env)),
80 // Wait for sandbox_, proxy_global_, and context_ to die
82 sandbox_.SetWeak(this, WeakCallback<Object, kSandbox>);
83 sandbox_.MarkIndependent();
86 // Allocation failure or maximum call stack size reached
87 if (context_.IsEmpty())
89 context_.SetWeak(this, WeakCallback<Context, kContext>);
90 context_.MarkIndependent();
93 proxy_global_.Reset(env->isolate(), context()->Global());
94 proxy_global_.SetWeak(this, WeakCallback<Object, kProxyGlobal>);
95 proxy_global_.MarkIndependent();
100 ~ContextifyContext() {
102 proxy_global_.Reset();
107 inline Environment* env() const {
112 inline Local<Context> context() const {
113 return PersistentToLocal(env()->isolate(), context_);
117 // XXX(isaacs): This function only exists because of a shortcoming of
118 // the V8 SetNamedPropertyHandler function.
120 // It does not provide a way to intercept Object.defineProperty(..)
121 // calls. As a result, these properties are not copied onto the
122 // contextified sandbox when a new global property is added via either
123 // a function declaration or a Object.defineProperty(global, ...) call.
125 // Note that any function declarations or Object.defineProperty()
126 // globals that are created asynchronously (in a setTimeout, callback,
127 // etc.) will happen AFTER the call to copy properties, and thus not be
130 // The way to properly fix this is to add some sort of a
131 // Object::SetNamedDefinePropertyHandler() function that takes a callback,
132 // which receives the property name and property descriptor as arguments.
134 // Luckily, such situations are rare, and asynchronously-added globals
135 // weren't supported by Node's VM module until 0.12 anyway. But, this
136 // should be fixed properly in V8, and this copy function should be
137 // removed once there is a better way.
138 void CopyProperties() {
139 HandleScope scope(env()->isolate());
141 Local<Context> context = PersistentToLocal(env()->isolate(), context_);
142 Local<Object> global = context->Global()->GetPrototype()->ToObject();
143 Local<Object> sandbox = PersistentToLocal(env()->isolate(), sandbox_);
145 Local<Function> clone_property_method;
147 Local<Array> names = global->GetOwnPropertyNames();
148 int length = names->Length();
149 for (int i = 0; i < length; i++) {
150 Local<String> key = names->Get(i)->ToString();
151 bool has = sandbox->HasOwnProperty(key);
153 // Could also do this like so:
155 // PropertyAttribute att = global->GetPropertyAttributes(key_v);
156 // Local<Value> val = global->Get(key_v);
157 // sandbox->ForceSet(key_v, val, att);
159 // However, this doesn't handle ES6-style properties configured with
160 // Object.defineProperty, and that's exactly what we're up against at
161 // this point. ForceSet(key,val,att) only supports value properties
162 // with the ES3-style attribute flags (DontDelete/DontEnum/ReadOnly),
163 // which doesn't faithfully capture the full range of configurations
164 // that can be done using Object.defineProperty.
165 if (clone_property_method.IsEmpty()) {
166 Local<String> code = FIXED_ONE_BYTE_STRING(env()->isolate(),
167 "(function cloneProperty(source, key, target) {\n"
168 " if (key === 'Proxy') return;\n"
170 " var desc = Object.getOwnPropertyDescriptor(source, key);\n"
171 " if (desc.value === source) desc.value = target;\n"
172 " Object.defineProperty(target, key, desc);\n"
174 " // Catch sealed properties errors\n"
178 Local<String> fname = FIXED_ONE_BYTE_STRING(env()->isolate(),
180 Local<Script> script = Script::Compile(code, fname);
181 clone_property_method = Local<Function>::Cast(script->Run());
182 assert(clone_property_method->IsFunction());
184 Local<Value> args[] = { global, key, sandbox };
185 clone_property_method->Call(global, ARRAY_SIZE(args), args);
191 // This is an object that just keeps an internal pointer to this
192 // ContextifyContext. It's passed to the NamedPropertyHandler. If we
193 // pass the main JavaScript context object we're embedded in, then the
194 // NamedPropertyHandler will store a reference to it forever and keep it
195 // from getting gc'd.
196 Local<Value> CreateDataWrapper(Environment* env) {
197 EscapableHandleScope scope(env->isolate());
198 Local<Object> wrapper =
199 env->script_data_constructor_function()->NewInstance();
200 if (wrapper.IsEmpty())
201 return scope.Escape(Local<Value>::New(env->isolate(), Handle<Value>()));
203 Wrap<ContextifyContext>(wrapper, this);
204 return scope.Escape(wrapper);
208 Local<Context> CreateV8Context(Environment* env) {
209 EscapableHandleScope scope(env->isolate());
210 Local<FunctionTemplate> function_template =
211 FunctionTemplate::New(env->isolate());
212 function_template->SetHiddenPrototype(true);
214 Local<Object> sandbox = PersistentToLocal(env->isolate(), sandbox_);
215 function_template->SetClassName(sandbox->GetConstructorName());
217 Local<ObjectTemplate> object_template =
218 function_template->InstanceTemplate();
219 object_template->SetNamedPropertyHandler(GlobalPropertyGetterCallback,
220 GlobalPropertySetterCallback,
221 GlobalPropertyQueryCallback,
222 GlobalPropertyDeleterCallback,
223 GlobalPropertyEnumeratorCallback,
224 CreateDataWrapper(env));
225 object_template->SetAccessCheckCallbacks(GlobalPropertyNamedAccessCheck,
226 GlobalPropertyIndexedAccessCheck);
227 return scope.Escape(Context::New(env->isolate(), NULL, object_template));
231 static void Init(Environment* env, Local<Object> target) {
232 Local<FunctionTemplate> function_template =
233 FunctionTemplate::New(env->isolate());
234 function_template->InstanceTemplate()->SetInternalFieldCount(1);
235 env->set_script_data_constructor_function(function_template->GetFunction());
237 NODE_SET_METHOD(target, "makeContext", MakeContext);
238 NODE_SET_METHOD(target, "isContext", IsContext);
242 static void MakeContext(const FunctionCallbackInfo<Value>& args) {
243 Environment* env = Environment::GetCurrent(args.GetIsolate());
244 HandleScope scope(env->isolate());
246 if (!args[0]->IsObject()) {
247 return env->ThrowTypeError("sandbox argument must be an object.");
249 Local<Object> sandbox = args[0].As<Object>();
251 Local<String> hidden_name =
252 FIXED_ONE_BYTE_STRING(env->isolate(), "_contextifyHidden");
254 // Don't allow contextifying a sandbox multiple times.
255 assert(sandbox->GetHiddenValue(hidden_name).IsEmpty());
258 ContextifyContext* context = new ContextifyContext(env, sandbox);
260 if (try_catch.HasCaught()) {
265 if (context->context().IsEmpty())
268 Local<External> hidden_context = External::New(env->isolate(), context);
269 sandbox->SetHiddenValue(hidden_name, hidden_context);
273 static void IsContext(const FunctionCallbackInfo<Value>& args) {
274 Environment* env = Environment::GetCurrent(args.GetIsolate());
275 HandleScope scope(env->isolate());
277 if (!args[0]->IsObject()) {
278 env->ThrowTypeError("sandbox must be an object");
281 Local<Object> sandbox = args[0].As<Object>();
283 Local<String> hidden_name =
284 FIXED_ONE_BYTE_STRING(env->isolate(), "_contextifyHidden");
286 args.GetReturnValue().Set(!sandbox->GetHiddenValue(hidden_name).IsEmpty());
290 template <class T, Kind kind>
291 static void WeakCallback(const WeakCallbackData<T, ContextifyContext>& data) {
292 ContextifyContext* context = data.GetParameter();
293 if (kind == kSandbox)
294 context->sandbox_.ClearWeak();
295 else if (kind == kContext)
296 context->context_.ClearWeak();
298 context->proxy_global_.ClearWeak();
300 if (--context->references_ == 0)
305 static ContextifyContext* ContextFromContextifiedSandbox(
307 const Local<Object>& sandbox) {
308 Local<String> hidden_name =
309 FIXED_ONE_BYTE_STRING(isolate, "_contextifyHidden");
310 Local<Value> context_external_v = sandbox->GetHiddenValue(hidden_name);
311 if (context_external_v.IsEmpty() || !context_external_v->IsExternal()) {
314 Local<External> context_external = context_external_v.As<External>();
316 return static_cast<ContextifyContext*>(context_external->Value());
320 static bool GlobalPropertyNamedAccessCheck(Local<Object> host,
328 static bool GlobalPropertyIndexedAccessCheck(Local<Object> host,
336 static void GlobalPropertyGetterCallback(
337 Local<String> property,
338 const PropertyCallbackInfo<Value>& args) {
339 Isolate* isolate = args.GetIsolate();
340 HandleScope scope(isolate);
342 ContextifyContext* ctx =
343 Unwrap<ContextifyContext>(args.Data().As<Object>());
345 Local<Object> sandbox = PersistentToLocal(isolate, ctx->sandbox_);
346 Local<Value> rv = sandbox->GetRealNamedProperty(property);
348 Local<Object> proxy_global = PersistentToLocal(isolate,
350 rv = proxy_global->GetRealNamedProperty(property);
352 if (!rv.IsEmpty() && rv == ctx->sandbox_) {
353 rv = PersistentToLocal(isolate, ctx->proxy_global_);
356 args.GetReturnValue().Set(rv);
360 static void GlobalPropertySetterCallback(
361 Local<String> property,
363 const PropertyCallbackInfo<Value>& args) {
364 Isolate* isolate = args.GetIsolate();
365 HandleScope scope(isolate);
367 ContextifyContext* ctx =
368 Unwrap<ContextifyContext>(args.Data().As<Object>());
370 PersistentToLocal(isolate, ctx->sandbox_)->Set(property, value);
374 static void GlobalPropertyQueryCallback(
375 Local<String> property,
376 const PropertyCallbackInfo<Integer>& args) {
377 Isolate* isolate = args.GetIsolate();
378 HandleScope scope(isolate);
380 ContextifyContext* ctx =
381 Unwrap<ContextifyContext>(args.Data().As<Object>());
383 Local<Object> sandbox = PersistentToLocal(isolate, ctx->sandbox_);
384 Local<Object> proxy_global = PersistentToLocal(isolate,
387 bool in_sandbox = sandbox->GetRealNamedProperty(property).IsEmpty();
388 bool in_proxy_global =
389 proxy_global->GetRealNamedProperty(property).IsEmpty();
390 if (!in_sandbox || !in_proxy_global) {
391 args.GetReturnValue().Set(None);
396 static void GlobalPropertyDeleterCallback(
397 Local<String> property,
398 const PropertyCallbackInfo<Boolean>& args) {
399 Isolate* isolate = args.GetIsolate();
400 HandleScope scope(isolate);
402 ContextifyContext* ctx =
403 Unwrap<ContextifyContext>(args.Data().As<Object>());
405 bool success = PersistentToLocal(isolate,
406 ctx->sandbox_)->Delete(property);
408 success = PersistentToLocal(isolate,
409 ctx->proxy_global_)->Delete(property);
411 args.GetReturnValue().Set(success);
415 static void GlobalPropertyEnumeratorCallback(
416 const PropertyCallbackInfo<Array>& args) {
417 HandleScope scope(args.GetIsolate());
419 ContextifyContext* ctx =
420 Unwrap<ContextifyContext>(args.Data().As<Object>());
422 Local<Object> sandbox = PersistentToLocal(args.GetIsolate(), ctx->sandbox_);
423 args.GetReturnValue().Set(sandbox->GetPropertyNames());
427 class ContextifyScript : public BaseObject {
429 Persistent<Script> script_;
432 static void Init(Environment* env, Local<Object> target) {
433 HandleScope scope(env->isolate());
434 Local<String> class_name =
435 FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");
437 Local<FunctionTemplate> script_tmpl = FunctionTemplate::New(env->isolate(),
439 script_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
440 script_tmpl->SetClassName(class_name);
441 NODE_SET_PROTOTYPE_METHOD(script_tmpl, "runInContext", RunInContext);
442 NODE_SET_PROTOTYPE_METHOD(script_tmpl,
446 target->Set(class_name, script_tmpl->GetFunction());
447 env->set_script_context_constructor_template(script_tmpl);
451 // args: code, [options]
452 static void New(const FunctionCallbackInfo<Value>& args) {
453 Environment* env = Environment::GetCurrent(args.GetIsolate());
454 HandleScope scope(env->isolate());
456 if (!args.IsConstructCall()) {
457 return env->ThrowError("Must call vm.Script as a constructor.");
460 ContextifyScript* contextify_script =
461 new ContextifyScript(env, args.This());
464 Local<String> code = args[0]->ToString();
465 Local<String> filename = GetFilenameArg(args, 1);
466 bool display_errors = GetDisplayErrorsArg(args, 1);
467 if (try_catch.HasCaught()) {
472 Local<Context> context = env->context();
473 Context::Scope context_scope(context);
475 Local<Script> v8_script = Script::New(code, filename);
477 if (v8_script.IsEmpty()) {
478 if (display_errors) {
479 AppendExceptionLine(env, try_catch.Exception(), try_catch.Message());
484 contextify_script->script_.Reset(env->isolate(), v8_script);
488 static bool InstanceOf(Environment* env, const Local<Value>& value) {
489 return !value.IsEmpty() &&
490 env->script_context_constructor_template()->HasInstance(value);
495 static void RunInThisContext(const FunctionCallbackInfo<Value>& args) {
496 Isolate* isolate = args.GetIsolate();
497 HandleScope handle_scope(isolate);
499 // Assemble arguments
501 uint64_t timeout = GetTimeoutArg(args, 0);
502 bool display_errors = GetDisplayErrorsArg(args, 0);
503 if (try_catch.HasCaught()) {
508 // Do the eval within this context
509 Environment* env = Environment::GetCurrent(isolate);
510 EvalMachine(env, timeout, display_errors, args, try_catch);
513 // args: sandbox, [options]
514 static void RunInContext(const FunctionCallbackInfo<Value>& args) {
515 Environment* env = Environment::GetCurrent(args.GetIsolate());
516 HandleScope scope(env->isolate());
518 // Assemble arguments
520 if (!args[0]->IsObject()) {
521 return env->ThrowTypeError(
522 "contextifiedSandbox argument must be an object.");
524 Local<Object> sandbox = args[0].As<Object>();
525 int64_t timeout = GetTimeoutArg(args, 1);
526 bool display_errors = GetDisplayErrorsArg(args, 1);
527 if (try_catch.HasCaught()) {
532 // Get the context from the sandbox
533 ContextifyContext* contextify_context =
534 ContextifyContext::ContextFromContextifiedSandbox(env->isolate(),
536 if (contextify_context == NULL) {
537 return env->ThrowTypeError(
538 "sandbox argument must have been converted to a context.");
541 if (contextify_context->context().IsEmpty())
544 // Do the eval within the context
545 Context::Scope context_scope(contextify_context->context());
546 if (EvalMachine(contextify_context->env(),
551 contextify_context->CopyProperties();
555 static int64_t GetTimeoutArg(const FunctionCallbackInfo<Value>& args,
557 if (args[i]->IsUndefined() || args[i]->IsString()) {
560 if (!args[i]->IsObject()) {
561 Environment::ThrowTypeError(args.GetIsolate(),
562 "options must be an object");
566 Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "timeout");
567 Local<Value> value = args[i].As<Object>()->Get(key);
568 if (value->IsUndefined()) {
571 int64_t timeout = value->IntegerValue();
574 Environment::ThrowRangeError(args.GetIsolate(),
575 "timeout must be a positive number");
582 static bool GetDisplayErrorsArg(const FunctionCallbackInfo<Value>& args,
584 if (args[i]->IsUndefined() || args[i]->IsString()) {
587 if (!args[i]->IsObject()) {
588 Environment::ThrowTypeError(args.GetIsolate(),
589 "options must be an object");
593 Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
595 Local<Value> value = args[i].As<Object>()->Get(key);
597 return value->IsUndefined() ? true : value->BooleanValue();
601 static Local<String> GetFilenameArg(const FunctionCallbackInfo<Value>& args,
603 Local<String> defaultFilename =
604 FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine.<anonymous>");
606 if (args[i]->IsUndefined()) {
607 return defaultFilename;
609 if (args[i]->IsString()) {
610 return args[i].As<String>();
612 if (!args[i]->IsObject()) {
613 Environment::ThrowTypeError(args.GetIsolate(),
614 "options must be an object");
615 return Local<String>();
618 Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename");
619 Local<Value> value = args[i].As<Object>()->Get(key);
621 return value->IsUndefined() ? defaultFilename : value->ToString();
625 static bool EvalMachine(Environment* env,
626 const int64_t timeout,
627 const bool display_errors,
628 const FunctionCallbackInfo<Value>& args,
629 TryCatch& try_catch) {
630 if (!ContextifyScript::InstanceOf(env, args.This())) {
632 "Script methods can only be called on script instances.");
636 ContextifyScript* wrapped_script =
637 Unwrap<ContextifyScript>(args.This());
638 Local<Script> script = PersistentToLocal(env->isolate(),
639 wrapped_script->script_);
643 Watchdog wd(timeout);
644 result = script->Run();
646 result = script->Run();
649 if (try_catch.HasCaught() && try_catch.HasTerminated()) {
650 V8::CancelTerminateExecution(args.GetIsolate());
651 env->ThrowError("Script execution timed out.");
655 if (result.IsEmpty()) {
656 // Error occurred during execution of the script.
657 if (display_errors) {
658 AppendExceptionLine(env, try_catch.Exception(), try_catch.Message());
664 args.GetReturnValue().Set(result);
669 ContextifyScript(Environment* env, Local<Object> object)
670 : BaseObject(env, object) {
671 MakeWeak<ContextifyScript>(this);
675 ~ContextifyScript() {
681 void InitContextify(Handle<Object> target,
682 Handle<Value> unused,
683 Handle<Context> context) {
684 Environment* env = Environment::GetCurrent(context);
685 ContextifyContext::Init(env, target);
686 ContextifyScript::Init(env, target);
691 NODE_MODULE_CONTEXT_AWARE_BUILTIN(contextify, node::InitContextify);