diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b9770e23a2e353..9cc673d5a62fe7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,11 @@ updates: directory: / schedule: interval: monthly + cooldown: + - semver-major-days: 5 + - semver-minor-days: 5 + - semver-patch-days: 5 + commit-message: prefix: meta open-pull-requests-limit: ${{secrets.OPEN_PR_LIMIT}} @@ -14,6 +19,10 @@ updates: directory: /tools/eslint schedule: interval: monthly + cooldown: + - semver-major-days: 5 + - semver-minor-days: 5 + - semver-patch-days: 5 commit-message: prefix: tools open-pull-requests-limit: ${{secrets.OPEN_PR_LIMIT}} @@ -27,6 +36,10 @@ updates: directory: /tools/lint-md schedule: interval: monthly + cooldown: + - semver-major-days: 5 + - semver-minor-days: 5 + - semver-patch-days: 5 commit-message: prefix: tools open-pull-requests-limit: ${{secrets.OPEN_PR_LIMIT}} @@ -35,3 +48,20 @@ updates: applies-to: version-updates patterns: - '*' + + - package-ecosystem: npm + directory: /tools/doc + schedule: + interval: weekly + cooldown: + - semver-major-days: 5 + - semver-minor-days: 5 + - semver-patch-days: 5 + commit-message: + prefix: tools + open-pull-requests-limit: ${{secrets.OPEN_PR_LIMIT}} + groups: + doc: + applies-to: version-updates + patterns: + - '*' diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index b258df039221ac..8ef41b0a20cf33 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -103,7 +103,31 @@ jobs: - name: Environment Information run: npx envinfo - name: Lint JavaScript files - run: NODE=$(command -v node) make lint-js + run: | + set +e + NODE=$(command -v node) make lint-js + EXIT_CODE="$?" + if [ "$EXIT_CODE" != "0" ]; then + echo + echo 'ERROR: The JavaScript lint validation failed (the errors are logged above).' + echo ' Please fix the lint errors.' + if NODE=$(command -v node) make lint-js-fix > /dev/null 2>&1; then + echo ' Run:' + echo ' make lint-js-fix' + echo ' to fix the lint issues.' + git --no-pager diff + elif git diff --quiet --exit-code; then + echo ' None of the issue is auto-fixable, so manual fixes for' + echo ' all of the issues are required.' + else + echo ' Run:' + echo ' make lint-js-fix' + echo ' to fix the auto-fixable lint issues.' + echo ' Note that some manual fixes are also required.' + fi + echo + exit "$EXIT_CODE" + fi - name: Get release version numbers if: ${{ github.event.pull_request && github.event.pull_request.base.ref == github.event.pull_request.base.repo.default_branch }} id: get-released-versions diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 302e6e29d04ca6..38157bfc00e2bb 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -21,7 +21,6 @@ on: - c-ares - cjs-module-lexer - corepack - - doc - googletest - gyp-next - histogram @@ -119,22 +118,6 @@ jobs: run: | make corepack-update echo "NEW_VERSION=$(node deps/corepack/dist/corepack.js --version)" >> $GITHUB_ENV - - id: doc - subsystem: tools - label: tools - run: | - cd tools/doc - npm ci - NEW_VERSION=$(npm outdated --parseable | cut -d: -f4 | xargs) - if [ "$NEW_VERSION" != "" ]; then - echo "NEW_VERSION=new version" >> $GITHUB_ENV - rm -rf package-lock.json node_modules - # Include $NEW_VERSION to explicitly update the package.json - # entry for the dependency and also so that semver-major updates - # are not skipped. - npm install --ignore-scripts $NEW_VERSION - npm install --ignore-scripts - fi - id: googletest subsystem: deps label: dependencies, test diff --git a/CHANGELOG.md b/CHANGELOG.md index 67aab9a199814b..e856c01f5e5d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,8 @@ release. -22.21.0
+22.21.1
+22.21.0
22.20.0
22.19.0
22.18.0
diff --git a/benchmark/dgram/offset-length.js b/benchmark/dgram/offset-length.js index ac0fee731eab20..85381da8de78e0 100644 --- a/benchmark/dgram/offset-length.js +++ b/benchmark/dgram/offset-length.js @@ -5,28 +5,28 @@ const common = require('../common.js'); const dgram = require('dgram'); const PORT = common.PORT; -// `num` is the number of send requests to queue up each time. +// `n` is the number of send requests to queue up each time. // Keep it reasonably high (>10) otherwise you're benchmarking the speed of // event loop cycles more than anything else. const bench = common.createBenchmark(main, { - len: [1, 64, 256, 1024], - num: [100], + len: [1, 512, 1024], + n: [100], type: ['send', 'recv'], dur: [5], }); -function main({ dur, len, num, type }) { +function main({ dur, len, n, type }) { const chunk = Buffer.allocUnsafe(len); let sent = 0; let received = 0; const socket = dgram.createSocket('udp4'); function onsend() { - if (sent++ % num === 0) { + if (sent++ % n === 0) { // The setImmediate() is necessary to have event loop progress on OSes // that only perform synchronous I/O on nonblocking UDP sockets. setImmediate(() => { - for (let i = 0; i < num; i++) { + for (let i = 0; i < n; i++) { socket.send(chunk, 0, chunk.length, PORT, '127.0.0.1', onsend); } }); diff --git a/benchmark/dgram/single-buffer.js b/benchmark/dgram/single-buffer.js index 6a7fa3dde22942..bab8cee1594f24 100644 --- a/benchmark/dgram/single-buffer.js +++ b/benchmark/dgram/single-buffer.js @@ -10,23 +10,23 @@ const PORT = common.PORT; // event loop cycles more than anything else. const bench = common.createBenchmark(main, { len: [1, 64, 256, 1024], - num: [100], + n: [100], type: ['send', 'recv'], dur: [5], }); -function main({ dur, len, num, type }) { +function main({ dur, len, num: n, type }) { const chunk = Buffer.allocUnsafe(len); let sent = 0; let received = 0; const socket = dgram.createSocket('udp4'); function onsend() { - if (sent++ % num === 0) { + if (sent++ % n === 0) { // The setImmediate() is necessary to have event loop progress on OSes // that only perform synchronous I/O on nonblocking UDP sockets. setImmediate(() => { - for (let i = 0; i < num; i++) { + for (let i = 0; i < n; i++) { socket.send(chunk, PORT, '127.0.0.1', onsend); } }); diff --git a/benchmark/permission/permission-startup.js b/benchmark/permission/permission-startup.js index 6a197cdff56111..d95caa01f605e0 100644 --- a/benchmark/permission/permission-startup.js +++ b/benchmark/permission/permission-startup.js @@ -19,12 +19,12 @@ const bench = common.createBenchmark(main, { ], prefixPath: ['/tmp'], nFiles: [10, 100, 1000], - count: [30], + n: [30], }); function spawnProcess(script, bench, state) { const cmd = process.execPath || process.argv[0]; - while (state.finished < state.count) { + while (state.finished < state.n) { const child = spawnSync(cmd, script); if (child.status !== 0) { console.log('---- STDOUT ----'); @@ -39,13 +39,13 @@ function spawnProcess(script, bench, state) { bench.start(); } - if (state.finished === state.count) { - bench.end(state.count); + if (state.finished === state.n) { + bench.end(state.n); } } } -function main({ count, script, nFiles, prefixPath }) { +function main({ n, script, nFiles, prefixPath }) { script = path.resolve(__dirname, '../../', `${script}.js`); const optionsWithScript = [ '--permission', @@ -54,6 +54,6 @@ function main({ count, script, nFiles, prefixPath }) { script, ]; const warmup = 3; - const state = { count, finished: -warmup }; + const state = { n, finished: -warmup }; spawnProcess(optionsWithScript, bench, state); } diff --git a/benchmark/util/priority-queue.js b/benchmark/util/priority-queue.js index 2301c5a1ef6892..0a880a1c7cf29d 100644 --- a/benchmark/util/priority-queue.js +++ b/benchmark/util/priority-queue.js @@ -6,7 +6,7 @@ const bench = common.createBenchmark(main, { n: [1e5], }, { flags: ['--expose-internals'] }); -function main({ n, type }) { +function main({ n }) { const PriorityQueue = require('internal/priority_queue'); const queue = new PriorityQueue(); bench.start(); diff --git a/doc/changelogs/CHANGELOG_V22.md b/doc/changelogs/CHANGELOG_V22.md index 6dd993bb8bf6e3..c06362ced99373 100644 --- a/doc/changelogs/CHANGELOG_V22.md +++ b/doc/changelogs/CHANGELOG_V22.md @@ -9,6 +9,7 @@ +22.21.1
22.21.0
22.20.0
22.19.0
@@ -66,6 +67,48 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + + +## 2025-10-28, Version 22.21.1 'Jod' (LTS), @aduh95 + +### Commits + +* \[[`af33e8e668`](https://github.com/nodejs/node/commit/af33e8e668)] - **benchmark**: remove unused variable from util/priority-queue (Bruno Rodrigues) [#59872](https://github.com/nodejs/node/pull/59872) +* \[[`6764ce8756`](https://github.com/nodejs/node/commit/6764ce8756)] - **benchmark**: update count to n in permission startup (Bruno Rodrigues) [#59872](https://github.com/nodejs/node/pull/59872) +* \[[`4e8d99f0dc`](https://github.com/nodejs/node/commit/4e8d99f0dc)] - **benchmark**: update num to n in dgram offset-length (Bruno Rodrigues) [#59872](https://github.com/nodejs/node/pull/59872) +* \[[`af0a8ba7f8`](https://github.com/nodejs/node/commit/af0a8ba7f8)] - **benchmark**: adjust dgram offset-length len values (Bruno Rodrigues) [#59708](https://github.com/nodejs/node/pull/59708) +* \[[`78efd1be4a`](https://github.com/nodejs/node/commit/78efd1be4a)] - **benchmark**: update num to n in dgram offset-length (Bruno Rodrigues) [#59708](https://github.com/nodejs/node/pull/59708) +* \[[`df72dc96e9`](https://github.com/nodejs/node/commit/df72dc96e9)] - **console,util**: improve array inspection performance (Ruben Bridgewater) [#60037](https://github.com/nodejs/node/pull/60037) +* \[[`ef67d09f50`](https://github.com/nodejs/node/commit/ef67d09f50)] - **http**: improve writeEarlyHints by avoiding for-of loop (Haram Jeong) [#59958](https://github.com/nodejs/node/pull/59958) +* \[[`23468fd76b`](https://github.com/nodejs/node/commit/23468fd76b)] - **http2**: fix allowHttp1+Upgrade, broken by shouldUpgradeCallback (Tim Perry) [#59924](https://github.com/nodejs/node/pull/59924) +* \[[`56abc4ac76`](https://github.com/nodejs/node/commit/56abc4ac76)] - **lib**: optimize priority queue (Gürgün Dayıoğlu) [#60039](https://github.com/nodejs/node/pull/60039) +* \[[`ea5cfd98c5`](https://github.com/nodejs/node/commit/ea5cfd98c5)] - **lib**: implement passive listener behavior per spec (BCD1me) [#59995](https://github.com/nodejs/node/pull/59995) +* \[[`c2dd6eed2f`](https://github.com/nodejs/node/commit/c2dd6eed2f)] - **process**: fix wrong asyncContext under unhandled-rejections=strict (Shima Ryuhei) [#60103](https://github.com/nodejs/node/pull/60103) +* \[[`81a3055710`](https://github.com/nodejs/node/commit/81a3055710)] - **process**: fix default `env` for `process.execve` (Richard Lau) [#60029](https://github.com/nodejs/node/pull/60029) +* \[[`fe492c7ace`](https://github.com/nodejs/node/commit/fe492c7ace)] - **process**: fix hrtime fast call signatures (Renegade334) [#59600](https://github.com/nodejs/node/pull/59600) +* \[[`76b4cab8fc`](https://github.com/nodejs/node/commit/76b4cab8fc)] - **src**: bring permissions macros in line with general C/C++ standards (Anna Henningsen) [#60053](https://github.com/nodejs/node/pull/60053) +* \[[`21970970c7`](https://github.com/nodejs/node/commit/21970970c7)] - **src**: remove `AnalyzeTemporaryDtors` option from .clang-tidy (iknoom) [#60008](https://github.com/nodejs/node/pull/60008) +* \[[`609c063e81`](https://github.com/nodejs/node/commit/609c063e81)] - **src**: remove unused variables from report (Moonki Choi) [#60047](https://github.com/nodejs/node/pull/60047) +* \[[`987841a773`](https://github.com/nodejs/node/commit/987841a773)] - **src**: avoid unnecessary string allocations in SPrintF impl (Anna Henningsen) [#60052](https://github.com/nodejs/node/pull/60052) +* \[[`6e386c0632`](https://github.com/nodejs/node/commit/6e386c0632)] - **src**: make ToLower/ToUpper input args more flexible (Anna Henningsen) [#60052](https://github.com/nodejs/node/pull/60052) +* \[[`c3be1226c7`](https://github.com/nodejs/node/commit/c3be1226c7)] - **src**: allow `std::string_view` arguments to `SPrintF()` and friends (Anna Henningsen) [#60058](https://github.com/nodejs/node/pull/60058) +* \[[`764d35647d`](https://github.com/nodejs/node/commit/764d35647d)] - **src**: remove unnecessary `std::string` error messages (Anna Henningsen) [#60057](https://github.com/nodejs/node/pull/60057) +* \[[`1289ef89ec`](https://github.com/nodejs/node/commit/1289ef89ec)] - **src**: remove unnecessary shadowed functions on Utf8Value & BufferValue (Anna Henningsen) [#60056](https://github.com/nodejs/node/pull/60056) +* \[[`d1fb8a538d`](https://github.com/nodejs/node/commit/d1fb8a538d)] - **src**: avoid unnecessary string -> `char*` -> string round trips (Anna Henningsen) [#60055](https://github.com/nodejs/node/pull/60055) +* \[[`54b439fb5a`](https://github.com/nodejs/node/commit/54b439fb5a)] - **src**: fill `options_args`, `options_env` after vectors are finalized (iknoom) [#59945](https://github.com/nodejs/node/pull/59945) +* \[[`c7c597e2ca`](https://github.com/nodejs/node/commit/c7c597e2ca)] - **src**: use RAII for uv\_process\_options\_t (iknoom) [#59945](https://github.com/nodejs/node/pull/59945) +* \[[`b928ea9716`](https://github.com/nodejs/node/commit/b928ea9716)] - **test**: ensure that the message event is fired (Luigi Pinca) [#59952](https://github.com/nodejs/node/pull/59952) +* \[[`e4b95a5158`](https://github.com/nodejs/node/commit/e4b95a5158)] - **test**: replace diagnostics\_channel stackframe in output snapshots (Chengzhong Wu) [#60024](https://github.com/nodejs/node/pull/60024) +* \[[`4206406694`](https://github.com/nodejs/node/commit/4206406694)] - **test**: mark test-web-locks skip on IBM i (SRAVANI GUNDEPALLI) [#59996](https://github.com/nodejs/node/pull/59996) +* \[[`26394cd5bf`](https://github.com/nodejs/node/commit/26394cd5bf)] - **test**: expand tls-check-server-identity coverage (Diango Gavidia) [#60002](https://github.com/nodejs/node/pull/60002) +* \[[`b58df47995`](https://github.com/nodejs/node/commit/b58df47995)] - **test**: fix typo of test-benchmark-readline.js (Deokjin Kim) [#59993](https://github.com/nodejs/node/pull/59993) +* \[[`af3a59dba8`](https://github.com/nodejs/node/commit/af3a59dba8)] - **test**: verify tracing channel doesn't swallow unhandledRejection (Gerhard Stöbich) [#59974](https://github.com/nodejs/node/pull/59974) +* \[[`cee362242b`](https://github.com/nodejs/node/commit/cee362242b)] - **timers**: fix binding fast call signatures (Renegade334) [#59600](https://github.com/nodejs/node/pull/59600) +* \[[`40fea57fdd`](https://github.com/nodejs/node/commit/40fea57fdd)] - **tools**: add message on auto-fixing js lint issues in gh workflow (Dario Piotrowicz) [#59128](https://github.com/nodejs/node/pull/59128) +* \[[`aac90d351b`](https://github.com/nodejs/node/commit/aac90d351b)] - **tools**: verify signatures when updating nghttp\* (Antoine du Hamel) [#60113](https://github.com/nodejs/node/pull/60113) +* \[[`9fae03c7d9`](https://github.com/nodejs/node/commit/9fae03c7d9)] - **tools**: use dependabot cooldown and move tools/doc (Rafael Gonzaga) [#59978](https://github.com/nodejs/node/pull/59978) +* \[[`81548abdf6`](https://github.com/nodejs/node/commit/81548abdf6)] - **wasi**: fix WasiFunction fast call signature (Renegade334) [#59600](https://github.com/nodejs/node/pull/59600) + ## 2025-10-20, Version 22.21.0 'Jod' (LTS), @aduh95 diff --git a/lib/_http_server.js b/lib/_http_server.js index 8cae06cfde9fa0..4ff976d02ca441 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -330,7 +330,9 @@ ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(hints, cb) { head += 'Link: ' + link + '\r\n'; - for (const key of ObjectKeys(hints)) { + const keys = ObjectKeys(hints); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; if (key !== 'link') { head += key + ': ' + hints[key] + '\r\n'; } diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index e6c24e3a058ca7..2f02510e3a3176 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -76,6 +76,7 @@ const { now } = require('internal/perf/utils'); const kType = Symbol('type'); const kDetail = Symbol('detail'); +const kInPassiveListener = Symbol('kInPassiveListener'); const isTrustedSet = new SafeWeakSet(); const isTrusted = ObjectGetOwnPropertyDescriptor({ @@ -127,6 +128,7 @@ class Event { this[kTarget] = null; this[kIsBeingDispatched] = false; + this[kInPassiveListener] = false; } /** @@ -178,6 +180,7 @@ class Event { preventDefault() { if (!isEvent(this)) throw new ERR_INVALID_THIS('Event'); + if (!this.#cancelable || this[kInPassiveListener]) return; this.#defaultPrevented = true; } @@ -266,6 +269,19 @@ class Event { return !this.#cancelable || !this.#defaultPrevented; } + /** + * @type {boolean} + */ + set returnValue(value) { + if (!isEvent(this)) + throw new ERR_INVALID_THIS('Event'); + + if (!value) { + if (!this.#cancelable || this[kInPassiveListener]) return; + this.#defaultPrevented = true; + } + } + /** * @type {boolean} */ @@ -760,7 +776,6 @@ class EventTarget { throw new ERR_EVENT_RECURSION(event.type); this[kHybridDispatch](event, event.type, event); - return event.defaultPrevented !== true; } @@ -813,8 +828,8 @@ class EventTarget { this[kRemoveListener](root.size, type, listener, capture); } + let arg; try { - let arg; if (handler.isNodeStyleListener) { arg = nodeValue; } else { @@ -824,6 +839,9 @@ class EventTarget { handler.callback.deref() : handler.callback; let result; if (callback) { + if (handler.passive && !handler.isNodeStyleListener) { + arg[kInPassiveListener] = true; + } result = FunctionPrototypeCall(callback, this, arg); if (!handler.isNodeStyleListener) { arg[kIsBeingDispatched] = false; @@ -833,6 +851,9 @@ class EventTarget { addCatch(result); } catch (err) { emitUncaughtException(err); + } finally { + if (arg?.[kInPassiveListener]) + arg[kInPassiveListener] = false; } handler = next; diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 73a478a7b708e2..335113acc8c23d 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -3379,6 +3379,9 @@ class Http2SecureServer extends TLSServer { this.headersTimeout = 60_000; // Minimum between 60 seconds or requestTimeout this.requestTimeout = 300_000; // 5 minutes this.connectionsCheckingInterval = 30_000; // 30 seconds + this.shouldUpgradeCallback = function() { + return this.listenerCount('upgrade') > 0; + }; this.on('listening', setupConnectionsTracking); } if (typeof requestListener === 'function') diff --git a/lib/internal/priority_queue.js b/lib/internal/priority_queue.js index c172e2351c93ab..e6fdb61e9997c9 100644 --- a/lib/internal/priority_queue.js +++ b/lib/internal/priority_queue.js @@ -1,9 +1,5 @@ 'use strict'; -const { - Array, -} = primordials; - // The PriorityQueue is a basic implementation of a binary heap that accepts // a custom sorting function via its constructor. This function is passed // the two nodes to compare, similar to the native Array#sort. Crucially @@ -12,7 +8,7 @@ const { module.exports = class PriorityQueue { #compare = (a, b) => a - b; - #heap = new Array(64); + #heap = [undefined, undefined]; #setPosition; #size = 0; @@ -28,9 +24,6 @@ module.exports = class PriorityQueue { const pos = ++this.#size; heap[pos] = value; - if (heap.length === pos) - heap.length *= 2; - this.percolateUp(pos); } @@ -45,6 +38,7 @@ module.exports = class PriorityQueue { percolateDown(pos) { const compare = this.#compare; const setPosition = this.#setPosition; + const hasSetPosition = setPosition !== undefined; const heap = this.#heap; const size = this.#size; const hsize = size >> 1; @@ -62,7 +56,7 @@ module.exports = class PriorityQueue { if (compare(item, childItem) <= 0) break; - if (setPosition !== undefined) + if (hasSetPosition) setPosition(childItem, pos); heap[pos] = childItem; @@ -70,7 +64,7 @@ module.exports = class PriorityQueue { } heap[pos] = item; - if (setPosition !== undefined) + if (hasSetPosition) setPosition(item, pos); } @@ -78,6 +72,7 @@ module.exports = class PriorityQueue { const heap = this.#heap; const compare = this.#compare; const setPosition = this.#setPosition; + const hasSetPosition = setPosition !== undefined; const item = heap[pos]; while (pos > 1) { @@ -86,13 +81,13 @@ module.exports = class PriorityQueue { if (compare(parentItem, item) <= 0) break; heap[pos] = parentItem; - if (setPosition !== undefined) + if (hasSetPosition) setPosition(parentItem, pos); pos = parent; } heap[pos] = item; - if (setPosition !== undefined) + if (hasSetPosition) setPosition(item, pos); } diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index 092464c9ffd440..78805a6b5b87bb 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -283,7 +283,7 @@ function wrapProcessMethods(binding) { return true; } - function execve(execPath, args = [], env) { + function execve(execPath, args = [], env = process.env) { emitExperimentalWarning('process.execve'); const { isMainThread } = require('internal/worker'); @@ -305,22 +305,20 @@ function wrapProcessMethods(binding) { } const envArray = []; - if (env !== undefined) { - validateObject(env, 'env'); - - for (const { 0: key, 1: value } of ObjectEntries(env)) { - if ( - typeof key !== 'string' || - typeof value !== 'string' || - StringPrototypeIncludes(key, '\u0000') || - StringPrototypeIncludes(value, '\u0000') - ) { - throw new ERR_INVALID_ARG_VALUE( - 'env', env, 'must be an object with string keys and values without null bytes', - ); - } else { - ArrayPrototypePush(envArray, `${key}=${value}`); - } + validateObject(env, 'env'); + + for (const { 0: key, 1: value } of ObjectEntries(env)) { + if ( + typeof key !== 'string' || + typeof value !== 'string' || + StringPrototypeIncludes(key, '\u0000') || + StringPrototypeIncludes(value, '\u0000') + ) { + throw new ERR_INVALID_ARG_VALUE( + 'env', env, 'must be an object with string keys and values without null bytes', + ); + } else { + ArrayPrototypePush(envArray, `${key}=${value}`); } } diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index 90eaef5c773718..db5cb8a9cf362b 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -343,7 +343,7 @@ function strictUnhandledRejectionsMode(promise, promiseInfo, promiseAsyncId) { reason : new UnhandledPromiseRejection(reason); // This destroys the async stack, don't clear it after triggerUncaughtException(err, true /* fromPromise */); - if (promiseAsyncId === undefined) { + if (promiseAsyncId !== undefined) { pushAsyncContext( promise[kAsyncIdSymbol], promise[kTriggerAsyncIdSymbol], diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index e81d13e5c787c4..f76e61756175da 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -2022,11 +2022,12 @@ function formatArray(ctx, value, recurseTimes) { const remaining = valLen - len; const output = []; for (let i = 0; i < len; i++) { - // Special handle sparse arrays. - if (!ObjectPrototypeHasOwnProperty(value, i)) { + const desc = ObjectGetOwnPropertyDescriptor(value, i); + if (desc === undefined) { + // Special handle sparse arrays. return formatSpecialArray(ctx, value, recurseTimes, len, output, i); } - ArrayPrototypePush(output, formatProperty(ctx, value, recurseTimes, i, kArrayType)); + ArrayPrototypePush(output, formatProperty(ctx, value, recurseTimes, i, kArrayType, desc)); } if (remaining > 0) { ArrayPrototypePush(output, remainingText(remaining)); diff --git a/src/.clang-tidy b/src/.clang-tidy index 9cb5edbe5bee8a..91aabe9a158575 100644 --- a/src/.clang-tidy +++ b/src/.clang-tidy @@ -22,7 +22,6 @@ Checks: '-*, readability-delete-null-pointer, ' WarningsAsErrors: '' HeaderFilterRegex: '' -AnalyzeTemporaryDtors: false FormatStyle: none User: nodejs/cpp CheckOptions: diff --git a/src/debug_utils-inl.h b/src/debug_utils-inl.h index 3736663d9977c0..c10af8b1b618bc 100644 --- a/src/debug_utils-inl.h +++ b/src/debug_utils-inl.h @@ -11,25 +11,41 @@ namespace node { +template +concept StringViewConvertible = requires(T a) { + { + a.ToStringView() + } -> std::convertible_to; + }; +template +concept StringConvertible = requires(T a) { + { + a.ToString() + } -> std::convertible_to; + }; + struct ToStringHelper { template - static std::string Convert( - const T& value, - std::string(T::* to_string)() const = &T::ToString) { - return (value.*to_string)(); + requires(StringConvertible) && (!StringViewConvertible) + static std::string Convert(const T& value) { + return value.ToString(); } + template + requires StringViewConvertible + static std::string_view Convert(const T& value) { + return value.ToStringView(); + } + template ::value, bool>::type, typename dummy = bool> static std::string Convert(const T& value) { return std::to_string(value); } - static std::string Convert(const char* value) { + static std::string_view Convert(const char* value) { return value != nullptr ? value : "(null)"; } static std::string Convert(const std::string& value) { return value; } - static std::string Convert(std::string_view value) { - return std::string(value); - } + static std::string_view Convert(std::string_view value) { return value; } static std::string Convert(bool value) { return value ? "true" : "false"; } template >> - static std::string BaseConvert(T& value) { // NOLINT(runtime/references) + static auto BaseConvert(T&& value) { return Convert(std::forward(value)); } }; template -std::string ToString(const T& value) { +auto ToStringOrStringView(const T& value) { return ToStringHelper::Convert(value); } +template +std::string ToString(const T& value) { + return std::string(ToStringOrStringView(value)); +} + template -std::string ToBaseString(const T& value) { +auto ToBaseString(const T& value) { return ToStringHelper::BaseConvert(value); } -inline std::string SPrintFImpl(const char* format) { - const char* p = strchr(format, '%'); - if (p == nullptr) [[unlikely]] - return format; - CHECK_EQ(p[1], '%'); // Only '%%' allowed when there are no arguments. +inline std::string SPrintFImpl(std::string_view format) { + auto offset = format.find('%'); + if (offset == std::string_view::npos) return std::string(format); + CHECK_LT(offset + 1, format.size()); + CHECK_EQ(format[offset + 1], + '%'); // Only '%%' allowed when there are no arguments. - return std::string(format, p + 1) + SPrintFImpl(p + 2); + return std::string(format.substr(0, offset + 1)) + + SPrintFImpl(format.substr(offset + 2)); } template std::string COLD_NOINLINE SPrintFImpl( // NOLINT(runtime/string) - const char* format, Arg&& arg, Args&&... args) { - const char* p = strchr(format, '%'); - CHECK_NOT_NULL(p); // If you hit this, you passed in too many arguments. - std::string ret(format, p); + std::string_view format, + Arg&& arg, + Args&&... args) { + auto offset = format.find('%'); + CHECK_NE(offset, std::string_view::npos); // If you hit this, you passed in + // too many arguments. + std::string ret(format.substr(0, offset)); // Ignore long / size_t modifiers - while (strchr("lz", *++p) != nullptr) {} - switch (*p) { + while (++offset < format.size() && + (format[offset] == 'l' || format[offset] == 'z')) { + } + switch (offset == format.size() ? '\0' : format[offset]) { case '%': { - return ret + '%' + SPrintFImpl(p + 1, - std::forward(arg), - std::forward(args)...); + return ret + '%' + + SPrintFImpl(format.substr(offset + 1), + std::forward(arg), + std::forward(args)...); } default: { - return ret + '%' + SPrintFImpl(p, - std::forward(arg), - std::forward(args)...); + return ret + '%' + + SPrintFImpl(format.substr(offset), + std::forward(arg), + std::forward(args)...); } case 'd': case 'i': case 'u': case 's': - ret += ToString(arg); + ret += ToStringOrStringView(arg); break; case 'o': ret += ToBaseString<3>(arg); @@ -120,17 +150,21 @@ std::string COLD_NOINLINE SPrintFImpl( // NOLINT(runtime/string) break; } } - return ret + SPrintFImpl(p + 1, std::forward(args)...); + return ret + + SPrintFImpl(format.substr(offset + 1), std::forward(args)...); } template std::string COLD_NOINLINE SPrintF( // NOLINT(runtime/string) - const char* format, Args&&... args) { + std::string_view format, + Args&&... args) { return SPrintFImpl(format, std::forward(args)...); } template -void COLD_NOINLINE FPrintF(FILE* file, const char* format, Args&&... args) { +void COLD_NOINLINE FPrintF(FILE* file, + std::string_view format, + Args&&... args) { FWrite(file, SPrintF(format, std::forward(args)...)); } diff --git a/src/debug_utils.h b/src/debug_utils.h index e1768d8a06159c..8f6165e1b5faf4 100644 --- a/src/debug_utils.h +++ b/src/debug_utils.h @@ -26,6 +26,8 @@ class Environment; template inline std::string ToString(const T& value); +template +inline auto ToStringOrStringView(const T& value); // C++-style variant of sprintf()/fprintf() that: // - Returns an std::string @@ -33,9 +35,9 @@ inline std::string ToString(const T& value); // - Supports %p and %s. %d, %i and %u are aliases for %s. // - Accepts any class that has a ToString() method for stringification. template -inline std::string SPrintF(const char* format, Args&&... args); +inline std::string SPrintF(std::string_view format, Args&&... args); template -inline void FPrintF(FILE* file, const char* format, Args&&... args); +inline void FPrintF(FILE* file, std::string_view format, Args&&... args); void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str); // Listing the AsyncWrap provider types first enables us to cast directly diff --git a/src/inspector/protocol_helper.h b/src/inspector/protocol_helper.h index a0f88aa93532a4..5529ef523a0101 100644 --- a/src/inspector/protocol_helper.h +++ b/src/inspector/protocol_helper.h @@ -19,7 +19,7 @@ inline std::unique_ptr ToInspectorString( inline protocol::String ToProtocolString(v8::Isolate* isolate, v8::Local value) { Utf8Value buffer(isolate, value); - return *buffer; + return buffer.ToString(); } } // namespace node::inspector diff --git a/src/node_blob.cc b/src/node_blob.cc index eb0fbebc9df152..bb019dc62c1000 100644 --- a/src/node_blob.cc +++ b/src/node_blob.cc @@ -439,11 +439,9 @@ void Blob::StoreDataObject(const FunctionCallbackInfo& args) { Utf8Value type(isolate, args[3]); binding_data->store_data_object( - std::string(*key, key.length()), + key.ToString(), BlobBindingData::StoredDataObject( - BaseObjectPtr(blob), - length, - std::string(*type, type.length()))); + BaseObjectPtr(blob), length, type.ToString())); } // Note: applying the V8 Fast API to the following function does not produce @@ -483,7 +481,7 @@ void Blob::GetDataObject(const FunctionCallbackInfo& args) { Utf8Value key(isolate, args[0]); BlobBindingData::StoredDataObject stored = - binding_data->get_data_object(std::string(*key, key.length())); + binding_data->get_data_object(key.ToString()); if (stored.blob) { Local type; if (!String::NewFromUtf8(isolate, diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 21a08a738e5c35..d52388717e5938 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -292,10 +292,10 @@ BaseObjectPtr ContextifyContext::New( options->allow_code_gen_wasm); Utf8Value name_val(env->isolate(), options->name); - ContextInfo info(*name_val); + ContextInfo info(name_val.ToString()); if (!options->origin.IsEmpty()) { Utf8Value origin_val(env->isolate(), options->origin); - info.origin = *origin_val; + info.origin = origin_val.ToString(); } BaseObjectPtr result; diff --git a/src/node_errors.h b/src/node_errors.h index 791e835c2c2851..9d0488d0dba657 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -134,7 +134,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); #define V(code, type) \ template \ inline v8::Local code( \ - v8::Isolate* isolate, const char* format, Args&&... args) { \ + v8::Isolate* isolate, std::string_view format, Args&&... args) { \ std::string message; \ if (sizeof...(Args) == 0) { \ message = format; \ @@ -159,17 +159,18 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); } \ template \ inline void THROW_##code( \ - v8::Isolate* isolate, const char* format, Args&&... args) { \ + v8::Isolate* isolate, std::string_view format, Args&&... args) { \ isolate->ThrowException( \ code(isolate, format, std::forward(args)...)); \ } \ template \ inline void THROW_##code( \ - Environment* env, const char* format, Args&&... args) { \ + Environment* env, std::string_view format, Args&&... args) { \ THROW_##code(env->isolate(), format, std::forward(args)...); \ } \ template \ - inline void THROW_##code(Realm* realm, const char* format, Args&&... args) { \ + inline void THROW_##code( \ + Realm* realm, std::string_view format, Args&&... args) { \ THROW_##code(realm->isolate(), format, std::forward(args)...); \ } ERRORS_WITH_CODE(V) @@ -250,10 +251,8 @@ PREDEFINED_ERROR_MESSAGES(V) // Errors with predefined non-static messages inline void THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(Environment* env, int64_t timeout) { - std::ostringstream message; - message << "Script execution timed out after "; - message << timeout << "ms"; - THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(env, message.str().c_str()); + THROW_ERR_SCRIPT_EXECUTION_TIMEOUT( + env, "Script execution timed out after %dms", timeout); } inline void THROW_ERR_REQUIRE_ASYNC_MODULE( @@ -268,14 +267,14 @@ inline void THROW_ERR_REQUIRE_ASYNC_MODULE( if (!parent_filename.IsEmpty() && parent_filename->IsString()) { Utf8Value utf8(env->isolate(), parent_filename); message += "\n From "; - message += utf8.out(); + message += utf8.ToStringView(); } if (!filename.IsEmpty() && filename->IsString()) { Utf8Value utf8(env->isolate(), filename); message += "\n Requiring "; - message += +utf8.out(); + message += utf8.ToStringView(); } - THROW_ERR_REQUIRE_ASYNC_MODULE(env, message.c_str()); + THROW_ERR_REQUIRE_ASYNC_MODULE(env, message); } inline v8::Local ERR_BUFFER_TOO_LARGE(v8::Isolate* isolate) { diff --git a/src/node_file.cc b/src/node_file.cc index 8e4847cbf89878..77f8f1bd4e8294 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -3147,24 +3147,25 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo& args) { if (!error_code) { // Check if src and dest are identical. if (std::filesystem::equivalent(src_path, dest_path)) { - std::string message = "src and dest cannot be the same %s"; - return THROW_ERR_FS_CP_EINVAL(env, message.c_str(), dest_path_str); + static constexpr const char* message = + "src and dest cannot be the same %s"; + return THROW_ERR_FS_CP_EINVAL(env, message, dest_path_str); } const bool dest_is_dir = dest_status.type() == std::filesystem::file_type::directory; if (src_is_dir && !dest_is_dir) { - std::string message = + static constexpr const char* message = "Cannot overwrite non-directory %s with directory %s"; return THROW_ERR_FS_CP_DIR_TO_NON_DIR( - env, message.c_str(), dest_path_str, src_path_str); + env, message, dest_path_str, src_path_str); } if (!src_is_dir && dest_is_dir) { - std::string message = + static constexpr const char* message = "Cannot overwrite directory %s with non-directory %s"; return THROW_ERR_FS_CP_NON_DIR_TO_DIR( - env, message.c_str(), dest_path_str, src_path_str); + env, message, dest_path_str, src_path_str); } } @@ -3173,9 +3174,9 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo& args) { } // Check if dest_path is a subdirectory of src_path. if (src_is_dir && dest_path_str.starts_with(src_path_str)) { - std::string message = "Cannot copy %s to a subdirectory of self %s"; - return THROW_ERR_FS_CP_EINVAL( - env, message.c_str(), src_path_str, dest_path_str); + static constexpr const char* message = + "Cannot copy %s to a subdirectory of self %s"; + return THROW_ERR_FS_CP_EINVAL(env, message, src_path_str, dest_path_str); } auto dest_parent = dest_path.parent_path(); @@ -3186,9 +3187,9 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo& args) { dest_parent.parent_path() != dest_parent) { if (std::filesystem::equivalent( src_path, dest_path.parent_path(), error_code)) { - std::string message = "Cannot copy %s to a subdirectory of self %s"; - return THROW_ERR_FS_CP_EINVAL( - env, message.c_str(), src_path_str, dest_path_str); + static constexpr const char* message = + "Cannot copy %s to a subdirectory of self %s"; + return THROW_ERR_FS_CP_EINVAL(env, message, src_path_str, dest_path_str); } // If equivalent fails, it's highly likely that dest_parent does not exist @@ -3200,23 +3201,24 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo& args) { } if (src_is_dir && !recursive) { - std::string message = + static constexpr const char* message = "Recursive option not enabled, cannot copy a directory: %s"; - return THROW_ERR_FS_EISDIR(env, message.c_str(), src_path_str); + return THROW_ERR_FS_EISDIR(env, message, src_path_str); } switch (src_status.type()) { case std::filesystem::file_type::socket: { - std::string message = "Cannot copy a socket file: %s"; - return THROW_ERR_FS_CP_SOCKET(env, message.c_str(), dest_path_str); + static constexpr const char* message = "Cannot copy a socket file: %s"; + return THROW_ERR_FS_CP_SOCKET(env, message, dest_path_str); } case std::filesystem::file_type::fifo: { - std::string message = "Cannot copy a FIFO pipe: %s"; - return THROW_ERR_FS_CP_FIFO_PIPE(env, message.c_str(), dest_path_str); + static constexpr const char* message = "Cannot copy a FIFO pipe: %s"; + return THROW_ERR_FS_CP_FIFO_PIPE(env, message, dest_path_str); } case std::filesystem::file_type::unknown: { - std::string message = "Cannot copy an unknown file type: %s"; - return THROW_ERR_FS_CP_UNKNOWN(env, message.c_str(), dest_path_str); + static constexpr const char* message = + "Cannot copy an unknown file type: %s"; + return THROW_ERR_FS_CP_UNKNOWN(env, message, dest_path_str); } default: break; diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 1eff9399ff8751..3c5f38ba4f4927 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -1357,7 +1357,7 @@ std::unique_ptr JSTransferable::TransferOrClone() const { } Utf8Value deserialize_info_str(env()->isolate(), deserialize_info); if (*deserialize_info_str == nullptr) return {}; - return std::make_unique(*deserialize_info_str, + return std::make_unique(deserialize_info_str.ToString(), Global(env()->isolate(), data)); } diff --git a/src/node_process.h b/src/node_process.h index b88a2f99483ad2..64393302d2cfd9 100644 --- a/src/node_process.h +++ b/src/node_process.h @@ -3,6 +3,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include "node_debug.h" #include "node_snapshotable.h" #include "v8-fast-api-calls.h" #include "v8.h" @@ -72,23 +73,23 @@ class BindingData : public SnapshotableObject { SET_SELF_SIZE(BindingData) static BindingData* FromV8Value(v8::Local receiver); - static void NumberImpl(BindingData* receiver); + static void HrtimeImpl(BindingData* receiver); - static void FastNumber(v8::Local unused, - v8::Local receiver) { - NumberImpl(FromV8Value(receiver)); + static void FastHrtime(v8::Local receiver) { + TRACK_V8_FAST_API_CALL("process.hrtime"); + HrtimeImpl(FromV8Value(receiver)); } - static void SlowNumber(const v8::FunctionCallbackInfo& args); + static void SlowHrtime(const v8::FunctionCallbackInfo& args); - static void BigIntImpl(BindingData* receiver); + static void HrtimeBigIntImpl(BindingData* receiver); - static void FastBigInt(v8::Local unused, - v8::Local receiver) { - BigIntImpl(FromV8Value(receiver)); + static void FastHrtimeBigInt(v8::Local receiver) { + TRACK_V8_FAST_API_CALL("process.hrtimeBigInt"); + HrtimeBigIntImpl(FromV8Value(receiver)); } - static void SlowBigInt(const v8::FunctionCallbackInfo& args); + static void SlowHrtimeBigInt(const v8::FunctionCallbackInfo& args); static void LoadEnvFile(const v8::FunctionCallbackInfo& args); @@ -101,8 +102,8 @@ class BindingData : public SnapshotableObject { // These need to be static so that we have their addresses available to // register as external references in the snapshot at environment creation // time. - static v8::CFunction fast_number_; - static v8::CFunction fast_bigint_; + static v8::CFunction fast_hrtime_; + static v8::CFunction fast_hrtime_bigint_; }; } // namespace process diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 1cb08b715865f8..9dcca7509f37f2 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -651,22 +651,22 @@ BindingData::BindingData(Realm* realm, hrtime_buffer_.MakeWeak(); } -v8::CFunction BindingData::fast_number_(v8::CFunction::Make(FastNumber)); -v8::CFunction BindingData::fast_bigint_(v8::CFunction::Make(FastBigInt)); +CFunction BindingData::fast_hrtime_(CFunction::Make(FastHrtime)); +CFunction BindingData::fast_hrtime_bigint_(CFunction::Make(FastHrtimeBigInt)); void BindingData::AddMethods(Isolate* isolate, Local target) { SetFastMethodNoSideEffect( - isolate, target, "hrtime", SlowNumber, &fast_number_); + isolate, target, "hrtime", SlowHrtime, &fast_hrtime_); SetFastMethodNoSideEffect( - isolate, target, "hrtimeBigInt", SlowBigInt, &fast_bigint_); + isolate, target, "hrtimeBigInt", SlowHrtimeBigInt, &fast_hrtime_bigint_); } void BindingData::RegisterExternalReferences( ExternalReferenceRegistry* registry) { - registry->Register(SlowNumber); - registry->Register(SlowBigInt); - registry->Register(fast_number_); - registry->Register(fast_bigint_); + registry->Register(SlowHrtime); + registry->Register(SlowHrtimeBigInt); + registry->Register(fast_hrtime_); + registry->Register(fast_hrtime_bigint_); } BindingData* BindingData::FromV8Value(Local value) { @@ -688,14 +688,14 @@ void BindingData::MemoryInfo(MemoryTracker* tracker) const { // broken into the upper/lower 32 bits to be converted back in JS, // because there is no Uint64Array in JS. // The third entry contains the remaining nanosecond part of the value. -void BindingData::NumberImpl(BindingData* receiver) { +void BindingData::HrtimeImpl(BindingData* receiver) { uint64_t t = uv_hrtime(); receiver->hrtime_buffer_[0] = (t / NANOS_PER_SEC) >> 32; receiver->hrtime_buffer_[1] = (t / NANOS_PER_SEC) & 0xffffffff; receiver->hrtime_buffer_[2] = t % NANOS_PER_SEC; } -void BindingData::BigIntImpl(BindingData* receiver) { +void BindingData::HrtimeBigIntImpl(BindingData* receiver) { uint64_t t = uv_hrtime(); // The buffer is a Uint32Array, so we need to reinterpret it as a // Uint64Array to write the value. The buffer is valid at this scope so we @@ -705,12 +705,12 @@ void BindingData::BigIntImpl(BindingData* receiver) { fields[0] = t; } -void BindingData::SlowBigInt(const FunctionCallbackInfo& args) { - BigIntImpl(FromJSObject(args.This())); +void BindingData::SlowHrtimeBigInt(const FunctionCallbackInfo& args) { + HrtimeBigIntImpl(FromJSObject(args.This())); } -void BindingData::SlowNumber(const v8::FunctionCallbackInfo& args) { - NumberImpl(FromJSObject(args.This())); +void BindingData::SlowHrtime(const FunctionCallbackInfo& args) { + HrtimeImpl(FromJSObject(args.This())); } bool BindingData::PrepareForSerialization(Local context, diff --git a/src/node_report.cc b/src/node_report.cc index df73a8204bc091..8ff711f12e19f7 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -752,7 +752,6 @@ static void PrintSystemInformation(JSONWriter* writer) { writer->json_objectstart("userLimits"); struct rlimit limit; - std::string soft, hard; for (size_t i = 0; i < arraysize(rlimit_strings); i++) { if (getrlimit(rlimit_strings[i].id, &limit) == 0) { @@ -788,8 +787,6 @@ static void PrintLoadedLibraries(JSONWriter* writer) { // Obtain and report the node and subcomponent version strings. static void PrintComponentVersions(JSONWriter* writer) { - std::stringstream buf; - writer->json_objectstart("componentVersions"); for (const auto& version : per_process::metadata.versions.pairs()) { @@ -857,7 +854,7 @@ std::string TriggerNodeReport(Isolate* isolate, THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemWrite, - std::string_view(Environment::GetCwd(env->exec_path())), + Environment::GetCwd(env->exec_path()), filename); } } diff --git a/src/node_version.h b/src/node_version.h index 73e57c69861765..ba16ca3abcf821 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -29,7 +29,7 @@ #define NODE_VERSION_IS_LTS 1 #define NODE_VERSION_LTS_CODENAME "Jod" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) diff --git a/src/node_wasi.cc b/src/node_wasi.cc index 090866960beb8f..85e549e4592e1d 100644 --- a/src/node_wasi.cc +++ b/src/node_wasi.cc @@ -265,7 +265,6 @@ inline void EinvalError() {} template R WASI::WasiFunction::FastCallback( - Local unused, Local receiver, Args... args, // NOLINTNEXTLINE(runtime/references) This is V8 api. diff --git a/src/node_wasi.h b/src/node_wasi.h index ef7d2e83b6728a..25551936e6be36 100644 --- a/src/node_wasi.h +++ b/src/node_wasi.h @@ -160,8 +160,7 @@ class WASI : public BaseObject, v8::Local); private: - static R FastCallback(v8::Local unused, - v8::Local receiver, + static R FastCallback(v8::Local receiver, Args..., v8::FastApiCallbackOptions&); diff --git a/src/permission/permission.h b/src/permission/permission.h index da796ab7b80559..00d612ee531ce9 100644 --- a/src/permission/permission.h +++ b/src/permission/permission.h @@ -27,25 +27,63 @@ class FSReqBase; namespace permission { -#define THROW_IF_INSUFFICIENT_PERMISSIONS(env, perm_, resource_, ...) \ +#define THROW_IF_INSUFFICIENT_PERMISSIONS(env, perm, resource, ...) \ do { \ - if (!env->permission()->is_granted(env, perm_, resource_)) [[unlikely]] { \ + node::Environment* env__ = (env); \ + const node::permission::PermissionScope perm__ = (perm); \ + const auto resource__ = (resource); \ + if (!env__->permission()->is_granted(env__, perm__, resource__)) \ + [[unlikely]] { \ node::permission::Permission::ThrowAccessDenied( \ - (env), perm_, resource_); \ + env__, perm__, resource__); \ return __VA_ARGS__; \ } \ } while (0) #define ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS( \ - env, wrap, perm_, resource_, ...) \ + env, wrap, perm, resource, ...) \ do { \ - if (!env->permission()->is_granted(env, perm_, resource_)) [[unlikely]] { \ + node::Environment* env__ = (env); \ + const node::permission::PermissionScope perm__ = (perm); \ + const auto resource__ = (resource); \ + if (!env__->permission()->is_granted(env__, perm__, resource__)) \ + [[unlikely]] { \ node::permission::Permission::AsyncThrowAccessDenied( \ - (env), wrap, perm_, resource_); \ + env__, (wrap), perm__, resource__); \ return __VA_ARGS__; \ } \ } while (0) +#define ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS( \ + env, perm, resource, args, ...) \ + do { \ + node::Environment* env__ = (env); \ + const node::permission::PermissionScope perm__ = (perm); \ + const auto resource__ = (resource); \ + if (!env__->permission()->is_granted(env__, perm__, resource__)) \ + [[unlikely]] { \ + Local err_access; \ + if (node::permission::CreateAccessDeniedError(env__, perm__, resource__) \ + .ToLocal(&err_access)) { \ + args.GetReturnValue().Set(err_access); \ + } else { \ + args.GetReturnValue().Set(UV_EACCES); \ + } \ + return __VA_ARGS__; \ + } \ + } while (0) + +#define SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(scope) \ + void InsufficientPermissionError(std::string_view resource) { \ + v8::HandleScope handle_scope(env()->isolate()); \ + v8::Context::Scope context_scope(env()->context()); \ + v8::Local arg; \ + if (!permission::CreateAccessDeniedError(env(), (scope), resource) \ + .ToLocal(&arg)) { \ + } \ + MakeCallback(env()->oncomplete_string(), 1, &arg); \ + } + class Permission { public: Permission(); diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 2853b0dc129fc3..d27ca7da7b587b 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -116,9 +116,10 @@ class ProcessWrap : public HandleWrap { return Just(stream); } - static Maybe ParseStdioOptions(Environment* env, - Local js_options, - uv_process_options_t* options) { + static Maybe ParseStdioOptions( + Environment* env, + Local js_options, + std::vector* options_stdio) { Local context = env->context(); Local stdio_key = env->stdio_string(); Local stdios_val; @@ -132,8 +133,7 @@ class ProcessWrap : public HandleWrap { Local stdios = stdios_val.As(); uint32_t len = stdios->Length(); - options->stdio = new uv_stdio_container_t[len]; - options->stdio_count = len; + options_stdio->resize(len); for (uint32_t i = 0; i < len; i++) { Local val; @@ -147,23 +147,23 @@ class ProcessWrap : public HandleWrap { } if (type->StrictEquals(env->ignore_string())) { - options->stdio[i].flags = UV_IGNORE; + (*options_stdio)[i].flags = UV_IGNORE; } else if (type->StrictEquals(env->pipe_string())) { - options->stdio[i].flags = static_cast( + (*options_stdio)[i].flags = static_cast( UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE); - if (!StreamForWrap(env, stdio).To(&options->stdio[i].data.stream)) { + if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) { return Nothing(); } } else if (type->StrictEquals(env->overlapped_string())) { - options->stdio[i].flags = static_cast( - UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE | - UV_OVERLAPPED_PIPE); - if (!StreamForWrap(env, stdio).To(&options->stdio[i].data.stream)) { + (*options_stdio)[i].flags = + static_cast(UV_CREATE_PIPE | UV_READABLE_PIPE | + UV_WRITABLE_PIPE | UV_OVERLAPPED_PIPE); + if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) { return Nothing(); } } else if (type->StrictEquals(env->wrap_string())) { - options->stdio[i].flags = UV_INHERIT_STREAM; - if (!StreamForWrap(env, stdio).To(&options->stdio[i].data.stream)) { + (*options_stdio)[i].flags = UV_INHERIT_STREAM; + if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) { return Nothing(); } } else { @@ -174,8 +174,8 @@ class ProcessWrap : public HandleWrap { } CHECK(fd_value->IsNumber()); int fd = FromV8Value(fd_value); - options->stdio[i].flags = UV_INHERIT_FD; - options->stdio[i].data.fd = fd; + (*options_stdio)[i].flags = UV_INHERIT_FD; + (*options_stdio)[i].data.fd = fd; } } return JustVoid(); @@ -199,8 +199,6 @@ class ProcessWrap : public HandleWrap { options.exit_cb = OnExit; - // TODO(bnoordhuis) is this possible to do without mallocing ? - // options.file Local file_v; if (!js_options->Get(context, env->file_string()).ToLocal(&file_v)) { @@ -251,23 +249,28 @@ class ProcessWrap : public HandleWrap { if (!js_options->Get(context, env->args_string()).ToLocal(&argv_v)) { return; } - if (!argv_v.IsEmpty() && argv_v->IsArray()) { + std::vector options_args; + std::vector args_vals; + if (argv_v->IsArray()) { Local js_argv = argv_v.As(); int argc = js_argv->Length(); CHECK_LT(argc, INT_MAX); // Check for overflow. - - // Heap allocate to detect errors. +1 is for nullptr. - options.args = new char*[argc + 1]; + args_vals.reserve(argc); for (int i = 0; i < argc; i++) { Local val; if (!js_argv->Get(context, i).ToLocal(&val)) { return; } node::Utf8Value arg(env->isolate(), val); - options.args[i] = strdup(*arg); - CHECK_NOT_NULL(options.args[i]); + args_vals.emplace_back(arg.ToString()); + } + options_args.resize(args_vals.size() + 1); + for (size_t i = 0; i < args_vals.size(); i++) { + options_args[i] = const_cast(args_vals[i].c_str()); + CHECK_NOT_NULL(options_args[i]); } - options.args[argc] = nullptr; + options_args.back() = nullptr; + options.args = options_args.data(); } // options.cwd @@ -286,27 +289,37 @@ class ProcessWrap : public HandleWrap { if (!js_options->Get(context, env->env_pairs_string()).ToLocal(&env_v)) { return; } - if (!env_v.IsEmpty() && env_v->IsArray()) { + std::vector options_env; + std::vector env_vals; + if (env_v->IsArray()) { Local env_opt = env_v.As(); int envc = env_opt->Length(); CHECK_LT(envc, INT_MAX); // Check for overflow. - options.env = new char*[envc + 1]; // Heap allocated to detect errors. + env_vals.reserve(envc); for (int i = 0; i < envc; i++) { Local val; if (!env_opt->Get(context, i).ToLocal(&val)) { return; } node::Utf8Value pair(env->isolate(), val); - options.env[i] = strdup(*pair); - CHECK_NOT_NULL(options.env[i]); + env_vals.emplace_back(pair.ToString()); } - options.env[envc] = nullptr; + options_env.resize(env_vals.size() + 1); + for (size_t i = 0; i < env_vals.size(); i++) { + options_env[i] = const_cast(env_vals[i].c_str()); + CHECK_NOT_NULL(options_env[i]); + } + options_env.back() = nullptr; + options.env = options_env.data(); } // options.stdio - if (ParseStdioOptions(env, js_options, &options).IsNothing()) { + std::vector options_stdio; + if (ParseStdioOptions(env, js_options, &options_stdio).IsNothing()) { return; } + options.stdio = options_stdio.data(); + options.stdio_count = options_stdio.size(); // options.windowsHide Local hide_v; @@ -361,18 +374,6 @@ class ProcessWrap : public HandleWrap { } } - if (options.args) { - for (int i = 0; options.args[i]; i++) free(options.args[i]); - delete [] options.args; - } - - if (options.env) { - for (int i = 0; options.env[i]; i++) free(options.env[i]); - delete [] options.env; - } - - delete[] options.stdio; - args.GetReturnValue().Set(err); } diff --git a/src/quic/defs.h b/src/quic/defs.h index 628b2b754a36a5..0816e1d09874db 100644 --- a/src/quic/defs.h +++ b/src/quic/defs.h @@ -39,7 +39,7 @@ bool SetOption(Environment* env, if (!object->Get(env->context(), name).ToLocal(&value)) return false; if (!value->IsUndefined()) { Utf8Value utf8(env->isolate(), value); - options->*member = *utf8; + options->*member = utf8.ToString(); } return true; } diff --git a/src/spawn_sync.cc b/src/spawn_sync.cc index 574afe60df8efe..47c4aaef348433 100644 --- a/src/spawn_sync.cc +++ b/src/spawn_sync.cc @@ -403,7 +403,6 @@ void SyncProcessRunner::Spawn(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(result); } - SyncProcessRunner::SyncProcessRunner(Environment* env) : max_buffer_(0), timeout_(0), @@ -412,14 +411,9 @@ SyncProcessRunner::SyncProcessRunner(Environment* env) uv_loop_(nullptr), stdio_count_(0), - uv_stdio_containers_(nullptr), stdio_pipes_initialized_(false), uv_process_options_(), - file_buffer_(nullptr), - args_buffer_(nullptr), - env_buffer_(nullptr), - cwd_buffer_(nullptr), uv_process_(), killed_(false), @@ -436,19 +430,12 @@ SyncProcessRunner::SyncProcessRunner(Environment* env) lifecycle_(kUninitialized), - env_(env) { -} - + env_(env) {} SyncProcessRunner::~SyncProcessRunner() { CHECK_EQ(lifecycle_, kHandlesClosed); stdio_pipes_.clear(); - delete[] file_buffer_; - delete[] args_buffer_; - delete[] cwd_buffer_; - delete[] env_buffer_; - delete[] uv_stdio_containers_; } @@ -808,12 +795,14 @@ Maybe SyncProcessRunner::ParseOptions(Local js_value) { Local js_options = js_value.As(); Local js_file; + const char* file_buffer; if (!js_options->Get(context, env()->file_string()).ToLocal(&js_file) || - !CopyJsString(js_file, &file_buffer_).To(&r)) { + !CopyJsString(js_file, &file_buffer).To(&r)) { return Nothing(); } if (r < 0) return Just(r); - uv_process_options_.file = file_buffer_; + file_buffer_.reset(file_buffer); + uv_process_options_.file = file_buffer_.get(); // Undocumented feature of Win32 CreateProcess API allows spawning // batch files directly but is potentially insecure because arguments @@ -825,23 +814,27 @@ Maybe SyncProcessRunner::ParseOptions(Local js_value) { #endif Local js_args; + char* args_buffer; if (!js_options->Get(context, env()->args_string()).ToLocal(&js_args) || - !CopyJsStringArray(js_args, &args_buffer_).To(&r)) { + !CopyJsStringArray(js_args, &args_buffer).To(&r)) { return Nothing(); } if (r < 0) return Just(r); - uv_process_options_.args = reinterpret_cast(args_buffer_); + args_buffer_.reset(args_buffer); + uv_process_options_.args = reinterpret_cast(args_buffer_.get()); Local js_cwd; if (!js_options->Get(context, env()->cwd_string()).ToLocal(&js_cwd)) { return Nothing(); } if (!js_cwd->IsNullOrUndefined()) { - if (!CopyJsString(js_cwd, &cwd_buffer_).To(&r)) { + const char* cwd_buffer; + if (!CopyJsString(js_cwd, &cwd_buffer).To(&r)) { return Nothing(); } if (r < 0) return Just(r); - uv_process_options_.cwd = cwd_buffer_; + cwd_buffer_.reset(cwd_buffer); + uv_process_options_.cwd = cwd_buffer_.get(); } Local js_env_pairs; @@ -850,12 +843,13 @@ Maybe SyncProcessRunner::ParseOptions(Local js_value) { return Nothing(); } if (!js_env_pairs->IsNullOrUndefined()) { - if (!CopyJsStringArray(js_env_pairs, &env_buffer_).To(&r)) { + char* env_buffer; + if (!CopyJsStringArray(js_env_pairs, &env_buffer).To(&r)) { return Nothing(); } if (r < 0) return Just(r); - - uv_process_options_.env = reinterpret_cast(env_buffer_); + env_buffer_.reset(env_buffer); + uv_process_options_.env = reinterpret_cast(env_buffer_.get()); } Local js_uid; if (!js_options->Get(context, env()->uid_string()).ToLocal(&js_uid)) { @@ -982,7 +976,7 @@ Maybe SyncProcessRunner::ParseStdioOptions(Local js_value) { js_stdio_options = js_value.As(); stdio_count_ = js_stdio_options->Length(); - uv_stdio_containers_ = new uv_stdio_container_t[stdio_count_]; + uv_stdio_containers_.resize(stdio_count_); stdio_pipes_.clear(); stdio_pipes_.resize(stdio_count_); @@ -1007,7 +1001,7 @@ Maybe SyncProcessRunner::ParseStdioOptions(Local js_value) { } } - uv_process_options_.stdio = uv_stdio_containers_; + uv_process_options_.stdio = uv_stdio_containers_.data(); uv_process_options_.stdio_count = stdio_count_; return Just(0); diff --git a/src/spawn_sync.h b/src/spawn_sync.h index 4478487c8f403e..9c8b0c563c4c45 100644 --- a/src/spawn_sync.h +++ b/src/spawn_sync.h @@ -205,15 +205,15 @@ class SyncProcessRunner { uv_loop_t* uv_loop_; uint32_t stdio_count_; - uv_stdio_container_t* uv_stdio_containers_; + std::vector uv_stdio_containers_; std::vector> stdio_pipes_; bool stdio_pipes_initialized_; uv_process_options_t uv_process_options_; - const char* file_buffer_; - char* args_buffer_; - char* env_buffer_; - const char* cwd_buffer_; + std::unique_ptr file_buffer_; + std::unique_ptr args_buffer_; + std::unique_ptr env_buffer_; + std::unique_ptr cwd_buffer_; uv_process_t uv_process_; bool killed_; diff --git a/src/timers.cc b/src/timers.cc index bf90e68479da14..da4206187f7c7d 100644 --- a/src/timers.cc +++ b/src/timers.cc @@ -53,9 +53,8 @@ void BindingData::SlowScheduleTimer(const FunctionCallbackInfo& args) { } } -void BindingData::FastScheduleTimer(Local unused, - Local receiver, - int64_t duration) { +void BindingData::FastScheduleTimer(Local receiver, int64_t duration) { + TRACK_V8_FAST_API_CALL("timers.scheduleTimer"); ScheduleTimerImpl(FromJSObject(receiver), duration); } @@ -69,9 +68,8 @@ void BindingData::SlowToggleTimerRef( args[0]->IsTrue()); } -void BindingData::FastToggleTimerRef(Local unused, - Local receiver, - bool ref) { +void BindingData::FastToggleTimerRef(Local receiver, bool ref) { + TRACK_V8_FAST_API_CALL("timers.toggleTimerRef"); ToggleTimerRefImpl(FromJSObject(receiver), ref); } @@ -85,9 +83,8 @@ void BindingData::SlowToggleImmediateRef( args[0]->IsTrue()); } -void BindingData::FastToggleImmediateRef(Local unused, - Local receiver, - bool ref) { +void BindingData::FastToggleImmediateRef(Local receiver, bool ref) { + TRACK_V8_FAST_API_CALL("timers.toggleImmediateRef"); ToggleImmediateRefImpl(FromJSObject(receiver), ref); } diff --git a/src/timers.h b/src/timers.h index 3c3a4d60d34ae8..01cc612e8b26a2 100644 --- a/src/timers.h +++ b/src/timers.h @@ -31,23 +31,18 @@ class BindingData : public SnapshotableObject { static void SlowScheduleTimer( const v8::FunctionCallbackInfo& args); - static void FastScheduleTimer(v8::Local unused, - v8::Local receiver, + static void FastScheduleTimer(v8::Local receiver, int64_t duration); static void ScheduleTimerImpl(BindingData* data, int64_t duration); static void SlowToggleTimerRef( const v8::FunctionCallbackInfo& args); - static void FastToggleTimerRef(v8::Local unused, - v8::Local receiver, - bool ref); + static void FastToggleTimerRef(v8::Local receiver, bool ref); static void ToggleTimerRefImpl(BindingData* data, bool ref); static void SlowToggleImmediateRef( const v8::FunctionCallbackInfo& args); - static void FastToggleImmediateRef(v8::Local unused, - v8::Local receiver, - bool ref); + static void FastToggleImmediateRef(v8::Local receiver, bool ref); static void ToggleImmediateRefImpl(BindingData* data, bool ref); static void CreatePerIsolateProperties(IsolateData* isolate_data, diff --git a/src/util-inl.h b/src/util-inl.h index eb097bcb00aaf0..17b870e2dd91ab 100644 --- a/src/util-inl.h +++ b/src/util-inl.h @@ -193,10 +193,14 @@ char ToLower(char c) { return std::tolower(c, std::locale::classic()); } -std::string ToLower(const std::string& in) { - std::string out(in.size(), 0); - for (size_t i = 0; i < in.size(); ++i) - out[i] = ToLower(in[i]); +template +std::string ToLower(const T& in) { + auto it = std::cbegin(in); + auto end = std::cend(in); + std::string out(std::distance(it, end), 0); + size_t i; + for (i = 0; it != end; ++it, ++i) out[i] = ToLower(*it); + DCHECK_EQ(i, out.size()); return out; } @@ -204,10 +208,14 @@ char ToUpper(char c) { return std::toupper(c, std::locale::classic()); } -std::string ToUpper(const std::string& in) { - std::string out(in.size(), 0); - for (size_t i = 0; i < in.size(); ++i) - out[i] = ToUpper(in[i]); +template +std::string ToUpper(const T& in) { + auto it = std::cbegin(in); + auto end = std::cend(in); + std::string out(std::distance(it, end), 0); + size_t i; + for (i = 0; it != end; ++it, ++i) out[i] = ToUpper(*it); + DCHECK_EQ(i, out.size()); return out; } diff --git a/src/util.h b/src/util.h index f5803eb383f2b8..8f27afbb9e4e45 100644 --- a/src/util.h +++ b/src/util.h @@ -365,11 +365,13 @@ inline v8::Local FIXED_ONE_BYTE_STRING(v8::Isolate* isolate, // tolower() is locale-sensitive. Use ToLower() instead. inline char ToLower(char c); -inline std::string ToLower(const std::string& in); +template +inline std::string ToLower(const T& in); // toupper() is locale-sensitive. Use ToUpper() instead. inline char ToUpper(char c); -inline std::string ToUpper(const std::string& in); +template +inline std::string ToUpper(const T& in); // strcasecmp() is locale-sensitive. Use StringEqualNoCase() instead. inline bool StringEqualNoCase(const char* a, const char* b); @@ -552,11 +554,6 @@ class Utf8Value : public MaybeStackBuffer { public: explicit Utf8Value(v8::Isolate* isolate, v8::Local value); - inline std::string ToString() const { return std::string(out(), length()); } - inline std::string_view ToStringView() const { - return std::string_view(out(), length()); - } - inline bool operator==(const char* a) const { return strcmp(out(), a) == 0; } inline bool operator!=(const char* a) const { return !(*this == a); } }; @@ -570,10 +567,6 @@ class BufferValue : public MaybeStackBuffer { public: explicit BufferValue(v8::Isolate* isolate, v8::Local value); - inline std::string ToString() const { return std::string(out(), length()); } - inline std::string_view ToStringView() const { - return std::string_view(out(), length()); - } inline std::u8string_view ToU8StringView() const { return std::u8string_view(reinterpret_cast(out()), length()); diff --git a/test/benchmark/test-bechmark-readline.js b/test/benchmark/test-benchmark-readline.js similarity index 100% rename from test/benchmark/test-bechmark-readline.js rename to test/benchmark/test-benchmark-readline.js diff --git a/test/benchmark/test-benchmark-validators.js b/test/benchmark/test-benchmark-validators.js index 37250f56588f51..4983991c0919a9 100644 --- a/test/benchmark/test-benchmark-validators.js +++ b/test/benchmark/test-benchmark-validators.js @@ -2,7 +2,7 @@ require('../common'); -// Minimal test for assert benchmarks. This makes sure the benchmarks aren't +// Minimal test for validators benchmarks. This makes sure the benchmarks aren't // completely broken but nothing more than that. const runBenchmark = require('../common/benchmark'); diff --git a/test/common/assertSnapshot.js b/test/common/assertSnapshot.js index 7a40c94389eda9..086561c9a0b52f 100644 --- a/test/common/assertSnapshot.js +++ b/test/common/assertSnapshot.js @@ -17,7 +17,9 @@ function replaceStackTrace(str, replacement = '$1*$7$8\n') { } function replaceInternalStackTrace(str) { - return str.replaceAll(/(\W+).*node:internal.*/g, '$1*'); + // Replace non-internal frame `at TracingChannel.traceSync (node:diagnostics_channel:328:14)` + // as well as `at node:internal/main/run_main_module:33:47` with `*`. + return str.replaceAll(/(\W+).*[(\s]node:.*/g, '$1*'); } function replaceWindowsLineEndings(str) { diff --git a/test/common/websocket-server.js b/test/common/websocket-server.js new file mode 100644 index 00000000000000..7f2447396972f7 --- /dev/null +++ b/test/common/websocket-server.js @@ -0,0 +1,108 @@ +'use strict'; +const common = require('./index'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const http = require('http'); +const crypto = require('crypto'); + +class WebSocketServer { + constructor({ + port = 0, + server, + }) { + this.port = port; + this.server = server || http.createServer(); + this.clients = new Set(); + + this.server.on('upgrade', this.handleUpgrade.bind(this)); + } + + start() { + return new Promise((resolve) => { + this.server.listen(this.port, () => { + this.port = this.server.address().port; + resolve(); + }); + }).catch((err) => { + console.error('Failed to start WebSocket server:', err); + }); + } + + handleUpgrade(req, socket, head) { + const key = req.headers['sec-websocket-key']; + const acceptKey = this.generateAcceptValue(key); + const responseHeaders = [ + 'HTTP/1.1 101 Switching Protocols', + 'Upgrade: websocket', + 'Connection: Upgrade', + `Sec-WebSocket-Accept: ${acceptKey}`, + ]; + + socket.write(responseHeaders.join('\r\n') + '\r\n\r\n'); + this.clients.add(socket); + + socket.on('data', (buffer) => { + const opcode = buffer[0] & 0x0f; + + if (opcode === 0x8) { + // Send a minimal close frame in response: + socket.write(Buffer.from([0x88, 0x00])); + socket.end(); + this.clients.delete(socket); + return; + } + + socket.write(this.encodeMessage('Hello from server!')); + }); + + socket.on('close', () => { + this.clients.delete(socket); + }); + + socket.on('error', (err) => { + console.error('Socket error:', err); + this.clients.delete(socket); + }); + } + + generateAcceptValue(secWebSocketKey) { + return crypto + .createHash('sha1') + .update(secWebSocketKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary') + .digest('base64'); + } + + decodeMessage(buffer) { + const secondByte = buffer[1]; + const length = secondByte & 127; + const maskStart = 2; + const dataStart = maskStart + 4; + const masks = buffer.slice(maskStart, dataStart); + const data = buffer.slice(dataStart, dataStart + length); + const result = Buffer.alloc(length); + + for (let i = 0; i < length; i++) { + result[i] = data[i] ^ masks[i % 4]; + } + + return result.toString(); + } + + encodeMessage(message) { + const msgBuffer = Buffer.from(message); + const length = msgBuffer.length; + const frame = [0x81]; + + if (length < 126) { + frame.push(length); + } else if (length < 65536) { + frame.push(126, (length >> 8) & 0xff, length & 0xff); + } else { + throw new Error('Message too long'); + } + + return Buffer.concat([Buffer.from(frame), msgBuffer]); + } +} + +module.exports = WebSocketServer; diff --git a/test/fixtures/source-map/output/source_map_assert_source_line.snapshot b/test/fixtures/source-map/output/source_map_assert_source_line.snapshot index 9def6eb4d7bedb..62e611f330f97f 100644 --- a/test/fixtures/source-map/output/source_map_assert_source_line.snapshot +++ b/test/fixtures/source-map/output/source_map_assert_source_line.snapshot @@ -7,7 +7,7 @@ AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value: * * * - at TracingChannel.traceSync (node:diagnostics_channel:328:14) + * * * * diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index 67c0c04d2365e5..9822dc622ebfc2 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -155,6 +155,7 @@ test-inspector-network-content-type: SKIP test-fetch: SKIP test-without-async-context-frame: SKIP test-process-cpuUsage: PASS, FLAKY +test-web-locks: SKIP [$asan==on] diff --git a/test/parallel/test-diagnostics-channel-tracing-channel-promise-unhandled.js b/test/parallel/test-diagnostics-channel-tracing-channel-promise-unhandled.js new file mode 100644 index 00000000000000..e24459774533ca --- /dev/null +++ b/test/parallel/test-diagnostics-channel-tracing-channel-promise-unhandled.js @@ -0,0 +1,38 @@ +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const channel = dc.tracingChannel('test'); + +const expectedError = new Error('test'); +const input = { foo: 'bar' }; +const thisArg = { baz: 'buz' }; + +process.on('unhandledRejection', common.mustCall((reason) => { + assert.deepStrictEqual(reason, expectedError); +})); + +function check(found) { + assert.deepStrictEqual(found, input); +} + +const handlers = { + start: common.mustCall(check), + end: common.mustCall(check), + asyncStart: common.mustCall(check), + asyncEnd: common.mustCall(check), + error: common.mustCall((found) => { + check(found); + assert.deepStrictEqual(found.error, expectedError); + }) +}; + +channel.subscribe(handlers); + +// Set no then/catch handler to verify unhandledRejection happens +channel.tracePromise(function(value) { + assert.deepStrictEqual(this, thisArg); + return Promise.reject(value); +}, input, thisArg, expectedError); diff --git a/test/parallel/test-http2-allow-http1-upgrade-ws.js b/test/parallel/test-http2-allow-http1-upgrade-ws.js new file mode 100644 index 00000000000000..028dc4e4cde71c --- /dev/null +++ b/test/parallel/test-http2-allow-http1-upgrade-ws.js @@ -0,0 +1,39 @@ +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +const fixtures = require('../common/fixtures'); +if (!common.hasCrypto) common.skip('missing crypto'); + +const http2 = require('http2'); + +const undici = require('internal/deps/undici/undici'); +const WebSocketServer = require('../common/websocket-server'); + +(async function main() { + const server = http2.createSecureServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem'), + allowHTTP1: true, + }); + + server.on('request', common.mustNotCall()); + new WebSocketServer({ server }); // Handles websocket 'upgrade' events + + await new Promise((resolve) => server.listen(0, resolve)); + + await new Promise((resolve, reject) => { + const ws = new WebSocket(`wss://localhost:${server.address().port}`, { + dispatcher: new undici.EnvHttpProxyAgent({ + connect: { rejectUnauthorized: false } + }) + }); + ws.addEventListener('open', common.mustCall(() => { + ws.close(); + resolve(); + })); + ws.addEventListener('error', reject); + }); + + server.close(); +})().then(common.mustCall()); diff --git a/test/parallel/test-process-hrtime-bigint.js b/test/parallel/test-process-hrtime-bigint.js index e5ce40a994d815..9d0e0e347b0179 100644 --- a/test/parallel/test-process-hrtime-bigint.js +++ b/test/parallel/test-process-hrtime-bigint.js @@ -1,10 +1,13 @@ +// Flags: --allow-natives-syntax --expose-internals --no-warnings 'use strict'; // Tests that process.hrtime.bigint() works. -require('../common'); +const common = require('../common'); const assert = require('assert'); +const { internalBinding } = require('internal/test/binding'); + const start = process.hrtime.bigint(); assert.strictEqual(typeof start, 'bigint'); @@ -12,3 +15,13 @@ const end = process.hrtime.bigint(); assert.strictEqual(typeof end, 'bigint'); assert(end - start >= 0n); + +eval('%PrepareFunctionForOptimization(process.hrtime.bigint)'); +assert(process.hrtime.bigint()); +eval('%OptimizeFunctionOnNextCall(process.hrtime.bigint)'); +assert(process.hrtime.bigint()); + +if (common.isDebug) { + const { getV8FastApiCallCount } = internalBinding('debug'); + assert.strictEqual(getV8FastApiCallCount('process.hrtimeBigInt'), 1); +} diff --git a/test/parallel/test-process-hrtime.js b/test/parallel/test-process-hrtime.js index 34ef514aac309b..e5815ebe10cc91 100644 --- a/test/parallel/test-process-hrtime.js +++ b/test/parallel/test-process-hrtime.js @@ -19,10 +19,13 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +// Flags: --allow-natives-syntax --expose-internals --no-warnings 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); +const { internalBinding } = require('internal/test/binding'); + // The default behavior, return an Array "tuple" of numbers const tuple = process.hrtime(); @@ -72,3 +75,13 @@ function validateTuple(tuple) { const diff = process.hrtime([0, 1e9 - 1]); assert(diff[1] >= 0); // https://github.com/nodejs/node/issues/4751 + +eval('%PrepareFunctionForOptimization(process.hrtime)'); +assert(process.hrtime()); +eval('%OptimizeFunctionOnNextCall(process.hrtime)'); +assert(process.hrtime()); + +if (common.isDebug) { + const { getV8FastApiCallCount } = internalBinding('debug'); + assert.strictEqual(getV8FastApiCallCount('process.hrtime'), 1); +} diff --git a/test/parallel/test-promise-unhandled-error-with-reading-file.js b/test/parallel/test-promise-unhandled-error-with-reading-file.js new file mode 100644 index 00000000000000..5a037eec7ca229 --- /dev/null +++ b/test/parallel/test-promise-unhandled-error-with-reading-file.js @@ -0,0 +1,29 @@ +// Flags: --unhandled-rejections=strict +'use strict'; + +const common = require('../common'); +const fs = require('fs'); +const assert = require('assert'); + +process.on('unhandledRejection', common.mustNotCall); + +process.on('uncaughtException', common.mustCall((err) => { + assert.ok(err.message.includes('foo')); +})); + + +async function readFile() { + return fs.promises.readFile(__filename); +} + +async function crash() { + throw new Error('foo'); +} + + +async function main() { + crash(); + readFile(); +} + +main(); diff --git a/test/parallel/test-timers-fast-calls.js b/test/parallel/test-timers-fast-calls.js new file mode 100644 index 00000000000000..06387f46c363d4 --- /dev/null +++ b/test/parallel/test-timers-fast-calls.js @@ -0,0 +1,28 @@ +// Flags: --allow-natives-syntax --expose-internals --no-warnings +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const { internalBinding } = require('internal/test/binding'); +const binding = internalBinding('timers'); + +function testFastCalls() { + binding.scheduleTimer(1); + binding.toggleTimerRef(true); + binding.toggleTimerRef(false); + binding.toggleImmediateRef(true); + binding.toggleImmediateRef(false); +} + +eval('%PrepareFunctionForOptimization(testFastCalls)'); +testFastCalls(); +eval('%OptimizeFunctionOnNextCall(testFastCalls)'); +testFastCalls(); + +if (common.isDebug) { + const { getV8FastApiCallCount } = internalBinding('debug'); + assert.strictEqual(getV8FastApiCallCount('timers.scheduleTimer'), 1); + assert.strictEqual(getV8FastApiCallCount('timers.toggleTimerRef'), 2); + assert.strictEqual(getV8FastApiCallCount('timers.toggleImmediateRef'), 2); +} diff --git a/test/parallel/test-tls-check-server-identity.js b/test/parallel/test-tls-check-server-identity.js index 3682aee37b9a56..6918638230c4da 100644 --- a/test/parallel/test-tls-check-server-identity.js +++ b/test/parallel/test-tls-check-server-identity.js @@ -62,6 +62,11 @@ const tests = [ cert: { subject: { CN: '.a.com' } }, error: 'Host: a.com. is not cert\'s CN: .a.com' }, + { + host: 'bad.x.example.com', + cert: { subject: { CN: 'bad..example.com' } }, + error: 'Host: bad.x.example.com. is not cert\'s CN: bad..example.com' + }, // IP address in CN. Technically allowed but so rare that we reject // it anyway. If we ever do start allowing them, we should take care @@ -129,6 +134,16 @@ const tests = [ cert: { subject: { CN: 'b*b.a.com' } }, error: 'Host: b.a.com. is not cert\'s CN: b*b.a.com' }, + { + host: 'bxa.a.com', + cert: { subject: { CN: 'b**.a.com' } }, + error: 'Host: bxa.a.com. is not cert\'s CN: b**.a.com' + }, + { + host: 'xbcd.a.com', + cert: { subject: { CN: 'ab*cd.a.com' } }, + error: 'Host: xbcd.a.com. is not cert\'s CN: ab*cd.a.com' + }, // Empty Cert { @@ -158,6 +173,11 @@ const tests = [ subject: { CN: ['foo.com', 'bar.com'] } // CN=foo.com; CN=bar.com; } }, + { + host: 'a.com', + cert: { subject: { CN: [''] } }, + error: 'Host: a.com. is not cert\'s CN: ' + }, // DNS names and CN { @@ -212,6 +232,46 @@ const tests = [ }, // DNS names + { + host: 'a.com', + cert: { + subjectaltname: 'DNS:', + subject: {} + }, + error: 'Host: a.com. is not in the cert\'s altnames: DNS:' + }, + { + host: 'bad.x.example.com', + cert: { + subjectaltname: 'DNS:bad..example.com', + subject: {} + }, + error: 'Host: bad.x.example.com. is not in the cert\'s altnames: DNS:bad..example.com' + }, + { + host: 'x.example.com', + cert: { + subjectaltname: 'DNS:caf\u00E9.example.com', // "café.example.com" + subject: {} + }, + error: 'Host: x.example.com. is not in the cert\'s altnames: DNS:caf\u00E9.example.com' + }, + { + host: 'xbcd.a.com', + cert: { + subjectaltname: 'DNS:ab*cd.a.com', + subject: {} + }, + error: 'Host: xbcd.a.com. is not in the cert\'s altnames: DNS:ab*cd.a.com' + }, + { + host: 'x.example.com', + cert: { + subjectaltname: 'DNS:bad label.com', + subject: {} + }, + error: 'Host: x.example.com. is not in the cert\'s altnames: DNS:bad label.com' + }, { host: 'a.com', cert: { subjectaltname: 'DNS:*.a.com', @@ -261,6 +321,14 @@ const tests = [ subject: {} } }, + { + host: 'bxa.a.com', + cert: { + subjectaltname: 'DNS:b**.a.com', + subject: {} + }, + error: 'Host: bxa.a.com. is not in the cert\'s altnames: DNS:b**.a.com' + }, // URI names { host: 'a.b.a.com', cert: { diff --git a/test/parallel/test-whatwg-events-add-event-listener-options-passive.js b/test/parallel/test-whatwg-events-add-event-listener-options-passive.js index 97984bd9aff828..0299c7fa5fb0cc 100644 --- a/test/parallel/test-whatwg-events-add-event-listener-options-passive.js +++ b/test/parallel/test-whatwg-events-add-event-listener-options-passive.js @@ -1,6 +1,6 @@ 'use strict'; -const common = require('../common'); +require('../common'); // Manually converted from https://github.com/web-platform-tests/wpt/blob/master/dom/events/AddEventListenerOptions-passive.html // in order to define the `document` ourselves @@ -58,7 +58,6 @@ const { testPassiveValue({}, true); testPassiveValue({ passive: false }, true); - common.skip('TODO: passive listeners is still broken'); testPassiveValue({ passive: 1 }, false); testPassiveValue({ passive: true }, false); testPassiveValue({ passive: 0 }, true); diff --git a/test/parallel/test-worker-message-port-infinite-message-loop.js b/test/parallel/test-worker-message-port-infinite-message-loop.js index 0cd1cc06802055..d5924d9c3bd086 100644 --- a/test/parallel/test-worker-message-port-infinite-message-loop.js +++ b/test/parallel/test-worker-message-port-infinite-message-loop.js @@ -11,7 +11,7 @@ const { MessageChannel } = require('worker_threads'); const { port1, port2 } = new MessageChannel(); let count = 0; -port1.on('message', () => { +port1.on('message', common.mustCallAtLeast(() => { if (count === 0) { setTimeout(common.mustCall(() => { port1.close(); @@ -20,7 +20,7 @@ port1.on('message', () => { port2.postMessage(0); assert(count++ < 10000, `hit ${count} loop iterations`); -}); +})); port2.postMessage(0); diff --git a/test/wpt/status/dom/events.json b/test/wpt/status/dom/events.json index c0f4104c452b85..8109e2372adfb8 100644 --- a/test/wpt/status/dom/events.json +++ b/test/wpt/status/dom/events.json @@ -1,13 +1,4 @@ { - "AddEventListenerOptions-passive.any.js": { - "fail": { - "expected": [ - "preventDefault should be ignored if-and-only-if the passive option is true", - "returnValue should be ignored if-and-only-if the passive option is true", - "passive behavior of one listener should be unaffected by the presence of other listeners" - ] - } - }, "Event-dispatch-listener-order.window.js": { "skip": "document is not defined" }, diff --git a/tools/dep_updaters/nghttp.kbx b/tools/dep_updaters/nghttp.kbx new file mode 100644 index 00000000000000..60ad5134ecc66a Binary files /dev/null and b/tools/dep_updaters/nghttp.kbx differ diff --git a/tools/dep_updaters/update-nghttp2.sh b/tools/dep_updaters/update-nghttp2.sh index ccb36caae13d4d..c19dedf1ca203f 100755 --- a/tools/dep_updaters/update-nghttp2.sh +++ b/tools/dep_updaters/update-nghttp2.sh @@ -42,18 +42,19 @@ cleanup () { trap cleanup INT TERM EXIT NGHTTP2_REF="v$NEW_VERSION" -NGHTTP2_TARBALL="nghttp2-$NEW_VERSION.tar.gz" +NGHTTP2_TARBALL="nghttp2-$NEW_VERSION.tar.xz" cd "$WORKSPACE" echo "Fetching nghttp2 source archive" curl -sL -o "$NGHTTP2_TARBALL" "https://github.com/nghttp2/nghttp2/releases/download/$NGHTTP2_REF/$NGHTTP2_TARBALL" -DEPOSITED_CHECKSUM=$(curl -sL "https://github.com/nghttp2/nghttp2/releases/download/$NGHTTP2_REF/checksums.txt" | grep "$NGHTTP2_TARBALL") +echo "Verifying PGP signature" +curl -sL "https://github.com/nghttp2/nghttp2/releases/download/${NGHTTP2_REF}/${NGHTTP2_TARBALL}.asc" \ +| gpgv --keyring "$BASE_DIR/tools/dep_updaters/nghttp.kbx" "$NGHTTP2_TARBALL" -log_and_verify_sha256sum "nghttp2" "$NGHTTP2_TARBALL" "$DEPOSITED_CHECKSUM" - -gzip -dc "$NGHTTP2_TARBALL" | tar xf - +echo "Unpacking archive" +tar xJf "$NGHTTP2_TARBALL" rm "$NGHTTP2_TARBALL" mv "nghttp2-$NEW_VERSION" nghttp2 diff --git a/tools/dep_updaters/update-nghttp3.sh b/tools/dep_updaters/update-nghttp3.sh index 1a4df351b8abba..dc71735300de35 100755 --- a/tools/dep_updaters/update-nghttp3.sh +++ b/tools/dep_updaters/update-nghttp3.sh @@ -48,8 +48,12 @@ cd "$WORKSPACE" echo "Fetching nghttp3 source archive..." curl -sL -o "$ARCHIVE_BASENAME.tar.xz" "https://github.com/ngtcp2/nghttp3/releases/download/${NGHTTP3_REF}/${ARCHIVE_BASENAME}.tar.xz" -SHA256="$(curl -sL "https://github.com/ngtcp2/nghttp3/releases/download/${NGHTTP3_REF}/checksums.txt" | grep 'tar.xz$')" -log_and_verify_sha256sum "nghttp3" "$ARCHIVE_BASENAME.tar.xz" "$SHA256" + +echo "Verifying PGP signature..." +curl -sL "https://github.com/ngtcp2/nghttp3/releases/download/${NGHTTP3_REF}/${ARCHIVE_BASENAME}.tar.xz.asc" \ +| gpgv --keyring "$BASE_DIR/tools/dep_updaters/nghttp.kbx" - "$ARCHIVE_BASENAME.tar.xz" + +echo "Unpacking archive..." tar -xJf "$ARCHIVE_BASENAME.tar.xz" rm "$ARCHIVE_BASENAME.tar.xz" mv "$ARCHIVE_BASENAME" nghttp3