Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: basic support for primordials in undici #55783

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

tlhunter
Copy link
Contributor

@tlhunter tlhunter commented Nov 8, 2024

This PR isn't intended to be merged in its current form. Most notably it modifies deps/undici/undici.js which is the output from a build script.

The gist of this change is that I'm working on a side project that, among other things, deletes a ton of global variables. So far all of the internal Node.js modules seem unaffected thanks to the usage of primordials. However, Undici is an external module bundled into Node.js and therefore doesn't have direct access to primordials.

I attempted to solve this by wrapping the Undici code in a function that provides the primordials as arguments. It's similar to the function that wraps CJS files to inject __filename. It's a pretty basic solution and I'm not sure if it's the best.

It seems like wrapping and injecting primordials could be a useful pattern for any sort of vendored code to get them to be more resilient to weird changes like deletion of globals like the internal modules are.

Also note that this change is only like a "phase 1" solution where globals are made available, such as WebAssembly. However the change isn't resilient to, say delete Array.prototype.push. Fixing that would require a lot of work and would probably require an AST transform to replace arr.push(1) with ArrayPrototypePush(arr, 1). This could maybe be doable if the library is written in TypeScript but I suspect it would be impossible if the library is written in JavaScript.

More discussion: nodejs/undici#3812

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net
  • @nodejs/security-wg
  • @nodejs/startup
  • @nodejs/web-standards

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Nov 8, 2024
@aduh95
Copy link
Contributor

aduh95 commented Nov 8, 2024

I attempted to solve this by wrapping the Undici code in a function that provides the primordials as arguments. It's similar to the function that wraps CJS files to inject __filename. It's a pretty basic solution and I'm not sure if it's the best.

Actually you don't need that, primordials is already an argument of the wrapper, so you could just use it instead of re-wrapping the code.

AbortController, WebAssembly, etc. are not available in primordials because they are created much later, either because it's created by us (e.g. AbortController is implemented in internal/abort_controller), either because it's behind a runtime V8 flag as explained in

// WebAssembly is not available in primordials because it can be
// disabled with --jitless CLI flag.

@tlhunter
Copy link
Contributor Author

tlhunter commented Nov 8, 2024

@aduh95 ah, so maybe I would need to do something like this instead?

  const globals = { WebAssembly, URL, URLSearchParams }; // probably export this from a file next to primordials.js
  return undici ??= require('internal/deps/undici/undici')(primordials, globals);

@aduh95
Copy link
Contributor

aduh95 commented Nov 8, 2024

No it's not what I meant, I'm saying the full diff should be:

diff --git a/deps/undici/undici.js b/deps/undici/undici.js
index 1a2312842a6..ef0becdda26 100644
--- a/deps/undici/undici.js
+++ b/deps/undici/undici.js
@@ -1,4 +1,10 @@
 "use strict";
+const {
+  globalThis,
+} = primordials;
+const { AbortController } = require('internal/abort_controller');
+const { URL } = require('internal/url');
+const { WebAssembly } = globalThis;
 var __defProp = Object.defineProperty;
 var __getOwnPropNames = Object.getOwnPropertyNames;
 var __name = (target, value) => __defProp(target, "name", { value, configurable: true });

I.e. you do not need to wrap the undici source again

$ # Before this change:
$ node -p 'delete globalThis.globalThis;fetch("data:application/json,[]").then(e => e.json())' 
node:internal/deps/undici/undici:7970
      return globalThis[globalDispatcher];
      ^

ReferenceError: globalThis is not defined
    at getGlobalDispatcher2 (node:internal/deps/undici/undici:7970:7)
    at lib/global.js (node:internal/deps/undici/undici:7954:9)
    at __require (node:internal/deps/undici/undici:6:50)
    at node:internal/deps/undici/undici:13179:52
    at BuiltinModule.compileForInternalLoader (node:internal/bootstrap/realm:398:7)
    at requireBuiltin (node:internal/bootstrap/realm:429:14)
    at fetch (node:internal/bootstrap/web/exposed-window-or-worker:69:28)
    at [eval]:1:30
    at runScriptInThisContext (node:internal/vm:209:10)
    at node:internal/process/execution:118:14

Node.js v20.18.0
$ # After applying my diff:
$  ./node -p 'delete globalThis.globalThis;fetch("data:application/json,[]").then(e => e.json())'
Promise { [] }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants