Node.js  v8.x
Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine
node_contextify.cc
Go to the documentation of this file.
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21 
22 #include "node.h"
23 #include "node_internals.h"
24 #include "node_watchdog.h"
25 #include "base-object.h"
26 #include "base-object-inl.h"
27 #include "env.h"
28 #include "env-inl.h"
29 #include "util.h"
30 #include "util-inl.h"
31 #include "v8-debug.h"
32 
33 namespace node {
34 
35 using v8::Array;
36 using v8::ArrayBuffer;
37 using v8::Boolean;
38 using v8::Context;
39 using v8::Debug;
40 using v8::EscapableHandleScope;
41 using v8::External;
42 using v8::Function;
43 using v8::FunctionCallbackInfo;
44 using v8::FunctionTemplate;
45 using v8::HandleScope;
46 using v8::Integer;
47 using v8::Just;
48 using v8::Local;
49 using v8::Maybe;
50 using v8::MaybeLocal;
51 using v8::Name;
52 using v8::NamedPropertyHandlerConfiguration;
53 using v8::Nothing;
54 using v8::Object;
55 using v8::ObjectTemplate;
56 using v8::Persistent;
57 using v8::PropertyAttribute;
58 using v8::PropertyCallbackInfo;
59 using v8::PropertyDescriptor;
60 using v8::Script;
61 using v8::ScriptCompiler;
62 using v8::ScriptOrigin;
63 using v8::String;
64 using v8::TryCatch;
65 using v8::Uint8Array;
66 using v8::UnboundScript;
67 using v8::Value;
68 using v8::WeakCallbackInfo;
69 
70 namespace {
71 
72 class ContextifyContext {
73  protected:
74  // V8 reserves the first field in context objects for the debugger. We use the
75  // second field to hold a reference to the sandbox object.
76  enum { kSandboxObjectIndex = 1 };
77 
78  Environment* const env_;
79  Persistent<Context> context_;
80 
81  public:
82  ContextifyContext(Environment* env, Local<Object> sandbox_obj) : env_(env) {
83  Local<Context> v8_context = CreateV8Context(env, sandbox_obj);
84  context_.Reset(env->isolate(), v8_context);
85 
86  // Allocation failure or maximum call stack size reached
87  if (context_.IsEmpty())
88  return;
89  context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
90  context_.MarkIndependent();
91  }
92 
93 
94  ~ContextifyContext() {
95  context_.Reset();
96  }
97 
98 
99  inline Environment* env() const {
100  return env_;
101  }
102 
103 
104  inline Local<Context> context() const {
105  return PersistentToLocal(env()->isolate(), context_);
106  }
107 
108 
109  inline Local<Object> global_proxy() const {
110  return context()->Global();
111  }
112 
113 
114  inline Local<Object> sandbox() const {
115  return Local<Object>::Cast(context()->GetEmbedderData(kSandboxObjectIndex));
116  }
117 
118  // XXX(isaacs): This function only exists because of a shortcoming of
119  // the V8 SetNamedPropertyHandler function.
120  //
121  // It does not provide a way to intercept Object.defineProperty(..)
122  // calls. As a result, these properties are not copied onto the
123  // contextified sandbox when a new global property is added via either
124  // a function declaration or a Object.defineProperty(global, ...) call.
125  //
126  // Note that any function declarations or Object.defineProperty()
127  // globals that are created asynchronously (in a setTimeout, callback,
128  // etc.) will happen AFTER the call to copy properties, and thus not be
129  // caught.
130  //
131  // The way to properly fix this is to add some sort of a
132  // Object::SetNamedDefinePropertyHandler() function that takes a callback,
133  // which receives the property name and property descriptor as arguments.
134  //
135  // Luckily, such situations are rare, and asynchronously-added globals
136  // weren't supported by Node's VM module until 0.12 anyway. But, this
137  // should be fixed properly in V8, and this copy function should be
138  // removed once there is a better way.
139  void CopyProperties() {
140  HandleScope scope(env()->isolate());
141 
142  Local<Context> context = PersistentToLocal(env()->isolate(), context_);
143  Local<Object> global =
144  context->Global()->GetPrototype()->ToObject(env()->isolate());
145  Local<Object> sandbox_obj = sandbox();
146 
147  Local<Function> clone_property_method;
148 
149  Local<Array> names = global->GetOwnPropertyNames();
150  int length = names->Length();
151  for (int i = 0; i < length; i++) {
152  Local<String> key = names->Get(i)->ToString(env()->isolate());
153  Maybe<bool> has = sandbox_obj->HasOwnProperty(context, key);
154 
155  // Check for pending exceptions
156  if (has.IsNothing())
157  return;
158 
159  if (!has.FromJust()) {
160  Local<Object> desc_vm_context =
161  global->GetOwnPropertyDescriptor(context, key)
162  .ToLocalChecked().As<Object>();
163 
164  bool is_accessor =
165  desc_vm_context->Has(context, env()->get_string()).FromJust() ||
166  desc_vm_context->Has(context, env()->set_string()).FromJust();
167 
168  auto define_property_on_sandbox = [&] (PropertyDescriptor* desc) {
169  desc->set_configurable(desc_vm_context
170  ->Get(context, env()->configurable_string()).ToLocalChecked()
171  ->BooleanValue(context).FromJust());
172  desc->set_enumerable(desc_vm_context
173  ->Get(context, env()->enumerable_string()).ToLocalChecked()
174  ->BooleanValue(context).FromJust());
175  CHECK(sandbox_obj->DefineProperty(context, key, *desc).FromJust());
176  };
177 
178  if (is_accessor) {
179  Local<Function> get =
180  desc_vm_context->Get(context, env()->get_string())
181  .ToLocalChecked().As<Function>();
182  Local<Function> set =
183  desc_vm_context->Get(context, env()->set_string())
184  .ToLocalChecked().As<Function>();
185 
186  PropertyDescriptor desc(get, set);
187  define_property_on_sandbox(&desc);
188  } else {
189  Local<Value> value =
190  desc_vm_context->Get(context, env()->value_string())
191  .ToLocalChecked();
192 
193  bool writable =
194  desc_vm_context->Get(context, env()->writable_string())
195  .ToLocalChecked()->BooleanValue(context).FromJust();
196 
197  PropertyDescriptor desc(value, writable);
198  define_property_on_sandbox(&desc);
199  }
200  }
201  }
202 }
203 
204 
205  // This is an object that just keeps an internal pointer to this
206  // ContextifyContext. It's passed to the NamedPropertyHandler. If we
207  // pass the main JavaScript context object we're embedded in, then the
208  // NamedPropertyHandler will store a reference to it forever and keep it
209  // from getting gc'd.
210  Local<Value> CreateDataWrapper(Environment* env) {
211  EscapableHandleScope scope(env->isolate());
212  Local<Object> wrapper =
213  env->script_data_constructor_function()
214  ->NewInstance(env->context()).FromMaybe(Local<Object>());
215  if (wrapper.IsEmpty())
216  return scope.Escape(Local<Value>::New(env->isolate(), Local<Value>()));
217 
218  Wrap(wrapper, this);
219  return scope.Escape(wrapper);
220  }
221 
222 
223  Local<Context> CreateV8Context(Environment* env, Local<Object> sandbox_obj) {
224  EscapableHandleScope scope(env->isolate());
225  Local<FunctionTemplate> function_template =
226  FunctionTemplate::New(env->isolate());
227  function_template->SetHiddenPrototype(true);
228 
229  function_template->SetClassName(sandbox_obj->GetConstructorName());
230 
231  Local<ObjectTemplate> object_template =
232  function_template->InstanceTemplate();
233 
234  NamedPropertyHandlerConfiguration config(GlobalPropertyGetterCallback,
235  GlobalPropertySetterCallback,
236  GlobalPropertyQueryCallback,
237  GlobalPropertyDeleterCallback,
238  GlobalPropertyEnumeratorCallback,
239  CreateDataWrapper(env));
240  object_template->SetHandler(config);
241 
242  Local<Context> ctx = Context::New(env->isolate(), nullptr, object_template);
243 
244  if (ctx.IsEmpty()) {
245  env->ThrowError("Could not instantiate context");
246  return Local<Context>();
247  }
248 
249  ctx->SetSecurityToken(env->context()->GetSecurityToken());
250 
251  // We need to tie the lifetime of the sandbox object with the lifetime of
252  // newly created context. We do this by making them hold references to each
253  // other. The context can directly hold a reference to the sandbox as an
254  // embedder data field. However, we cannot hold a reference to a v8::Context
255  // directly in an Object, we instead hold onto the new context's global
256  // object instead (which then has a reference to the context).
257  ctx->SetEmbedderData(kSandboxObjectIndex, sandbox_obj);
258  sandbox_obj->SetPrivate(env->context(),
259  env->contextify_global_private_symbol(),
260  ctx->Global());
261 
262  env->AssignToContext(ctx);
263 
264  return scope.Escape(ctx);
265  }
266 
267 
268  static void Init(Environment* env, Local<Object> target) {
269  Local<FunctionTemplate> function_template =
270  FunctionTemplate::New(env->isolate());
271  function_template->InstanceTemplate()->SetInternalFieldCount(1);
272  env->set_script_data_constructor_function(function_template->GetFunction());
273 
274  env->SetMethod(target, "runInDebugContext", RunInDebugContext);
275  env->SetMethod(target, "makeContext", MakeContext);
276  env->SetMethod(target, "isContext", IsContext);
277  }
278 
279 
280  static void RunInDebugContext(const FunctionCallbackInfo<Value>& args) {
281  Local<String> script_source(args[0]->ToString(args.GetIsolate()));
282  if (script_source.IsEmpty())
283  return; // Exception pending.
284  Local<Context> debug_context = Debug::GetDebugContext(args.GetIsolate());
285  Environment* env = Environment::GetCurrent(args);
286  if (debug_context.IsEmpty()) {
287  // Force-load the debug context.
288  auto dummy_event_listener = [] (const Debug::EventDetails&) {};
289  Debug::SetDebugEventListener(args.GetIsolate(), dummy_event_listener);
290  debug_context = Debug::GetDebugContext(args.GetIsolate());
291  CHECK(!debug_context.IsEmpty());
292  // Ensure that the debug context has an Environment assigned in case
293  // a fatal error is raised. The fatal exception handler in node.cc
294  // is not equipped to deal with contexts that don't have one and
295  // can't easily be taught that due to a deficiency in the V8 API:
296  // there is no way for the embedder to tell if the data index is
297  // in use.
298  const int index = Environment::kContextEmbedderDataIndex;
299  debug_context->SetAlignedPointerInEmbedderData(index, env);
300  }
301 
302  Context::Scope context_scope(debug_context);
303  MaybeLocal<Script> script = Script::Compile(debug_context, script_source);
304  if (script.IsEmpty())
305  return; // Exception pending.
306  args.GetReturnValue().Set(script.ToLocalChecked()->Run());
307  }
308 
309 
310  static void MakeContext(const FunctionCallbackInfo<Value>& args) {
311  Environment* env = Environment::GetCurrent(args);
312 
313  if (!args[0]->IsObject()) {
314  return env->ThrowTypeError("sandbox argument must be an object.");
315  }
316  Local<Object> sandbox = args[0].As<Object>();
317 
318  // Don't allow contextifying a sandbox multiple times.
319  CHECK(
320  !sandbox->HasPrivate(
321  env->context(),
322  env->contextify_context_private_symbol()).FromJust());
323 
324  TryCatch try_catch(env->isolate());
325  ContextifyContext* context = new ContextifyContext(env, sandbox);
326 
327  if (try_catch.HasCaught()) {
328  try_catch.ReThrow();
329  return;
330  }
331 
332  if (context->context().IsEmpty())
333  return;
334 
335  sandbox->SetPrivate(
336  env->context(),
337  env->contextify_context_private_symbol(),
338  External::New(env->isolate(), context));
339  }
340 
341 
342  static void IsContext(const FunctionCallbackInfo<Value>& args) {
343  Environment* env = Environment::GetCurrent(args);
344 
345  if (!args[0]->IsObject()) {
346  env->ThrowTypeError("sandbox must be an object");
347  return;
348  }
349  Local<Object> sandbox = args[0].As<Object>();
350 
351  Maybe<bool> result =
352  sandbox->HasPrivate(env->context(),
353  env->contextify_context_private_symbol());
354  args.GetReturnValue().Set(result.FromJust());
355  }
356 
357 
358  static void WeakCallback(const WeakCallbackInfo<ContextifyContext>& data) {
359  ContextifyContext* context = data.GetParameter();
360  delete context;
361  }
362 
363 
364  static ContextifyContext* ContextFromContextifiedSandbox(
365  Environment* env,
366  const Local<Object>& sandbox) {
367  MaybeLocal<Value> maybe_value =
368  sandbox->GetPrivate(env->context(),
369  env->contextify_context_private_symbol());
370  Local<Value> context_external_v;
371  if (maybe_value.ToLocal(&context_external_v) &&
372  context_external_v->IsExternal()) {
373  Local<External> context_external = context_external_v.As<External>();
374  return static_cast<ContextifyContext*>(context_external->Value());
375  }
376  return nullptr;
377  }
378 
379 
380  static void GlobalPropertyGetterCallback(
381  Local<Name> property,
382  const PropertyCallbackInfo<Value>& args) {
383  ContextifyContext* ctx;
384  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As<Object>());
385 
386  // Still initializing
387  if (ctx->context_.IsEmpty())
388  return;
389 
390  Local<Context> context = ctx->context();
391  Local<Object> sandbox = ctx->sandbox();
392  MaybeLocal<Value> maybe_rv =
393  sandbox->GetRealNamedProperty(context, property);
394  if (maybe_rv.IsEmpty()) {
395  maybe_rv =
396  ctx->global_proxy()->GetRealNamedProperty(context, property);
397  }
398 
399  Local<Value> rv;
400  if (maybe_rv.ToLocal(&rv)) {
401  if (rv == sandbox)
402  rv = ctx->global_proxy();
403 
404  args.GetReturnValue().Set(rv);
405  }
406  }
407 
408 
409  static void GlobalPropertySetterCallback(
410  Local<Name> property,
411  Local<Value> value,
412  const PropertyCallbackInfo<Value>& args) {
413  ContextifyContext* ctx;
414  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As<Object>());
415 
416  // Still initializing
417  if (ctx->context_.IsEmpty())
418  return;
419 
420  auto attributes = PropertyAttribute::None;
421  bool is_declared =
422  ctx->global_proxy()->GetRealNamedPropertyAttributes(ctx->context(),
423  property)
424  .To(&attributes);
425  bool read_only =
426  static_cast<int>(attributes) &
427  static_cast<int>(PropertyAttribute::ReadOnly);
428 
429  if (is_declared && read_only)
430  return;
431 
432  // true for x = 5
433  // false for this.x = 5
434  // false for Object.defineProperty(this, 'foo', ...)
435  // false for vmResult.x = 5 where vmResult = vm.runInContext();
436  bool is_contextual_store = ctx->global_proxy() != args.This();
437 
438  // Indicator to not return before setting (undeclared) function declarations
439  // on the sandbox in strict mode, i.e. args.ShouldThrowOnError() = true.
440  // True for 'function f() {}', 'this.f = function() {}',
441  // 'var f = function()'.
442  // In effect only for 'function f() {}' because
443  // var f = function(), is_declared = true
444  // this.f = function() {}, is_contextual_store = false.
445  bool is_function = value->IsFunction();
446 
447  if (!is_declared && args.ShouldThrowOnError() && is_contextual_store &&
448  !is_function)
449  return;
450 
451  ctx->sandbox()->Set(property, value);
452  }
453 
454 
455  static void GlobalPropertyQueryCallback(
456  Local<Name> property,
457  const PropertyCallbackInfo<Integer>& args) {
458  ContextifyContext* ctx;
459  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As<Object>());
460 
461  // Still initializing
462  if (ctx->context_.IsEmpty())
463  return;
464 
465  Local<Context> context = ctx->context();
466  Maybe<PropertyAttribute> maybe_prop_attr =
467  ctx->sandbox()->GetRealNamedPropertyAttributes(context, property);
468 
469  if (maybe_prop_attr.IsNothing()) {
470  maybe_prop_attr =
471  ctx->global_proxy()->GetRealNamedPropertyAttributes(context,
472  property);
473  }
474 
475  if (maybe_prop_attr.IsJust()) {
476  PropertyAttribute prop_attr = maybe_prop_attr.FromJust();
477  args.GetReturnValue().Set(prop_attr);
478  }
479  }
480 
481 
482  static void GlobalPropertyDeleterCallback(
483  Local<Name> property,
484  const PropertyCallbackInfo<Boolean>& args) {
485  ContextifyContext* ctx;
486  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As<Object>());
487 
488  // Still initializing
489  if (ctx->context_.IsEmpty())
490  return;
491 
492  Maybe<bool> success = ctx->sandbox()->Delete(ctx->context(), property);
493 
494  if (success.FromMaybe(false))
495  return;
496 
497  // Delete failed on the sandbox, intercept and do not delete on
498  // the global object.
499  args.GetReturnValue().Set(false);
500  }
501 
502 
503  static void GlobalPropertyEnumeratorCallback(
504  const PropertyCallbackInfo<Array>& args) {
505  ContextifyContext* ctx;
506  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As<Object>());
507 
508  // Still initializing
509  if (ctx->context_.IsEmpty())
510  return;
511 
512  args.GetReturnValue().Set(ctx->sandbox()->GetPropertyNames());
513  }
514 };
515 
516 class ContextifyScript : public BaseObject {
517  private:
518  Persistent<UnboundScript> script_;
519 
520  public:
521  static void Init(Environment* env, Local<Object> target) {
522  HandleScope scope(env->isolate());
523  Local<String> class_name =
524  FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");
525 
526  Local<FunctionTemplate> script_tmpl = env->NewFunctionTemplate(New);
527  script_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
528  script_tmpl->SetClassName(class_name);
529  env->SetProtoMethod(script_tmpl, "runInContext", RunInContext);
530  env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);
531 
532  target->Set(class_name, script_tmpl->GetFunction());
533  env->set_script_context_constructor_template(script_tmpl);
534  }
535 
536 
537  // args: code, [options]
538  static void New(const FunctionCallbackInfo<Value>& args) {
539  Environment* env = Environment::GetCurrent(args);
540 
541  if (!args.IsConstructCall()) {
542  return env->ThrowError("Must call vm.Script as a constructor.");
543  }
544 
545  ContextifyScript* contextify_script =
546  new ContextifyScript(env, args.This());
547 
548  TryCatch try_catch(env->isolate());
549  Local<String> code = args[0]->ToString(env->isolate());
550 
551  Local<Value> options = args[1];
552  MaybeLocal<String> filename = GetFilenameArg(env, options);
553  MaybeLocal<Integer> lineOffset = GetLineOffsetArg(env, options);
554  MaybeLocal<Integer> columnOffset = GetColumnOffsetArg(env, options);
555  Maybe<bool> maybe_display_errors = GetDisplayErrorsArg(env, options);
556  MaybeLocal<Uint8Array> cached_data_buf = GetCachedData(env, options);
557  Maybe<bool> maybe_produce_cached_data = GetProduceCachedData(env, options);
558  if (try_catch.HasCaught()) {
559  try_catch.ReThrow();
560  return;
561  }
562 
563  bool display_errors = maybe_display_errors.ToChecked();
564  bool produce_cached_data = maybe_produce_cached_data.ToChecked();
565 
566  ScriptCompiler::CachedData* cached_data = nullptr;
567  Local<Uint8Array> ui8;
568  if (cached_data_buf.ToLocal(&ui8)) {
569  ArrayBuffer::Contents contents = ui8->Buffer()->GetContents();
570  cached_data = new ScriptCompiler::CachedData(
571  static_cast<uint8_t*>(contents.Data()) + ui8->ByteOffset(),
572  ui8->ByteLength());
573  }
574 
575  ScriptOrigin origin(filename.ToLocalChecked(), lineOffset.ToLocalChecked(),
576  columnOffset.ToLocalChecked());
577  ScriptCompiler::Source source(code, origin, cached_data);
578  ScriptCompiler::CompileOptions compile_options =
579  ScriptCompiler::kNoCompileOptions;
580 
581  if (source.GetCachedData() != nullptr)
582  compile_options = ScriptCompiler::kConsumeCodeCache;
583  else if (produce_cached_data)
584  compile_options = ScriptCompiler::kProduceCodeCache;
585 
586  MaybeLocal<UnboundScript> v8_script = ScriptCompiler::CompileUnboundScript(
587  env->isolate(),
588  &source,
589  compile_options);
590 
591  if (v8_script.IsEmpty()) {
592  if (display_errors) {
593  DecorateErrorStack(env, try_catch);
594  }
595  try_catch.ReThrow();
596  return;
597  }
598  contextify_script->script_.Reset(env->isolate(),
599  v8_script.ToLocalChecked());
600 
601  if (compile_options == ScriptCompiler::kConsumeCodeCache) {
602  args.This()->Set(
603  env->cached_data_rejected_string(),
604  Boolean::New(env->isolate(), source.GetCachedData()->rejected));
605  } else if (compile_options == ScriptCompiler::kProduceCodeCache) {
606  const ScriptCompiler::CachedData* cached_data = source.GetCachedData();
607  bool cached_data_produced = cached_data != nullptr;
608  if (cached_data_produced) {
609  MaybeLocal<Object> buf = Buffer::Copy(
610  env,
611  reinterpret_cast<const char*>(cached_data->data),
612  cached_data->length);
613  args.This()->Set(env->cached_data_string(), buf.ToLocalChecked());
614  }
615  args.This()->Set(
616  env->cached_data_produced_string(),
617  Boolean::New(env->isolate(), cached_data_produced));
618  }
619  }
620 
621 
622  static bool InstanceOf(Environment* env, const Local<Value>& value) {
623  return !value.IsEmpty() &&
624  env->script_context_constructor_template()->HasInstance(value);
625  }
626 
627 
628  // args: [options]
629  static void RunInThisContext(const FunctionCallbackInfo<Value>& args) {
630  Environment* env = Environment::GetCurrent(args);
631 
632  // Assemble arguments
633  TryCatch try_catch(args.GetIsolate());
634  Maybe<int64_t> maybe_timeout = GetTimeoutArg(env, args[0]);
635  Maybe<bool> maybe_display_errors = GetDisplayErrorsArg(env, args[0]);
636  Maybe<bool> maybe_break_on_sigint = GetBreakOnSigintArg(env, args[0]);
637  if (try_catch.HasCaught()) {
638  try_catch.ReThrow();
639  return;
640  }
641 
642  int64_t timeout = maybe_timeout.ToChecked();
643  bool display_errors = maybe_display_errors.ToChecked();
644  bool break_on_sigint = maybe_break_on_sigint.ToChecked();
645 
646  // Do the eval within this context
647  EvalMachine(env, timeout, display_errors, break_on_sigint, args,
648  &try_catch);
649  }
650 
651  // args: sandbox, [options]
652  static void RunInContext(const FunctionCallbackInfo<Value>& args) {
653  Environment* env = Environment::GetCurrent(args);
654 
655  int64_t timeout;
656  bool display_errors;
657  bool break_on_sigint;
658 
659  // Assemble arguments
660  if (!args[0]->IsObject()) {
661  return env->ThrowTypeError(
662  "contextifiedSandbox argument must be an object.");
663  }
664 
665  Local<Object> sandbox = args[0].As<Object>();
666  {
667  TryCatch try_catch(env->isolate());
668  Maybe<int64_t> maybe_timeout = GetTimeoutArg(env, args[1]);
669  Maybe<bool> maybe_display_errors = GetDisplayErrorsArg(env, args[1]);
670  Maybe<bool> maybe_break_on_sigint = GetBreakOnSigintArg(env, args[1]);
671  if (try_catch.HasCaught()) {
672  try_catch.ReThrow();
673  return;
674  }
675 
676  timeout = maybe_timeout.ToChecked();
677  display_errors = maybe_display_errors.ToChecked();
678  break_on_sigint = maybe_break_on_sigint.ToChecked();
679  }
680 
681  // Get the context from the sandbox
682  ContextifyContext* contextify_context =
683  ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
684  if (contextify_context == nullptr) {
685  return env->ThrowTypeError(
686  "sandbox argument must have been converted to a context.");
687  }
688 
689  if (contextify_context->context().IsEmpty())
690  return;
691 
692  {
693  TryCatch try_catch(env->isolate());
694  // Do the eval within the context
695  Context::Scope context_scope(contextify_context->context());
696  if (EvalMachine(contextify_context->env(),
697  timeout,
698  display_errors,
699  break_on_sigint,
700  args,
701  &try_catch)) {
702  contextify_context->CopyProperties();
703  }
704 
705  if (try_catch.HasCaught()) {
706  try_catch.ReThrow();
707  return;
708  }
709  }
710  }
711 
712  static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) {
713  Local<Value> exception = try_catch.Exception();
714 
715  if (!exception->IsObject())
716  return;
717 
718  Local<Object> err_obj = exception.As<Object>();
719 
720  if (IsExceptionDecorated(env, err_obj))
721  return;
722 
723  AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR);
724  Local<Value> stack = err_obj->Get(env->stack_string());
725  MaybeLocal<Value> maybe_value =
726  err_obj->GetPrivate(
727  env->context(),
728  env->arrow_message_private_symbol());
729 
730  Local<Value> arrow;
731  if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) {
732  return;
733  }
734 
735  if (stack.IsEmpty() || !stack->IsString()) {
736  return;
737  }
738 
739  Local<String> decorated_stack = String::Concat(
740  String::Concat(arrow.As<String>(),
741  FIXED_ONE_BYTE_STRING(env->isolate(), "\n")),
742  stack.As<String>());
743  err_obj->Set(env->stack_string(), decorated_stack);
744  err_obj->SetPrivate(
745  env->context(),
746  env->decorated_private_symbol(),
747  True(env->isolate()));
748  }
749 
750  static Maybe<bool> GetBreakOnSigintArg(Environment* env,
751  Local<Value> options) {
752  if (options->IsUndefined() || options->IsString()) {
753  return Just(false);
754  }
755  if (!options->IsObject()) {
756  env->ThrowTypeError("options must be an object");
757  return Nothing<bool>();
758  }
759 
760  Local<String> key = FIXED_ONE_BYTE_STRING(env->isolate(), "breakOnSigint");
761  MaybeLocal<Value> maybe_value =
762  options.As<Object>()->Get(env->context(), key);
763  if (maybe_value.IsEmpty())
764  return Nothing<bool>();
765 
766  Local<Value> value = maybe_value.ToLocalChecked();
767  return Just(value->IsTrue());
768  }
769 
770  static Maybe<int64_t> GetTimeoutArg(Environment* env, Local<Value> options) {
771  if (options->IsUndefined() || options->IsString()) {
772  return Just<int64_t>(-1);
773  }
774  if (!options->IsObject()) {
775  env->ThrowTypeError("options must be an object");
776  return Nothing<int64_t>();
777  }
778 
779  MaybeLocal<Value> maybe_value =
780  options.As<Object>()->Get(env->context(), env->timeout_string());
781  if (maybe_value.IsEmpty())
782  return Nothing<int64_t>();
783 
784  Local<Value> value = maybe_value.ToLocalChecked();
785  if (value->IsUndefined()) {
786  return Just<int64_t>(-1);
787  }
788 
789  Maybe<int64_t> timeout = value->IntegerValue(env->context());
790 
791  if (timeout.IsJust() && timeout.ToChecked() <= 0) {
792  env->ThrowRangeError("timeout must be a positive number");
793  return Nothing<int64_t>();
794  }
795 
796  return timeout;
797  }
798 
799 
800  static Maybe<bool> GetDisplayErrorsArg(Environment* env,
801  Local<Value> options) {
802  if (options->IsUndefined() || options->IsString()) {
803  return Just(true);
804  }
805  if (!options->IsObject()) {
806  env->ThrowTypeError("options must be an object");
807  return Nothing<bool>();
808  }
809 
810  Local<String> key = FIXED_ONE_BYTE_STRING(env->isolate(), "displayErrors");
811  MaybeLocal<Value> maybe_value =
812  options.As<Object>()->Get(env->context(), key);
813  if (maybe_value.IsEmpty())
814  return Nothing<bool>();
815 
816  Local<Value> value = maybe_value.ToLocalChecked();
817  if (value->IsUndefined())
818  return Just(true);
819 
820  return value->BooleanValue(env->context());
821  }
822 
823 
824  static MaybeLocal<String> GetFilenameArg(Environment* env,
825  Local<Value> options) {
826  Local<String> defaultFilename =
827  FIXED_ONE_BYTE_STRING(env->isolate(), "evalmachine.<anonymous>");
828 
829  if (options->IsUndefined()) {
830  return defaultFilename;
831  }
832  if (options->IsString()) {
833  return options.As<String>();
834  }
835  if (!options->IsObject()) {
836  env->ThrowTypeError("options must be an object");
837  return Local<String>();
838  }
839 
840  Local<String> key = FIXED_ONE_BYTE_STRING(env->isolate(), "filename");
841  MaybeLocal<Value> maybe_value =
842  options.As<Object>()->Get(env->context(), key);
843  if (maybe_value.IsEmpty())
844  return MaybeLocal<String>();
845 
846  Local<Value> value = maybe_value.ToLocalChecked();
847  if (value->IsUndefined())
848  return defaultFilename;
849  return value->ToString(env->context());
850  }
851 
852 
853  static MaybeLocal<Uint8Array> GetCachedData(Environment* env,
854  Local<Value> options) {
855  if (!options->IsObject()) {
856  return MaybeLocal<Uint8Array>();
857  }
858 
859  MaybeLocal<Value> maybe_value =
860  options.As<Object>()->Get(env->context(), env->cached_data_string());
861  if (maybe_value.IsEmpty())
862  return MaybeLocal<Uint8Array>();
863 
864  Local<Value> value = maybe_value.ToLocalChecked();
865  if (value->IsUndefined()) {
866  return MaybeLocal<Uint8Array>();
867  }
868 
869  if (!value->IsUint8Array()) {
870  env->ThrowTypeError("options.cachedData must be a Buffer instance");
871  return MaybeLocal<Uint8Array>();
872  }
873 
874  return value.As<Uint8Array>();
875  }
876 
877 
878  static Maybe<bool> GetProduceCachedData(Environment* env,
879  Local<Value> options) {
880  if (!options->IsObject()) {
881  return Just(false);
882  }
883 
884  MaybeLocal<Value> maybe_value =
885  options.As<Object>()->Get(env->context(),
886  env->produce_cached_data_string());
887  if (maybe_value.IsEmpty())
888  return Nothing<bool>();
889 
890  Local<Value> value = maybe_value.ToLocalChecked();
891  return Just(value->IsTrue());
892  }
893 
894 
895  static MaybeLocal<Integer> GetLineOffsetArg(Environment* env,
896  Local<Value> options) {
897  Local<Integer> defaultLineOffset = Integer::New(env->isolate(), 0);
898 
899  if (!options->IsObject()) {
900  return defaultLineOffset;
901  }
902 
903  Local<String> key = FIXED_ONE_BYTE_STRING(env->isolate(), "lineOffset");
904  MaybeLocal<Value> maybe_value =
905  options.As<Object>()->Get(env->context(), key);
906  if (maybe_value.IsEmpty())
907  return MaybeLocal<Integer>();
908 
909  Local<Value> value = maybe_value.ToLocalChecked();
910  if (value->IsUndefined())
911  return defaultLineOffset;
912 
913  return value->ToInteger(env->context());
914  }
915 
916 
917  static MaybeLocal<Integer> GetColumnOffsetArg(Environment* env,
918  Local<Value> options) {
919  Local<Integer> defaultColumnOffset = Integer::New(env->isolate(), 0);
920 
921  if (!options->IsObject()) {
922  return defaultColumnOffset;
923  }
924 
925  Local<String> key = FIXED_ONE_BYTE_STRING(env->isolate(), "columnOffset");
926  MaybeLocal<Value> maybe_value =
927  options.As<Object>()->Get(env->context(), key);
928  if (maybe_value.IsEmpty())
929  return MaybeLocal<Integer>();
930 
931  Local<Value> value = maybe_value.ToLocalChecked();
932  if (value->IsUndefined())
933  return defaultColumnOffset;
934 
935  return value->ToInteger(env->context());
936  }
937 
938 
939  static bool EvalMachine(Environment* env,
940  const int64_t timeout,
941  const bool display_errors,
942  const bool break_on_sigint,
943  const FunctionCallbackInfo<Value>& args,
944  TryCatch* try_catch) {
945  if (!ContextifyScript::InstanceOf(env, args.Holder())) {
946  env->ThrowTypeError(
947  "Script methods can only be called on script instances.");
948  return false;
949  }
950 
951  ContextifyScript* wrapped_script;
952  ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder(), false);
953  Local<UnboundScript> unbound_script =
954  PersistentToLocal(env->isolate(), wrapped_script->script_);
955  Local<Script> script = unbound_script->BindToCurrentContext();
956 
957  Local<Value> result;
958  bool timed_out = false;
959  bool received_signal = false;
960  if (break_on_sigint && timeout != -1) {
961  Watchdog wd(env->isolate(), timeout, &timed_out);
962  SigintWatchdog swd(env->isolate(), &received_signal);
963  result = script->Run();
964  } else if (break_on_sigint) {
965  SigintWatchdog swd(env->isolate(), &received_signal);
966  result = script->Run();
967  } else if (timeout != -1) {
968  Watchdog wd(env->isolate(), timeout, &timed_out);
969  result = script->Run();
970  } else {
971  result = script->Run();
972  }
973 
974  if (timed_out || received_signal) {
975  // It is possible that execution was terminated by another timeout in
976  // which this timeout is nested, so check whether one of the watchdogs
977  // from this invocation is responsible for termination.
978  if (timed_out) {
979  env->ThrowError("Script execution timed out.");
980  } else if (received_signal) {
981  env->ThrowError("Script execution interrupted.");
982  }
983  env->isolate()->CancelTerminateExecution();
984  }
985 
986  if (try_catch->HasCaught()) {
987  if (!timed_out && !received_signal && display_errors) {
988  // We should decorate non-termination exceptions
989  DecorateErrorStack(env, *try_catch);
990  }
991 
992  // If there was an exception thrown during script execution, re-throw it.
993  // If one of the above checks threw, re-throw the exception instead of
994  // letting try_catch catch it.
995  // If execution has been terminated, but not by one of the watchdogs from
996  // this invocation, this will re-throw a `null` value.
997  try_catch->ReThrow();
998 
999  return false;
1000  }
1001 
1002  args.GetReturnValue().Set(result);
1003  return true;
1004  }
1005 
1006 
1007  ContextifyScript(Environment* env, Local<Object> object)
1008  : BaseObject(env, object) {
1009  MakeWeak<ContextifyScript>(this);
1010  }
1011 
1012 
1013  ~ContextifyScript() override {
1014  script_.Reset();
1015  }
1016 };
1017 
1018 
1019 void InitContextify(Local<Object> target,
1020  Local<Value> unused,
1021  Local<Context> context) {
1022  Environment* env = Environment::GetCurrent(context);
1023  ContextifyContext::Init(env, target);
1024  ContextifyScript::Init(env, target);
1025 }
1026 
1027 } // anonymous namespace
1028 } // namespace node
1029 
1030 NODE_MODULE_CONTEXT_AWARE_BUILTIN(contextify, node::InitContextify)
bool IsExceptionDecorated(Environment *env, Local< Value > er)
Definition: node.cc:1607
void AppendExceptionLine(Environment *env, Local< Value > er, Local< Message > message, enum ErrorHandlingMode mode)
Definition: node.cc:1618
unsigned char * buf
Definition: cares_wrap.cc:483
NODE_MODULE_CONTEXT_AWARE_BUILTIN(inspector, node::inspector::Agent::InitInspector)
std::string source
Definition: module_wrap.cc:306
Environment *const env_
Persistent< Context > context_
this script
Definition: v8ustack.d:378
union node::cares_wrap::@8::CaresAsyncData::@0 data
MaybeLocal< Object > New(Isolate *isolate, Local< String > string, enum encoding enc)
Definition: node_buffer.cc:241
this ctx
Definition: v8ustack.d:369
void Init(int *argc, const char **argv, int *exec_argc, const char ***exec_argv)
Definition: node.cc:4351
MaybeLocal< Object > Copy(Isolate *isolate, const char *data, size_t length)
Definition: node_buffer.cc:320