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