| 'use strict'; |
| |
| const {Script} = require('vm'); |
| const { |
| lookupCompiler, |
| removeShebang |
| } = require('./compiler'); |
| const { |
| transformer |
| } = require('./transformer'); |
| |
| const objectDefineProperties = Object.defineProperties; |
| |
| const MODULE_PREFIX = '(function (exports, require, module, __filename, __dirname) { '; |
| const STRICT_MODULE_PREFIX = MODULE_PREFIX + '"use strict"; '; |
| const MODULE_SUFFIX = '\n});'; |
| |
| /** |
| * Class Script |
| * |
| * @public |
| */ |
| class VMScript { |
| |
| /** |
| * The script code with wrapping. If set will invalidate the cache.<br> |
| * Writable only for backwards compatibility. |
| * |
| * @public |
| * @readonly |
| * @member {string} code |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The filename used for this script. |
| * |
| * @public |
| * @readonly |
| * @since v3.9.0 |
| * @member {string} filename |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The line offset use for stack traces. |
| * |
| * @public |
| * @readonly |
| * @since v3.9.0 |
| * @member {number} lineOffset |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The column offset use for stack traces. |
| * |
| * @public |
| * @readonly |
| * @since v3.9.0 |
| * @member {number} columnOffset |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The compiler to use to get the JavaScript code. |
| * |
| * @public |
| * @readonly |
| * @since v3.9.0 |
| * @member {(string|compileCallback)} compiler |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The prefix for the script. |
| * |
| * @private |
| * @member {string} _prefix |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The suffix for the script. |
| * |
| * @private |
| * @member {string} _suffix |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The compiled vm.Script for the VM or if not compiled <code>null</code>. |
| * |
| * @private |
| * @member {?vm.Script} _compiledVM |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The compiled vm.Script for the NodeVM or if not compiled <code>null</code>. |
| * |
| * @private |
| * @member {?vm.Script} _compiledNodeVM |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The compiled vm.Script for the NodeVM in strict mode or if not compiled <code>null</code>. |
| * |
| * @private |
| * @member {?vm.Script} _compiledNodeVMStrict |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The resolved compiler to use to get the JavaScript code. |
| * |
| * @private |
| * @readonly |
| * @member {compileCallback} _compiler |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * The script to run without wrapping. |
| * |
| * @private |
| * @member {string} _code |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * Whether or not the script contains async functions. |
| * |
| * @private |
| * @member {boolean} _hasAsync |
| * @memberOf VMScript# |
| */ |
| |
| /** |
| * Create VMScript instance. |
| * |
| * @public |
| * @param {string} code - Code to run. |
| * @param {(string|Object)} [options] - Options map or filename. |
| * @param {string} [options.filename="vm.js"] - Filename that shows up in any stack traces produced from this script. |
| * @param {number} [options.lineOffset=0] - Passed to vm.Script options. |
| * @param {number} [options.columnOffset=0] - Passed to vm.Script options. |
| * @param {(string|compileCallback)} [options.compiler="javascript"] - The compiler to use. |
| * @throws {VMError} If the compiler is unknown or if coffee-script was requested but the module not found. |
| */ |
| constructor(code, options) { |
| const sCode = `${code}`; |
| let useFileName; |
| let useOptions; |
| if (arguments.length === 2) { |
| if (typeof options === 'object') { |
| useOptions = options || {__proto__: null}; |
| useFileName = useOptions.filename; |
| } else { |
| useOptions = {__proto__: null}; |
| useFileName = options; |
| } |
| } else if (arguments.length > 2) { |
| // We do it this way so that there are no more arguments in the function. |
| // eslint-disable-next-line prefer-rest-params |
| useOptions = arguments[2] || {__proto__: null}; |
| useFileName = options || useOptions.filename; |
| } else { |
| useOptions = {__proto__: null}; |
| } |
| |
| const { |
| compiler = 'javascript', |
| lineOffset = 0, |
| columnOffset = 0 |
| } = useOptions; |
| |
| // Throw if the compiler is unknown. |
| const resolvedCompiler = lookupCompiler(compiler); |
| |
| objectDefineProperties(this, { |
| __proto__: null, |
| code: { |
| __proto__: null, |
| // Put this here so that it is enumerable, and looks like a property. |
| get() { |
| return this._prefix + this._code + this._suffix; |
| }, |
| set(value) { |
| const strNewCode = String(value); |
| if (strNewCode === this._code && this._prefix === '' && this._suffix === '') return; |
| this._code = strNewCode; |
| this._prefix = ''; |
| this._suffix = ''; |
| this._compiledVM = null; |
| this._compiledNodeVM = null; |
| this._compiledCode = null; |
| }, |
| enumerable: true |
| }, |
| filename: { |
| __proto__: null, |
| value: useFileName || 'vm.js', |
| enumerable: true |
| }, |
| lineOffset: { |
| __proto__: null, |
| value: lineOffset, |
| enumerable: true |
| }, |
| columnOffset: { |
| __proto__: null, |
| value: columnOffset, |
| enumerable: true |
| }, |
| compiler: { |
| __proto__: null, |
| value: compiler, |
| enumerable: true |
| }, |
| _code: { |
| __proto__: null, |
| value: sCode, |
| writable: true |
| }, |
| _prefix: { |
| __proto__: null, |
| value: '', |
| writable: true |
| }, |
| _suffix: { |
| __proto__: null, |
| value: '', |
| writable: true |
| }, |
| _compiledVM: { |
| __proto__: null, |
| value: null, |
| writable: true |
| }, |
| _compiledNodeVM: { |
| __proto__: null, |
| value: null, |
| writable: true |
| }, |
| _compiledNodeVMStrict: { |
| __proto__: null, |
| value: null, |
| writable: true |
| }, |
| _compiledCode: { |
| __proto__: null, |
| value: null, |
| writable: true |
| }, |
| _hasAsync: { |
| __proto__: null, |
| value: false, |
| writable: true |
| }, |
| _compiler: {__proto__: null, value: resolvedCompiler} |
| }); |
| } |
| |
| /** |
| * Wraps the code.<br> |
| * This will replace the old wrapping.<br> |
| * Will invalidate the code cache. |
| * |
| * @public |
| * @deprecated Since v3.9.0. Wrap your code before passing it into the VMScript object. |
| * @param {string} prefix - String that will be appended before the script code. |
| * @param {script} suffix - String that will be appended behind the script code. |
| * @return {this} This for chaining. |
| * @throws {TypeError} If prefix or suffix is a Symbol. |
| */ |
| wrap(prefix, suffix) { |
| const strPrefix = `${prefix}`; |
| const strSuffix = `${suffix}`; |
| if (this._prefix === strPrefix && this._suffix === strSuffix) return this; |
| this._prefix = strPrefix; |
| this._suffix = strSuffix; |
| this._compiledVM = null; |
| this._compiledNodeVM = null; |
| this._compiledNodeVMStrict = null; |
| return this; |
| } |
| |
| /** |
| * Compile this script. <br> |
| * This is useful to detect syntax errors in the script. |
| * |
| * @public |
| * @return {this} This for chaining. |
| * @throws {SyntaxError} If there is a syntax error in the script. |
| */ |
| compile() { |
| this._compileVM(); |
| return this; |
| } |
| |
| /** |
| * Get the compiled code. |
| * |
| * @private |
| * @return {string} The code. |
| */ |
| getCompiledCode() { |
| if (!this._compiledCode) { |
| const comp = this._compiler(this._prefix + removeShebang(this._code) + this._suffix, this.filename); |
| const res = transformer(null, comp, false, false, this.filename); |
| this._compiledCode = res.code; |
| this._hasAsync = res.hasAsync; |
| } |
| return this._compiledCode; |
| } |
| |
| /** |
| * Compiles this script to a vm.Script. |
| * |
| * @private |
| * @param {string} prefix - JavaScript code that will be used as prefix. |
| * @param {string} suffix - JavaScript code that will be used as suffix. |
| * @return {vm.Script} The compiled vm.Script. |
| * @throws {SyntaxError} If there is a syntax error in the script. |
| */ |
| _compile(prefix, suffix) { |
| return new Script(prefix + this.getCompiledCode() + suffix, { |
| __proto__: null, |
| filename: this.filename, |
| displayErrors: false, |
| lineOffset: this.lineOffset, |
| columnOffset: this.columnOffset |
| }); |
| } |
| |
| /** |
| * Will return the cached version of the script intended for VM or compile it. |
| * |
| * @private |
| * @return {vm.Script} The compiled script |
| * @throws {SyntaxError} If there is a syntax error in the script. |
| */ |
| _compileVM() { |
| let script = this._compiledVM; |
| if (!script) { |
| this._compiledVM = script = this._compile('', ''); |
| } |
| return script; |
| } |
| |
| /** |
| * Will return the cached version of the script intended for NodeVM or compile it. |
| * |
| * @private |
| * @return {vm.Script} The compiled script |
| * @throws {SyntaxError} If there is a syntax error in the script. |
| */ |
| _compileNodeVM() { |
| let script = this._compiledNodeVM; |
| if (!script) { |
| this._compiledNodeVM = script = this._compile(MODULE_PREFIX, MODULE_SUFFIX); |
| } |
| return script; |
| } |
| |
| /** |
| * Will return the cached version of the script intended for NodeVM in strict mode or compile it. |
| * |
| * @private |
| * @return {vm.Script} The compiled script |
| * @throws {SyntaxError} If there is a syntax error in the script. |
| */ |
| _compileNodeVMStrict() { |
| let script = this._compiledNodeVMStrict; |
| if (!script) { |
| this._compiledNodeVMStrict = script = this._compile(STRICT_MODULE_PREFIX, MODULE_SUFFIX); |
| } |
| return script; |
| } |
| |
| } |
| |
| exports.MODULE_PREFIX = MODULE_PREFIX; |
| exports.STRICT_MODULE_PREFIX = STRICT_MODULE_PREFIX; |
| exports.MODULE_SUFFIX = MODULE_SUFFIX; |
| exports.VMScript = VMScript; |