• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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'use strict';
23
24const {
25  ArrayPrototypeForEach,
26  ArrayPrototypeUnshift,
27  Symbol,
28  PromiseReject,
29  ReflectApply,
30} = primordials;
31
32const {
33  ContextifyScript,
34  MicrotaskQueue,
35  makeContext,
36  isContext: _isContext,
37  constants,
38  compileFunction: _compileFunction,
39  measureMemory: _measureMemory,
40} = internalBinding('contextify');
41const {
42  ERR_CONTEXT_NOT_INITIALIZED,
43  ERR_INVALID_ARG_TYPE,
44} = require('internal/errors').codes;
45const {
46  isArrayBufferView,
47} = require('internal/util/types');
48const {
49  validateInt32,
50  validateUint32,
51  validateString,
52  validateArray,
53  validateBoolean,
54  validateBuffer,
55  validateObject,
56  validateOneOf,
57} = require('internal/validators');
58const {
59  kVmBreakFirstLineSymbol,
60  emitExperimentalWarning,
61} = require('internal/util');
62const kParsingContext = Symbol('script parsing context');
63
64class Script extends ContextifyScript {
65  constructor(code, options = {}) {
66    code = `${code}`;
67    if (typeof options === 'string') {
68      options = { filename: options };
69    } else if (typeof options !== 'object' || options === null) {
70      throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
71    }
72
73    const {
74      filename = 'evalmachine.<anonymous>',
75      lineOffset = 0,
76      columnOffset = 0,
77      cachedData,
78      produceCachedData = false,
79      importModuleDynamically,
80      [kParsingContext]: parsingContext,
81    } = options;
82
83    validateString(filename, 'options.filename');
84    validateInt32(lineOffset, 'options.lineOffset');
85    validateInt32(columnOffset, 'options.columnOffset');
86    if (cachedData !== undefined && !isArrayBufferView(cachedData)) {
87      throw new ERR_INVALID_ARG_TYPE(
88        'options.cachedData',
89        ['Buffer', 'TypedArray', 'DataView'],
90        cachedData
91      );
92    }
93    if (typeof produceCachedData !== 'boolean') {
94      throw new ERR_INVALID_ARG_TYPE('options.produceCachedData', 'boolean',
95                                     produceCachedData);
96    }
97
98    // Calling `ReThrow()` on a native TryCatch does not generate a new
99    // abort-on-uncaught-exception check. A dummy try/catch in JS land
100    // protects against that.
101    try { // eslint-disable-line no-useless-catch
102      super(code,
103            filename,
104            lineOffset,
105            columnOffset,
106            cachedData,
107            produceCachedData,
108            parsingContext);
109    } catch (e) {
110      throw e; /* node-do-not-add-exception-line */
111    }
112
113    if (importModuleDynamically !== undefined) {
114      if (typeof importModuleDynamically !== 'function') {
115        throw new ERR_INVALID_ARG_TYPE('options.importModuleDynamically',
116                                       'function',
117                                       importModuleDynamically);
118      }
119      const { importModuleDynamicallyWrap } =
120        require('internal/vm/module');
121      const { callbackMap } = internalBinding('module_wrap');
122      callbackMap.set(this, {
123        importModuleDynamically:
124          importModuleDynamicallyWrap(importModuleDynamically),
125      });
126    }
127  }
128
129  runInThisContext(options) {
130    const { breakOnSigint, args } = getRunInContextArgs(options);
131    if (breakOnSigint && process.listenerCount('SIGINT') > 0) {
132      return sigintHandlersWrap(super.runInThisContext, this, args);
133    }
134    return ReflectApply(super.runInThisContext, this, args);
135  }
136
137  runInContext(contextifiedObject, options) {
138    validateContext(contextifiedObject);
139    const { breakOnSigint, args } = getRunInContextArgs(options);
140    ArrayPrototypeUnshift(args, contextifiedObject);
141    if (breakOnSigint && process.listenerCount('SIGINT') > 0) {
142      return sigintHandlersWrap(super.runInContext, this, args);
143    }
144    return ReflectApply(super.runInContext, this, args);
145  }
146
147  runInNewContext(contextObject, options) {
148    const context = createContext(contextObject, getContextOptions(options));
149    return this.runInContext(context, options);
150  }
151}
152
153function validateContext(contextifiedObject) {
154  if (!isContext(contextifiedObject)) {
155    throw new ERR_INVALID_ARG_TYPE('contextifiedObject', 'vm.Context',
156                                   contextifiedObject);
157  }
158}
159
160function getRunInContextArgs(options = {}) {
161  validateObject(options, 'options');
162
163  let timeout = options.timeout;
164  if (timeout === undefined) {
165    timeout = -1;
166  } else {
167    validateUint32(timeout, 'options.timeout', true);
168  }
169
170  const {
171    displayErrors = true,
172    breakOnSigint = false,
173    [kVmBreakFirstLineSymbol]: breakFirstLine = false,
174  } = options;
175
176  validateBoolean(displayErrors, 'options.displayErrors');
177  validateBoolean(breakOnSigint, 'options.breakOnSigint');
178
179  return {
180    breakOnSigint,
181    args: [timeout, displayErrors, breakOnSigint, breakFirstLine]
182  };
183}
184
185function getContextOptions(options) {
186  if (!options)
187    return {};
188  const contextOptions = {
189    name: options.contextName,
190    origin: options.contextOrigin,
191    codeGeneration: undefined,
192    microtaskMode: options.microtaskMode,
193  };
194  if (contextOptions.name !== undefined)
195    validateString(contextOptions.name, 'options.contextName');
196  if (contextOptions.origin !== undefined)
197    validateString(contextOptions.origin, 'options.contextOrigin');
198  if (options.contextCodeGeneration !== undefined) {
199    validateObject(options.contextCodeGeneration,
200                   'options.contextCodeGeneration');
201    const { strings, wasm } = options.contextCodeGeneration;
202    if (strings !== undefined)
203      validateBoolean(strings, 'options.contextCodeGeneration.strings');
204    if (wasm !== undefined)
205      validateBoolean(wasm, 'options.contextCodeGeneration.wasm');
206    contextOptions.codeGeneration = { strings, wasm };
207  }
208  if (options.microtaskMode !== undefined)
209    validateString(options.microtaskMode, 'options.microtaskMode');
210  return contextOptions;
211}
212
213function isContext(object) {
214  validateObject(object, 'object', { allowArray: true });
215
216  return _isContext(object);
217}
218
219let defaultContextNameIndex = 1;
220function createContext(contextObject = {}, options = {}) {
221  if (isContext(contextObject)) {
222    return contextObject;
223  }
224
225  validateObject(options, 'options');
226
227  const {
228    name = `VM Context ${defaultContextNameIndex++}`,
229    origin,
230    codeGeneration,
231    microtaskMode
232  } = options;
233
234  validateString(name, 'options.name');
235  if (origin !== undefined)
236    validateString(origin, 'options.origin');
237  if (codeGeneration !== undefined)
238    validateObject(codeGeneration, 'options.codeGeneration');
239
240  let strings = true;
241  let wasm = true;
242  if (codeGeneration !== undefined) {
243    ({ strings = true, wasm = true } = codeGeneration);
244    validateBoolean(strings, 'options.codeGeneration.strings');
245    validateBoolean(wasm, 'options.codeGeneration.wasm');
246  }
247
248  let microtaskQueue = null;
249  if (microtaskMode !== undefined) {
250    validateOneOf(microtaskMode, 'options.microtaskMode',
251                  ['afterEvaluate', undefined]);
252
253    if (microtaskMode === 'afterEvaluate')
254      microtaskQueue = new MicrotaskQueue();
255  }
256
257  makeContext(contextObject, name, origin, strings, wasm, microtaskQueue);
258  return contextObject;
259}
260
261function createScript(code, options) {
262  return new Script(code, options);
263}
264
265// Remove all SIGINT listeners and re-attach them after the wrapped function
266// has executed, so that caught SIGINT are handled by the listeners again.
267function sigintHandlersWrap(fn, thisArg, argsArray) {
268  const sigintListeners = process.rawListeners('SIGINT');
269
270  process.removeAllListeners('SIGINT');
271
272  try {
273    return ReflectApply(fn, thisArg, argsArray);
274  } finally {
275    // Add using the public methods so that the `newListener` handler of
276    // process can re-attach the listeners.
277    ArrayPrototypeForEach(sigintListeners, (listener) => {
278      process.addListener('SIGINT', listener);
279    });
280  }
281}
282
283function runInContext(code, contextifiedObject, options) {
284  validateContext(contextifiedObject);
285  if (typeof options === 'string') {
286    options = {
287      filename: options,
288      [kParsingContext]: contextifiedObject
289    };
290  } else {
291    options = { ...options, [kParsingContext]: contextifiedObject };
292  }
293  return createScript(code, options)
294    .runInContext(contextifiedObject, options);
295}
296
297function runInNewContext(code, contextObject, options) {
298  if (typeof options === 'string') {
299    options = { filename: options };
300  }
301  contextObject = createContext(contextObject, getContextOptions(options));
302  options = { ...options, [kParsingContext]: contextObject };
303  return createScript(code, options).runInNewContext(contextObject, options);
304}
305
306function runInThisContext(code, options) {
307  if (typeof options === 'string') {
308    options = { filename: options };
309  }
310  return createScript(code, options).runInThisContext(options);
311}
312
313function compileFunction(code, params, options = {}) {
314  validateString(code, 'code');
315  if (params !== undefined) {
316    validateArray(params, 'params');
317    ArrayPrototypeForEach(params,
318                          (param, i) => validateString(param, `params[${i}]`));
319  }
320
321  const {
322    filename = '',
323    columnOffset = 0,
324    lineOffset = 0,
325    cachedData = undefined,
326    produceCachedData = false,
327    parsingContext = undefined,
328    contextExtensions = [],
329  } = options;
330
331  validateString(filename, 'options.filename');
332  validateUint32(columnOffset, 'options.columnOffset');
333  validateUint32(lineOffset, 'options.lineOffset');
334  if (cachedData !== undefined)
335    validateBuffer(cachedData, 'options.cachedData');
336  validateBoolean(produceCachedData, 'options.produceCachedData');
337  if (parsingContext !== undefined) {
338    if (
339      typeof parsingContext !== 'object' ||
340      parsingContext === null ||
341      !isContext(parsingContext)
342    ) {
343      throw new ERR_INVALID_ARG_TYPE(
344        'options.parsingContext',
345        'Context',
346        parsingContext
347      );
348    }
349  }
350  validateArray(contextExtensions, 'options.contextExtensions');
351  ArrayPrototypeForEach(contextExtensions, (extension, i) => {
352    const name = `options.contextExtensions[${i}]`;
353    validateObject(extension, name, { nullable: true });
354  });
355
356  const result = _compileFunction(
357    code,
358    filename,
359    lineOffset,
360    columnOffset,
361    cachedData,
362    produceCachedData,
363    parsingContext,
364    contextExtensions,
365    params
366  );
367
368  if (produceCachedData) {
369    result.function.cachedDataProduced = result.cachedDataProduced;
370  }
371
372  if (result.cachedData) {
373    result.function.cachedData = result.cachedData;
374  }
375
376  return result.function;
377}
378
379const measureMemoryModes = {
380  summary: constants.measureMemory.mode.SUMMARY,
381  detailed: constants.measureMemory.mode.DETAILED,
382};
383
384const measureMemoryExecutions = {
385  default: constants.measureMemory.execution.DEFAULT,
386  eager: constants.measureMemory.execution.EAGER,
387};
388
389function measureMemory(options = {}) {
390  emitExperimentalWarning('vm.measureMemory');
391  validateObject(options, 'options');
392  const { mode = 'summary', execution = 'default' } = options;
393  validateOneOf(mode, 'options.mode', ['summary', 'detailed']);
394  validateOneOf(execution, 'options.execution', ['default', 'eager']);
395  const result = _measureMemory(measureMemoryModes[mode],
396                                measureMemoryExecutions[execution]);
397  if (result === undefined) {
398    return PromiseReject(new ERR_CONTEXT_NOT_INITIALIZED());
399  }
400  return result;
401}
402
403module.exports = {
404  Script,
405  createContext,
406  createScript,
407  runInContext,
408  runInNewContext,
409  runInThisContext,
410  isContext,
411  compileFunction,
412  measureMemory,
413};
414
415if (require('internal/options').getOptionValue('--experimental-vm-modules')) {
416  const {
417    Module, SourceTextModule, SyntheticModule,
418  } = require('internal/vm/module');
419  module.exports.Module = Module;
420  module.exports.SourceTextModule = SourceTextModule;
421  module.exports.SyntheticModule = SyntheticModule;
422}
423