1'use strict';
2
3const {
4  NumberParseInt,
5  ObjectDefineProperties,
6  ObjectDefineProperty,
7  SafeMap,
8  StringPrototypeStartsWith,
9  Symbol,
10  SymbolDispose,
11  SymbolAsyncDispose,
12  globalThis,
13} = primordials;
14
15const {
16  getOptionValue,
17  refreshOptions,
18} = require('internal/options');
19const { reconnectZeroFillToggle } = require('internal/buffer');
20const {
21  defineOperation,
22  exposeInterface,
23  exposeLazyInterfaces,
24  defineReplaceableLazyAttribute,
25  setupCoverageHooks,
26} = require('internal/util');
27
28const {
29  ERR_MANIFEST_ASSERT_INTEGRITY,
30} = require('internal/errors').codes;
31const assert = require('internal/assert');
32const {
33  namespace: {
34    addSerializeCallback,
35    isBuildingSnapshot,
36  },
37} = require('internal/v8/startup_snapshot');
38
39function prepareMainThreadExecution(expandArgv1 = false, initializeModules = true) {
40  return prepareExecution({
41    expandArgv1,
42    initializeModules,
43    isMainThread: true,
44  });
45}
46
47function prepareWorkerThreadExecution() {
48  prepareExecution({
49    expandArgv1: false,
50    initializeModules: false,  // Will need to initialize it after policy setup
51    isMainThread: false,
52  });
53}
54
55function prepareExecution(options) {
56  const { expandArgv1, initializeModules, isMainThread } = options;
57
58  refreshRuntimeOptions();
59  reconnectZeroFillToggle();
60
61  // Patch the process object and get the resolved main entry point.
62  const mainEntry = patchProcessObject(expandArgv1);
63  setupTraceCategoryState();
64  setupPerfHooks();
65  setupInspectorHooks();
66  setupWarningHandler();
67  setupFetch();
68  setupWebCrypto();
69  setupCustomEvent();
70  setupCodeCoverage();
71  setupDebugEnv();
72  // Process initial diagnostic reporting configuration, if present.
73  initializeReport();
74  initializeSourceMapsHandlers();
75  initializeDeprecations();
76
77  require('internal/dns/utils').initializeDns();
78
79  setupSymbolDisposePolyfill();
80
81  if (isMainThread) {
82    assert(internalBinding('worker').isMainThread);
83    // Worker threads will get the manifest in the message handler.
84    const policy = readPolicyFromDisk();
85    if (policy) {
86      require('internal/process/policy')
87        .setup(policy.manifestSrc, policy.manifestURL);
88    }
89
90    // Print stack trace on `SIGINT` if option `--trace-sigint` presents.
91    setupStacktracePrinterOnSigint();
92    initializeReportSignalHandlers();  // Main-thread-only.
93    initializeHeapSnapshotSignalHandlers();
94    // If the process is spawned with env NODE_CHANNEL_FD, it's probably
95    // spawned by our child_process module, then initialize IPC.
96    // This attaches some internal event listeners and creates:
97    // process.send(), process.channel, process.connected,
98    // process.disconnect().
99    setupChildProcessIpcChannel();
100    // If this is a worker in cluster mode, start up the communication
101    // channel. This needs to be done before any user code gets executed
102    // (including preload modules).
103    initializeClusterIPC();
104
105    // TODO(joyeecheung): do this for worker threads as well.
106    require('internal/v8/startup_snapshot').runDeserializeCallbacks();
107  } else {
108    assert(!internalBinding('worker').isMainThread);
109    // The setup should be called in LOAD_SCRIPT message handler.
110    assert(!initializeModules);
111  }
112
113  if (initializeModules) {
114    setupUserModules();
115  }
116
117  return mainEntry;
118}
119
120function setupSymbolDisposePolyfill() {
121  // TODO(MoLow): Remove this polyfill once Symbol.dispose and Symbol.asyncDispose are available in V8.
122  // eslint-disable-next-line node-core/prefer-primordials
123  if (typeof Symbol.dispose !== 'symbol') {
124    ObjectDefineProperty(Symbol, 'dispose', {
125      __proto__: null,
126      configurable: false,
127      enumerable: false,
128      value: SymbolDispose,
129      writable: false,
130    });
131  }
132
133  // eslint-disable-next-line node-core/prefer-primordials
134  if (typeof Symbol.asyncDispose !== 'symbol') {
135    ObjectDefineProperty(Symbol, 'asyncDispose', {
136      __proto__: null,
137      configurable: false,
138      enumerable: false,
139      value: SymbolAsyncDispose,
140      writable: false,
141    });
142  }
143}
144
145function setupUserModules(isLoaderWorker = false) {
146  initializeCJSLoader();
147  initializeESMLoader(isLoaderWorker);
148  const CJSLoader = require('internal/modules/cjs/loader');
149  assert(!CJSLoader.hasLoadedAnyUserCJSModule);
150  // Loader workers are responsible for doing this themselves.
151  if (isLoaderWorker) {
152    return;
153  }
154  loadPreloadModules();
155  // Need to be done after --require setup.
156  initializeFrozenIntrinsics();
157}
158
159function refreshRuntimeOptions() {
160  refreshOptions();
161}
162
163/**
164 * Patch the process object with legacy properties and normalizations.
165 * Replace `process.argv[0]` with `process.execPath`, preserving the original `argv[0]` value as `process.argv0`.
166 * Replace `process.argv[1]` with the resolved absolute file path of the entry point, if found.
167 * @param {boolean} expandArgv1 - Whether to replace `process.argv[1]` with the resolved absolute file path of
168 * the main entry point.
169 */
170function patchProcessObject(expandArgv1) {
171  const binding = internalBinding('process_methods');
172  binding.patchProcessObject(process);
173
174  require('internal/process/per_thread').refreshHrtimeBuffer();
175
176  // Since we replace process.argv[0] below, preserve the original value in case the user needs it.
177  ObjectDefineProperty(process, 'argv0', {
178    __proto__: null,
179    enumerable: true,
180    // Only set it to true during snapshot building.
181    configurable: getOptionValue('--build-snapshot'),
182    value: process.argv[0],
183  });
184
185  process.exitCode = undefined;
186  process._exiting = false;
187  process.argv[0] = process.execPath;
188
189  /** @type {string} */
190  let mainEntry;
191  // If requested, update process.argv[1] to replace whatever the user provided with the resolved absolute file path of
192  // the entry point.
193  if (expandArgv1 && process.argv[1] &&
194      !StringPrototypeStartsWith(process.argv[1], '-')) {
195    // Expand process.argv[1] into a full path.
196    const path = require('path');
197    try {
198      mainEntry = path.resolve(process.argv[1]);
199      process.argv[1] = mainEntry;
200    } catch {
201      // Continue regardless of error.
202    }
203  }
204
205  // We need to initialize the global console here again with process.stdout
206  // and friends for snapshot deserialization.
207  const globalConsole = require('internal/console/global');
208  const { initializeGlobalConsole } = require('internal/console/constructor');
209  initializeGlobalConsole(globalConsole);
210
211  // TODO(joyeecheung): most of these should be deprecated and removed,
212  // except some that we need to be able to mutate during run time.
213  addReadOnlyProcessAlias('_eval', '--eval');
214  addReadOnlyProcessAlias('_print_eval', '--print');
215  addReadOnlyProcessAlias('_syntax_check_only', '--check');
216  addReadOnlyProcessAlias('_forceRepl', '--interactive');
217  addReadOnlyProcessAlias('_preload_modules', '--require');
218  addReadOnlyProcessAlias('noDeprecation', '--no-deprecation');
219  addReadOnlyProcessAlias('noProcessWarnings', '--no-warnings');
220  addReadOnlyProcessAlias('traceProcessWarnings', '--trace-warnings');
221  addReadOnlyProcessAlias('throwDeprecation', '--throw-deprecation');
222  addReadOnlyProcessAlias('profProcess', '--prof-process');
223  addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation');
224  addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false);
225  addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false);
226
227  return mainEntry;
228}
229
230function addReadOnlyProcessAlias(name, option, enumerable = true) {
231  const value = getOptionValue(option);
232  if (value) {
233    ObjectDefineProperty(process, name, {
234      __proto__: null,
235      writable: false,
236      configurable: true,
237      enumerable,
238      value,
239    });
240  }
241}
242
243function setupWarningHandler() {
244  const {
245    onWarning,
246    resetForSerialization,
247  } = require('internal/process/warning');
248  if (getOptionValue('--warnings') &&
249    process.env.NODE_NO_WARNINGS !== '1') {
250    process.on('warning', onWarning);
251
252    // The code above would add the listener back during deserialization,
253    // if applicable.
254    if (isBuildingSnapshot()) {
255      addSerializeCallback(() => {
256        process.removeListener('warning', onWarning);
257        resetForSerialization();
258      });
259    }
260  }
261}
262
263// https://fetch.spec.whatwg.org/
264function setupFetch() {
265  if (process.config.variables.node_no_browser_globals ||
266      getOptionValue('--no-experimental-fetch')) {
267    return;
268  }
269
270  let undici;
271  function lazyUndici() {
272    if (undici) {
273      return undici;
274    }
275
276    undici = require('internal/deps/undici/undici');
277    return undici;
278  }
279
280  async function fetch(input, init = undefined) {
281    return lazyUndici().fetch(input, init);
282  }
283
284  defineOperation(globalThis, 'fetch', fetch);
285
286  function lazyInterface(name) {
287    return {
288      configurable: true,
289      enumerable: false,
290      get() {
291        return lazyUndici()[name];
292      },
293      set(value) {
294        exposeInterface(globalThis, name, value);
295      },
296    };
297  }
298
299  ObjectDefineProperties(globalThis, {
300    FormData: lazyInterface('FormData'),
301    Headers: lazyInterface('Headers'),
302    Request: lazyInterface('Request'),
303    Response: lazyInterface('Response'),
304  });
305
306  // The WebAssembly Web API: https://webassembly.github.io/spec/web-api
307  internalBinding('wasm_web_api').setImplementation((streamState, source) => {
308    require('internal/wasm_web_api').wasmStreamingCallback(streamState, source);
309  });
310}
311
312// TODO(aduh95): move this to internal/bootstrap/browser when the CLI flag is
313//               removed.
314function setupWebCrypto() {
315  if (process.config.variables.node_no_browser_globals ||
316      !getOptionValue('--experimental-global-webcrypto')) {
317    return;
318  }
319
320  if (internalBinding('config').hasOpenSSL) {
321    defineReplaceableLazyAttribute(
322      globalThis, 'internal/crypto/webcrypto', ['crypto'], false,
323    );
324    exposeLazyInterfaces(
325      globalThis, 'internal/crypto/webcrypto',
326      ['Crypto', 'CryptoKey', 'SubtleCrypto'],
327    );
328  }
329}
330
331function setupCodeCoverage() {
332  // Resolve the coverage directory to an absolute path, and
333  // overwrite process.env so that the original path gets passed
334  // to child processes even when they switch cwd. Don't do anything if the
335  // --experimental-test-coverage flag is present, as the test runner will
336  // handle coverage.
337  if (process.env.NODE_V8_COVERAGE &&
338      !getOptionValue('--experimental-test-coverage')) {
339    process.env.NODE_V8_COVERAGE =
340      setupCoverageHooks(process.env.NODE_V8_COVERAGE);
341  }
342}
343
344// TODO(daeyeon): move this to internal/bootstrap/browser when the CLI flag is
345//                removed.
346function setupCustomEvent() {
347  if (process.config.variables.node_no_browser_globals ||
348      !getOptionValue('--experimental-global-customevent')) {
349    return;
350  }
351  const { CustomEvent } = require('internal/event_target');
352  exposeInterface(globalThis, 'CustomEvent', CustomEvent);
353}
354
355function setupStacktracePrinterOnSigint() {
356  if (!getOptionValue('--trace-sigint')) {
357    return;
358  }
359  const { SigintWatchdog } = require('internal/watchdog');
360
361  const watchdog = new SigintWatchdog();
362  watchdog.start();
363}
364
365function initializeReport() {
366  ObjectDefineProperty(process, 'report', {
367    __proto__: null,
368    enumerable: true,
369    configurable: true,
370    get() {
371      const { report } = require('internal/process/report');
372      return report;
373    },
374  });
375}
376
377function setupDebugEnv() {
378  require('internal/util/debuglog').initializeDebugEnv(process.env.NODE_DEBUG);
379  if (getOptionValue('--expose-internals')) {
380    require('internal/bootstrap/realm').BuiltinModule.exposeInternals();
381  }
382}
383
384// This has to be called after initializeReport() is called
385function initializeReportSignalHandlers() {
386  if (getOptionValue('--report-on-signal')) {
387    const { addSignalHandler } = require('internal/process/report');
388    addSignalHandler();
389  }
390}
391
392function initializeHeapSnapshotSignalHandlers() {
393  const signal = getOptionValue('--heapsnapshot-signal');
394
395  if (!signal)
396    return;
397
398  require('internal/validators').validateSignalName(signal);
399  const { writeHeapSnapshot } = require('v8');
400
401  function doWriteHeapSnapshot() {
402    writeHeapSnapshot();
403  }
404  process.on(signal, doWriteHeapSnapshot);
405
406  // The code above would add the listener back during deserialization,
407  // if applicable.
408  if (isBuildingSnapshot()) {
409    addSerializeCallback(() => {
410      process.removeListener(signal, doWriteHeapSnapshot);
411    });
412  }
413}
414
415function setupTraceCategoryState() {
416  const { isTraceCategoryEnabled } = internalBinding('trace_events');
417  const { toggleTraceCategoryState } = require('internal/process/per_thread');
418  toggleTraceCategoryState(isTraceCategoryEnabled('node.async_hooks'));
419}
420
421function setupPerfHooks() {
422  require('internal/perf/utils').refreshTimeOrigin();
423}
424
425function setupInspectorHooks() {
426  // If Debugger.setAsyncCallStackDepth is sent during bootstrap,
427  // we cannot immediately call into JS to enable the hooks, which could
428  // interrupt the JS execution of bootstrap. So instead we save the
429  // notification in the inspector agent if it's sent in the middle of
430  // bootstrap, and process the notification later here.
431  if (internalBinding('config').hasInspector) {
432    const {
433      enable,
434      disable,
435    } = require('internal/inspector_async_hook');
436    internalBinding('inspector').registerAsyncHook(enable, disable);
437  }
438}
439
440// In general deprecations are initialized wherever the APIs are implemented,
441// this is used to deprecate APIs implemented in C++ where the deprecation
442// utilities are not easily accessible.
443function initializeDeprecations() {
444  const { deprecate } = require('internal/util');
445  const pendingDeprecation = getOptionValue('--pending-deprecation');
446
447  // DEP0103: access to `process.binding('util').isX` type checkers
448  // TODO(addaleax): Turn into a full runtime deprecation.
449  const utilBinding = internalBinding('util');
450  const types = require('internal/util/types');
451  for (const name of [
452    'isArrayBuffer',
453    'isArrayBufferView',
454    'isAsyncFunction',
455    'isDataView',
456    'isDate',
457    'isExternal',
458    'isMap',
459    'isMapIterator',
460    'isNativeError',
461    'isPromise',
462    'isRegExp',
463    'isSet',
464    'isSetIterator',
465    'isTypedArray',
466    'isUint8Array',
467    'isAnyArrayBuffer',
468  ]) {
469    utilBinding[name] = pendingDeprecation ?
470      deprecate(types[name],
471                'Accessing native typechecking bindings of Node ' +
472                'directly is deprecated. ' +
473                `Please use \`util.types.${name}\` instead.`,
474                'DEP0103') :
475      types[name];
476  }
477
478  // TODO(joyeecheung): this is a legacy property exposed to process.
479  // Now that we use the config binding to carry this information, remove
480  // it from the process. We may consider exposing it properly in
481  // process.features.
482  const { noBrowserGlobals } = internalBinding('config');
483  if (noBrowserGlobals) {
484    ObjectDefineProperty(process, '_noBrowserGlobals', {
485      __proto__: null,
486      writable: false,
487      enumerable: true,
488      configurable: true,
489      value: noBrowserGlobals,
490    });
491  }
492
493  if (pendingDeprecation) {
494    process.binding = deprecate(process.binding,
495                                'process.binding() is deprecated. ' +
496                                'Please use public APIs instead.', 'DEP0111');
497
498    process._tickCallback = deprecate(process._tickCallback,
499                                      'process._tickCallback() is deprecated',
500                                      'DEP0134');
501  }
502}
503
504function setupChildProcessIpcChannel() {
505  if (process.env.NODE_CHANNEL_FD) {
506    const assert = require('internal/assert');
507
508    const fd = NumberParseInt(process.env.NODE_CHANNEL_FD, 10);
509    assert(fd >= 0);
510
511    // Make sure it's not accidentally inherited by child processes.
512    delete process.env.NODE_CHANNEL_FD;
513
514    const serializationMode =
515      process.env.NODE_CHANNEL_SERIALIZATION_MODE || 'json';
516    delete process.env.NODE_CHANNEL_SERIALIZATION_MODE;
517
518    require('child_process')._forkChild(fd, serializationMode);
519    assert(process.send);
520  }
521}
522
523function initializeClusterIPC() {
524  if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
525    const cluster = require('cluster');
526    cluster._setupWorker();
527    // Make sure it's not accidentally inherited by child processes.
528    delete process.env.NODE_UNIQUE_ID;
529  }
530}
531
532function readPolicyFromDisk() {
533  const experimentalPolicy = getOptionValue('--experimental-policy');
534  if (experimentalPolicy) {
535    process.emitWarning('Policies are experimental.',
536                        'ExperimentalWarning');
537    const { pathToFileURL, URL } = require('internal/url');
538    // URL here as it is slightly different parsing
539    // no bare specifiers for now
540    let manifestURL;
541    if (require('path').isAbsolute(experimentalPolicy)) {
542      manifestURL = pathToFileURL(experimentalPolicy);
543    } else {
544      const cwdURL = pathToFileURL(process.cwd());
545      cwdURL.pathname += '/';
546      manifestURL = new URL(experimentalPolicy, cwdURL);
547    }
548    const fs = require('fs');
549    const src = fs.readFileSync(manifestURL, 'utf8');
550    const experimentalPolicyIntegrity = getOptionValue('--policy-integrity');
551    if (experimentalPolicyIntegrity) {
552      const SRI = require('internal/policy/sri');
553      const { createHash, timingSafeEqual } = require('crypto');
554      const realIntegrities = new SafeMap();
555      const integrityEntries = SRI.parse(experimentalPolicyIntegrity);
556      let foundMatch = false;
557      for (let i = 0; i < integrityEntries.length; i++) {
558        const {
559          algorithm,
560          value: expected,
561        } = integrityEntries[i];
562        const hash = createHash(algorithm);
563        hash.update(src);
564        const digest = hash.digest();
565        if (digest.length === expected.length &&
566          timingSafeEqual(digest, expected)) {
567          foundMatch = true;
568          break;
569        }
570        realIntegrities.set(algorithm, digest.toString('base64'));
571      }
572      if (!foundMatch) {
573        throw new ERR_MANIFEST_ASSERT_INTEGRITY(manifestURL, realIntegrities);
574      }
575    }
576    return {
577      manifestSrc: src, manifestURL: manifestURL.href,
578    };
579  }
580}
581
582function initializeCJSLoader() {
583  const { initializeCJS } = require('internal/modules/cjs/loader');
584  initializeCJS();
585}
586
587function initializeESMLoader(isLoaderWorker) {
588  const { initializeESM } = require('internal/modules/esm/utils');
589  initializeESM(isLoaderWorker);
590
591  // Patch the vm module when --experimental-vm-modules is on.
592  // Please update the comments in vm.js when this block changes.
593  if (getOptionValue('--experimental-vm-modules')) {
594    const {
595      Module, SourceTextModule, SyntheticModule,
596    } = require('internal/vm/module');
597    const vm = require('vm');
598    vm.Module = Module;
599    vm.SourceTextModule = SourceTextModule;
600    vm.SyntheticModule = SyntheticModule;
601  }
602}
603
604function initializeSourceMapsHandlers() {
605  const {
606    setSourceMapsEnabled,
607  } = require('internal/source_map/source_map_cache');
608  setSourceMapsEnabled(getOptionValue('--enable-source-maps'));
609}
610
611function initializeFrozenIntrinsics() {
612  if (getOptionValue('--frozen-intrinsics')) {
613    process.emitWarning('The --frozen-intrinsics flag is experimental',
614                        'ExperimentalWarning');
615    require('internal/freeze_intrinsics')();
616  }
617}
618
619function loadPreloadModules() {
620  // For user code, we preload modules if `-r` is passed
621  const preloadModules = getOptionValue('--require');
622  if (preloadModules && preloadModules.length > 0) {
623    const {
624      Module: {
625        _preloadModules,
626      },
627    } = require('internal/modules/cjs/loader');
628    _preloadModules(preloadModules);
629  }
630}
631
632function markBootstrapComplete() {
633  internalBinding('performance').markBootstrapComplete();
634}
635
636module.exports = {
637  setupUserModules,
638  prepareMainThreadExecution,
639  prepareWorkerThreadExecution,
640  markBootstrapComplete,
641  loadPreloadModules,
642  initializeFrozenIntrinsics,
643};
644