• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// This file creates the internal module & binding loaders used by built-in
2// modules. In contrast, user land modules are loaded using
3// lib/internal/modules/cjs/loader.js (CommonJS Modules) or
4// lib/internal/modules/esm/* (ES Modules).
5//
6// This file is compiled and run by node.cc before bootstrap/node.js
7// was called, therefore the loaders are bootstrapped before we start to
8// actually bootstrap Node.js. It creates the following objects:
9//
10// C++ binding loaders:
11// - process.binding(): the legacy C++ binding loader, accessible from user land
12//   because it is an object attached to the global process object.
13//   These C++ bindings are created using NODE_BUILTIN_MODULE_CONTEXT_AWARE()
14//   and have their nm_flags set to NM_F_BUILTIN. We do not make any guarantees
15//   about the stability of these bindings, but still have to take care of
16//   compatibility issues caused by them from time to time.
17// - process._linkedBinding(): intended to be used by embedders to add
18//   additional C++ bindings in their applications. These C++ bindings
19//   can be created using NODE_BINDING_CONTEXT_AWARE_CPP() with the flag
20//   NM_F_LINKED.
21// - internalBinding(): the private internal C++ binding loader, inaccessible
22//   from user land unless through `require('internal/test/binding')`.
23//   These C++ bindings are created using NODE_BINDING_CONTEXT_AWARE_INTERNAL()
24//   and have their nm_flags set to NM_F_INTERNAL.
25//
26// Internal JavaScript module loader:
27// - BuiltinModule: a minimal module system used to load the JavaScript core
28//   modules found in lib/**/*.js and deps/**/*.js. All core modules are
29//   compiled into the node binary via node_javascript.cc generated by js2c.py,
30//   so they can be loaded faster without the cost of I/O. This class makes the
31//   lib/internal/*, deps/internal/* modules and internalBinding() available by
32//   default to core modules, and lets the core modules require itself via
33//   require('internal/bootstrap/loaders') even when this file is not written in
34//   CommonJS style.
35//
36// Other objects:
37// - process.moduleLoadList: an array recording the bindings and the modules
38//   loaded in the process and the order in which they are loaded.
39
40'use strict';
41
42// This file is compiled as if it's wrapped in a function with arguments
43// passed by node::RunBootstrapping()
44/* global process, getLinkedBinding, getInternalBinding, primordials */
45
46const {
47  ArrayFrom,
48  ArrayPrototypeMap,
49  ArrayPrototypePush,
50  ArrayPrototypeSlice,
51  Error,
52  ObjectCreate,
53  ObjectDefineProperty,
54  ObjectKeys,
55  ObjectPrototypeHasOwnProperty,
56  ObjectSetPrototypeOf,
57  ReflectGet,
58  SafeMap,
59  SafeSet,
60  String,
61  StringPrototypeStartsWith,
62  StringPrototypeSlice,
63  TypeError,
64} = primordials;
65
66// Set up process.moduleLoadList.
67const moduleLoadList = [];
68ObjectDefineProperty(process, 'moduleLoadList', {
69  __proto__: null,
70  value: moduleLoadList,
71  configurable: true,
72  enumerable: true,
73  writable: false,
74});
75
76
77// internalBindingAllowlist contains the name of internalBinding modules
78// that are allowed for access via process.binding()... This is used
79// to provide a transition path for modules that are being moved over to
80// internalBinding.
81const internalBindingAllowlist = new SafeSet([
82  'async_wrap',
83  'buffer',
84  'cares_wrap',
85  'config',
86  'constants',
87  'contextify',
88  'crypto',
89  'fs',
90  'fs_event_wrap',
91  'http_parser',
92  'icu',
93  'inspector',
94  'js_stream',
95  'natives',
96  'os',
97  'pipe_wrap',
98  'process_wrap',
99  'signal_wrap',
100  'spawn_sync',
101  'stream_wrap',
102  'tcp_wrap',
103  'tls_wrap',
104  'tty_wrap',
105  'udp_wrap',
106  'url',
107  'util',
108  'uv',
109  'v8',
110  'zlib',
111]);
112
113const runtimeDeprecatedList = new SafeSet([
114  'async_wrap',
115  'crypto',
116  'http_parser',
117  'signal_wrap',
118  'url',
119  'v8',
120]);
121
122const legacyWrapperList = new SafeSet([
123  'util',
124]);
125
126// Modules that can only be imported via the node: scheme.
127const schemelessBlockList = new SafeSet([
128  'test',
129  'test/reporters',
130]);
131
132// Set up process.binding() and process._linkedBinding().
133{
134  const bindingObj = ObjectCreate(null);
135
136  process.binding = function binding(module) {
137    module = String(module);
138    // Deprecated specific process.binding() modules, but not all, allow
139    // selective fallback to internalBinding for the deprecated ones.
140    if (internalBindingAllowlist.has(module)) {
141      if (runtimeDeprecatedList.has(module)) {
142        runtimeDeprecatedList.delete(module);
143        process.emitWarning(
144          `Access to process.binding('${module}') is deprecated.`,
145          'DeprecationWarning',
146          'DEP0111');
147      }
148      if (legacyWrapperList.has(module)) {
149        return requireBuiltin('internal/legacy/processbinding')[module]();
150      }
151      return internalBinding(module);
152    }
153    // eslint-disable-next-line no-restricted-syntax
154    throw new Error(`No such module: ${module}`);
155  };
156
157  process._linkedBinding = function _linkedBinding(module) {
158    module = String(module);
159    let mod = bindingObj[module];
160    if (typeof mod !== 'object')
161      mod = bindingObj[module] = getLinkedBinding(module);
162    return mod;
163  };
164}
165
166// Set up internalBinding() in the closure.
167/**
168 * @type {InternalBinding}
169 */
170let internalBinding;
171{
172  const bindingObj = ObjectCreate(null);
173  // eslint-disable-next-line no-global-assign
174  internalBinding = function internalBinding(module) {
175    let mod = bindingObj[module];
176    if (typeof mod !== 'object') {
177      mod = bindingObj[module] = getInternalBinding(module);
178      ArrayPrototypePush(moduleLoadList, `Internal Binding ${module}`);
179    }
180    return mod;
181  };
182}
183
184const loaderId = 'internal/bootstrap/loaders';
185const {
186  builtinIds,
187  compileFunction,
188} = internalBinding('builtins');
189
190const getOwn = (target, property, receiver) => {
191  return ObjectPrototypeHasOwnProperty(target, property) ?
192    ReflectGet(target, property, receiver) :
193    undefined;
194};
195
196/**
197 * An internal abstraction for the built-in JavaScript modules of Node.js.
198 * Be careful not to expose this to user land unless --expose-internals is
199 * used, in which case there is no compatibility guarantee about this class.
200 */
201class BuiltinModule {
202  /**
203   * A map from the module IDs to the module instances.
204   * @type {Map<string, BuiltinModule>}
205   */
206  static map = new SafeMap(
207    ArrayPrototypeMap(builtinIds, (id) => [id, new BuiltinModule(id)]),
208  );
209
210  constructor(id) {
211    this.filename = `${id}.js`;
212    this.id = id;
213    this.canBeRequiredByUsers = !StringPrototypeStartsWith(id, 'internal/');
214
215    // The CJS exports object of the module.
216    this.exports = {};
217    // States used to work around circular dependencies.
218    this.loaded = false;
219    this.loading = false;
220
221    // The following properties are used by the ESM implementation and only
222    // initialized when the built-in module is loaded by users.
223    /**
224     * The C++ ModuleWrap binding used to interface with the ESM implementation.
225     * @type {ModuleWrap|undefined}
226     */
227    this.module = undefined;
228    /**
229     * Exported names for the ESM imports.
230     * @type {string[]|undefined}
231     */
232    this.exportKeys = undefined;
233  }
234
235  // To be called during pre-execution when --expose-internals is on.
236  // Enables the user-land module loader to access internal modules.
237  static exposeInternals() {
238    for (const { 0: id, 1: mod } of BuiltinModule.map) {
239      // Do not expose this to user land even with --expose-internals.
240      if (id !== loaderId) {
241        mod.canBeRequiredByUsers = true;
242      }
243    }
244  }
245
246  static exists(id) {
247    return BuiltinModule.map.has(id);
248  }
249
250  static canBeRequiredByUsers(id) {
251    const mod = BuiltinModule.map.get(id);
252    return mod && mod.canBeRequiredByUsers;
253  }
254
255  // Determine if a core module can be loaded without the node: prefix. This
256  // function does not validate if the module actually exists.
257  static canBeRequiredWithoutScheme(id) {
258    return !schemelessBlockList.has(id);
259  }
260
261  static getSchemeOnlyModuleNames() {
262    return ArrayFrom(schemelessBlockList);
263  }
264
265  // Used by user-land module loaders to compile and load builtins.
266  compileForPublicLoader() {
267    if (!this.canBeRequiredByUsers) {
268      // No code because this is an assertion against bugs
269      // eslint-disable-next-line no-restricted-syntax
270      throw new Error(`Should not compile ${this.id} for public use`);
271    }
272    this.compileForInternalLoader();
273    if (!this.exportKeys) {
274      // When using --expose-internals, we do not want to reflect the named
275      // exports from core modules as this can trigger unnecessary getters.
276      const internal = StringPrototypeStartsWith(this.id, 'internal/');
277      this.exportKeys = internal ? [] : ObjectKeys(this.exports);
278    }
279    this.getESMFacade();
280    this.syncExports();
281    return this.exports;
282  }
283
284  getESMFacade() {
285    if (this.module) return this.module;
286    const { ModuleWrap } = internalBinding('module_wrap');
287    // TODO(aduh95): move this to C++, alongside the initialization of the class.
288    ObjectSetPrototypeOf(ModuleWrap.prototype, null);
289    const url = `node:${this.id}`;
290    const builtin = this;
291    const exportsKeys = ArrayPrototypeSlice(this.exportKeys);
292    ArrayPrototypePush(exportsKeys, 'default');
293    this.module = new ModuleWrap(
294      url, undefined, exportsKeys,
295      function() {
296        builtin.syncExports();
297        this.setExport('default', builtin.exports);
298      });
299    // Ensure immediate sync execution to capture exports now
300    this.module.instantiate();
301    this.module.evaluate(-1, false);
302    return this.module;
303  }
304
305  // Provide named exports for all builtin libraries so that the libraries
306  // may be imported in a nicer way for ESM users. The default export is left
307  // as the entire namespace (module.exports) and updates when this function is
308  // called so that APMs and other behavior are supported.
309  syncExports() {
310    const names = this.exportKeys;
311    if (this.module) {
312      for (let i = 0; i < names.length; i++) {
313        const exportName = names[i];
314        if (exportName === 'default') continue;
315        this.module.setExport(exportName,
316                              getOwn(this.exports, exportName, this.exports));
317      }
318    }
319  }
320
321  compileForInternalLoader() {
322    if (this.loaded || this.loading) {
323      return this.exports;
324    }
325
326    const id = this.id;
327    this.loading = true;
328
329    try {
330      const requireFn = StringPrototypeStartsWith(this.id, 'internal/deps/') ?
331        requireWithFallbackInDeps : requireBuiltin;
332
333      const fn = compileFunction(id);
334      // Arguments must match the parameters specified in
335      // BuiltinLoader::LookupAndCompile().
336      fn(this.exports, requireFn, this, process, internalBinding, primordials);
337
338      this.loaded = true;
339    } finally {
340      this.loading = false;
341    }
342
343    // "NativeModule" is a legacy name of "BuiltinModule". We keep it
344    // here to avoid breaking users who parse process.moduleLoadList.
345    ArrayPrototypePush(moduleLoadList, `NativeModule ${id}`);
346    return this.exports;
347  }
348}
349
350// Think of this as module.exports in this file even though it is not
351// written in CommonJS style.
352const loaderExports = {
353  internalBinding,
354  BuiltinModule,
355  require: requireBuiltin,
356};
357
358function requireBuiltin(id) {
359  if (id === loaderId) {
360    return loaderExports;
361  }
362
363  const mod = BuiltinModule.map.get(id);
364  // Can't load the internal errors module from here, have to use a raw error.
365  // eslint-disable-next-line no-restricted-syntax
366  if (!mod) throw new TypeError(`Missing internal module '${id}'`);
367  return mod.compileForInternalLoader();
368}
369
370// Allow internal modules from dependencies to require
371// other modules from dependencies by providing fallbacks.
372function requireWithFallbackInDeps(request) {
373  if (StringPrototypeStartsWith(request, 'node:')) {
374    request = StringPrototypeSlice(request, 5);
375  } else if (!BuiltinModule.map.has(request)) {
376    request = `internal/deps/${request}`;
377  }
378  return requireBuiltin(request);
379}
380
381// Pass the exports back to C++ land for C++ internals to use.
382return loaderExports;
383