[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/starkwang/node/buffer-transcode-error/src/node_contextify.cc [Back]  [Original]

// 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_internals.h"
#include "node_watchdog.h"
#include "base-object.h"
#include "base-object-inl.h"
#include "v8-debug.h"

namespace node {

using v8::Array;
using v8::ArrayBuffer;
using v8::Boolean;
using v8::Context;
using v8::Debug;
using v8::EscapableHandleScope;
using v8::External;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Integer;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Name;
using v8::NamedPropertyHandlerConfiguration;
using v8::Nothing;
using v8::Object;
using v8::ObjectTemplate;
using v8::Persistent;
using v8::PropertyAttribute;
using v8::PropertyCallbackInfo;
using v8::PropertyDescriptor;
using v8::Script;
using v8::ScriptCompiler;
using v8::ScriptOrigin;
using v8::String;
using v8::Symbol;
using v8::TryCatch;
using v8::Uint8Array;
using v8::UnboundScript;
using v8::Value;
using v8::WeakCallbackInfo;

namespace {

class ContextifyContext {
 protected:
  // V8 reserves the first field in context objects for the debugger. We use the
  // second field to hold a reference to the sandbox object.
  enum { kSandboxObjectIndex = 1 };

  Environment* const env_;
  Persistent context_;

 public:
  ContextifyContext(Environment* env, Local sandbox_obj) : env_(env) {
    Local v8_context = CreateV8Context(env, sandbox_obj);
    context_.Reset(env->isolate(), v8_context);

    // Allocation failure or maximum call stack size reached
    if (context_.IsEmpty())
      return;
    context_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
    context_.MarkIndependent();
  }


  ~ContextifyContext() {
    context_.Reset();
  }


  inline Environment* env() const {
    return env_;
  }


  inline Local context() const {
    return PersistentToLocal(env()->isolate(), context_);
  }


  inline Local global_proxy() const {
    return context()->Global();
  }


  inline Local sandbox() const {
    return Local::Cast(context()->GetEmbedderData(kSandboxObjectIndex));
  }

  // XXX(isaacs): This function only exists because of a shortcoming of
  // the V8 SetNamedPropertyHandler function.
  //
  // It does not provide a way to intercept Object.defineProperty(..)
  // calls.  As a result, these properties are not copied onto the
  // contextified sandbox when a new global property is added via either
  // a function declaration or a Object.defineProperty(global, ...) call.
  //
  // Note that any function declarations or Object.defineProperty()
  // globals that are created asynchronously (in a setTimeout, callback,
  // etc.) will happen AFTER the call to copy properties, and thus not be
  // caught.
  //
  // The way to properly fix this is to add some sort of a
  // Object::SetNamedDefinePropertyHandler() function that takes a callback,
  // which receives the property name and property descriptor as arguments.
  //
  // Luckily, such situations are rare, and asynchronously-added globals
  // weren't supported by Node's VM module until 0.12 anyway.  But, this
  // should be fixed properly in V8, and this copy function should be
  // removed once there is a better way.
  void CopyProperties() {
    HandleScope scope(env()->isolate());

    Local context = PersistentToLocal(env()->isolate(), context_);
    Local global =
        context->Global()->GetPrototype()->ToObject(env()->isolate());
    Local sandbox_obj = sandbox();

    Local clone_property_method;

    Local names = global->GetOwnPropertyNames();
    int length = names->Length();
    for (int i = 0; i < length; i++) {
      Local key = names->Get(i)->ToString(env()->isolate());
      Maybe has = sandbox_obj->HasOwnProperty(context, key);

      // Check for pending exceptions
      if (has.IsNothing())
        return;

      if (!has.FromJust()) {
        Local desc_vm_context =
            global->GetOwnPropertyDescriptor(context, key)
            .ToLocalChecked().As();

        bool is_accessor =
            desc_vm_context->Has(context, env()->get_string()).FromJust() ||
            desc_vm_context->Has(context, env()->set_string()).FromJust();

        auto define_property_on_sandbox = [&] (PropertyDescriptor* desc) {
            desc->set_configurable(desc_vm_context
                ->Get(context, env()->configurable_string()).ToLocalChecked()
                ->BooleanValue(context).FromJust());
            desc->set_enumerable(desc_vm_context
                ->Get(context, env()->enumerable_string()).ToLocalChecked()
                ->BooleanValue(context).FromJust());
            CHECK(sandbox_obj->DefineProperty(context, key, *desc).FromJust());
        };

        if (is_accessor) {
          Local get =
              desc_vm_context->Get(context, env()->get_string())
              .ToLocalChecked().As();
          Local set =
              desc_vm_context->Get(context, env()->set_string())
              .ToLocalChecked().As();

          PropertyDescriptor desc(get, set);
          define_property_on_sandbox(&desc);
        } else {
          Local value =
              desc_vm_context->Get(context, env()->value_string())
              .ToLocalChecked();

          bool writable =
              desc_vm_context->Get(context, env()->writable_string())
              .ToLocalChecked()->BooleanValue(context).FromJust();

          PropertyDescriptor desc(value, writable);
          define_property_on_sandbox(&desc);
        }
    }
  }
}


  // This is an object that just keeps an internal pointer to this
  // ContextifyContext.  It's passed to the NamedPropertyHandler.  If we
  // pass the main JavaScript context object we're embedded in, then the
  // NamedPropertyHandler will store a reference to it forever and keep it
  // from getting gc'd.
  Local CreateDataWrapper(Environment* env) {
    EscapableHandleScope scope(env->isolate());
    Local wrapper =
        env->script_data_constructor_function()
            ->NewInstance(env->context()).FromMaybe(Local());
    if (wrapper.IsEmpty())
      return scope.Escape(Local::New(env->isolate(), Local()));

    Wrap(wrapper, this);
    return scope.Escape(wrapper);
  }


  Local CreateV8Context(Environment* env, Local sandbox_obj) {
    EscapableHandleScope scope(env->isolate());
    Local function_template =
        FunctionTemplate::New(env->isolate());
    function_template->SetHiddenPrototype(true);

    function_template->SetClassName(sandbox_obj->GetConstructorName());

    Local object_template =
        function_template->InstanceTemplate();

    NamedPropertyHandlerConfiguration config(GlobalPropertyGetterCallback,
                                             GlobalPropertySetterCallback,
                                             GlobalPropertyQueryCallback,
                                             GlobalPropertyDeleterCallback,
                                             GlobalPropertyEnumeratorCallback,
                                             CreateDataWrapper(env));
    object_template->SetHandler(config);

    Local ctx = NewContext(env->isolate(), object_template);

    if (ctx.IsEmpty()) {
      env->ThrowError("Could not instantiate context");
      return Local();
    }

    ctx->SetSecurityToken(env->context()->GetSecurityToken());

    // We need to tie the lifetime of the sandbox object with the lifetime of
    // newly created context. We do this by making them hold references to each
    // other. The context can directly hold a reference to the sandbox as an
    // embedder data field. However, we cannot hold a reference to a v8::Context
    // directly in an Object, we instead hold onto the new context's global
    // object instead (which then has a reference to the context).
    ctx->SetEmbedderData(kSandboxObjectIndex, sandbox_obj);
    sandbox_obj->SetPrivate(env->context(),
                            env->contextify_global_private_symbol(),
                            ctx->Global());

    env->AssignToContext(ctx);

    return scope.Escape(ctx);
  }


  static void Init(Environment* env, Local target) {
    Local function_template =
        FunctionTemplate::New(env->isolate());
    function_template->InstanceTemplate()->SetInternalFieldCount(1);
    env->set_script_data_constructor_function(function_template->GetFunction());

    env->SetMethod(target, "runInDebugContext", RunInDebugContext);
    env->SetMethod(target, "makeContext", MakeContext);
    env->SetMethod(target, "isContext", IsContext);
  }


  static void RunInDebugContext(const FunctionCallbackInfo& args) {
    Local script_source(args[0]->ToString(args.GetIsolate()));
    if (script_source.IsEmpty())
      return;  // Exception pending.
    Local debug_context = Debug::GetDebugContext(args.GetIsolate());
    Environment* env = Environment::GetCurrent(args);
    if (debug_context.IsEmpty()) {
      // Force-load the debug context.
      auto dummy_event_listener = [] (const Debug::EventDetails&) {};
      Debug::SetDebugEventListener(args.GetIsolate(), dummy_event_listener);
      debug_context = Debug::GetDebugContext(args.GetIsolate());
      CHECK(!debug_context.IsEmpty());
      // Ensure that the debug context has an Environment assigned in case
      // a fatal error is raised.  The fatal exception handler in node.cc
      // is not equipped to deal with contexts that don't have one and
      // can't easily be taught that due to a deficiency in the V8 API:
      // there is no way for the embedder to tell if the data index is
      // in use.
      const int index = Environment::kContextEmbedderDataIndex;
      debug_context->SetAlignedPointerInEmbedderData(index, env);
    }

    Context::Scope context_scope(debug_context);
    MaybeLocal script = Script::Compile(debug_context, script_source);
    if (script.IsEmpty())
      return;  // Exception pending.
    args.GetReturnValue().Set(script.ToLocalChecked()->Run());
  }


  static void MakeContext(const FunctionCallbackInfo& args) {
    Environment* env = Environment::GetCurrent(args);

    if (!args[0]->IsObject()) {
      return env->ThrowTypeError("sandbox argument must be an object.");
    }
    Local sandbox = args[0].As();

    // Don't allow contextifying a sandbox multiple times.
    CHECK(
        !sandbox->HasPrivate(
            env->context(),
            env->contextify_context_private_symbol()).FromJust());

    TryCatch try_catch(env->isolate());
    ContextifyContext* context = new ContextifyContext(env, sandbox);

    if (try_catch.HasCaught()) {
      try_catch.ReThrow();
      return;
    }

    if (context->context().IsEmpty())
      return;

    sandbox->SetPrivate(
        env->context(),
        env->contextify_context_private_symbol(),
        External::New(env->isolate(), context));
  }


  static void IsContext(const FunctionCallbackInfo& args) {
    Environment* env = Environment::GetCurrent(args);

    if (!args[0]->IsObject()) {
      env->ThrowTypeError("sandbox must be an object");
      return;
    }
    Local sandbox = args[0].As();

    Maybe result =
        sandbox->HasPrivate(env->context(),
                            env->contextify_context_private_symbol());
    args.GetReturnValue().Set(result.FromJust());
  }


  static void WeakCallback(const WeakCallbackInfo& data) {
    ContextifyContext* context = data.GetParameter();
    delete context;
  }


  static ContextifyContext* ContextFromContextifiedSandbox(
      Environment* env,
      const Local& sandbox) {
    MaybeLocal maybe_value =
        sandbox->GetPrivate(env->context(),
                            env->contextify_context_private_symbol());
    Local context_external_v;
    if (maybe_value.ToLocal(&context_external_v) &&
        context_external_v->IsExternal()) {
      Local context_external = context_external_v.As();
      return static_cast(context_external->Value());
    }
    return nullptr;
  }


  static void GlobalPropertyGetterCallback(
      Local property,
      const PropertyCallbackInfo& args) {
    ContextifyContext* ctx;
    ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As());

    // Still initializing
    if (ctx->context_.IsEmpty())
      return;

    Local context = ctx->context();
    Local sandbox = ctx->sandbox();
    MaybeLocal maybe_rv =
        sandbox->GetRealNamedProperty(context, property);
    if (maybe_rv.IsEmpty()) {
      maybe_rv =
          ctx->global_proxy()->GetRealNamedProperty(context, property);
    }

    Local rv;
    if (maybe_rv.ToLocal(&rv)) {
      if (rv == sandbox)
        rv = ctx->global_proxy();

      args.GetReturnValue().Set(rv);
    }
  }


  static void GlobalPropertySetterCallback(
      Local property,
      Local value,
      const PropertyCallbackInfo& args) {
    ContextifyContext* ctx;
    ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As());

    // Still initializing
    if (ctx->context_.IsEmpty())
      return;

    auto attributes = PropertyAttribute::None;
    bool is_declared =
        ctx->global_proxy()->GetRealNamedPropertyAttributes(ctx->context(),
                                                            property)
        .To(&attributes);
    bool read_only =
        static_cast(attributes) &
        static_cast(PropertyAttribute::ReadOnly);

    if (is_declared && read_only)
      return;

    // true for x = 5
    // false for this.x = 5
    // false for Object.defineProperty(this, 'foo', ...)
    // false for vmResult.x = 5 where vmResult = vm.runInContext();
    bool is_contextual_store = ctx->global_proxy() != args.This();

    // Indicator to not return before setting (undeclared) function declarations
    // on the sandbox in strict mode, i.e. args.ShouldThrowOnError() = true.
    // True for 'function f() {}', 'this.f = function() {}',
    // 'var f = function()'.
    // In effect only for 'function f() {}' because
    // var f = function(), is_declared = true
    // this.f = function() {}, is_contextual_store = false.
    bool is_function = value->IsFunction();

    if (!is_declared && args.ShouldThrowOnError() && is_contextual_store &&
    !is_function)
      return;

    ctx->sandbox()->Set(property, value);
  }


  static void GlobalPropertyQueryCallback(
      Local property,
      const PropertyCallbackInfo& args) {
    ContextifyContext* ctx;
    ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As());

    // Still initializing
    if (ctx->context_.IsEmpty())
      return;

    Local context = ctx->context();
    Maybe maybe_prop_attr =
        ctx->sandbox()->GetRealNamedPropertyAttributes(context, property);

    if (maybe_prop_attr.IsNothing()) {
      maybe_prop_attr =
          ctx->global_proxy()->GetRealNamedPropertyAttributes(context,
                                                              property);
    }

    if (maybe_prop_attr.IsJust()) {
      PropertyAttribute prop_attr = maybe_prop_attr.FromJust();
      args.GetReturnValue().Set(prop_attr);
    }
  }


  static void GlobalPropertyDeleterCallback(
      Local property,
      const PropertyCallbackInfo& args) {
    ContextifyContext* ctx;
    ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As());

    // Still initializing
    if (ctx->context_.IsEmpty())
      return;

    Maybe success = ctx->sandbox()->Delete(ctx->context(), property);

    if (success.FromMaybe(false))
      return;

    // Delete failed on the sandbox, intercept and do not delete on
    // the global object.
    args.GetReturnValue().Set(false);
  }


  static void GlobalPropertyEnumeratorCallback(
      const PropertyCallbackInfo& args) {
    ContextifyContext* ctx;
    ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Data().As());

    // Still initializing
    if (ctx->context_.IsEmpty())
      return;

    args.GetReturnValue().Set(ctx->sandbox()->GetPropertyNames());
  }
};

class ContextifyScript : public BaseObject {
 private:
  Persistent script_;

 public:
  static void Init(Environment* env, Local target) {
    HandleScope scope(env->isolate());
    Local class_name =
        FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");

    Local script_tmpl = env->NewFunctionTemplate(New);
    script_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
    script_tmpl->SetClassName(class_name);
    env->SetProtoMethod(script_tmpl, "runInContext", RunInContext);
    env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);

    target->Set(class_name, script_tmpl->GetFunction());
    env->set_script_context_constructor_template(script_tmpl);

    Local parsing_context_symbol =
        Symbol::New(env->isolate(),
                    FIXED_ONE_BYTE_STRING(env->isolate(),
                                          "script parsing context"));
    env->set_vm_parsing_context_symbol(parsing_context_symbol);
    target->Set(env->context(),
                FIXED_ONE_BYTE_STRING(env->isolate(), "kParsingContext"),
                parsing_context_symbol)
        .FromJust();
  }


  // args: code, [options]
  static void New(const FunctionCallbackInfo& args) {
    Environment* env = Environment::GetCurrent(args);

    if (!args.IsConstructCall()) {
      return env->ThrowError("Must call vm.Script as a constructor.");
    }

    ContextifyScript* contextify_script =
        new ContextifyScript(env, args.This());

    TryCatch try_catch(env->isolate());
    Local code = args[0]->ToString(env->isolate());

    Local options = args[1];
    MaybeLocal filename = GetFilenameArg(env, options);
    MaybeLocal lineOffset = GetLineOffsetArg(env, options);
    MaybeLocal columnOffset = GetColumnOffsetArg(env, options);
    Maybe maybe_display_errors = GetDisplayErrorsArg(env, options);
    MaybeLocal cached_data_buf = GetCachedData(env, options);
    Maybe maybe_produce_cached_data = GetProduceCachedData(env, options);
    MaybeLocal maybe_context = GetContext(env, options);
    if (try_catch.HasCaught()) {
      try_catch.ReThrow();
      return;
    }

    bool display_errors = maybe_display_errors.ToChecked();
    bool produce_cached_data = maybe_produce_cached_data.ToChecked();

    ScriptCompiler::CachedData* cached_data = nullptr;
    Local ui8;
    if (cached_data_buf.ToLocal(&ui8)) {
      ArrayBuffer::Contents contents = ui8->Buffer()->GetContents();
      cached_data = new ScriptCompiler::CachedData(
          static_cast(contents.Data()) + ui8->ByteOffset(),
          ui8->ByteLength());
    }

    ScriptOrigin origin(filename.ToLocalChecked(), lineOffset.ToLocalChecked(),
                        columnOffset.ToLocalChecked());
    ScriptCompiler::Source source(code, origin, cached_data);
    ScriptCompiler::CompileOptions compile_options =
        ScriptCompiler::kNoCompileOptions;

    if (source.GetCachedData() != nullptr)
      compile_options = ScriptCompiler::kConsumeCodeCache;
    else if (produce_cached_data)
      compile_options = ScriptCompiler::kProduceCodeCache;

    Context::Scope scope(maybe_context.FromMaybe(env->context()));

    MaybeLocal v8_script = ScriptCompiler::CompileUnboundScript(
        env->isolate(),
        &source,
        compile_options);

    if (v8_script.IsEmpty()) {
      if (display_errors) {
        DecorateErrorStack(env, try_catch);
      }
      try_catch.ReThrow();
      return;
    }
    contextify_script->script_.Reset(env->isolate(),
                                     v8_script.ToLocalChecked());

    if (compile_options == ScriptCompiler::kConsumeCodeCache) {
      args.This()->Set(
          env->cached_data_rejected_string(),
          Boolean::New(env->isolate(), source.GetCachedData()->rejected));
    } else if (compile_options == ScriptCompiler::kProduceCodeCache) {
      const ScriptCompiler::CachedData* cached_data = source.GetCachedData();
      bool cached_data_produced = cached_data != nullptr;
      if (cached_data_produced) {
        MaybeLocal buf = Buffer::Copy(
            env,
            reinterpret_cast(cached_data->data),
            cached_data->length);
        args.This()->Set(env->cached_data_string(), buf.ToLocalChecked());
      }
      args.This()->Set(
          env->cached_data_produced_string(),
          Boolean::New(env->isolate(), cached_data_produced));
    }
  }


  static bool InstanceOf(Environment* env, const Local& value) {
    return !value.IsEmpty() &&
           env->script_context_constructor_template()->HasInstance(value);
  }


  // args: [options]
  static void RunInThisContext(const FunctionCallbackInfo& args) {
    Environment* env = Environment::GetCurrent(args);

    // Assemble arguments
    TryCatch try_catch(args.GetIsolate());
    Maybe maybe_timeout = GetTimeoutArg(env, args[0]);
    Maybe maybe_display_errors = GetDisplayErrorsArg(env, args[0]);
    Maybe maybe_break_on_sigint = GetBreakOnSigintArg(env, args[0]);
    if (try_catch.HasCaught()) {
      try_catch.ReThrow();
      return;
    }

    int64_t timeout = maybe_timeout.ToChecked();
    bool display_errors = maybe_display_errors.ToChecked();
    bool break_on_sigint = maybe_break_on_sigint.ToChecked();

    // Do the eval within this context
    EvalMachine(env, timeout, display_errors, break_on_sigint, args,
                &try_catch);
  }

  // args: sandbox, [options]
  static void RunInContext(const FunctionCallbackInfo& args) {
    Environment* env = Environment::GetCurrent(args);

    int64_t timeout;
    bool display_errors;
    bool break_on_sigint;

    // Assemble arguments
    if (!args[0]->IsObject()) {
      return env->ThrowTypeError(
          "contextifiedSandbox argument must be an object.");
    }

    Local sandbox = args[0].As();
    {
      TryCatch try_catch(env->isolate());
      Maybe maybe_timeout = GetTimeoutArg(env, args[1]);
      Maybe maybe_display_errors = GetDisplayErrorsArg(env, args[1]);
      Maybe maybe_break_on_sigint = GetBreakOnSigintArg(env, args[1]);
      if (try_catch.HasCaught()) {
        try_catch.ReThrow();
        return;
      }

      timeout = maybe_timeout.ToChecked();
      display_errors = maybe_display_errors.ToChecked();
      break_on_sigint = maybe_break_on_sigint.ToChecked();
    }

    // Get the context from the sandbox
    ContextifyContext* contextify_context =
        ContextifyContext::ContextFromContextifiedSandbox(env, sandbox);
    if (contextify_context == nullptr) {
      return env->ThrowTypeError(
          "sandbox argument must have been converted to a context.");
    }

    if (contextify_context->context().IsEmpty())
      return;

    {
      TryCatch try_catch(env->isolate());
      // Do the eval within the context
      Context::Scope context_scope(contextify_context->context());
      if (EvalMachine(contextify_context->env(),
                      timeout,
                      display_errors,
                      break_on_sigint,
                      args,
                      &try_catch)) {
        contextify_context->CopyProperties();
      }

      if (try_catch.HasCaught()) {
        try_catch.ReThrow();
        return;
      }
    }
  }

  static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) {
    Local exception = try_catch.Exception();

    if (!exception->IsObject())
      return;

    Local err_obj = exception.As();

    if (IsExceptionDecorated(env, err_obj))
      return;

    AppendExceptionLine(env, exception, try_catch.Message(), CONTEXTIFY_ERROR);
    Local stack = err_obj->Get(env->stack_string());
    MaybeLocal maybe_value =
        err_obj->GetPrivate(
            env->context(),
            env->arrow_message_private_symbol());

    Local arrow;
    if (!(maybe_value.ToLocal(&arrow) && arrow->IsString())) {
      return;
    }

    if (stack.IsEmpty() || !stack->IsString()) {
      return;
    }

    Local decorated_stack = String::Concat(
        String::Concat(arrow.As(),
          FIXED_ONE_BYTE_STRING(env->isolate(), "\n")),
        stack.As());
    err_obj->Set(env->stack_string(), decorated_stack);
    err_obj->SetPrivate(
        env->context(),
        env->decorated_private_symbol(),
        True(env->isolate()));
  }

  static Maybe GetBreakOnSigintArg(Environment* env,
                                         Local options) {
    if (options->IsUndefined() || options->IsString()) {
      return Just(false);
    }
    if (!options->IsObject()) {
      env->ThrowTypeError("options must be an object");
      return Nothing();
    }

    Local key = FIXED_ONE_BYTE_STRING(env->isolate(), "breakOnSigint");
    MaybeLocal maybe_value =
        options.As()->Get(env->context(), key);
    if (maybe_value.IsEmpty())
      return Nothing();

    Local value = maybe_value.ToLocalChecked();
    return Just(value->IsTrue());
  }

  static Maybe GetTimeoutArg(Environment* env, Local options) {
    if (options->IsUndefined() || options->IsString()) {
      return Just(-1);
    }
    if (!options->IsObject()) {
      env->ThrowTypeError("options must be an object");
      return Nothing();
    }

    MaybeLocal maybe_value =
        options.As()->Get(env->context(), env->timeout_string());
    if (maybe_value.IsEmpty())
      return Nothing();

    Local value = maybe_value.ToLocalChecked();
    if (value->IsUndefined()) {
      return Just(-1);
    }

    Maybe timeout = value->IntegerValue(env->context());

    if (timeout.IsJust() && timeout.ToChecked() ThrowRangeError("timeout must be a positive number");
      return Nothing();
    }

    return timeout;
  }


  static Maybe GetDisplayErrorsArg(Environment* env,
                                         Local options) {
    if (options->IsUndefined() || options->IsString()) {
      return Just(true);
    }
    if (!options->IsObject()) {
      env->ThrowTypeError("options must be an object");
      return Nothing();
    }

    Local key = FIXED_ONE_BYTE_STRING(env->isolate(), "displayErrors");
    MaybeLocal maybe_value =
        options.As()->Get(env->context(), key);
    if (maybe_value.IsEmpty())
      return Nothing();

    Local value = maybe_value.ToLocalChecked();
    if (value->IsUndefined())
      return Just(true);

    return value->BooleanValue(env->context());
  }


  static MaybeLocal GetFilenameArg(Environment* env,
                                           Local options) {
    Local defaultFilename =
        FIXED_ONE_BYTE_STRING(env->isolate(), "evalmachine.");

    if (options->IsUndefined()) {
      return defaultFilename;
    }
    if (options->IsString()) {
      return options.As();
    }
    if (!options->IsObject()) {
      env->ThrowTypeError("options must be an object");
      return Local();
    }

    Local key = FIXED_ONE_BYTE_STRING(env->isolate(), "filename");
    MaybeLocal maybe_value =
        options.As()->Get(env->context(), key);
    if (maybe_value.IsEmpty())
      return MaybeLocal();

    Local value = maybe_value.ToLocalChecked();
    if (value->IsUndefined())
      return defaultFilename;
    return value->ToString(env->context());
  }


  static MaybeLocal GetCachedData(Environment* env,
                                              Local options) {
    if (!options->IsObject()) {
      return MaybeLocal();
    }

    MaybeLocal maybe_value =
        options.As()->Get(env->context(), env->cached_data_string());
    if (maybe_value.IsEmpty())
      return MaybeLocal();

    Local value = maybe_value.ToLocalChecked();
    if (value->IsUndefined()) {
      return MaybeLocal();
    }

    if (!value->IsUint8Array()) {
      env->ThrowTypeError("options.cachedData must be a Buffer instance");
      return MaybeLocal();
    }

    return value.As();
  }


  static Maybe GetProduceCachedData(Environment* env,
                                          Local options) {
    if (!options->IsObject()) {
      return Just(false);
    }

    MaybeLocal maybe_value =
        options.As()->Get(env->context(),
                                  env->produce_cached_data_string());
    if (maybe_value.IsEmpty())
      return Nothing();

    Local value = maybe_value.ToLocalChecked();
    return Just(value->IsTrue());
  }


  static MaybeLocal GetLineOffsetArg(Environment* env,
                                              Local options) {
    Local defaultLineOffset = Integer::New(env->isolate(), 0);

    if (!options->IsObject()) {
      return defaultLineOffset;
    }

    Local key = FIXED_ONE_BYTE_STRING(env->isolate(), "lineOffset");
    MaybeLocal maybe_value =
        options.As()->Get(env->context(), key);
    if (maybe_value.IsEmpty())
      return MaybeLocal();

    Local value = maybe_value.ToLocalChecked();
    if (value->IsUndefined())
      return defaultLineOffset;

    return value->ToInteger(env->context());
  }


  static MaybeLocal GetColumnOffsetArg(Environment* env,
                                                Local options) {
    Local defaultColumnOffset = Integer::New(env->isolate(), 0);

    if (!options->IsObject()) {
      return defaultColumnOffset;
    }

    Local key = FIXED_ONE_BYTE_STRING(env->isolate(), "columnOffset");
    MaybeLocal maybe_value =
      options.As()->Get(env->context(), key);
    if (maybe_value.IsEmpty())
      return MaybeLocal();

    Local value = maybe_value.ToLocalChecked();
    if (value->IsUndefined())
      return defaultColumnOffset;

    return value->ToInteger(env->context());
  }

  static MaybeLocal GetContext(Environment* env,
                                        Local options) {
    if (!options->IsObject())
      return MaybeLocal();

    MaybeLocal maybe_value =
        options.As()->Get(env->context(),
                                  env->vm_parsing_context_symbol());
    Local value;
    if (!maybe_value.ToLocal(&value))
      return MaybeLocal();

    if (!value->IsObject()) {
      if (!value->IsNullOrUndefined()) {
        env->ThrowTypeError(
            "contextifiedSandbox argument must be an object.");
      }
      return MaybeLocal();
    }

    ContextifyContext* sandbox =
        ContextifyContext::ContextFromContextifiedSandbox(
            env, value.As());
    if (!sandbox) {
      env->ThrowTypeError(
          "sandbox argument must have been converted to a context.");
      return MaybeLocal();
    }

    Local context = sandbox->context();
    if (context.IsEmpty())
      return MaybeLocal();
    return context;
  }


  static bool EvalMachine(Environment* env,
                          const int64_t timeout,
                          const bool display_errors,
                          const bool break_on_sigint,
                          const FunctionCallbackInfo& args,
                          TryCatch* try_catch) {
    if (!ContextifyScript::InstanceOf(env, args.Holder())) {
      env->ThrowTypeError(
          "Script methods can only be called on script instances.");
      return false;
    }

    ContextifyScript* wrapped_script;
    ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder(), false);
    Local unbound_script =
        PersistentToLocal(env->isolate(), wrapped_script->script_);
    Local script = unbound_script->BindToCurrentContext();

    Local result;
    bool timed_out = false;
    bool received_signal = false;
    if (break_on_sigint && timeout != -1) {
      Watchdog wd(env->isolate(), timeout, &timed_out);
      SigintWatchdog swd(env->isolate(), &received_signal);
      result = script->Run();
    } else if (break_on_sigint) {
      SigintWatchdog swd(env->isolate(), &received_signal);
      result = script->Run();
    } else if (timeout != -1) {
      Watchdog wd(env->isolate(), timeout, &timed_out);
      result = script->Run();
    } else {
      result = script->Run();
    }

    if (timed_out || received_signal) {
      // It is possible that execution was terminated by another timeout in
      // which this timeout is nested, so check whether one of the watchdogs
      // from this invocation is responsible for termination.
      if (timed_out) {
        env->ThrowError("Script execution timed out.");
      } else if (received_signal) {
        env->ThrowError("Script execution interrupted.");
      }
      env->isolate()->CancelTerminateExecution();
    }

    if (try_catch->HasCaught()) {
      if (!timed_out && !received_signal && display_errors) {
        // We should decorate non-termination exceptions
        DecorateErrorStack(env, *try_catch);
      }

      // If there was an exception thrown during script execution, re-throw it.
      // If one of the above checks threw, re-throw the exception instead of
      // letting try_catch catch it.
      // If execution has been terminated, but not by one of the watchdogs from
      // this invocation, this will re-throw a `null` value.
      try_catch->ReThrow();

      return false;
    }

    args.GetReturnValue().Set(result);
    return true;
  }


  ContextifyScript(Environment* env, Local object)
      : BaseObject(env, object) {
    MakeWeak(this);
  }


  ~ContextifyScript() override {
    script_.Reset();
  }
};


void InitContextify(Local target,
                    Local unused,
                    Local context) {
  Environment* env = Environment::GetCurrent(context);
  ContextifyContext::Init(env, target);
  ContextifyScript::Init(env, target);
}

}  // anonymous namespace
}  // namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(contextify, node::InitContextify)

Web Proxy Viewer  |  New URL  |  Original Page