enclose-io/compiler

View on GitHub
lts/lib/vm.js

Summary

Maintainability
F
6 days
Test Coverage
// 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.

'use strict';

const {
  ArrayIsArray,
  ArrayPrototypeForEach,
  Symbol,
} = primordials;

const {
  ContextifyScript,
  makeContext,
  isContext: _isContext,
  compileFunction: _compileFunction
} = internalBinding('contextify');
const {
  ERR_INVALID_ARG_TYPE,
} = require('internal/errors').codes;
const {
  isArrayBufferView,
} = require('internal/util/types');
const {
  validateInt32,
  validateUint32,
  validateString
} = require('internal/validators');
const { kVmBreakFirstLineSymbol } = require('internal/util');
const kParsingContext = Symbol('script parsing context');

class Script extends ContextifyScript {
  constructor(code, options = {}) {
    code = `${code}`;
    if (typeof options === 'string') {
      options = { filename: options };
    } else if (typeof options !== 'object' || options === null) {
      throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
    }

    const {
      filename = 'evalmachine.<anonymous>',
      lineOffset = 0,
      columnOffset = 0,
      cachedData,
      produceCachedData = false,
      importModuleDynamically,
      [kParsingContext]: parsingContext,
    } = options;

    validateString(filename, 'options.filename');
    validateInt32(lineOffset, 'options.lineOffset');
    validateInt32(columnOffset, 'options.columnOffset');
    if (cachedData !== undefined && !isArrayBufferView(cachedData)) {
      throw new ERR_INVALID_ARG_TYPE(
        'options.cachedData',
        ['Buffer', 'TypedArray', 'DataView'],
        cachedData
      );
    }
    if (typeof produceCachedData !== 'boolean') {
      throw new ERR_INVALID_ARG_TYPE('options.produceCachedData', 'boolean',
                                     produceCachedData);
    }

    // Calling `ReThrow()` on a native TryCatch does not generate a new
    // abort-on-uncaught-exception check. A dummy try/catch in JS land
    // protects against that.
    try { // eslint-disable-line no-useless-catch
      super(code,
            filename,
            lineOffset,
            columnOffset,
            cachedData,
            produceCachedData,
            parsingContext);
    } catch (e) {
      throw e; /* node-do-not-add-exception-line */
    }

    if (importModuleDynamically !== undefined) {
      if (typeof importModuleDynamically !== 'function') {
        throw new ERR_INVALID_ARG_TYPE('options.importModuleDynamically',
                                       'function',
                                       importModuleDynamically);
      }
      const { importModuleDynamicallyWrap } =
        require('internal/vm/module');
      const { callbackMap } = internalBinding('module_wrap');
      callbackMap.set(this, {
        importModuleDynamically:
          importModuleDynamicallyWrap(importModuleDynamically),
      });
    }
  }

  runInThisContext(options) {
    const { breakOnSigint, args } = getRunInContextArgs(options);
    if (breakOnSigint && process.listenerCount('SIGINT') > 0) {
      return sigintHandlersWrap(super.runInThisContext, this, args);
    }
    return super.runInThisContext(...args);
  }

  runInContext(contextifiedObject, options) {
    validateContext(contextifiedObject);
    const { breakOnSigint, args } = getRunInContextArgs(options);
    if (breakOnSigint && process.listenerCount('SIGINT') > 0) {
      return sigintHandlersWrap(super.runInContext, this,
                                [contextifiedObject, ...args]);
    }
    return super.runInContext(contextifiedObject, ...args);
  }

  runInNewContext(contextObject, options) {
    const context = createContext(contextObject, getContextOptions(options));
    return this.runInContext(context, options);
  }
}

function validateContext(contextifiedObject) {
  if (typeof contextifiedObject !== 'object' || contextifiedObject === null) {
    throw new ERR_INVALID_ARG_TYPE('contextifiedObject', 'Object',
                                   contextifiedObject);
  }
  if (!_isContext(contextifiedObject)) {
    throw new ERR_INVALID_ARG_TYPE('contextifiedObject', 'vm.Context',
                                   contextifiedObject);
  }
}

function validateBool(prop, propName) {
  if (prop !== undefined && typeof prop !== 'boolean')
    throw new ERR_INVALID_ARG_TYPE(propName, 'boolean', prop);
}

function validateObject(prop, propName) {
  if (prop !== undefined && (typeof prop !== 'object' || prop === null))
    throw new ERR_INVALID_ARG_TYPE(propName, 'Object', prop);
}

function getRunInContextArgs(options = {}) {
  if (typeof options !== 'object' || options === null) {
    throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
  }

  let timeout = options.timeout;
  if (timeout === undefined) {
    timeout = -1;
  } else {
    validateUint32(timeout, 'options.timeout', true);
  }

  const {
    displayErrors = true,
    breakOnSigint = false,
    [kVmBreakFirstLineSymbol]: breakFirstLine = false,
  } = options;

  if (typeof displayErrors !== 'boolean') {
    throw new ERR_INVALID_ARG_TYPE('options.displayErrors', 'boolean',
                                   displayErrors);
  }
  if (typeof breakOnSigint !== 'boolean') {
    throw new ERR_INVALID_ARG_TYPE('options.breakOnSigint', 'boolean',
                                   breakOnSigint);
  }

  return {
    breakOnSigint,
    args: [timeout, displayErrors, breakOnSigint, breakFirstLine]
  };
}

function getContextOptions(options) {
  if (options) {
    validateObject(options.contextCodeGeneration,
                   'options.contextCodeGeneration');
    const contextOptions = {
      name: options.contextName,
      origin: options.contextOrigin,
      codeGeneration: typeof options.contextCodeGeneration === 'object' ? {
        strings: options.contextCodeGeneration.strings,
        wasm: options.contextCodeGeneration.wasm,
      } : undefined,
    };
    if (contextOptions.name !== undefined)
      validateString(contextOptions.name, 'options.contextName');
    if (contextOptions.origin !== undefined)
      validateString(contextOptions.origin, 'options.contextOrigin');
    if (contextOptions.codeGeneration) {
      validateBool(contextOptions.codeGeneration.strings,
                   'options.contextCodeGeneration.strings');
      validateBool(contextOptions.codeGeneration.wasm,
                   'options.contextCodeGeneration.wasm');
    }
    return contextOptions;
  }
  return {};
}

function isContext(object) {
  if (typeof object !== 'object' || object === null) {
    throw new ERR_INVALID_ARG_TYPE('object', 'Object', object);
  }
  return _isContext(object);
}

let defaultContextNameIndex = 1;
function createContext(contextObject = {}, options = {}) {
  if (isContext(contextObject)) {
    return contextObject;
  }

  if (typeof options !== 'object' || options === null) {
    throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
  }

  const {
    name = `VM Context ${defaultContextNameIndex++}`,
    origin,
    codeGeneration
  } = options;

  validateString(name, 'options.name');
  if (origin !== undefined)
    validateString(origin, 'options.origin');
  validateObject(codeGeneration, 'options.codeGeneration');

  let strings = true;
  let wasm = true;
  if (codeGeneration !== undefined) {
    ({ strings = true, wasm = true } = codeGeneration);
    validateBool(strings, 'options.codeGeneration.strings');
    validateBool(wasm, 'options.codeGeneration.wasm');
  }

  makeContext(contextObject, name, origin, strings, wasm);
  return contextObject;
}

function createScript(code, options) {
  return new Script(code, options);
}

// Remove all SIGINT listeners and re-attach them after the wrapped function
// has executed, so that caught SIGINT are handled by the listeners again.
function sigintHandlersWrap(fn, thisArg, argsArray) {
  const sigintListeners = process.rawListeners('SIGINT');

  process.removeAllListeners('SIGINT');

  try {
    return fn.apply(thisArg, argsArray);
  } finally {
    // Add using the public methods so that the `newListener` handler of
    // process can re-attach the listeners.
    for (const listener of sigintListeners) {
      process.addListener('SIGINT', listener);
    }
  }
}

function runInContext(code, contextifiedObject, options) {
  validateContext(contextifiedObject);
  if (typeof options === 'string') {
    options = {
      filename: options,
      [kParsingContext]: contextifiedObject
    };
  } else {
    options = { ...options, [kParsingContext]: contextifiedObject };
  }
  return createScript(code, options)
    .runInContext(contextifiedObject, options);
}

function runInNewContext(code, contextObject, options) {
  if (typeof options === 'string') {
    options = { filename: options };
  }
  contextObject = createContext(contextObject, getContextOptions(options));
  options = { ...options, [kParsingContext]: contextObject };
  return createScript(code, options).runInNewContext(contextObject, options);
}

function runInThisContext(code, options) {
  if (typeof options === 'string') {
    options = { filename: options };
  }
  return createScript(code, options).runInThisContext(options);
}

function compileFunction(code, params, options = {}) {
  validateString(code, 'code');
  if (params !== undefined) {
    if (!ArrayIsArray(params)) {
      throw new ERR_INVALID_ARG_TYPE('params', 'Array', params);
    }
    ArrayPrototypeForEach(params,
                          (param, i) => validateString(param, `params[${i}]`));
  }

  const {
    filename = '',
    columnOffset = 0,
    lineOffset = 0,
    cachedData = undefined,
    produceCachedData = false,
    parsingContext = undefined,
    contextExtensions = [],
  } = options;

  validateString(filename, 'options.filename');
  validateUint32(columnOffset, 'options.columnOffset');
  validateUint32(lineOffset, 'options.lineOffset');
  if (cachedData !== undefined && !isArrayBufferView(cachedData)) {
    throw new ERR_INVALID_ARG_TYPE(
      'options.cachedData',
      ['Buffer', 'TypedArray', 'DataView'],
      cachedData
    );
  }
  if (typeof produceCachedData !== 'boolean') {
    throw new ERR_INVALID_ARG_TYPE(
      'options.produceCachedData',
      'boolean',
      produceCachedData
    );
  }
  if (parsingContext !== undefined) {
    if (
      typeof parsingContext !== 'object' ||
      parsingContext === null ||
      !isContext(parsingContext)
    ) {
      throw new ERR_INVALID_ARG_TYPE(
        'options.parsingContext',
        'Context',
        parsingContext
      );
    }
  }
  if (!ArrayIsArray(contextExtensions)) {
    throw new ERR_INVALID_ARG_TYPE(
      'options.contextExtensions',
      'Array',
      contextExtensions
    );
  }
  ArrayPrototypeForEach(contextExtensions, (extension, i) => {
    if (typeof extension !== 'object') {
      throw new ERR_INVALID_ARG_TYPE(
        `options.contextExtensions[${i}]`,
        'object',
        extension
      );
    }
  });

  const result = _compileFunction(
    code,
    filename,
    lineOffset,
    columnOffset,
    cachedData,
    produceCachedData,
    parsingContext,
    contextExtensions,
    params
  );

  if (produceCachedData) {
    result.function.cachedDataProduced = result.cachedDataProduced;
  }

  if (result.cachedData) {
    result.function.cachedData = result.cachedData;
  }

  return result.function;
}


module.exports = {
  Script,
  createContext,
  createScript,
  runInContext,
  runInNewContext,
  runInThisContext,
  isContext,
  compileFunction,
};

if (require('internal/options').getOptionValue('--experimental-vm-modules')) {
  const {
    Module, SourceTextModule, SyntheticModule,
  } = require('internal/vm/module');
  module.exports.Module = Module;
  module.exports.SourceTextModule = SourceTextModule;
  module.exports.SyntheticModule = SyntheticModule;
}