Flatten dictionary members at construction while preserving inheritance
order. Skip member scans for nullish inputs without defaults or required
members, and define own descriptor properties to avoid inherited
setters.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65857
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This adds support for transferring net.BoundSocket instances to other
threads via the worker_threads postMessage() transfer list, and for
sending them to child processes as the sendHandle argument of
subprocess.send(), following on from the BoundSocket introduction.
A BoundSocket reserves a port synchronously at construction time.
Making it transferable means a port can be reserved on one thread or
process and the bound (but not yet listening or connected) TCP handle
handed off to another to listen or connect on, without racing on the
bind.
For threads, BoundSocket implements kTransfer/kTransferList/
kDeserialize, moving the underlying TCP handle with the same mechanism
used for net.Socket and net.Server transfer. For child processes, the
handleConversion entry reuses the same transfer protocol on the
sending side and the same _TransferredBoundSocket deserialization path
on the receiving side; the underlying transport is that of cluster's
shared-handle scheduling: SCM_RIGHTS on Unix and WSADuplicateSocket on
Windows, both of which carry bind state.
In both cases the source instance is left in the adopted state:
address(), fd() and close() throw ERR_SOCKET_HANDLE_ADOPTED. Transfer
requires an un-adopted, open TCP handle, otherwise
ERR_WORKER_HANDLE_NOT_TRANSFERABLE is thrown; pipe (path) binds are
not transferable and throw ERR_INVALID_HANDLE_TYPE when sent over IPC.
On the receiving side the local address is re-derived from the handle
rather than trusted from serialized state.
Signed-off-by: Guy Bedford <guybedford@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64725
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
A file descriptor obtained on a mounted path answers several `node:fs`
calls differently from one on a real file, in both providers:
* `writeFileSync(path, data, { flag: 'r+' })` replaces the whole file
instead of overwriting bytes from offset 0 and keeping the tail.
* Numeric open flags are mapped by treating any write-ish bit as "w":
`O_WRONLY` alone truncates, and `O_RDONLY | O_CREAT` opens the file
write-only and truncates it.
* A handle opened with "a+" starts its read offset at the end of the
file, so the first read returns nothing; O_APPEND only affects writes.
The ZipProvider handle additionally:
* throws EISDIR instead of EBADF when reading a write-only handle or
writing a read-only one;
* leaves stale bytes in place when `ftruncate` grows a file that was
previously shrunk, where real files read back as zeros;
* rejects a BigInt `position` with a TypeError from mixing number and
BigInt arithmetic.
This adds a test that runs the same sequence of calls against a memory
mount and a ZIP mount and expects the real-fs result, so every
divergence shows up as its own failing case.
Proposed solution: decode numeric flags bit by bit (O_TRUNC decides
truncation, O_CREAT decides creation, O_WRONLY/O_RDWR decide access)
instead of collapsing them to a flag string; keep the read offset at 0
for append handles and only force writes to the end; make the handle
`writeFile` for non-truncating flags write at offset 0 without
shrinking; in the ZIP handle use EBADF for access-mode violations,
zero-fill on growth in `#doTruncate`, and coerce `position` with
`Number()` as the memory handle does.
Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
PR-URL: https://github.com/nodejs/node/pull/65854
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Use explicit brand predicates for interface conversion instead of
prototype ancestry. Update CryptoKey and AbortSignal together with the
shared converter contract.
Read internal AbortSignal state during composition. Preserve genuine
signals after prototype changes without invoking shadowed getters.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: GitHub Copilot
PR-URL: https://github.com/nodejs/node/pull/65846
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
Every stream write created a WriteWrap JS object up front, even though
most writes complete synchronously via uv_try_write() and never use it.
Let stream_base_commons pass null instead of a request object.
StreamBase::Write() already creates the wrap object only when the write
does not complete synchronously; return that object to JS (which
attaches oncomplete/callback to it) and a plain error code otherwise.
Writes that complete synchronously now cross the JS/C++ boundary once
and allocate nothing. Callers that pass in a request object
(child_process IPC, webstreams adapters) behave as before.
Since Http2Stream::DoWrite() can invoke the completion callback
synchronously - before JS has attached oncomplete - such completions
are now recorded on the request object's writeStatus field and replayed
by stream_base_commons after dispatch. This also replaces a Has() plus
name-based MakeCallback() pair with a single Get().
Also pre-create the JS fields of WriteWrap instances in the object
template, as was already done for ShutdownWrap, so that they are
in-object properties.
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/64455
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Read buffers for streams that emit their data to JS were allocated per
read: a 64KB backing store, tracked in a map, and then - since reads
rarely fill the whole buffer - reallocated to the right size and copied.
Allocate read buffers from a 64KB slab instead. Reads reserve the
suggested size from the slab and JS receives a view over the slab's
ArrayBuffer at the read's offset, using the offset mechanism that
onStreamRead already supports. Unused reservation space is rewound when
a read returns less than was reserved, so small reads (e.g. TLS records)
share a slab.
This removes the per-read allocations, the map bookkeeping and the
resize copy.
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/64455
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
The module loader manufactures paths under the reserved VFS root that
no layer owns: resolving a mount point as a directory first probes the
sibling names `<mount>.js`, `<mount>.json` and `<mount>.node`, and a
package.json walk-up passes the parents of the mount point. The lookup
declined those because their layer segment is not a plain id, so they
fell through to the native loader and the real file system.
On POSIX that is harmless (ENOTDIR under /dev/null), but on Windows
the root sits under `\\.\nul`, and `\\.\nul\<anything>` opens the NUL
device: libuv reports it as a character device and a read returns
nothing. The loader therefore picked `\\.\nul\vfs\<id>.js` as an
existing file, and the native walk-up above it then read the device as
an empty package.json and failed with ERR_INVALID_PACKAGE_CONFIG for
`\\.\nul\package.json`. Any require() of a mount point hits this on
Windows.
Distinguish "under the root but unowned" from "outside the root" in the
lookup and have every loader override report the former as not found:
stat gives ENOENT, reads and realpath throw ENOENT, the package.json
lookups return their "no package.json" results, and upward walks stop
at the reserved root. Paths outside the root still go to the native
loader as before.
Refs: https://github.com/nodejs/node/pull/65748
Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
PR-URL: https://github.com/nodejs/node/pull/65814
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
fs.cp() and fs.promises.cp() walked the tree in JavaScript with several
thread pool round trips per entry (opendir batches, two stat()s, the
copyFile(), a chmod()), all awaited in sequence: a 2 100-file tree took
~215 ms with ~110 ms of that on the main thread, against ~36 ms for
fs.cpSync(), which copies the tree in C++ when no filter is given.
Factor that C++ walk into CopyDirRecursive(), which records the error
instead of throwing so that it can run on any thread, and run it as one
ThreadPoolWork request (CpDirJob) for fs.cp()/fs.promises.cp() when
the destination directory does not exist yet and nothing has to run per
entry (no filter, no dereference, permission model off). Copying into
an existing tree keeps the JavaScript walk and its rules for what may
already be there. The same tree now takes ~30 ms with under 1 ms on the
main thread.
For that job the walk follows the JavaScript walk's rules rather than
cpSync's: it creates every directory with mkdir() and every file with
an exclusive uv_fs_copyfile() (honouring the copyFile() mode flags) and
fails with EEXIST if anything has appeared in their place since the
JavaScript check, so it never opens or follows something it did not
create; sockets, FIFOs and unknown entries are reported back to
JavaScript, which rejects them with the same SystemErrors as before;
relative link targets are made absolute lexically as path.resolve()
does. cpSync keeps merging into existing directories, skipping special
files and canonicalizing link targets.
The walk now uses the error_code overloads of std::filesystem throughout
(directory iteration included), so an unreadable directory inside the
tree is reported as EACCES by both cp() and cpSync() instead of
terminating the process, which cpSync() has done since the walk moved
to C++. Filesystem errors raised inside the walk keep their codes, with
'cp' or 'copyfile'/'mkdir' as the syscall. With preserveTimestamps the
walk stamps each directory it filled, root included, so cp() keeps
preserving directory times and cpSync() without a filter now does too.
Both file copies set the destination's mode before writing the data,
which clears setuid and setgid, so the walk puts the source mode back
after each file it copied; the JavaScript walk already chmod()s there.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65488
Refs: https://github.com/nodejs/node/pull/58461
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
Reviewed-By: Jake Yuesong Li <jake.yuesong@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
With V8_ENABLE_SANDBOX every ArrayBuffer backing store has to be
allocated inside the sandbox, so memory that Node.js or a library
allocated itself cannot be wrapped and has to be copied in. Several
places already special-cased this, each with its own #ifdef, but a
build with the sandbox enabled still failed to compile
(`kMaxSafeBufferSizeForSandbox`), aborted in `crypto.randomUUID()`
(`SecureBuffer` was UNREACHABLE) and threw from every caller of the
malloc-owning `Buffer::New()` (v8.serialize, string transcoding, IPC,
the public `Buffer::New(isolate, data, length)`), and trace_events,
SEA assets and FFI still wrapped outside memory unconditionally.
Add `AdoptIntoBackingStore()`, which wraps the memory as before in
regular builds and copies it into an isolate-allocated backing store,
running the deleter right away, when the sandbox is enabled, and use it
at all of those sites so the #ifdef lives in one place. The trace
category flag cannot be copied since JS polls it, so under the sandbox
`getCategoryEnabledBuffer()` returns nothing and lib falls back to
`isTraceCategoryEnabled()`; FFI zero-copy views throw
ERR_OPERATION_FAILED there for the same reason.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/62237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
--permission-audit is documented to perform permission checks without
denying access, publishing each violation through the diagnostics
channel while execution continues. Three code paths still enforced:
* fs.lstat() and fs.symlink() are checked in the JavaScript layer,
which threw ERR_ACCESS_DENIED without consulting audit mode. The
isAuditMode() helper introduced for ffi was never wired into fs.
* Addon loading was disabled upfront in Environment setup, so
process.dlopen() failed with ERR_DLOPEN_DISABLED before reaching
the audit-aware permission check, and no audit event was published.
Skip the throw in the JavaScript checks when audit mode is on, keeping
the permission.has() call first so the diagnostics channel event is
still published. Leave native addons enabled in audit mode while still
applying the kAddon scope, so DLOpen() publishes the denial through
the channel and continues to the regular loading path. This also
covers the worker path, which inherits kNoNativeAddons from the same
option.
Each fs API carries its own copy of the check in its sync, callback
and promise flavour, so the regression test exercises all six call
sites, asserting the --permission enforce behaviour as a control.
Fixes: https://github.com/nodejs/node/issues/65419
Refs: https://github.com/nodejs/node/pull/63047
Assisted-by: claude:opus-5
Signed-off-by: Ji Hoon Kang <ivory.ma9ic@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65659
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
When a stream reaches its end via push(null) while nobody is observing
it (no 'readable' listener, not flowing, no pending readable need), do
not schedule the deferred 'readable' emission: it fires into the void
and costs a tick. Record that the end-of-stream notification is owed
instead; attaching a 'readable' listener later emits it, and read()
and resume() reach the end of the stream on their own.
As a side effect, a 'readable' listener attached more than a tick
after an unobserved end now receives the owed 'readable' before 'end',
where it previously received only 'end'.
For an HTTP server this removes one nextTick and one dead emit per
request whose body the handler never reads.
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/65749
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: James M Snell <jasnell@gmail.com>
A native addon inside a virtual file system could not be require()d:
dlopen() and LoadLibrary() open a shared object by path, and a VFS path
has no inode for them to open.
Read the addon's bytes from the VFS and load them from a private,
self-cleaning image instead, using the smallest on-disk footprint each
platform allows: on Linux an anonymous memfd loaded through
/proc/self/fd, so the bytes never reach the file system; on other POSIX
platforms a file in a 0700 mkdtemp() directory, unlinked right after
loading, with the mapping keeping it alive; on Windows a temp file
opened FILE_FLAG_DELETE_ON_CLOSE whose handle is held until exit.
This is internal. process.dlopen() keeps its documented
(module, filename[, flags]) signature and ignores anything further; the
bytes are passed to a dlopenBinary() reachable only through the
process_methods binding, which the VFS hook calls for VFS paths. Addons
on the real file system are untouched and load directly.
Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
PR-URL: https://github.com/nodejs/node/pull/65680
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: James M Snell <jasnell@gmail.com>
Every `new Worker()` runs the whole internal bootstrap (realm, node, web
exposure, thread and process-state switches) in its fresh isolate,
compiling ~80 builtins with the code cache; only the main thread
deserializes its principal context from the built-in snapshot. That
bootstrap is about half of a worker's cold start.
The bootstrapped principal context in the snapshot is nearly
thread-neutral: the worker-side switch scripts (is_not_main_thread,
does_not_own_process_state) are written as overrides of the main-thread
ones, and the per-thread values of the `worker` binding were the only
thread-specific data baked into the context. Let a worker deserialize
that same context and EnvSerializeInfo and apply the two worker-side
switches on top:
- worker binding: threadId, threadName, isMainThread, isInternalThread,
ownsProcessState and resourceLimits become lazy properties of the
per-isolate template, computed from the Environment on first read.
- CreateEnvironment(): when a worker (its IsolateData has a Worker)
passes an empty context, deserialize kNodeMainContextIndex and run
internal/bootstrap/switches/is_not_main_thread and, unless the worker
owns process state, does_not_own_process_state after
InitializeMainContext().
- Worker::Run(): take that path when the embedded built-in snapshot is
in use (not an embedder's or a --snapshot-blob one, which has run
application code), browser globals are not disabled and
--no-worker-snapshot was not given; otherwise bootstrap as before.
- is_not_main_thread.js: also delete _debugPause and the profiler idle
notifier helpers that is_main_thread.js installs.
- pre_execution: run the snapshot's deserialize callbacks (Buffer pool,
default resolver, cached cwd) on worker threads too, now that a worker
can come from a snapshot.
- --[no-]worker-snapshot per-isolate option, documented.
Sequential new Worker() -> 'online' -> terminate goes from ~20.9 ms to
~10.3 ms per worker on x64 Linux; --no-worker-snapshot restores the old
number.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65336
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Readable.fromWeb() and the read side of Duplex.fromWeb() allocated a
promise, a read-result object, and two reaction closures for every
chunk through reader.read(); a single reused read request now delivers
chunks through readableStreamDefaultReaderRead() instead, forwarding
each chunk in a microtask to preserve the delivery order relative to
errors and destroy.
Writable.fromWeb() and the write side of Duplex.fromWeb() paid two
derived promises off writer.ready plus the writer.write() promise and a
fresh closure pair per chunk; a single shared write request now
dispatches chunks directly and settles the node callback, with failures
delivered in a microtask.
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/65548
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: James M Snell <jasnell@gmail.com>
determineSpecificType() checks for a usable constructor name with
`'name' in value.constructor`. That accepts an empty name, and the `in`
operator requires its right-hand side to be an object.
Anonymous classes own a `name` that is the empty string, so they produce
messages ending in a dangling "Received an instance of ". A truthy
primitive `constructor` reaches the `in` operator and throws while the
message is being built, so ERR_INVALID_ARG_TYPE is replaced by a
TypeError carrying no `code`. The latter is reachable from untrusted
input, since `constructor` is an ordinary JSON key.
The check changed in #49696, while this function was rewritten as a
switch. That pull request updated test/common's invalidArgTypeHelper to
match its deliberate change to the `function` branch, but left the
helper's `object` branch on the original truthy check, so the two have
disagreed since.
Read `constructor` and its `name` once and use the name only when it is
a non-empty string. Requiring a string also stops meaningless names from
being interpolated: 42 currently yields "an instance of 42", and a
symbol name throws outright. The current check reads `constructor` three
times, which an accessor can observe.
Move the helper to the same check so the two cannot drift apart again.
Signed-off-by: Christian Aurich <christian.aurichzm@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65607
Refs: https://github.com/nodejs/node/pull/49696
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
fs.writeFile(path, data) took three libuv thread pool round trips
(open, write, close), each its own request with its own queue wait,
completion callback and JS/C++ crossing, and fs.promises.writeFile()
did the same through a FileHandle. For the small files applications
write most, the round trips are the cost, and each occupies a pool slot
that concurrent fs, dns.lookup() and crypto work is also queueing for.
Add WriteFileJob next to ReadFileJob: an AsyncWrap + ThreadPoolWork
that opens, writes the whole buffer (looping on short writes) and
closes as one pool task, keeping the buffer alive until it is done.
fs.writeFile() uses it for path arguments without flush;
fs.promises.writeFile() additionally keeps data above one write chunk
(and iterables) on the FileHandle path, so large writes stay abortable
between chunks as before. File descriptors, FileHandles, flush: true
and an active VFS keep their existing paths.
Behavior is otherwise kept: open failures report syscall 'open' with
the path, write failures 'write'; permission errors are delivered
through the callback/promise; an abort signalled while the write is in
flight is still reported as an AbortError; the job is an FSREQCALLBACK
resource for async_hooks and emits the 'write' fs trace event.
Tests that used fs.writeFile() as a proxy for open/close trace events,
or injected FileHandle faults for path-based writes, are adjusted to
keep testing what they test.
The job holds the buffer's backing store, so the memory stays valid if
the buffer is detached or collected before the write finishes; a
resizable ArrayBuffer could still have its pages decommitted by a
shrink, so its contents are copied when the job is created.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65489
Refs: https://github.com/nodejs/node/pull/65327
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
BlockList.toJSON() serializes IPv4-mapped IPv6 addresses in mixed
dotted-quad form (e.g. "::ffff:192.0.2.128"), but the fromJSON() rule
parser's IPv6 regexes excluded "." and capped the length at 39, so such
rules were silently corrupted or dropped on a toJSON()/fromJSON()
round-trip. Allow "." and raise the cap to 45 (the longest mixed form).
Signed-off-by: Daijiro Wachi <daijiro.wachi@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64125
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Richard Lau <richard.lau@ibm.com>