diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 3cb2689061d67b..4ac3f26dc66800 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -154,7 +154,12 @@ jobs: run: | nix-shell -I nixpkgs=./tools/nix/pkgs.nix -p 'nixfmt-tree' --run ' treefmt --quiet --ci - ' || git --no-pager diff --exit-code + ' && EXIT_CODE="$?" || EXIT_CODE="$?" + if [ "$EXIT_CODE" != "0" ] + then + git --no-pager diff || true + exit "$EXIT_CODE" + fi lint-py: if: github.event.pull_request.draft == false diff --git a/.github/workflows/notify-on-push.yml b/.github/workflows/notify-on-push.yml index 9b704b788261ba..605a896541f2f3 100644 --- a/.github/workflows/notify-on-push.yml +++ b/.github/workflows/notify-on-push.yml @@ -30,20 +30,16 @@ jobs: validateCommitMessage: name: Notify on Push on `main` with invalid message - if: github.repository == 'nodejs/node' # cannot use ubuntu-slim here because rtCamp/action-slack-notify is dockerized runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Check commit message + - name: Validate commits + run: echo "$COMMITS" | npx -q core-validate-commit - id: commit-check - run: npx -q core-validate-commit "$COMMIT" env: - COMMIT: ${{ github.event.after }} + COMMITS: ${{ toJSON(github.event.commits) }} - name: Slack Notification - if: ${{ failure() && steps.commit-check.conclusion == 'failure' }} + if: ${{ failure() && steps.commit-check.conclusion == 'failure' && github.repository == 'nodejs/node' }} uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # 2.3.3 env: SLACK_COLOR: '#DE512A' diff --git a/.github/workflows/timezone-update.yml b/.github/workflows/timezone-update.yml index 176944e3bd72d3..3901a589ab2fa2 100644 --- a/.github/workflows/timezone-update.yml +++ b/.github/workflows/timezone-update.yml @@ -57,7 +57,7 @@ jobs: with: author: Node.js GitHub Bot body: | - This PR was generated by tools/timezone-update.yml. + This PR was generated by `.github/workflows/timezone-update.yml` and `tools/update-timezone.mjs`. Updates the ICU files as per the instructions present in https://github.com/nodejs/node/blob/main/doc/contributing/maintaining/maintaining-icu.md#time-zone-data diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index ecb8f1914487b6..59d8e6a3fe84d3 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -121,14 +121,6 @@ jobs: run: | make corepack-update echo "NEW_VERSION=$(node deps/corepack/dist/corepack.js --version)" >> $GITHUB_ENV - - id: doc-kit - subsystem: tools - label: tools - run: | - ./tools/dep_updaters/update-doc.sh > temp-output - cat temp-output - tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true - rm temp-output - id: googletest subsystem: deps label: dependencies, test diff --git a/BUILDING.md b/BUILDING.md index 59ec18ba86f628..cdb1965f623ba1 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -403,11 +403,9 @@ If you are running tests before submitting a pull request, use: make -j4 test ``` -`make -j4 test` does a full check on the codebase, including running linters and -documentation tests. +`make -j4 test` does a full check on the codebase, including documentation tests. -To run the linter without running tests, use -`make lint`/`vcbuild lint`. It will lint JavaScript, C++, and Markdown files. +To run the linter, use `make lint`/`vcbuild lint`. It will lint JavaScript, C++, and Markdown files. To fix auto fixable JavaScript linting errors, use `make lint-js-fix`. diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bfdd5d10aa3b..c1be689f240cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,8 @@ release. -25.8.2
+25.9.0
+25.8.2
25.8.1
25.8.0
25.7.0
diff --git a/Makefile b/Makefile index c50a5c26da16f5..231bbe11f4c011 100644 --- a/Makefile +++ b/Makefile @@ -338,7 +338,7 @@ coverage-run-js: ## Run JavaScript tests with coverage. .PHONY: test # This does not run tests of third-party libraries inside deps. -test: all ## Run default tests, linters, and build docs. +test: all ## Run default tests and build docs. $(MAKE) -s tooltest $(MAKE) -s build-addons $(MAKE) -s build-js-native-api-tests @@ -348,7 +348,7 @@ test: all ## Run default tests, linters, and build docs. $(MAKE) -s jstest .PHONY: test-only -test-only: all ## Run default tests, without linters or building the docs. +test-only: all ## Run default tests without building the docs. $(MAKE) build-addons $(MAKE) build-js-native-api-tests $(MAKE) build-node-api-tests @@ -384,7 +384,7 @@ ifeq ($(OSTYPE),os400) DOCBUILDSTAMP_PREREQS := $(DOCBUILDSTAMP_PREREQS) out/$(BUILDTYPE)/node.exp endif -DOC_KIT ?= tools/doc/node_modules/@nodejs/doc-kit/bin/cli.mjs +DOC_KIT ?= tools/doc/node_modules/@node-core/doc-kit/bin/cli.mjs node_use_openssl_and_icu = $(call available-node,"-p" \ "process.versions.openssl != undefined && process.versions.icu != undefined") @@ -838,6 +838,10 @@ out/doc/api: doc/api # Using grouped targets (&:) so Make knows one command produces all outputs ifeq ($(OSTYPE),aix) # TODO(@nodejs/web-infra): AIX is currently hanging during HTML minification +$(apidocs_html) $(apidocs_json) out/doc/api/all.html out/doc/api/all.json: + @echo "Skipping $@ (not currently supported by $(OSTYPE) machines)" +else ifeq ($(OSTYPE),os400) +# TODO(@nodejs/web-infra): IBMi is currently hanging during HTML minification $(apidocs_html) $(apidocs_json) out/doc/api/all.html out/doc/api/all.json: @echo "Skipping $@ (not currently supported by $(OSTYPE) machines)" else diff --git a/benchmark/buffers/buffer-bytelength-string.js b/benchmark/buffers/buffer-bytelength-string.js index 557bc8d6fe38d6..65d92cb6f1cbba 100644 --- a/benchmark/buffers/buffer-bytelength-string.js +++ b/benchmark/buffers/buffer-bytelength-string.js @@ -4,7 +4,7 @@ const common = require('../common'); const bench = common.createBenchmark(main, { type: ['one_byte', 'two_bytes', 'three_bytes', 'four_bytes', 'latin1'], - encoding: ['utf8', 'base64'], + encoding: ['utf8', 'base64', 'latin1', 'hex'], repeat: [1, 2, 16, 256], // x16 n: [4e6], }); diff --git a/benchmark/buffers/buffer-copy-bytes-from.js b/benchmark/buffers/buffer-copy-bytes-from.js new file mode 100644 index 00000000000000..508092264f3a43 --- /dev/null +++ b/benchmark/buffers/buffer-copy-bytes-from.js @@ -0,0 +1,31 @@ +'use strict'; + +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + type: ['Uint8Array', 'Uint16Array', 'Uint32Array', 'Float64Array'], + len: [64, 256, 2048], + partial: ['none', 'offset', 'offset-length'], + n: [6e5], +}); + +function main({ n, len, type, partial }) { + const TypedArrayCtor = globalThis[type]; + const src = new TypedArrayCtor(len); + for (let i = 0; i < len; i++) src[i] = i; + + let offset; + let length; + if (partial === 'offset') { + offset = len >>> 2; + } else if (partial === 'offset-length') { + offset = len >>> 2; + length = len >>> 1; + } + + bench.start(); + for (let i = 0; i < n; i++) { + Buffer.copyBytesFrom(src, offset, length); + } + bench.end(n); +} diff --git a/benchmark/buffers/buffer-fill.js b/benchmark/buffers/buffer-fill.js index f1ae896843eee0..95e584e246af73 100644 --- a/benchmark/buffers/buffer-fill.js +++ b/benchmark/buffers/buffer-fill.js @@ -10,6 +10,7 @@ const bench = common.createBenchmark(main, { 'fill("t")', 'fill("test")', 'fill("t", "utf8")', + 'fill("t", "ascii")', 'fill("t", 0, "utf8")', 'fill("t", 0)', 'fill(Buffer.alloc(1), 0)', diff --git a/benchmark/buffers/buffer-indexof.js b/benchmark/buffers/buffer-indexof.js index 52cc95ccb9cf7e..ac5dffdd6d9103 100644 --- a/benchmark/buffers/buffer-indexof.js +++ b/benchmark/buffers/buffer-indexof.js @@ -19,7 +19,7 @@ const searchStrings = [ const bench = common.createBenchmark(main, { search: searchStrings, - encoding: ['undefined', 'utf8', 'ucs2'], + encoding: ['undefined', 'utf8', 'ascii', 'latin1', 'ucs2'], type: ['buffer', 'string'], n: [5e4], }, { diff --git a/benchmark/buffers/buffer-tostring.js b/benchmark/buffers/buffer-tostring.js index 0638dc996b39ac..64d2373d49980d 100644 --- a/benchmark/buffers/buffer-tostring.js +++ b/benchmark/buffers/buffer-tostring.js @@ -3,7 +3,7 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { - encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'UCS-2'], + encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'base64', 'base64url', 'UCS-2'], args: [0, 1, 3], len: [1, 64, 1024], n: [1e6], diff --git a/benchmark/crypto/hash-stream-creation.js b/benchmark/crypto/hash-stream-creation.js index 5143be03263d74..0adb7d87f93959 100644 --- a/benchmark/crypto/hash-stream-creation.js +++ b/benchmark/crypto/hash-stream-creation.js @@ -5,7 +5,7 @@ const common = require('../common.js'); const crypto = require('crypto'); const bench = common.createBenchmark(main, { - writes: [500], + n: [500], algo: [ 'sha256', 'md5' ], type: ['asc', 'utf', 'buf'], out: ['hex', 'binary', 'buffer'], @@ -13,7 +13,7 @@ const bench = common.createBenchmark(main, { api: ['legacy', 'stream'], }); -function main({ api, type, len, out, writes, algo }) { +function main({ api, type, len, out, n, algo }) { if (api === 'stream' && /^v0\.[0-8]\./.test(process.version)) { console.error('Crypto streams not available until v0.10'); // Use the legacy, just so that we can compare them. @@ -41,15 +41,15 @@ function main({ api, type, len, out, writes, algo }) { const fn = api === 'stream' ? streamWrite : legacyWrite; bench.start(); - fn(algo, message, encoding, writes, len, out); + fn(algo, message, encoding, n, len, out); } -function legacyWrite(algo, message, encoding, writes, len, outEnc) { - const written = writes * len; +function legacyWrite(algo, message, encoding, n, len, outEnc) { + const written = n * len; const bits = written * 8; const gbits = bits / (1024 * 1024 * 1024); - while (writes-- > 0) { + while (n-- > 0) { const h = crypto.createHash(algo); h.update(message, encoding); h.digest(outEnc); @@ -58,12 +58,12 @@ function legacyWrite(algo, message, encoding, writes, len, outEnc) { bench.end(gbits); } -function streamWrite(algo, message, encoding, writes, len, outEnc) { - const written = writes * len; +function streamWrite(algo, message, encoding, n, len, outEnc) { + const written = n * len; const bits = written * 8; const gbits = bits / (1024 * 1024 * 1024); - while (writes-- > 0) { + while (n-- > 0) { const h = crypto.createHash(algo); if (outEnc !== 'buffer') diff --git a/benchmark/crypto/hash-stream-throughput.js b/benchmark/crypto/hash-stream-throughput.js index db10c439e0e446..4a5dbbd440ba16 100644 --- a/benchmark/crypto/hash-stream-throughput.js +++ b/benchmark/crypto/hash-stream-throughput.js @@ -5,14 +5,14 @@ const common = require('../common.js'); const crypto = require('crypto'); const bench = common.createBenchmark(main, { - writes: [500], + n: [500], algo: ['sha1', 'sha256', 'sha512'], type: ['asc', 'utf', 'buf'], len: [2, 1024, 102400, 1024 * 1024], api: ['legacy', 'stream'], }); -function main({ api, type, len, algo, writes }) { +function main({ api, type, len, algo, n }) { if (api === 'stream' && /^v0\.[0-8]\./.test(process.version)) { console.error('Crypto streams not available until v0.10'); // Use the legacy, just so that we can compare them. @@ -40,16 +40,16 @@ function main({ api, type, len, algo, writes }) { const fn = api === 'stream' ? streamWrite : legacyWrite; bench.start(); - fn(algo, message, encoding, writes, len); + fn(algo, message, encoding, n, len); } -function legacyWrite(algo, message, encoding, writes, len) { - const written = writes * len; +function legacyWrite(algo, message, encoding, n, len) { + const written = n * len; const bits = written * 8; const gbits = bits / (1024 * 1024 * 1024); const h = crypto.createHash(algo); - while (writes-- > 0) + while (n-- > 0) h.update(message, encoding); h.digest(); @@ -57,13 +57,13 @@ function legacyWrite(algo, message, encoding, writes, len) { bench.end(gbits); } -function streamWrite(algo, message, encoding, writes, len) { - const written = writes * len; +function streamWrite(algo, message, encoding, n, len) { + const written = n * len; const bits = written * 8; const gbits = bits / (1024 * 1024 * 1024); const h = crypto.createHash(algo); - while (writes-- > 0) + while (n-- > 0) h.write(message, encoding); h.end(); diff --git a/benchmark/crypto/rsa-sign-verify-throughput.js b/benchmark/crypto/rsa-sign-verify-throughput.js index ceaa2942780a8e..8cdd1378b359dc 100644 --- a/benchmark/crypto/rsa-sign-verify-throughput.js +++ b/benchmark/crypto/rsa-sign-verify-throughput.js @@ -17,20 +17,20 @@ keylen_list.forEach((key) => { }); const bench = common.createBenchmark(main, { - writes: [500], + n: [500], algo: ['SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512'], keylen: keylen_list, len: [1024, 102400, 2 * 102400, 3 * 102400, 1024 * 1024], }); -function main({ len, algo, keylen, writes }) { +function main({ len, algo, keylen, n }) { const message = Buffer.alloc(len, 'b'); bench.start(); - StreamWrite(algo, keylen, message, writes, len); + StreamWrite(algo, keylen, message, n, len); } -function StreamWrite(algo, keylen, message, writes, len) { - const written = writes * len; +function StreamWrite(algo, keylen, message, n, len) { + const written = n * len; const bits = written * 8; const kbits = bits / (1024); @@ -38,7 +38,7 @@ function StreamWrite(algo, keylen, message, writes, len) { const s = crypto.createSign(algo); const v = crypto.createVerify(algo); - while (writes-- > 0) { + while (n-- > 0) { s.update(message); v.update(message); } diff --git a/benchmark/dgram/single-buffer.js b/benchmark/dgram/single-buffer.js index bab8cee1594f24..9ac41d15da8d20 100644 --- a/benchmark/dgram/single-buffer.js +++ b/benchmark/dgram/single-buffer.js @@ -5,7 +5,7 @@ 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, { @@ -15,7 +15,7 @@ const bench = common.createBenchmark(main, { dur: [5], }); -function main({ dur, len, num: n, type }) { +function main({ dur, len, n, type }) { const chunk = Buffer.allocUnsafe(len); let sent = 0; let received = 0; diff --git a/benchmark/fs/bench-filehandle-pipetosync.js b/benchmark/fs/bench-filehandle-pipetosync.js new file mode 100644 index 00000000000000..e62ade89472341 --- /dev/null +++ b/benchmark/fs/bench-filehandle-pipetosync.js @@ -0,0 +1,103 @@ +// Benchmark: pipeToSync with sync compression transforms. +// Measures fully synchronous file-to-file pipeline (no threadpool, no promises). +'use strict'; + +const common = require('../common.js'); +const fs = require('fs'); +const { openSync, closeSync, writeSync, unlinkSync } = fs; + +const tmpdir = require('../../test/common/tmpdir'); +tmpdir.refresh(); +const srcFile = tmpdir.resolve(`.removeme-sync-bench-src-${process.pid}`); +const dstFile = tmpdir.resolve(`.removeme-sync-bench-dst-${process.pid}`); + +const bench = common.createBenchmark(main, { + compression: ['gzip', 'deflate', 'brotli', 'zstd'], + filesize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], +}); + +function main({ compression, filesize, n }) { + // Create the fixture file with repeating lowercase ASCII + const chunk = Buffer.alloc(Math.min(filesize, 64 * 1024), 'abcdefghij'); + const fd = openSync(srcFile, 'w'); + let remaining = filesize; + while (remaining > 0) { + const toWrite = Math.min(remaining, chunk.length); + writeSync(fd, chunk, 0, toWrite); + remaining -= toWrite; + } + closeSync(fd); + + const { pipeToSync } = require('stream/iter'); + const { + compressGzipSync, + compressDeflateSync, + compressBrotliSync, + compressZstdSync, + } = require('zlib/iter'); + const { open } = fs.promises; + + const compressFactory = { + gzip: compressGzipSync, + deflate: compressDeflateSync, + brotli: compressBrotliSync, + zstd: compressZstdSync, + }[compression]; + + // Stateless uppercase transform (sync) + const upper = (chunks) => { + if (chunks === null) return null; + const out = new Array(chunks.length); + for (let j = 0; j < chunks.length; j++) { + const src = chunks[j]; + const buf = Buffer.allocUnsafe(src.length); + for (let i = 0; i < src.length; i++) { + const b = src[i]; + buf[i] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b; + } + out[j] = buf; + } + return out; + }; + + // Use a synchronous wrapper since pipeToSync is fully sync. + // We need FileHandle for pullSync/writer, so open async then run sync. + (async () => { + const srcFh = await open(srcFile, 'r'); + const dstFh = await open(dstFile, 'w'); + + // Warm up + runSync(srcFh, dstFh, upper, compressFactory, pipeToSync); + + // Reset file positions for the benchmark + await srcFh.close(); + await dstFh.close(); + + bench.start(); + let totalBytes = 0; + for (let i = 0; i < n; i++) { + const src = await open(srcFile, 'r'); + const dst = await open(dstFile, 'w'); + totalBytes += runSync(src, dst, upper, compressFactory, pipeToSync); + await src.close(); + await dst.close(); + } + bench.end(totalBytes / (1024 * 1024)); + + cleanup(); + })(); +} + +function runSync(srcFh, dstFh, upper, compressFactory, pipeToSync) { + const w = dstFh.writer(); + pipeToSync(srcFh.pullSync(upper, compressFactory()), w); + return w.endSync(); +} + +function cleanup() { + try { unlinkSync(srcFile); } catch { /* Ignore */ } + try { unlinkSync(dstFile); } catch { /* Ignore */ } +} diff --git a/benchmark/fs/bench-filehandle-pull-vs-webstream.js b/benchmark/fs/bench-filehandle-pull-vs-webstream.js new file mode 100644 index 00000000000000..9ced02e817db4d --- /dev/null +++ b/benchmark/fs/bench-filehandle-pull-vs-webstream.js @@ -0,0 +1,201 @@ +// Compare FileHandle.createReadStream() vs readableWebStream() vs pull() +// reading a large file through two transforms: uppercase then compress. +'use strict'; + +const common = require('../common.js'); +const fs = require('fs'); +const zlib = require('zlib'); +const { Transform, Writable, pipeline } = require('stream'); + +const tmpdir = require('../../test/common/tmpdir'); +tmpdir.refresh(); +const filename = tmpdir.resolve(`.removeme-benchmark-garbage-${process.pid}`); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'pull'], + compression: ['gzip', 'deflate', 'brotli', 'zstd'], + filesize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], + // Classic and webstream only support gzip (native zlib / CompressionStream). + // Brotli, deflate, zstd are pull-only via stream/iter transforms. + combinationFilter({ api, compression }) { + if (api === 'classic' && compression !== 'gzip') return false; + if (api === 'webstream' && compression !== 'gzip') return false; + return true; + }, +}); + +function main({ api, compression, filesize, n }) { + // Create the fixture file with repeating lowercase ASCII + const chunk = Buffer.alloc(Math.min(filesize, 64 * 1024), 'abcdefghij'); + const fd = fs.openSync(filename, 'w'); + let remaining = filesize; + while (remaining > 0) { + const toWrite = Math.min(remaining, chunk.length); + fs.writeSync(fd, chunk, 0, toWrite); + remaining -= toWrite; + } + fs.closeSync(fd); + + if (api === 'classic') { + benchClassic(n, filesize).then(() => cleanup()); + } else if (api === 'webstream') { + benchWebStream(n, filesize).then(() => cleanup()); + } else { + benchPull(n, filesize, compression).then(() => cleanup()); + } +} + +function cleanup() { + try { fs.unlinkSync(filename); } catch { /* ignore */ } +} + +// Stateless uppercase transform (shared by all paths) +function uppercaseChunk(chunk) { + const buf = Buffer.allocUnsafe(chunk.length); + for (let i = 0; i < chunk.length; i++) { + const b = chunk[i]; + buf[i] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b; + } + return buf; +} + +// --------------------------------------------------------------------------- +// Classic streams path: createReadStream -> Transform (upper) -> createGzip +// --------------------------------------------------------------------------- +async function benchClassic(n, filesize) { + await runClassic(); + + bench.start(); + let totalBytes = 0; + for (let i = 0; i < n; i++) { + totalBytes += await runClassic(); + } + bench.end(totalBytes / (1024 * 1024)); +} + +function runClassic() { + return new Promise((resolve, reject) => { + const rs = fs.createReadStream(filename); + + const upper = new Transform({ + transform(chunk, encoding, callback) { + callback(null, uppercaseChunk(chunk)); + }, + }); + + const gz = zlib.createGzip(); + + let totalBytes = 0; + const sink = new Writable({ + write(chunk, encoding, callback) { + totalBytes += chunk.length; + callback(); + }, + }); + + pipeline(rs, upper, gz, sink, (err) => { + if (err) reject(err); + else resolve(totalBytes); + }); + }); +} + +// --------------------------------------------------------------------------- +// WebStream path: readableWebStream -> TransformStream (upper) -> CompressionStream +// --------------------------------------------------------------------------- +async function benchWebStream(n, filesize) { + await runWebStream(); + + bench.start(); + let totalBytes = 0; + for (let i = 0; i < n; i++) { + totalBytes += await runWebStream(); + } + bench.end(totalBytes / (1024 * 1024)); +} + +async function runWebStream() { + const fh = await fs.promises.open(filename, 'r'); + try { + const rs = fh.readableWebStream(); + + const upper = new TransformStream({ + transform(chunk, controller) { + const buf = new Uint8Array(chunk.length); + for (let i = 0; i < chunk.length; i++) { + const b = chunk[i]; + buf[i] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b; + } + controller.enqueue(buf); + }, + }); + + const compress = new CompressionStream('gzip'); + const output = rs.pipeThrough(upper).pipeThrough(compress); + const reader = output.getReader(); + + let totalBytes = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + totalBytes += value.byteLength; + } + return totalBytes; + } finally { + await fh.close(); + } +} + +// --------------------------------------------------------------------------- +// Pull/iter path: pull() with uppercase transform + selected compression +// --------------------------------------------------------------------------- +async function benchPull(n, filesize, compression) { + const iter = require('zlib/iter'); + + const compressFactory = { + gzip: iter.compressGzip, + deflate: iter.compressDeflate, + brotli: iter.compressBrotli, + zstd: iter.compressZstd, + }[compression]; + + // Warm up + await runPull(compressFactory); + + bench.start(); + let totalBytes = 0; + for (let i = 0; i < n; i++) { + totalBytes += await runPull(compressFactory); + } + bench.end(totalBytes / (1024 * 1024)); +} + +async function runPull(compressFactory) { + const fh = await fs.promises.open(filename, 'r'); + try { + // Stateless transform: uppercase each chunk in the batch + const upper = (chunks) => { + if (chunks === null) return null; + const out = new Array(chunks.length); + for (let j = 0; j < chunks.length; j++) { + out[j] = uppercaseChunk(chunks[j]); + } + return out; + }; + + const readable = fh.pull(upper, compressFactory()); + + let totalBytes = 0; + for await (const chunks of readable) { + for (let i = 0; i < chunks.length; i++) { + totalBytes += chunks[i].byteLength; + } + } + return totalBytes; + } finally { + await fh.close(); + } +} diff --git a/benchmark/streams/iter-creation.js b/benchmark/streams/iter-creation.js new file mode 100644 index 00000000000000..c5495fc29f42ec --- /dev/null +++ b/benchmark/streams/iter-creation.js @@ -0,0 +1,99 @@ +// Object creation overhead benchmark. +// Measures the cost of constructing stream infrastructure (no data flow). +'use strict'; + +const common = require('../common.js'); +const { Readable, Writable, Transform, PassThrough } = require('stream'); +const { ok } = require('assert'); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'iter'], + type: ['readable', 'writable', 'transform', 'pair'], + n: [1e5], +}, { + flags: ['--experimental-stream-iter'], + // Iter has no standalone Transform class; transforms are plain functions. + combinationFilter: ({ api, type }) => + !(api === 'iter' && type === 'transform'), +}); + +function main({ api, type, n }) { + switch (api) { + case 'classic': + return benchClassic(type, n); + case 'webstream': + return benchWebStream(type, n); + case 'iter': + return benchIter(type, n); + } +} + +function benchClassic(type, n) { + let tmp; + bench.start(); + switch (type) { + case 'readable': + for (let i = 0; i < n; i++) tmp = new Readable({ read() {} }); + break; + case 'writable': + for (let i = 0; i < n; i++) tmp = new Writable({ write(c, e, cb) { cb(); } }); + break; + case 'transform': + for (let i = 0; i < n; i++) tmp = new Transform({ + transform(c, e, cb) { cb(null, c); }, + }); + break; + case 'pair': + for (let i = 0; i < n; i++) tmp = new PassThrough(); + break; + } + bench.end(n); + ok(tmp !== undefined); +} + +function benchWebStream(type, n) { + let tmp; + bench.start(); + switch (type) { + case 'readable': + for (let i = 0; i < n; i++) tmp = new ReadableStream({ pull() {} }); + break; + case 'writable': + for (let i = 0; i < n; i++) tmp = new WritableStream({ write() {} }); + break; + case 'transform': + for (let i = 0; i < n; i++) tmp = new TransformStream({ + transform(c, controller) { controller.enqueue(c); }, + }); + break; + case 'pair': { + // TransformStream gives a readable+writable pair + for (let i = 0; i < n; i++) tmp = new TransformStream(); + break; + } + } + bench.end(n); + ok(tmp !== undefined); +} + +function benchIter(type, n) { + const { push, from, duplex } = require('stream/iter'); + let tmp; + + bench.start(); + switch (type) { + case 'readable': + for (let i = 0; i < n; i++) tmp = from('x'); + break; + case 'writable': + // push() creates a writer+readable pair + for (let i = 0; i < n; i++) tmp = push(); + break; + case 'pair': + // duplex() creates a bidirectional channel pair + for (let i = 0; i < n; i++) tmp = duplex(); + break; + } + bench.end(n); + ok(tmp !== undefined); +} diff --git a/benchmark/streams/iter-file-read.js b/benchmark/streams/iter-file-read.js new file mode 100644 index 00000000000000..6d8139dc6de399 --- /dev/null +++ b/benchmark/streams/iter-file-read.js @@ -0,0 +1,107 @@ +// File reading throughput benchmark. +// Reads a real file through the three stream APIs. +'use strict'; + +const common = require('../common.js'); +const fs = require('fs'); +const { Writable, pipeline } = require('stream'); +const tmpdir = require('../../test/common/tmpdir'); + +tmpdir.refresh(); +const filename = tmpdir.resolve(`.removeme-bench-file-read-${process.pid}`); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'iter'], + filesize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], +}); + +function main({ api, filesize, n }) { + // Create fixture file + const chunk = Buffer.alloc(Math.min(filesize, 64 * 1024), 'abcdefghij'); + const fd = fs.openSync(filename, 'w'); + let remaining = filesize; + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + fs.writeSync(fd, chunk, 0, size); + remaining -= size; + } + fs.closeSync(fd); + + const totalOps = (filesize * n) / (1024 * 1024); + + switch (api) { + case 'classic': + return benchClassic(filesize, n, totalOps); + case 'webstream': + return benchWebStream(filesize, n, totalOps); + case 'iter': + return benchIter(filesize, n, totalOps); + } +} + +function benchClassic(filesize, n, totalOps) { + function run(cb) { + const r = fs.createReadStream(filename); + const w = new Writable({ write(data, enc, cb) { cb(); } }); + pipeline(r, w, cb); + } + + // Warmup + run(() => { + let i = 0; + bench.start(); + (function next() { + if (i++ >= n) { + fs.unlinkSync(filename); + return bench.end(totalOps); + } + run(next); + })(); + }); +} + +function benchWebStream(filesize, n, totalOps) { + const fsp = require('fs/promises'); + + async function run() { + const fh = await fsp.open(filename, 'r'); + const rs = fh.readableWebStream(); + const ws = new WritableStream({ write() {} }); + await rs.pipeTo(ws); + await fh.close(); + } + + (async () => { + // Warmup + await run(); + + bench.start(); + for (let i = 0; i < n; i++) await run(); + fs.unlinkSync(filename); + bench.end(totalOps); + })(); +} + +function benchIter(filesize, n, totalOps) { + const fsp = require('fs/promises'); + const { pipeTo } = require('stream/iter'); + + async function run() { + const fh = await fsp.open(filename, 'r'); + await pipeTo(fh.pull(), { write() {} }); + await fh.close(); + } + + (async () => { + // Warmup + await run(); + + bench.start(); + for (let i = 0; i < n; i++) await run(); + fs.unlinkSync(filename); + bench.end(totalOps); + })(); +} diff --git a/benchmark/streams/iter-throughput-broadcast.js b/benchmark/streams/iter-throughput-broadcast.js new file mode 100644 index 00000000000000..459d78e7c75f25 --- /dev/null +++ b/benchmark/streams/iter-throughput-broadcast.js @@ -0,0 +1,145 @@ +// Throughput benchmark: fan-out data to N consumers simultaneously. +// Classic streams use PassThrough + pipe, Web Streams use tee() chains, +// stream/iter uses broadcast() with push() consumers. +'use strict'; + +const common = require('../common.js'); +const { PassThrough, Writable } = require('stream'); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'iter'], + consumers: [1, 2, 4], + datasize: [1024 * 1024, 16 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], +}); + +const CHUNK_SIZE = 64 * 1024; + +function main({ api, consumers, datasize, n }) { + const chunk = Buffer.alloc(CHUNK_SIZE, 'abcdefghij'); + const totalOps = (datasize * n) / (1024 * 1024); + + switch (api) { + case 'classic': + return benchClassic(chunk, consumers, datasize, n, totalOps); + case 'webstream': + return benchWebStream(chunk, consumers, datasize, n, totalOps); + case 'iter': + return benchIter(chunk, consumers, datasize, n, totalOps); + } +} + +function benchClassic(chunk, numConsumers, datasize, n, totalOps) { + function run(cb) { + const source = new PassThrough(); + const sinks = []; + let finished = 0; + + for (let c = 0; c < numConsumers; c++) { + const w = new Writable({ write(data, enc, cb) { cb(); } }); + source.pipe(w); + w.on('finish', () => { if (++finished === numConsumers) cb(); }); + sinks.push(w); + } + + let remaining = datasize; + function write() { + let ok = true; + while (ok && remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + const buf = size === chunk.length ? chunk : chunk.subarray(0, size); + ok = source.write(buf); + } + if (remaining > 0) { + source.once('drain', write); + } else { + source.end(); + } + } + write(); + } + + let i = 0; + bench.start(); + (function next() { + if (i++ >= n) return bench.end(totalOps); + run(next); + })(); +} + +function benchWebStream(chunk, numConsumers, datasize, n, totalOps) { + async function run() { + let remaining = datasize; + const rs = new ReadableStream({ + pull(controller) { + if (remaining <= 0) { controller.close(); return; } + const size = Math.min(remaining, chunk.length); + remaining -= size; + controller.enqueue( + size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + + // Chain tee() calls to get numConsumers branches. + // tee() gives 2; for 4 we tee twice at each level. + const branches = []; + if (numConsumers === 1) { + branches.push(rs); + } else { + const pending = [rs]; + while (branches.length + pending.length < numConsumers) { + const stream = pending.shift(); + const [a, b] = stream.tee(); + pending.push(a, b); + } + branches.push(...pending); + } + + const ws = () => new WritableStream({ write() {} }); + await Promise.all(branches.map((b) => b.pipeTo(ws()))); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIter(chunk, numConsumers, datasize, n, totalOps) { + const { broadcast } = require('stream/iter'); + + // No-op consumer: drain all batches without collecting + async function drain(source) { + // eslint-disable-next-line no-unused-vars + for await (const _ of source) { /* drain */ } + } + + async function run() { + const { writer, broadcast: bc } = broadcast(); + const consumers = []; + for (let c = 0; c < numConsumers; c++) { + consumers.push(drain(bc.push())); + } + + let remaining = datasize; + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + const buf = size === chunk.length ? chunk : chunk.subarray(0, size); + writer.writeSync(buf); + } + writer.endSync(); + + await Promise.all(consumers); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} diff --git a/benchmark/streams/iter-throughput-compression.js b/benchmark/streams/iter-throughput-compression.js new file mode 100644 index 00000000000000..0c32ddc9afca98 --- /dev/null +++ b/benchmark/streams/iter-throughput-compression.js @@ -0,0 +1,104 @@ +// Throughput benchmark: gzip compress then decompress round-trip. +// Tests real-world compression performance across stream APIs. +'use strict'; + +const common = require('../common.js'); +const { Readable, Writable, pipeline } = require('stream'); +const zlib = require('zlib'); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'iter'], + datasize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], +}); + +const CHUNK_SIZE = 64 * 1024; + +function main({ api, datasize, n }) { + const chunk = Buffer.alloc(CHUNK_SIZE, 'abcdefghij'); + const totalOps = (datasize * n) / (1024 * 1024); + + switch (api) { + case 'classic': + return benchClassic(chunk, datasize, n, totalOps); + case 'webstream': + return benchWebStream(chunk, datasize, n, totalOps); + case 'iter': + return benchIter(chunk, datasize, n, totalOps); + } +} + +function benchClassic(chunk, datasize, n, totalOps) { + function run(cb) { + let remaining = datasize; + const r = new Readable({ + read() { + if (remaining <= 0) { this.push(null); return; } + const size = Math.min(remaining, chunk.length); + remaining -= size; + this.push(size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const w = new Writable({ write(data, enc, cb) { cb(); } }); + pipeline(r, zlib.createGzip(), zlib.createGunzip(), w, cb); + } + + let i = 0; + bench.start(); + (function next() { + if (i++ >= n) return bench.end(totalOps); + run(next); + })(); +} + +function benchWebStream(chunk, datasize, n, totalOps) { + async function run() { + let remaining = datasize; + const rs = new ReadableStream({ + pull(controller) { + if (remaining <= 0) { controller.close(); return; } + const size = Math.min(remaining, chunk.length); + remaining -= size; + controller.enqueue( + size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const ws = new WritableStream({ write() {} }); + await rs + .pipeThrough(new CompressionStream('gzip')) + .pipeThrough(new DecompressionStream('gzip')) + .pipeTo(ws); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIter(chunk, datasize, n, totalOps) { + const { pipeTo } = require('stream/iter'); + const { compressGzip, decompressGzip } = require('zlib/iter'); + + async function run() { + let remaining = datasize; + async function* source() { + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + yield [size === chunk.length ? chunk : chunk.subarray(0, size)]; + } + } + await pipeTo(source(), compressGzip(), decompressGzip(), + { write() {}, writeSync() { return true; } }); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} diff --git a/benchmark/streams/iter-throughput-identity.js b/benchmark/streams/iter-throughput-identity.js new file mode 100644 index 00000000000000..42640cf0f9d857 --- /dev/null +++ b/benchmark/streams/iter-throughput-identity.js @@ -0,0 +1,132 @@ +// Throughput benchmark: raw data flow from source to consumer, no transforms. +// Compares Node.js classic streams, Web Streams, and stream/iter. +'use strict'; + +const common = require('../common.js'); +const { Readable, Writable, pipeline } = require('stream'); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'iter', 'iter-sync'], + datasize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], +}); + +const CHUNK_SIZE = 64 * 1024; + +function main({ api, datasize, n }) { + const chunk = Buffer.alloc(CHUNK_SIZE, 'abcdefghij'); + const totalOps = (datasize * n) / (1024 * 1024); // MB + + switch (api) { + case 'classic': + return benchClassic(chunk, datasize, n, totalOps); + case 'webstream': + return benchWebStream(chunk, datasize, n, totalOps); + case 'iter': + return benchIter(chunk, datasize, n, totalOps); + case 'iter-sync': + return benchIterSync(chunk, datasize, n, totalOps); + } +} + +function benchClassic(chunk, datasize, n, totalOps) { + let remaining = 0; + + function run(cb) { + remaining = datasize; + const r = new Readable({ + read() { + if (remaining <= 0) { + this.push(null); + return; + } + const size = Math.min(remaining, chunk.length); + remaining -= size; + this.push(size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const w = new Writable({ + write(data, enc, cb) { cb(); }, + }); + pipeline(r, w, cb); + } + + let i = 0; + bench.start(); + (function next() { + if (i++ >= n) return bench.end(totalOps); + run(next); + })(); +} + +function benchWebStream(chunk, datasize, n, totalOps) { + async function run() { + let remaining = datasize; + const rs = new ReadableStream({ + pull(controller) { + if (remaining <= 0) { + controller.close(); + return; + } + const size = Math.min(remaining, chunk.length); + remaining -= size; + controller.enqueue( + size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const ws = new WritableStream({ + write() {}, + }); + await rs.pipeTo(ws); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIter(chunk, datasize, n, totalOps) { + const { pipeTo } = require('stream/iter'); + + async function run() { + let remaining = datasize; + async function* source() { + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + yield [size === chunk.length ? chunk : chunk.subarray(0, size)]; + } + } + // Drain to no-op sink, matching classic/webstream behavior + await pipeTo(source(), { write() {}, writeSync() { return true; } }); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIterSync(chunk, datasize, n, totalOps) { + const { pipeToSync } = require('stream/iter'); + + bench.start(); + for (let i = 0; i < n; i++) { + let remaining = datasize; + function* source() { + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + yield [size === chunk.length ? chunk : chunk.subarray(0, size)]; + } + } + // Drain to no-op sink, matching other benchmarks + pipeToSync(source(), { writeSync() {} }); + } + bench.end(totalOps); +} diff --git a/benchmark/streams/iter-throughput-pipeto.js b/benchmark/streams/iter-throughput-pipeto.js new file mode 100644 index 00000000000000..117a78aead1088 --- /dev/null +++ b/benchmark/streams/iter-throughput-pipeto.js @@ -0,0 +1,121 @@ +// Throughput benchmark: pipe source to a no-op sink (write-only destination). +// Measures pure pipe throughput without consumer-side collection overhead. +'use strict'; + +const common = require('../common.js'); +const { Readable, Writable, pipeline } = require('stream'); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'iter', 'iter-sync'], + datasize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], +}); + +const CHUNK_SIZE = 64 * 1024; + +function main({ api, datasize, n }) { + const chunk = Buffer.alloc(CHUNK_SIZE, 'abcdefghij'); + const totalOps = (datasize * n) / (1024 * 1024); + + switch (api) { + case 'classic': + return benchClassic(chunk, datasize, n, totalOps); + case 'webstream': + return benchWebStream(chunk, datasize, n, totalOps); + case 'iter': + return benchIter(chunk, datasize, n, totalOps); + case 'iter-sync': + return benchIterSync(chunk, datasize, n, totalOps); + } +} + +function benchClassic(chunk, datasize, n, totalOps) { + function run(cb) { + let remaining = datasize; + const r = new Readable({ + read() { + if (remaining <= 0) { this.push(null); return; } + const size = Math.min(remaining, chunk.length); + remaining -= size; + this.push(size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const w = new Writable({ write(data, enc, cb) { cb(); } }); + pipeline(r, w, cb); + } + + let i = 0; + bench.start(); + (function next() { + if (i++ >= n) return bench.end(totalOps); + run(next); + })(); +} + +function benchWebStream(chunk, datasize, n, totalOps) { + async function run() { + let remaining = datasize; + const rs = new ReadableStream({ + pull(controller) { + if (remaining <= 0) { controller.close(); return; } + const size = Math.min(remaining, chunk.length); + remaining -= size; + controller.enqueue( + size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const ws = new WritableStream({ write() {} }); + await rs.pipeTo(ws); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIter(chunk, datasize, n, totalOps) { + const { pipeTo } = require('stream/iter'); + + async function run() { + let remaining = datasize; + async function* source() { + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + yield [size === chunk.length ? chunk : chunk.subarray(0, size)]; + } + } + // Provide writeSync for the sync fast path in pipeTo + const writer = { write() {}, writeSync() { return true; } }; + await pipeTo(source(), writer); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIterSync(chunk, datasize, n, totalOps) { + const { pipeToSync } = require('stream/iter'); + + bench.start(); + for (let i = 0; i < n; i++) { + let remaining = datasize; + function* source() { + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + yield [size === chunk.length ? chunk : chunk.subarray(0, size)]; + } + } + const writer = { writeSync() {} }; + pipeToSync(source(), writer); + } + bench.end(totalOps); +} diff --git a/benchmark/streams/iter-throughput-transform.js b/benchmark/streams/iter-throughput-transform.js new file mode 100644 index 00000000000000..b251aea4af2752 --- /dev/null +++ b/benchmark/streams/iter-throughput-transform.js @@ -0,0 +1,146 @@ +// Throughput benchmark: data flow through a single stateless transform. +// Uses buffer copy (allocate + memcpy) so pipeline overhead is measurable. +'use strict'; + +const common = require('../common.js'); +const { Readable, Transform, Writable, pipeline } = require('stream'); + +const bench = common.createBenchmark(main, { + api: ['classic', 'webstream', 'iter', 'iter-sync'], + datasize: [1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024], + n: [5], +}, { + flags: ['--experimental-stream-iter'], +}); + +const CHUNK_SIZE = 64 * 1024; + +// Buffer copy transform: allocate + memcpy. Cheap enough that pipeline +// overhead is a measurable fraction of total time, but non-trivial (new +// buffer per chunk, so it's a real transform that produces new data). +function copyBuf(buf) { + return Buffer.copyBytesFrom(buf); +} + +function main({ api, datasize, n }) { + const chunk = Buffer.alloc(CHUNK_SIZE, 'abcdefghij'); + const totalOps = (datasize * n) / (1024 * 1024); + + switch (api) { + case 'classic': + return benchClassic(chunk, datasize, n, totalOps); + case 'webstream': + return benchWebStream(chunk, datasize, n, totalOps); + case 'iter': + return benchIter(chunk, datasize, n, totalOps); + case 'iter-sync': + return benchIterSync(chunk, datasize, n, totalOps); + } +} + +function benchClassic(chunk, datasize, n, totalOps) { + function run(cb) { + let remaining = datasize; + const r = new Readable({ + read() { + if (remaining <= 0) { this.push(null); return; } + const size = Math.min(remaining, chunk.length); + remaining -= size; + this.push(size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const t = new Transform({ + transform(data, enc, cb) { + cb(null, copyBuf(data)); + }, + }); + const w = new Writable({ write(data, enc, cb) { cb(); } }); + pipeline(r, t, w, cb); + } + + let i = 0; + bench.start(); + (function next() { + if (i++ >= n) return bench.end(totalOps); + run(next); + })(); +} + +function benchWebStream(chunk, datasize, n, totalOps) { + async function run() { + let remaining = datasize; + const rs = new ReadableStream({ + pull(controller) { + if (remaining <= 0) { controller.close(); return; } + const size = Math.min(remaining, chunk.length); + remaining -= size; + controller.enqueue( + size === chunk.length ? chunk : chunk.subarray(0, size)); + }, + }); + const ts = new TransformStream({ + transform(c, controller) { + controller.enqueue(copyBuf(c)); + }, + }); + const ws = new WritableStream({ write() {} }); + await rs.pipeThrough(ts).pipeTo(ws); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIter(chunk, datasize, n, totalOps) { + const { pipeTo } = require('stream/iter'); + + const upper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => copyBuf(c)); + }; + + async function run() { + let remaining = datasize; + async function* source() { + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + yield [size === chunk.length ? chunk : chunk.subarray(0, size)]; + } + } + await pipeTo(source(), upper, + { write() {}, writeSync() { return true; } }); + } + + (async () => { + bench.start(); + for (let i = 0; i < n; i++) await run(); + bench.end(totalOps); + })(); +} + +function benchIterSync(chunk, datasize, n, totalOps) { + const { pipeToSync } = require('stream/iter'); + + const upper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => copyBuf(c)); + }; + + bench.start(); + for (let i = 0; i < n; i++) { + let remaining = datasize; + function* source() { + while (remaining > 0) { + const size = Math.min(remaining, chunk.length); + remaining -= size; + yield [size === chunk.length ? chunk : chunk.subarray(0, size)]; + } + } + pipeToSync(source(), upper, { writeSync() {} }); + } + bench.end(totalOps); +} diff --git a/common.gypi b/common.gypi index 7147836170d398..bf66a942bf8370 100644 --- a/common.gypi +++ b/common.gypi @@ -38,7 +38,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.24', + 'v8_embedder_string': '-node.25', ##### V8 defaults for Node.js ##### diff --git a/configure.py b/configure.py index eff244f9c41b3f..f7fababe19eb92 100755 --- a/configure.py +++ b/configure.py @@ -1945,10 +1945,9 @@ def configure_library(lib, output, pkgname=None): output['libraries'] += [pkg_libpath] default_libs = getattr(options, shared_lib + '_libname') - default_libs = [f'-l{l}' for l in default_libs.split(',')] if default_libs: - output['libraries'] += default_libs + output['libraries'] += [f'-l{l}' for l in default_libs.split(',')] elif pkg_libs: output['libraries'] += pkg_libs.split() diff --git a/deps/ada/ada.cpp b/deps/ada/ada.cpp index 6c2db6f9e30a5c..80bec6c67f313a 100644 --- a/deps/ada/ada.cpp +++ b/deps/ada/ada.cpp @@ -1,4 +1,4 @@ -/* auto-generated on 2026-02-23 21:29:24 -0500. Do not edit! */ +/* auto-generated on 2026-03-23 17:52:13 -0400. Do not edit! */ /* begin file src/ada.cpp */ #include "ada.h" /* begin file src/checkers.cpp */ @@ -10725,7 +10725,7 @@ constexpr static std::array is_forbidden_domain_code_point_table = for (uint8_t c = 0; c <= 32; c++) { result[c] = true; } - for (size_t c = 127; c < 255; c++) { + for (size_t c = 127; c < 256; c++) { result[c] = true; } return result; @@ -10767,7 +10767,7 @@ constexpr static std::array for (uint8_t c = 0; c <= 32; c++) { result[c] = 1; } - for (size_t c = 127; c < 255; c++) { + for (size_t c = 127; c < 256; c++) { result[c] = 1; } return result; @@ -13404,7 +13404,13 @@ result_type parse_url_impl(std::string_view user_input, url.query = base_url->query; } else { url.update_base_pathname(base_url->get_pathname()); - url.update_base_search(base_url->get_search()); + if (base_url->has_search()) { + // get_search() returns "" for an empty query string (URL ends + // with '?'). update_base_search("") would incorrectly clear the + // query, so pass "?" to preserve the empty query distinction. + auto s = base_url->get_search(); + url.update_base_search(s.empty() ? std::string_view("?") : s); + } } url.update_unencoded_base_hash(*fragment); return url; @@ -13628,7 +13634,13 @@ result_type parse_url_impl(std::string_view user_input, // cloning the base path includes cloning the has_opaque_path flag url.has_opaque_path = base_url->has_opaque_path; url.update_base_pathname(base_url->get_pathname()); - url.update_base_search(base_url->get_search()); + if (base_url->has_search()) { + // get_search() returns "" for an empty query string (URL ends + // with '?'). update_base_search("") would incorrectly clear the + // query, so pass "?" to preserve the empty query distinction. + auto s = base_url->get_search(); + url.update_base_search(s.empty() ? std::string_view("?") : s); + } } url.has_opaque_path = base_url->has_opaque_path; @@ -14046,7 +14058,13 @@ result_type parse_url_impl(std::string_view user_input, } else { url.update_host_to_base_host(base_url->get_hostname()); url.update_base_pathname(base_url->get_pathname()); - url.update_base_search(base_url->get_search()); + if (base_url->has_search()) { + // get_search() returns "" for an empty query string (URL ends + // with '?'). update_base_search("") would incorrectly clear the + // query, so pass "?" to preserve the empty query distinction. + auto s = base_url->get_search(); + url.update_base_search(s.empty() ? std::string_view("?") : s); + } } url.has_opaque_path = base_url->has_opaque_path; @@ -16657,8 +16675,15 @@ tl::expected canonicalize_pathname( const auto pathname = url->get_pathname(); // If leading slash is false, then set result to the code point substring // from 2 to the end of the string within result. - return leading_slash ? std::string(pathname) - : std::string(pathname.substr(2)); + if (!leading_slash) { + // pathname should start with "/-" but path traversal (e.g. "../../") + // can reduce it to just "/" which is shorter than 2 characters. + if (pathname.size() < 2) { + return tl::unexpected(errors::type_error); + } + return std::string(pathname.substr(2)); + } + return std::string(pathname); } // If parseResult is failure, then throw a TypeError. return tl::unexpected(errors::type_error); @@ -17195,7 +17220,8 @@ std::string generate_pattern_string( // point. bool needs_grouping = !part.suffix.empty() || - (!part.prefix.empty() && part.prefix[0] != options.get_prefix()[0]); + (!part.prefix.empty() && !options.get_prefix().empty() && + part.prefix[0] != options.get_prefix()[0]); // If all of the following are true: // - needs grouping is false; and @@ -17233,9 +17259,8 @@ std::string generate_pattern_string( // then set needs grouping to true. if (!needs_grouping && part.prefix.empty() && previous_part && previous_part->type == url_pattern_part_type::FIXED_TEXT && - !options.get_prefix().empty() && - previous_part->value.at(previous_part->value.size() - 1) == - options.get_prefix()[0]) { + !previous_part->value.empty() && !options.get_prefix().empty() && + previous_part->value.back() == options.get_prefix()[0]) { needs_grouping = true; } @@ -17358,8 +17383,14 @@ std_regex_provider::regex_search(std::string_view input, const std::regex& pattern) { // Use iterator-based regex_search to avoid string allocation std::match_results match_result; - if (!std::regex_search(input.begin(), input.end(), match_result, pattern, - std::regex_constants::match_any)) { + try { + if (!std::regex_search(input.begin(), input.end(), match_result, pattern, + std::regex_constants::match_any)) { + return std::nullopt; + } + } catch (const std::regex_error& e) { + (void)e; + ada_log("std_regex_provider::regex_search failed:", e.what()); return std::nullopt; } std::vector> matches; @@ -17378,7 +17409,13 @@ std_regex_provider::regex_search(std::string_view input, bool std_regex_provider::regex_match(std::string_view input, const std::regex& pattern) { - return std::regex_match(input.begin(), input.end(), pattern); + try { + return std::regex_match(input.begin(), input.end(), pattern); + } catch (const std::regex_error& e) { + (void)e; + ada_log("std_regex_provider::regex_match failed:", e.what()); + return false; + } } #endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER diff --git a/deps/ada/ada.gyp b/deps/ada/ada.gyp index 4c8910dcb5c915..30054e63e567fd 100644 --- a/deps/ada/ada.gyp +++ b/deps/ada/ada.gyp @@ -6,10 +6,19 @@ { 'target_name': 'ada', 'type': 'static_library', - 'include_dirs': ['.'], + 'include_dirs': [ + '.', + '<(DEPTH)/deps/v8/third_party/simdutf', + ], 'direct_dependent_settings': { 'include_dirs': ['.'], }, + 'defines': [ + 'ADA_USE_SIMDUTF=1', + ], + 'dependencies': [ + '../../tools/v8_gypfiles/v8.gyp:simdutf', + ], 'sources': [ '<@(ada_sources)' ] }, ] diff --git a/deps/ada/ada.h b/deps/ada/ada.h index 1210d7ddb7a123..8f9089d2210a74 100644 --- a/deps/ada/ada.h +++ b/deps/ada/ada.h @@ -1,4 +1,4 @@ -/* auto-generated on 2026-02-23 21:29:24 -0500. Do not edit! */ +/* auto-generated on 2026-03-23 17:52:13 -0400. Do not edit! */ /* begin file include/ada.h */ /** * @file ada.h @@ -6458,6 +6458,39 @@ constexpr std::string_view is_special_list[] = {"http", " ", "https", "ws", "ftp", "wss", "file", " "}; // for use with get_special_port constexpr uint16_t special_ports[] = {80, 0, 443, 80, 21, 443, 0, 0}; + +// @private +// convert a string_view to a 64-bit integer key for fast comparison +constexpr uint64_t make_key(std::string_view sv) { + uint64_t val = 0; + for (size_t i = 0; i < sv.size(); i++) + val |= (uint64_t)(uint8_t)sv[i] << (i * 8); + return val; +} +// precomputed keys for the special schemes, indexed by a hash of the input +// string +constexpr uint64_t scheme_keys[] = { + make_key("http"), // 0: HTTP + 0, // 1: sentinel + make_key("https"), // 2: HTTPS + make_key("ws"), // 3: WS + make_key("ftp"), // 4: FTP + make_key("wss"), // 5: WSS + make_key("file"), // 6: FILE + 0, // 7: sentinel +}; + +// @private +// branchless load of up to 5 characters into a uint64_t, padding with zeros if +// n < 5 +inline uint64_t branchless_load5(const char *p, size_t n) { + uint64_t input = (uint8_t)p[0]; + input |= ((uint64_t)(uint8_t)p[n > 1] << 8) & (0 - (uint64_t)(n > 1)); + input |= ((uint64_t)(uint8_t)p[(n > 2) * 2] << 16) & (0 - (uint64_t)(n > 2)); + input |= ((uint64_t)(uint8_t)p[(n > 3) * 3] << 24) & (0 - (uint64_t)(n > 3)); + input |= ((uint64_t)(uint8_t)p[(n > 4) * 4] << 32) & (0 - (uint64_t)(n > 4)); + return input; +} } // namespace details /**** @@ -6498,7 +6531,9 @@ constexpr uint16_t get_special_port(std::string_view scheme) noexcept { } int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; const std::string_view target = details::is_special_list[hash_value]; - if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) { + if (scheme.size() == target.size() && + details::branchless_load5(scheme.data(), scheme.size()) == + details::scheme_keys[hash_value]) { return details::special_ports[hash_value]; } else { return 0; @@ -6513,7 +6548,9 @@ constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept { } int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; const std::string_view target = details::is_special_list[hash_value]; - if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) { + if (scheme.size() == target.size() && + details::branchless_load5(scheme.data(), scheme.size()) == + details::scheme_keys[hash_value]) { return ada::scheme::type(hash_value); } else { return ada::scheme::NOT_SPECIAL; @@ -9368,7 +9405,8 @@ inline void url_search_params::remove(const std::string_view key, } inline void url_search_params::sort() { - // We rely on the fact that the content is valid UTF-8. + // Keys are expected to be valid UTF-8, but percent_decode can produce + // arbitrary byte sequences. Handle truncated/invalid sequences gracefully. std::ranges::stable_sort(params, [](const key_value_pair &lhs, const key_value_pair &rhs) { size_t i = 0, j = 0; @@ -9382,18 +9420,15 @@ inline void url_search_params::sort() { low_surrogate1 = 0; } else { uint8_t c1 = uint8_t(lhs.first[i]); - if (c1 <= 0x7F) { - codePoint1 = c1; - i++; - } else if (c1 <= 0xDF) { + if (c1 > 0x7F && c1 <= 0xDF && i + 1 < lhs.first.size()) { codePoint1 = ((c1 & 0x1F) << 6) | (uint8_t(lhs.first[i + 1]) & 0x3F); i += 2; - } else if (c1 <= 0xEF) { + } else if (c1 > 0xDF && c1 <= 0xEF && i + 2 < lhs.first.size()) { codePoint1 = ((c1 & 0x0F) << 12) | ((uint8_t(lhs.first[i + 1]) & 0x3F) << 6) | (uint8_t(lhs.first[i + 2]) & 0x3F); i += 3; - } else { + } else if (c1 > 0xEF && c1 <= 0xF7 && i + 3 < lhs.first.size()) { codePoint1 = ((c1 & 0x07) << 18) | ((uint8_t(lhs.first[i + 1]) & 0x3F) << 12) | ((uint8_t(lhs.first[i + 2]) & 0x3F) << 6) | @@ -9404,6 +9439,10 @@ inline void url_search_params::sort() { uint16_t high_surrogate = uint16_t(0xD800 + (codePoint1 >> 10)); low_surrogate1 = uint16_t(0xDC00 + (codePoint1 & 0x3FF)); codePoint1 = high_surrogate; + } else { + // ASCII (c1 <= 0x7F) or truncated/invalid UTF-8: treat as raw byte + codePoint1 = c1; + i++; } } @@ -9412,18 +9451,15 @@ inline void url_search_params::sort() { low_surrogate2 = 0; } else { uint8_t c2 = uint8_t(rhs.first[j]); - if (c2 <= 0x7F) { - codePoint2 = c2; - j++; - } else if (c2 <= 0xDF) { + if (c2 > 0x7F && c2 <= 0xDF && j + 1 < rhs.first.size()) { codePoint2 = ((c2 & 0x1F) << 6) | (uint8_t(rhs.first[j + 1]) & 0x3F); j += 2; - } else if (c2 <= 0xEF) { + } else if (c2 > 0xDF && c2 <= 0xEF && j + 2 < rhs.first.size()) { codePoint2 = ((c2 & 0x0F) << 12) | ((uint8_t(rhs.first[j + 1]) & 0x3F) << 6) | (uint8_t(rhs.first[j + 2]) & 0x3F); j += 3; - } else { + } else if (c2 > 0xEF && c2 <= 0xF7 && j + 3 < rhs.first.size()) { codePoint2 = ((c2 & 0x07) << 18) | ((uint8_t(rhs.first[j + 1]) & 0x3F) << 12) | ((uint8_t(rhs.first[j + 2]) & 0x3F) << 6) | @@ -9433,6 +9469,10 @@ inline void url_search_params::sort() { uint16_t high_surrogate = uint16_t(0xD800 + (codePoint2 >> 10)); low_surrogate2 = uint16_t(0xDC00 + (codePoint2 & 0x3FF)); codePoint2 = high_surrogate; + } else { + // ASCII (c2 <= 0x7F) or truncated/invalid UTF-8: treat as raw byte + codePoint2 = c2; + j++; } } @@ -11228,14 +11268,14 @@ constructor_string_parser::parse(std::string_view input) { #ifndef ADA_ADA_VERSION_H #define ADA_ADA_VERSION_H -#define ADA_VERSION "3.4.3" +#define ADA_VERSION "3.4.4" namespace ada { enum { ADA_VERSION_MAJOR = 3, ADA_VERSION_MINOR = 4, - ADA_VERSION_REVISION = 3, + ADA_VERSION_REVISION = 4, }; } // namespace ada diff --git a/deps/crates/Cargo.lock b/deps/crates/Cargo.lock index f8f6913c843a4d..b8586e1b704145 100644 --- a/deps/crates/Cargo.lock +++ b/deps/crates/Cargo.lock @@ -191,11 +191,9 @@ name = "node_crates" version = "0.1.0" dependencies = [ "icu_calendar", - "icu_calendar_data", "icu_collections", "icu_locale", "icu_locale_core", - "icu_locale_data", "icu_provider", "temporal_capi", "temporal_rs", diff --git a/deps/crates/Cargo.toml b/deps/crates/Cargo.toml index 8c6224e0ed5f64..7c67cc30e31944 100644 --- a/deps/crates/Cargo.toml +++ b/deps/crates/Cargo.toml @@ -12,18 +12,15 @@ crate-type = ["staticlib"] [dependencies] # Pin all temporal dependencies to the last version support rustc 1.82 -icu_calendar = "~2.0.0" -icu_calendar_data = "~2.0.0" icu_collections = "~2.0.0" -icu_locale = "~2.0.0" icu_locale_core = "~2.0.0" -icu_locale_data = "~2.0.0" icu_provider = "~2.0.0" timezone_provider = "=0.1.0" [dependencies.temporal_capi] version = "=0.1.0" features = ["zoneinfo64"] +default-features = false [dependencies.temporal_rs] version = "=0.1.0" @@ -31,6 +28,20 @@ default-features = false # This is necessary to enable a spec-compliance quirk when upgrading to v0.1.2 # features = ["float64_representable_durations"] +# Disable `icu_calendar_data` and `icu_locale_data` crates with disabling +# `compiled_data` features. However these datasets are still enabled until +# https://github.com/boa-dev/temporal/pull/694 lands. +[dependencies.icu_calendar] +version = "~2.0.0" +features = [ + "ixdtf", # Parser for Internet eXtended DateTime Format +] +default-features = false + +[dependencies.icu_locale] +version = "~2.0.0" +default-features = false + [patch.crates-io] # Float https://github.com/unicode-org/icu4x/pull/7658 until crate is updated. resb = { path="patches/resb" } diff --git a/deps/googletest/include/gtest/gtest-test-part.h b/deps/googletest/include/gtest/gtest-test-part.h index 90380a973070b9..ce1e21945cf399 100644 --- a/deps/googletest/include/gtest/gtest-test-part.h +++ b/deps/googletest/include/gtest/gtest-test-part.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include "gtest/internal/gtest-internal.h" @@ -65,10 +66,10 @@ class GTEST_API_ [[nodiscard]] TestPartResult { // C'tor. TestPartResult does NOT have a default constructor. // Always use this constructor (with parameters) to create a // TestPartResult object. - TestPartResult(Type a_type, const char* a_file_name, int a_line_number, - const char* a_message) + TestPartResult(Type a_type, std::string_view a_file_name, int a_line_number, + std::string_view a_message) : type_(a_type), - file_name_(a_file_name == nullptr ? "" : a_file_name), + file_name_(a_file_name), line_number_(a_line_number), summary_(ExtractSummary(a_message)), message_(a_message) {} @@ -112,7 +113,7 @@ class GTEST_API_ [[nodiscard]] TestPartResult { // Gets the summary of the failure message by omitting the stack // trace in it. - static std::string ExtractSummary(const char* message); + static std::string ExtractSummary(std::string_view message); // The name of the source file where the test part took place, or // "" if the source file is unknown. diff --git a/deps/googletest/include/gtest/gtest.h b/deps/googletest/include/gtest/gtest.h index b63685380c8a14..4218adebde20d8 100644 --- a/deps/googletest/include/gtest/gtest.h +++ b/deps/googletest/include/gtest/gtest.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include @@ -1246,7 +1247,7 @@ class GTEST_API_ [[nodiscard]] UnitTest { // eventually call this to report their results. The user code // should use the assertion macros instead of calling this directly. void AddTestPartResult(TestPartResult::Type result_type, - const char* file_name, int line_number, + std::string_view file_name, int line_number, const std::string& message, const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_); @@ -1619,6 +1620,8 @@ class GTEST_API_ [[nodiscard]] AssertHelper { // Constructor. AssertHelper(TestPartResult::Type type, const char* file, int line, const char* message); + AssertHelper(TestPartResult::Type type, std::string_view file, int line, + std::string_view message); ~AssertHelper(); // Message assignment is a semantic trick to enable assertion @@ -1632,12 +1635,12 @@ class GTEST_API_ [[nodiscard]] AssertHelper { // re-using stack space even for temporary variables, so every EXPECT_EQ // reserves stack space for another AssertHelper. struct AssertHelperData { - AssertHelperData(TestPartResult::Type t, const char* srcfile, int line_num, - const char* msg) + AssertHelperData(TestPartResult::Type t, std::string_view srcfile, + int line_num, std::string_view msg) : type(t), file(srcfile), line(line_num), message(msg) {} TestPartResult::Type const type; - const char* const file; + const std::string_view file; int const line; std::string const message; diff --git a/deps/googletest/include/gtest/internal/gtest-internal.h b/deps/googletest/include/gtest/internal/gtest-internal.h index 4379137d77eba3..7096355d5dd6be 100644 --- a/deps/googletest/include/gtest/internal/gtest-internal.h +++ b/deps/googletest/include/gtest/internal/gtest-internal.h @@ -1452,8 +1452,7 @@ class [[nodiscard]] NeverThrown { ; \ else \ fail(::testing::internal::GetBoolAssertionFailureMessage( \ - gtest_ar_, text, #actual, #expected) \ - .c_str()) + gtest_ar_, text, #actual, #expected)) #define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ diff --git a/deps/googletest/include/gtest/internal/gtest-port.h b/deps/googletest/include/gtest/internal/gtest-port.h index 3ea95ba5560714..b607e0adee74f4 100644 --- a/deps/googletest/include/gtest/internal/gtest-port.h +++ b/deps/googletest/include/gtest/internal/gtest-port.h @@ -1236,9 +1236,6 @@ class GTEST_API_ [[nodiscard]] AutoHandle { // Nothing to do here. #else -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - // Allows a controller thread to pause execution of newly created // threads until notified. Instances of this class must be created // and destroyed in the controller thread. @@ -1246,6 +1243,39 @@ GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ // This class is only for testing Google Test's own constructs. Do not // use it in user tests, either directly or indirectly. // TODO(b/203539622): Replace unconditionally with absl::Notification. +#ifdef GTEST_OS_WINDOWS_MINGW +// GCC version < 13 with the win32 thread model does not provide std::mutex and +// std::condition_variable in the and headers. So +// we implement the Notification class using a Windows manual-reset event. See +// https://gcc.gnu.org/gcc-13/changes.html#windows. +class GTEST_API_ [[nodiscard]] Notification { + public: + Notification(); + Notification(const Notification&) = delete; + Notification& operator=(const Notification&) = delete; + ~Notification(); + + // Notifies all threads created with this notification to start. Must + // be called from the controller thread. + void Notify(); + + // Blocks until the controller thread notifies. Must be called from a test + // thread. + void WaitForNotification(); + + private: + // Assume that Win32 HANDLE type is equivalent to void*. Doing so allows us to + // avoid including in this header file. Including is + // undesirable because it defines a lot of symbols and macros that tend to + // conflict with client code. This assumption is verified by + // WindowsTypesTest.HANDLEIsVoidStar. + typedef void* Handle; + Handle event_; +}; +#else +GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ +/* class A needs to have dll-interface to be used by clients of class B */) + class GTEST_API_ [[nodiscard]] Notification { public: Notification() : notified_(false) {} @@ -1273,6 +1303,7 @@ class GTEST_API_ [[nodiscard]] Notification { bool notified_; }; GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 +#endif // GTEST_OS_WINDOWS_MINGW #endif // GTEST_HAS_NOTIFICATION_ // On MinGW, we can have both GTEST_OS_WINDOWS and GTEST_HAS_PTHREAD diff --git a/deps/googletest/src/gtest-port.cc b/deps/googletest/src/gtest-port.cc index f8ecb37c48d943..d34a693e4500f2 100644 --- a/deps/googletest/src/gtest-port.cc +++ b/deps/googletest/src/gtest-port.cc @@ -303,6 +303,22 @@ bool AutoHandle::IsCloseable() const { return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE; } +#if !GTEST_HAS_NOTIFICATION_ && defined(GTEST_OS_WINDOWS_MINGW) +Notification::Notification() { + // Create a manual-reset event object. + event_ = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + GTEST_CHECK_(event_ != nullptr); +} + +Notification::~Notification() { ::CloseHandle(event_); } + +void Notification::Notify() { GTEST_CHECK_(::SetEvent(event_)); } + +void Notification::WaitForNotification() { + GTEST_CHECK_(::WaitForSingleObject(event_, INFINITE) == WAIT_OBJECT_0); +} +#endif // !GTEST_HAS_NOTIFICATION_ && defined(GTEST_OS_WINDOWS_MINGW) + Mutex::Mutex() : owner_thread_id_(0), type_(kDynamic), diff --git a/deps/googletest/src/gtest-test-part.cc b/deps/googletest/src/gtest-test-part.cc index 6f8ddd7c485c2f..c7f993c8b4304d 100644 --- a/deps/googletest/src/gtest-test-part.cc +++ b/deps/googletest/src/gtest-test-part.cc @@ -34,7 +34,9 @@ #include #include +#include +#include "gtest/internal/gtest-internal.h" #include "gtest/internal/gtest-port.h" #include "src/gtest-internal-inl.h" @@ -42,9 +44,9 @@ namespace testing { // Gets the summary of the failure message by omitting the stack trace // in it. -std::string TestPartResult::ExtractSummary(const char* message) { - const char* const stack_trace = strstr(message, internal::kStackTraceMarker); - return stack_trace == nullptr ? message : std::string(message, stack_trace); +std::string TestPartResult::ExtractSummary(const std::string_view message) { + auto stack_trace = message.find(internal::kStackTraceMarker); + return std::string(message.substr(0, stack_trace)); } // Prints a TestPartResult object. diff --git a/deps/googletest/src/gtest.cc b/deps/googletest/src/gtest.cc index 193f880be27fff..8a3801807e7dfa 100644 --- a/deps/googletest/src/gtest.cc +++ b/deps/googletest/src/gtest.cc @@ -58,6 +58,7 @@ #include // NOLINT #include #include +#include #include #include #include @@ -485,6 +486,15 @@ bool ShouldEmitStackTraceForResultType(TestPartResult::Type type) { // AssertHelper constructor. AssertHelper::AssertHelper(TestPartResult::Type type, const char* file, int line, const char* message) + : AssertHelper( + type, file == nullptr ? std::string_view() : std::string_view(file), + line, + message == nullptr ? std::string_view() : std::string_view(message)) { +} + +AssertHelper::AssertHelper(TestPartResult::Type type, + const std::string_view file, int line, + const std::string_view message) : data_(new AssertHelperData(type, file, line, message)) {} AssertHelper::~AssertHelper() { delete data_; } @@ -875,7 +885,11 @@ class PositiveAndNegativeUnitTestFilter { // and does not match the negative filter. bool MatchesTest(const std::string& test_suite_name, const std::string& test_name) const { +#ifdef GTEST_HAS_ABSL + return MatchesName(absl::StrCat(test_suite_name, ".", test_name)); +#else return MatchesName(test_suite_name + "." + test_name); +#endif } // Returns true if and only if name matches the positive filter and does not @@ -2547,8 +2561,9 @@ void ReportFailureInUnknownLocation(TestPartResult::Type result_type, // AddTestPartResult. UnitTest::GetInstance()->AddTestPartResult( result_type, - nullptr, // No info about the source file where the exception occurred. - -1, // We have no info on which line caused the exception. + std::string_view(), // No info about the source file where the exception + // occurred. + -1, // We have no info on which line caused the exception. message, ""); // No stack trace, either. } @@ -5428,8 +5443,8 @@ Environment* UnitTest::AddEnvironment(Environment* env) { // this to report their results. The user code should use the // assertion macros instead of calling this directly. void UnitTest::AddTestPartResult(TestPartResult::Type result_type, - const char* file_name, int line_number, - const std::string& message, + const std::string_view file_name, + int line_number, const std::string& message, const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_) { Message msg; diff --git a/deps/icu-small/source/data/in/icudt78l.dat.bz2 b/deps/icu-small/source/data/in/icudt78l.dat.bz2 index 1ae38435e2cdde..5be75974fa3edd 100644 Binary files a/deps/icu-small/source/data/in/icudt78l.dat.bz2 and b/deps/icu-small/source/data/in/icudt78l.dat.bz2 differ diff --git a/deps/merve/merve.cpp b/deps/merve/merve.cpp index 9d8b7717c68f5a..d3a488650a078a 100644 --- a/deps/merve/merve.cpp +++ b/deps/merve/merve.cpp @@ -1,4 +1,4 @@ -/* auto-generated on 2026-03-06 11:46:19 -0500. Do not edit! */ +/* auto-generated on 2026-03-11 12:53:21 -0400. Do not edit! */ #include "merve.h" /* begin file src/parser.cpp */ @@ -1478,11 +1478,13 @@ class CJSLexer { } void tryBacktrackAddStarExportBinding(const char* bPos) { - while (*bPos == ' ' && bPos > source) + if (bPos < source) return; + while (bPos > source && *bPos == ' ') bPos--; if (*bPos == '=') { + if (bPos <= source) return; bPos--; - while (*bPos == ' ' && bPos > source) + while (bPos > source && *bPos == ' ') bPos--; const char* id_end = bPos; bool identifierStart = false; @@ -1497,7 +1499,7 @@ class CJSLexer { if (starExportStack == STAR_EXPORT_STACK_END) return; starExportStack->id = std::string_view(bPos + 1, static_cast(id_end - bPos)); - while (*bPos == ' ' && bPos > source) + while (bPos > source && *bPos == ' ') bPos--; switch (*bPos) { case 'r': diff --git a/deps/merve/merve.h b/deps/merve/merve.h index a783ed2094b209..a96d23171c2e2c 100644 --- a/deps/merve/merve.h +++ b/deps/merve/merve.h @@ -1,4 +1,4 @@ -/* auto-generated on 2026-03-06 11:46:19 -0500. Do not edit! */ +/* auto-generated on 2026-03-11 12:53:21 -0400. Do not edit! */ /* begin file include/merve.h */ #ifndef MERVE_H #define MERVE_H @@ -15,14 +15,14 @@ #ifndef MERVE_VERSION_H #define MERVE_VERSION_H -#define MERVE_VERSION "1.2.0" // x-release-please-version +#define MERVE_VERSION "1.2.2" // x-release-please-version namespace lexer { enum { MERVE_VERSION_MAJOR = 1, // x-release-please-major MERVE_VERSION_MINOR = 2, // x-release-please-minor - MERVE_VERSION_REVISION = 0, // x-release-please-patch + MERVE_VERSION_REVISION = 2, // x-release-please-patch }; } // namespace lexer diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 3a26cfbdcab52e..3e3e8d4720a456 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -777,11 +777,15 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { // Note that the preferred name syntax (see RFCs 5280 and 1034) with // wildcards is a subset of what we consider "safe", so spec-compliant DNS // names will never need to be escaped. - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_EMAIL) { ASN1_IA5STRING* name = gen->d.rfc822Name; BIO_write(out.get(), "email:", 6); - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_URI) { ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; BIO_write(out.get(), "URI:", 4); @@ -789,7 +793,9 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { // with a few exceptions, most notably URIs that contains commas (see // RFC 2396). In other words, most legitimate URIs will not require // escaping. - PrintAltName(out, reinterpret_cast(name->data), name->length); + PrintAltName(out, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name)); } else if (gen->type == GEN_DIRNAME) { // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME // object. The format was non standard and should be avoided. The use of @@ -822,17 +828,18 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { } else if (gen->type == GEN_IPADD) { BIO_printf(out.get(), "IP Address:"); const ASN1_OCTET_STRING* ip = gen->d.ip; - const unsigned char* b = ip->data; - if (ip->length == 4) { + const unsigned char* b = ASN1_STRING_get0_data(ip); + int ip_len = ASN1_STRING_length(ip); + if (ip_len == 4) { BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); - } else if (ip->length == 16) { + } else if (ip_len == 16) { for (unsigned int j = 0; j < 8; j++) { uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); } } else { #if OPENSSL_VERSION_MAJOR >= 3 - BIO_printf(out.get(), "", ip->length); + BIO_printf(out.get(), "", ip_len); #else BIO_printf(out.get(), ""); #endif @@ -882,15 +889,15 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { if (unicode) { auto name = gen->d.otherName->value->value.utf8string; PrintAltName(out, - reinterpret_cast(name->data), - name->length, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name), AltNameOption::UTF8, prefix); } else { auto name = gen->d.otherName->value->value.ia5string; PrintAltName(out, - reinterpret_cast(name->data), - name->length, + reinterpret_cast(ASN1_STRING_get0_data(name)), + ASN1_STRING_length(name), AltNameOption::NONE, prefix); } @@ -911,11 +918,14 @@ bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { } } // namespace -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, + const X509_EXTENSION* ext) { + // const_cast needed for OpenSSL < 4.0 which lacks const-correctness + auto* mext = const_cast(ext); + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_subject_alt_name) return false; - GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); + GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(mext)); if (names == nullptr) return false; bool ok = true; @@ -934,12 +944,14 @@ bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) { return ok; } -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) { - auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); +bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext) { + // const_cast needed for OpenSSL < 4.0 which lacks const-correctness + auto* mext = const_cast(ext); + auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_info_access) return false; AUTHORITY_INFO_ACCESS* descs = - static_cast(X509V3_EXT_d2i(ext)); + static_cast(X509V3_EXT_d2i(mext)); if (descs == nullptr) return false; bool ok = true; @@ -1083,7 +1095,7 @@ BIOPointer X509View::getValidFrom() const { if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notBefore(cert_)); + ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert_)); return bio; } @@ -1092,7 +1104,7 @@ BIOPointer X509View::getValidTo() const { if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; - ASN1_TIME_print(bio.get(), X509_get_notAfter(cert_)); + ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert_)); return bio; } @@ -3526,8 +3538,38 @@ bool ECKeyPointer::setPublicKey(const ECPointPointer& pub) { bool ECKeyPointer::setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y) { if (!key_) return false; - return EC_KEY_set_public_key_affine_coordinates( - key_.get(), x.get(), y.get()) == 1; + const EC_GROUP* group = EC_KEY_get0_group(key_.get()); + if (group == nullptr) return false; + + // For curves with cofactor h=1, use EC_POINT_oct2point + + // EC_KEY_set_public_key instead of EC_KEY_set_public_key_affine_coordinates. + // The latter internally calls EC_KEY_check_key() which performs a scalar + // multiplication (n*Q) for order validation — redundant when h=1 since every + // on-curve point already has order n. EC_POINT_oct2point validates the point + // is on the curve, which is sufficient. For curves with h!=1, fall back to + // the full check. + auto cofactor = BignumPointer::New(); + if (!cofactor || !EC_GROUP_get_cofactor(group, cofactor.get(), nullptr) || + !cofactor.isOne()) { + return EC_KEY_set_public_key_affine_coordinates( + key_.get(), x.get(), y.get()) == 1; + } + + // Field element byte length: ceil(degree_bits / 8). + size_t field_len = (EC_GROUP_get_degree(group) + 7) / 8; + // Build an uncompressed point: 0x04 || x || y, each padded to field_len. + size_t uncompressed_len = 1 + 2 * field_len; + auto buf = DataPointer::Alloc(uncompressed_len); + if (!buf) return false; + unsigned char* ptr = static_cast(buf.get()); + ptr[0] = POINT_CONVERSION_UNCOMPRESSED; + x.encodePaddedInto(ptr + 1, field_len); + y.encodePaddedInto(ptr + 1 + field_len, field_len); + + auto point = ECPointPointer::New(group); + if (!point) return false; + if (!point.setFromBuffer({ptr, uncompressed_len}, group)) return false; + return EC_KEY_set_public_key(key_.get(), point.get()) == 1; } bool ECKeyPointer::setPrivateKey(const BignumPointer& priv) { @@ -4643,12 +4685,12 @@ bool X509Name::Iterator::operator!=(const Iterator& other) const { std::pair X509Name::Iterator::operator*() const { if (loc_ == name_.total_) return {{}, {}}; - X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); + const X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); if (entry == nullptr) [[unlikely]] return {{}, {}}; - ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); - ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); + const ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); + const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); if (name == nullptr || value == nullptr) [[unlikely]] { return {{}, {}}; diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 5ffaee379e481a..c781d7e2e0288f 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -1578,8 +1578,9 @@ int NoPasswordCallback(char* buf, int size, int rwflag, void* u); int PasswordCallback(char* buf, int size, int rwflag, void* u); -bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext); -bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext); +bool SafeX509SubjectAltNamePrint(const BIOPointer& out, + const X509_EXTENSION* ext); +bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext); // ============================================================================ // SPKAC diff --git a/deps/ngtcp2/ngtcp2/examples/client.cc b/deps/ngtcp2/ngtcp2/examples/client.cc index c25242962ca44d..56b757b2d91ed1 100644 --- a/deps/ngtcp2/ngtcp2/examples/client.cc +++ b/deps/ngtcp2/ngtcp2/examples/client.cc @@ -1049,7 +1049,7 @@ ngtcp2_ssize write_pkt(ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_ssize Client::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_tstamp ts) { - std::array vec; + std::array vec; for (;;) { int64_t stream_id = -1; @@ -1057,9 +1057,8 @@ ngtcp2_ssize Client::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, nghttp3_ssize sveccnt = 0; if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) { - sveccnt = nghttp3_conn_writev_stream( - httpconn_, &stream_id, &fin, - reinterpret_cast(vec.data()), vec.size()); + sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin, + vec.data(), vec.size()); if (sveccnt < 0) { std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(static_cast(sveccnt)) << std::endl; @@ -1072,6 +1071,7 @@ ngtcp2_ssize Client::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, } ngtcp2_ssize ndatalen; + auto v = vec.data(); auto vcnt = static_cast(sveccnt); uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; @@ -1081,7 +1081,7 @@ ngtcp2_ssize Client::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, auto nwrite = ngtcp2_conn_writev_stream( conn_, path, pi, dest, destlen, &ndatalen, flags, stream_id, - reinterpret_cast(vec.data()), vcnt, ts); + reinterpret_cast(v), vcnt, ts); if (nwrite < 0) { switch (nwrite) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: diff --git a/deps/ngtcp2/ngtcp2/examples/server.cc b/deps/ngtcp2/ngtcp2/examples/server.cc index 4f081eab920069..f9463eacd9cc7d 100644 --- a/deps/ngtcp2/ngtcp2/examples/server.cc +++ b/deps/ngtcp2/ngtcp2/examples/server.cc @@ -1652,7 +1652,7 @@ ngtcp2_ssize write_pkt(ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_ssize Handler::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_tstamp ts) { - std::array vec; + std::array vec; for (;;) { int64_t stream_id = -1; @@ -1660,9 +1660,8 @@ ngtcp2_ssize Handler::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, nghttp3_ssize sveccnt = 0; if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) { - sveccnt = nghttp3_conn_writev_stream( - httpconn_, &stream_id, &fin, - reinterpret_cast(vec.data()), vec.size()); + sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin, + vec.data(), vec.size()); if (sveccnt < 0) { std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(static_cast(sveccnt)) << std::endl; @@ -1675,6 +1674,7 @@ ngtcp2_ssize Handler::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, } ngtcp2_ssize ndatalen; + auto v = vec.data(); auto vcnt = static_cast(sveccnt); uint32_t flags = @@ -1685,7 +1685,7 @@ ngtcp2_ssize Handler::write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, auto nwrite = ngtcp2_conn_writev_stream( conn_, path, pi, dest, destlen, &ndatalen, flags, stream_id, - reinterpret_cast(vec.data()), vcnt, ts); + reinterpret_cast(v), vcnt, ts); if (nwrite < 0) { switch (nwrite) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: diff --git a/deps/ngtcp2/ngtcp2/examples/shared.h b/deps/ngtcp2/ngtcp2/examples/shared.h index 5455a529a6ee0f..26e678816e8f98 100644 --- a/deps/ngtcp2/ngtcp2/examples/shared.h +++ b/deps/ngtcp2/ngtcp2/examples/shared.h @@ -34,7 +34,6 @@ #include #include -#include #include "network.h" @@ -64,11 +63,6 @@ inline constexpr uint32_t TLS_ALERT_ECH_REQUIRED = 121; inline constexpr size_t MAX_RECV_PKTS = 64; -union SharedVec { - ngtcp2_vec v2; - nghttp3_vec v3; -}; - // msghdr_get_ecn gets ECN bits from |msg|. |family| is the address // family from which packet is received. uint8_t msghdr_get_ecn(msghdr *msg, int family); diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h index a09ceea382bab1..ba3f7a7e1ae215 100644 --- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h +++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/ngtcp2.h @@ -1281,7 +1281,7 @@ typedef struct ngtcp2_sockaddr_in { } ngtcp2_sockaddr_in; typedef struct ngtcp2_in6_addr { - uint8_t in6_addr[16]; + uint8_t s6_addr[16]; } ngtcp2_in6_addr; typedef struct ngtcp2_sockaddr_in6 { @@ -1866,8 +1866,8 @@ typedef struct ngtcp2_settings { uint64_t max_stream_window; /** * :member:`ack_thresh` is the minimum number of the received ACK - * eliciting packets that trigger the immediate acknowledgement from - * the local endpoint. + * eliciting packets that triggers the immediate acknowledgement + * from the local endpoint. */ size_t ack_thresh; /** diff --git a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h index 6a1ca013450d24..ff43c9c8f8c7c0 100644 --- a/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h +++ b/deps/ngtcp2/ngtcp2/lib/includes/ngtcp2/version.h @@ -36,7 +36,7 @@ * * Version number of the ngtcp2 library release. */ -#define NGTCP2_VERSION "1.20.0" +#define NGTCP2_VERSION "1.21.0" /** * @macro @@ -46,6 +46,6 @@ * number, 8 bits for minor and 8 bits for patch. Version 1.2.3 * becomes 0x010203. */ -#define NGTCP2_VERSION_NUM 0x011400 +#define NGTCP2_VERSION_NUM 0x011500 #endif /* !defined(NGTCP2_VERSION_H) */ diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c index 02865e2a2fc5f6..3db6a9f2bff088 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.c @@ -76,13 +76,15 @@ static void bbr_init_round_counting(ngtcp2_cc_bbr *bbr); static void bbr_reset_full_bw(ngtcp2_cc_bbr *bbr); -static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static void bbr_init_pacing_rate(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); -static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, +static void bbr_set_pacing_rate_with_gain(const ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, uint64_t pacing_gain_h); -static void bbr_set_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static void bbr_set_pacing_rate(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); static void bbr_enter_startup(ngtcp2_cc_bbr *bbr); @@ -100,47 +102,47 @@ static void bbr_update_control_parameters(ngtcp2_cc_bbr *cc, ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack); -static void bbr_update_on_loss(ngtcp2_cc_bbr *cc, ngtcp2_conn_stat *cstat, - const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); - static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); + const ngtcp2_conn_stat *cstat); static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); + const ngtcp2_conn_stat *cstat); static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack); -static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); +static void +bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); -static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); static void bbr_loss_lower_bounds(ngtcp2_cc_bbr *bbr); static void bbr_bound_bw_for_model(ngtcp2_cc_bbr *bbr); -static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack); static void bbr_update_round(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack); static void bbr_start_round(ngtcp2_cc_bbr *bbr); -static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr); +static int bbr_is_in_probe_bw_state(const ngtcp2_cc_bbr *bbr); -static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr); +static int bbr_is_probing_bw(const ngtcp2_cc_bbr *bbr); static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); static void bbr_enter_drain(ngtcp2_cc_bbr *bbr); -static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); static void bbr_enter_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); @@ -151,57 +153,68 @@ static void bbr_start_probe_bw_cruise(ngtcp2_cc_bbr *bbr); static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr); -static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); -static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); -static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); -static int bbr_has_elapsed_in_phase(ngtcp2_cc_bbr *bbr, +static int bbr_has_elapsed_in_phase(const ngtcp2_cc_bbr *bbr, ngtcp2_duration interval, ngtcp2_tstamp ts); -static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); +static uint64_t bbr_inflight_with_headroom(const ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); + const ngtcp2_conn_stat *cstat); static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack); static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack); -static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr); -static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); +static int bbr_is_reno_coexistence_probe_time(const ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); -static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); +static uint64_t bbr_target_inflight(const ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); -static int bbr_is_inflight_too_high(ngtcp2_cc_bbr *bbr, const ngtcp2_rs *rs); +static int bbr_is_inflight_too_high(const ngtcp2_cc_bbr *bbr, + const ngtcp2_rs *rs); static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_rs *rs, ngtcp2_tstamp ts); static void bbr_note_loss(ngtcp2_cc_bbr *bbr); -static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr); + +static void bbr_handle_spurious_loss_detection(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); + +static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); -static uint64_t bbr_inflight_at_loss(ngtcp2_cc_bbr *bbr, +static uint64_t bbr_inflight_at_loss(const ngtcp2_cc_bbr *bbr, const ngtcp2_cc_pkt *pkt, const ngtcp2_rs *rs); @@ -220,7 +233,7 @@ static void bbr_check_probe_rtt_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); static void bbr_mark_connection_app_limited(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); + const ngtcp2_conn_stat *cstat); static void bbr_exit_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); @@ -231,27 +244,28 @@ static void bbr_handle_restart_from_idle(ngtcp2_cc_bbr *bbr, static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t gain_h); static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, uint64_t inflight); -static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, uint64_t gain_h); static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); + const ngtcp2_conn_stat *cstat); static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat); + const ngtcp2_conn_stat *cstat); static uint64_t min_pipe_cwnd(size_t max_udp_payload_size); static void bbr_advance_max_bw_filter(ngtcp2_cc_bbr *bbr); -static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat); -static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static void bbr_restore_cwnd(const ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); -static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat); static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); @@ -259,10 +273,11 @@ static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr, static void bbr_set_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack); -static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr, +static void bbr_bound_cwnd_for_model(const ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); -static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); +static void bbr_set_send_quantum(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, ngtcp2_tstamp sent_time); @@ -336,6 +351,7 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, bbr->bdp = 0; + bbr->undo_state = 0; bbr->undo_bw_shortterm = 0; bbr->undo_inflight_shortterm = 0; bbr->undo_inflight_longterm = 0; @@ -365,7 +381,7 @@ static void bbr_reset_full_bw(ngtcp2_cc_bbr *bbr) { } static void bbr_check_full_bw_reached(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { if (bbr->full_bw_now || !bbr->round_start || bbr->rst->rs.is_app_limited) { return; } @@ -404,7 +420,8 @@ static void bbr_check_startup_high_loss(ngtcp2_cc_bbr *bbr) { bbr_bdp_multiple(bbr, bbr->cwnd_gain_h), bbr->inflight_latest); } -static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static void bbr_init_pacing_rate(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { cstat->pacing_interval_m = ngtcp2_max_uint64( ((cstat->first_rtt_sample_ts == UINT64_MAX ? NGTCP2_MILLISECONDS : cstat->smoothed_rtt) @@ -413,7 +430,7 @@ static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { 1); } -static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, +static void bbr_set_pacing_rate_with_gain(const ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, uint64_t pacing_gain_h) { uint64_t interval_m; @@ -431,7 +448,8 @@ static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, } } -static void bbr_set_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static void bbr_set_pacing_rate(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { bbr_set_pacing_rate_with_gain(bbr, cstat, bbr->pacing_gain_h); } @@ -487,13 +505,8 @@ static void bbr_update_control_parameters(ngtcp2_cc_bbr *bbr, bbr_set_cwnd(bbr, cstat, ack); } -static void bbr_update_on_loss(ngtcp2_cc_bbr *cc, ngtcp2_conn_stat *cstat, - const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { - bbr_handle_lost_packet(cc, cstat, pkt, ts); -} - static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { bbr->loss_round_start = 0; bbr->bw_latest = ngtcp2_max_uint64(bbr->bw_latest, cstat->delivery_rate_sec); bbr->inflight_latest = @@ -506,7 +519,7 @@ static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr, } static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { if (bbr->loss_round_start) { bbr->bw_latest = cstat->delivery_rate_sec; bbr->inflight_latest = bbr->rst->rs.delivered; @@ -514,7 +527,7 @@ static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, } static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack) { bbr_update_max_bw(bbr, cstat, ack); @@ -532,8 +545,9 @@ static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, bbr->loss_in_round = 0; } -static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { +static void +bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { if (bbr_is_probing_bw(bbr)) { return; } @@ -544,7 +558,8 @@ static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, } } -static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { if (bbr->bw_shortterm == UINT64_MAX) { bbr->bw_shortterm = bbr->max_bw; } @@ -567,7 +582,7 @@ static void bbr_bound_bw_for_model(ngtcp2_cc_bbr *bbr) { bbr->bw = ngtcp2_min_uint64(bbr->max_bw, bbr->bw_shortterm); } -static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack) { bbr_update_round(bbr, ack); @@ -603,7 +618,7 @@ static void bbr_start_round(ngtcp2_cc_bbr *bbr) { bbr->next_round_delivered = bbr->rst->delivered; } -static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr) { +static int bbr_is_in_probe_bw_state(const ngtcp2_cc_bbr *bbr) { switch (bbr->state) { case NGTCP2_BBR_STATE_PROBE_BW_DOWN: case NGTCP2_BBR_STATE_PROBE_BW_CRUISE: @@ -615,7 +630,7 @@ static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr) { } } -static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr) { +static int bbr_is_probing_bw(const ngtcp2_cc_bbr *bbr) { switch (bbr->state) { case NGTCP2_BBR_STATE_STARTUP: case NGTCP2_BBR_STATE_PROBE_BW_REFILL: @@ -627,7 +642,7 @@ static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr) { } static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { ngtcp2_duration interval = ts - bbr->extra_acked_interval_start; @@ -669,7 +684,8 @@ static void bbr_enter_drain(ngtcp2_cc_bbr *bbr) { bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H; } -static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { if (bbr->state == NGTCP2_BBR_STATE_DRAIN && cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, 100)) { @@ -726,7 +742,8 @@ static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr) { bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H; } -static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr start ProbeBW_UP"); bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING; @@ -743,7 +760,7 @@ static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { } static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { if (!bbr->full_bw_reached) { @@ -791,14 +808,16 @@ static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, } } -static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { uint64_t inflight = ngtcp2_min_uint64(bbr_inflight_with_headroom(bbr, cstat), bbr_inflight(bbr, cstat, 100)); return cstat->bytes_in_flight <= inflight; } -static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { if (bbr->rst->is_cwnd_limited && cstat->cwnd >= bbr->inflight_longterm) { bbr_reset_full_bw(bbr); bbr->full_bw = cstat->delivery_rate_sec; @@ -809,14 +828,14 @@ static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { return bbr->full_bw_now; } -static int bbr_has_elapsed_in_phase(ngtcp2_cc_bbr *bbr, +static int bbr_has_elapsed_in_phase(const ngtcp2_cc_bbr *bbr, ngtcp2_duration interval, ngtcp2_tstamp ts) { return ts > bbr->cycle_stamp + interval; } -static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { +static uint64_t bbr_inflight_with_headroom(const ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { uint64_t headroom; uint64_t mpcwnd; if (bbr->inflight_longterm == UINT64_MAX) { @@ -837,7 +856,7 @@ static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr, } static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { uint64_t growth_this_round = cstat->max_tx_udp_payload_size << bbr->bw_probe_up_rounds; @@ -846,7 +865,7 @@ static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr, } static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack) { uint64_t delta; @@ -870,7 +889,7 @@ static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, } static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_ack *ack) { if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING && bbr->round_start) { @@ -899,7 +918,8 @@ static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, } } -static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { if (bbr_has_elapsed_in_phase(bbr, bbr->bw_probe_wait, ts) || bbr_is_reno_coexistence_probe_time(bbr, cstat)) { @@ -917,27 +937,28 @@ static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr) { 2 * NGTCP2_SECONDS + ngtcp2_pcg32_rand_n(bbr->pcg, NGTCP2_SECONDS + 1); } -static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { +static int bbr_is_reno_coexistence_probe_time(const ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { uint64_t reno_rounds = bbr_target_inflight(bbr, cstat) / cstat->max_tx_udp_payload_size; return bbr->rounds_since_bw_probe >= ngtcp2_min_uint64(reno_rounds, 63); } -static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { +static uint64_t bbr_target_inflight(const ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { return ngtcp2_min_uint64(bbr->bdp, cstat->cwnd); } -static int bbr_is_inflight_too_high(ngtcp2_cc_bbr *bbr, const ngtcp2_rs *rs) { +static int bbr_is_inflight_too_high(const ngtcp2_cc_bbr *bbr, + const ngtcp2_rs *rs) { (void)bbr; return rs->lost * NGTCP2_BBR_LOSS_THRESH_DENOM > rs->tx_in_flight * NGTCP2_BBR_LOSS_THRESH_NUMER; } static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, const ngtcp2_rs *rs, ngtcp2_tstamp ts) { bbr->bw_probe_samples = 0; @@ -956,12 +977,49 @@ static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, static void bbr_note_loss(ngtcp2_cc_bbr *bbr) { if (!bbr->loss_in_round) { bbr->loss_round_delivered = bbr->rst->delivered; + bbr_save_state_upon_loss(bbr); } bbr->loss_in_round = 1; } -static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr) { + bbr->undo_state = bbr->state; + bbr->undo_bw_shortterm = bbr->bw_shortterm; + bbr->undo_inflight_shortterm = bbr->inflight_shortterm; + bbr->undo_inflight_longterm = bbr->inflight_longterm; +} + +static void bbr_handle_spurious_loss_detection(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat) { + bbr->loss_in_round = 0; + + bbr_reset_full_bw(bbr); + + bbr->bw_shortterm = + ngtcp2_max_uint64(bbr->bw_shortterm, bbr->undo_bw_shortterm); + bbr->inflight_shortterm = + ngtcp2_max_uint64(bbr->inflight_shortterm, bbr->undo_inflight_shortterm); + bbr->inflight_longterm = + ngtcp2_max_uint64(bbr->inflight_longterm, bbr->undo_inflight_longterm); + + if (bbr->state != NGTCP2_BBR_STATE_PROBE_RTT && + bbr->state != bbr->undo_state) { + switch (bbr->undo_state) { + case NGTCP2_BBR_STATE_STARTUP: + bbr_enter_startup(bbr); + break; + case NGTCP2_BBR_STATE_PROBE_BW_UP: + bbr_start_probe_bw_up(bbr, cstat); + break; + default: + break; + } + } +} + +static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, + const ngtcp2_conn_stat *cstat, const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { ngtcp2_rs rs = {0}; @@ -983,7 +1041,7 @@ static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, } } -static uint64_t bbr_inflight_at_loss(ngtcp2_cc_bbr *bbr, +static uint64_t bbr_inflight_at_loss(const ngtcp2_cc_bbr *bbr, const ngtcp2_cc_pkt *pkt, const ngtcp2_rs *rs) { uint64_t inflight_prev, lost_prev, lost_prefix; @@ -1100,7 +1158,7 @@ static void bbr_check_probe_rtt_done(ngtcp2_cc_bbr *bbr, } static void bbr_mark_connection_app_limited(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { bbr->rst->app_limited = ngtcp2_max_uint64(bbr->rst->delivered + cstat->bytes_in_flight, 1); } @@ -1148,7 +1206,7 @@ static uint64_t min_pipe_cwnd(size_t max_udp_payload_size) { } static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat, + const ngtcp2_conn_stat *cstat, uint64_t inflight) { bbr_update_offload_budget(bbr, cstat); @@ -1163,7 +1221,7 @@ static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, return inflight; } -static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, uint64_t gain_h) { uint64_t inflight = bbr_bdp_multiple(bbr, gain_h); @@ -1171,7 +1229,7 @@ static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, } static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { uint64_t inflight; inflight = bbr_bdp_multiple(bbr, bbr->cwnd_gain_h) + bbr->extra_acked; @@ -1179,7 +1237,7 @@ static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, } static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { bbr->offload_budget = 3 * cstat->send_quantum; } @@ -1187,7 +1245,7 @@ static void bbr_advance_max_bw_filter(ngtcp2_cc_bbr *bbr) { ++bbr->cycle_count; } -static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat) { if (!bbr->in_loss_recovery && bbr->state != NGTCP2_BBR_STATE_PROBE_RTT) { bbr->prior_cwnd = cstat->cwnd; return; @@ -1196,12 +1254,13 @@ static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { bbr->prior_cwnd = ngtcp2_max_uint64(bbr->prior_cwnd, cstat->cwnd); } -static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static void bbr_restore_cwnd(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { cstat->cwnd = ngtcp2_max_uint64(cstat->cwnd, bbr->prior_cwnd); } static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, - ngtcp2_conn_stat *cstat) { + const ngtcp2_conn_stat *cstat) { uint64_t probe_rtt_cwnd = bbr_bdp_multiple(bbr, NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H); uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); @@ -1238,7 +1297,7 @@ static void bbr_set_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, bbr_bound_cwnd_for_model(bbr, cstat); } -static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr, +static void bbr_bound_cwnd_for_model(const ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { uint64_t cap = UINT64_MAX; uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); @@ -1257,7 +1316,8 @@ static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr, cstat->cwnd = ngtcp2_min_uint64(cstat->cwnd, cap); } -static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +static void bbr_set_send_quantum(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { size_t send_quantum = 64 * 1024; (void)bbr; @@ -1293,7 +1353,7 @@ static void bbr_cc_on_pkt_lost(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); - bbr_update_on_loss(bbr, cstat, pkt, ts); + bbr_handle_lost_packet(bbr, cstat, pkt, ts); } static void bbr_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, @@ -1311,9 +1371,6 @@ static void bbr_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, bbr->round_count_at_recovery = bbr->round_start ? bbr->round_count : bbr->round_count + 1; bbr_save_cwnd(bbr, cstat); - bbr->undo_bw_shortterm = bbr->bw_shortterm; - bbr->undo_inflight_shortterm = bbr->inflight_shortterm; - bbr->undo_inflight_longterm = bbr->inflight_longterm; cstat->congestion_recovery_start_ts = ts; } @@ -1328,15 +1385,9 @@ static void bbr_cc_on_spurious_congestion(ngtcp2_cc *cc, bbr->in_loss_recovery = 0; bbr->round_count_at_recovery = UINT64_MAX; - bbr_reset_full_bw(bbr); - bbr->loss_in_round = 0; + bbr_restore_cwnd(bbr, cstat); - bbr->bw_shortterm = - ngtcp2_max_uint64(bbr->bw_shortterm, bbr->undo_bw_shortterm); - bbr->inflight_shortterm = - ngtcp2_max_uint64(bbr->inflight_shortterm, bbr->undo_inflight_shortterm); - bbr->inflight_longterm = - ngtcp2_max_uint64(bbr->inflight_longterm, bbr->undo_inflight_longterm); + bbr_handle_spurious_loss_detection(bbr, cstat); } static void bbr_cc_on_persistent_congestion(ngtcp2_cc *cc, diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h index 98b23122ff9908..5177944b290781 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_bbr.h @@ -107,6 +107,7 @@ typedef struct ngtcp2_cc_bbr { uint64_t cwnd_gain_h; /* Backup for spurious losses */ + ngtcp2_bbr_state undo_state; uint64_t undo_bw_shortterm; uint64_t undo_inflight_shortterm; uint64_t undo_inflight_longterm; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c index 84a41943a801d5..29fe7b03911aa3 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_conn.c @@ -11913,6 +11913,8 @@ conn_write_vmsg_wrapper(ngtcp2_conn *conn, ngtcp2_path *path, return nwrite; } + assert((size_t)nwrite <= destlen); + if (cstat->bytes_in_flight >= cstat->cwnd) { conn->rst.is_cwnd_limited = 1; } else if ((cstat->cwnd >= cstat->ssthresh || @@ -12355,7 +12357,7 @@ ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, /* We only exceed CWND to avoid deadlock. Do no write 1RTT packet if CWND is depleted. */ if (conn_cwnd_is_zero(conn) && conn->pktns.rtb.probe_pkt_left == 0) { - goto fin; + return res; } } else if (destlen == 0) { res = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c index 0c1c0a36c5bcf9..be3c9e182b5d06 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.c @@ -31,7 +31,6 @@ #include "ngtcp2_macro.h" #include "ngtcp2_mem.h" -#include "ngtcp2_range.h" static ngtcp2_ksl_blk null_blk; @@ -815,12 +814,6 @@ int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it) { return it->i == 0 && it->blk->prev == NULL; } -int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs) { - const ngtcp2_range *a = lhs, *b = rhs; - return a->begin < b->begin; -} - ngtcp2_ksl_search_def(range, ngtcp2_ksl_range_compar) size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, @@ -828,13 +821,6 @@ size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, return ksl_range_search(ksl, blk, key); } -int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs) { - const ngtcp2_range *a = lhs, *b = rhs; - return a->begin < b->begin && !(ngtcp2_max_uint64(a->begin, b->begin) < - ngtcp2_min_uint64(a->end, b->end)); -} - ngtcp2_ksl_search_def(range_exclusive, ngtcp2_ksl_range_exclusive_compar) size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl, @@ -843,11 +829,6 @@ size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl, return ksl_range_exclusive_search(ksl, blk, key); } -int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs) { - return *(uint64_t *)lhs < *(uint64_t *)rhs; -} - ngtcp2_ksl_search_def(uint64_less, ngtcp2_ksl_uint64_less) size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, @@ -855,11 +836,6 @@ size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, return ksl_uint64_less_search(ksl, blk, key); } -int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs) { - return *(int64_t *)lhs > *(int64_t *)rhs; -} - ngtcp2_ksl_search_def(int64_greater, ngtcp2_ksl_int64_greater) size_t ngtcp2_ksl_int64_greater_search(const ngtcp2_ksl *ksl, diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.h index cb972f94dca2e2..8024a360cdb4fc 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_ksl.h @@ -34,6 +34,7 @@ #include #include "ngtcp2_objalloc.h" +#include "ngtcp2_range.h" #define NGTCP2_KSL_DEGR 16 /* NGTCP2_KSL_MAX_NBLK is the maximum number of nodes which a single @@ -356,8 +357,12 @@ static inline const ngtcp2_ksl_key *ngtcp2_ksl_it_key(const ngtcp2_ksl_it *it) { * returns nonzero if ((const ngtcp2_range *)lhs)->begin < ((const * ngtcp2_range *)rhs)->begin. */ -int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs); +static inline int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + const ngtcp2_range *a = (const ngtcp2_range *)lhs, + *b = (const ngtcp2_range *)rhs; + return a->begin < b->begin; +} /* * ngtcp2_ksl_range_search is an implementation of ngtcp2_ksl_search @@ -373,8 +378,13 @@ size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, * *)lhs)->begin < ((const ngtcp2_range *)rhs)->begin, and the 2 * ranges do not intersect. */ -int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs); +static inline int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + const ngtcp2_range *a = (const ngtcp2_range *)lhs, + *b = (const ngtcp2_range *)rhs; + return a->begin < b->begin && !(ngtcp2_max_uint64(a->begin, b->begin) < + ngtcp2_min_uint64(a->end, b->end)); +} /* * ngtcp2_ksl_range_exclusive_search is an implementation of @@ -389,8 +399,10 @@ size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl, * |lhs| and |rhs| must point to uint64_t objects, and the function * returns nonzero if *(uint64_t *)|lhs| < *(uint64_t *)|rhs|. */ -int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs); +static inline int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + return *(const uint64_t *)lhs < *(const uint64_t *)rhs; +} /* * ngtcp2_ksl_uint64_less_search is an implementation of @@ -404,8 +416,10 @@ size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, * |lhs| and |rhs| must point to int64_t objects, and the function * returns nonzero if *(int64_t *)|lhs| > *(int64_t *)|rhs|. */ -int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs, - const ngtcp2_ksl_key *rhs); +static inline int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + return *(const int64_t *)lhs > *(const int64_t *)rhs; +} /* * ngtcp2_ksl_int64_greater_search is an implementation of diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c index 2015e401ef2609..39fd6969a5e3f6 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.c @@ -146,33 +146,22 @@ static const char *strapperrorcode(uint64_t app_error_code) { return "(unknown)"; } -static const char *strpkttype_long(uint8_t type) { - switch (type) { +static const char *strpkttype(const ngtcp2_pkt_hd *hd) { + switch (hd->type) { case NGTCP2_PKT_INITIAL: return "Initial"; - case NGTCP2_PKT_RETRY: - return "Retry"; - case NGTCP2_PKT_HANDSHAKE: - return "Handshake"; case NGTCP2_PKT_0RTT: return "0RTT"; - default: - return "(unknown)"; - } -} - -static const char *strpkttype(const ngtcp2_pkt_hd *hd) { - if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { - return strpkttype_long(hd->type); - } - - switch (hd->type) { + case NGTCP2_PKT_HANDSHAKE: + return "Handshake"; + case NGTCP2_PKT_RETRY: + return "Retry"; + case NGTCP2_PKT_1RTT: + return "1RTT"; case NGTCP2_PKT_VERSION_NEGOTIATION: return "VN"; case NGTCP2_PKT_STATELESS_RESET: return "SR"; - case NGTCP2_PKT_1RTT: - return "1RTT"; default: return "(unknown)"; } @@ -266,7 +255,7 @@ static void log_fr_connection_close(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, log, NGTCP2_LOG_EVENT_FRM, NGTCP2_LOG_PKT " CONNECTION_CLOSE(0x%02" PRIx64 ") error_code=%s(0x%" PRIx64 ") " - "frame_type=%" PRIx64 " reason_len=%zu reason=[%s]", + "frame_type=0x%" PRIx64 " reason_len=%zu reason=[%s]", NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->type == NGTCP2_FRAME_CONNECTION_CLOSE ? strerrorcode(fr->error_code) : strapperrorcode(fr->error_code), @@ -766,12 +755,6 @@ void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { log_pkt_hd(log, hd, "tx"); } -void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { - ngtcp2_log_infof(log, NGTCP2_LOG_EVENT_PKT, - "cancel tx pkn=%" PRId64 " type=%s", hd->pkt_num, - strpkttype(hd)); -} - uint64_t ngtcp2_log_timestamp(const ngtcp2_log *log) { return (log->last_ts - log->ts) / NGTCP2_MILLISECONDS; } diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.h index fabd5908e0d492..02726bc19ded6f 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_log.h @@ -119,8 +119,6 @@ void ngtcp2_log_rx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); -void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); - #define NGTCP2_LOG_HD "I%08" PRIu64 " 0x%s %s" uint64_t ngtcp2_log_timestamp(const ngtcp2_log *log); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c index 0179dbcde96445..a63913f8af797a 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.c @@ -543,7 +543,7 @@ ngtcp2_ssize ngtcp2_frame_decoder_decode(ngtcp2_frame_decoder *frd, return ngtcp2_pkt_decode_path_response_frame(&dest->path_response, payload, payloadlen); case NGTCP2_FRAME_CRYPTO: - dest->stream.data = &frd->buf.stream_data; + dest->stream.data = &frd->buf.data; return ngtcp2_pkt_decode_crypto_frame(&dest->stream, payload, payloadlen); case NGTCP2_FRAME_NEW_TOKEN: return ngtcp2_pkt_decode_new_token_frame(&dest->new_token, payload, @@ -556,11 +556,12 @@ ngtcp2_ssize ngtcp2_frame_decoder_decode(ngtcp2_frame_decoder *frd, payload, payloadlen); case NGTCP2_FRAME_DATAGRAM: case NGTCP2_FRAME_DATAGRAM_LEN: + dest->datagram.data = &frd->buf.data; return ngtcp2_pkt_decode_datagram_frame(&dest->datagram, payload, payloadlen); default: if ((type & ~(NGTCP2_FRAME_STREAM - 1)) == NGTCP2_FRAME_STREAM) { - dest->stream.data = &frd->buf.stream_data; + dest->stream.data = &frd->buf.data; return ngtcp2_pkt_decode_stream_frame(&dest->stream, payload, payloadlen); } @@ -1490,16 +1491,13 @@ ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, dest->type = type; - if (datalen == 0) { - dest->datacnt = 0; - dest->data = NULL; - } else { + if (datalen) { + dest->data[0].len = datalen; + dest->data[0].base = (uint8_t *)p; dest->datacnt = 1; - dest->data = dest->rdata; - dest->rdata[0].len = datalen; - - dest->rdata[0].base = (uint8_t *)p; p += datalen; + } else { + dest->datacnt = 0; } assert((size_t)(p - payload) == len); diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h index 78fcd6dcc2b54d..abf13693b916e7 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_pkt.h @@ -340,13 +340,9 @@ typedef struct ngtcp2_datagram { uint64_t dgram_id; /* datacnt is the number of elements that data contains. */ size_t datacnt; - /* data is a pointer to ngtcp2_vec array that stores data. */ + /* data is a pointer to ngtcp2_vec array that stores data. If + datacnt == 0, this field may be NULL.*/ ngtcp2_vec *data; - /* rdata is conveniently embedded to ngtcp2_datagram, so that data - field can just point to the address of this field to store a - single vector which is the case when DATAGRAM is received from a - remote endpoint. */ - ngtcp2_vec rdata[1]; } ngtcp2_datagram; typedef union ngtcp2_frame { @@ -457,7 +453,7 @@ ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, */ typedef struct ngtcp2_frame_decoder { union { - ngtcp2_vec stream_data; + ngtcp2_vec data; ngtcp2_ack_range ack_ranges[NGTCP2_MAX_ACK_RANGES]; } buf; } ngtcp2_frame_decoder; diff --git a/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c b/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c index 7c1142d14d3c6a..c10e2ff6700451 100644 --- a/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c +++ b/deps/ngtcp2/ngtcp2/lib/ngtcp2_qlog.c @@ -223,28 +223,21 @@ static const ngtcp2_vec vec_pkt_type_stateless_reset = static const ngtcp2_vec vec_pkt_type_unknown = ngtcp2_make_vec_lit("unknown"); static const ngtcp2_vec *qlog_pkt_type(const ngtcp2_pkt_hd *hd) { - if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { - switch (hd->type) { - case NGTCP2_PKT_INITIAL: - return &vec_pkt_type_initial; - case NGTCP2_PKT_HANDSHAKE: - return &vec_pkt_type_handshake; - case NGTCP2_PKT_0RTT: - return &vec_pkt_type_0rtt; - case NGTCP2_PKT_RETRY: - return &vec_pkt_type_retry; - default: - return &vec_pkt_type_unknown; - } - } - switch (hd->type) { + case NGTCP2_PKT_INITIAL: + return &vec_pkt_type_initial; + case NGTCP2_PKT_0RTT: + return &vec_pkt_type_0rtt; + case NGTCP2_PKT_HANDSHAKE: + return &vec_pkt_type_handshake; + case NGTCP2_PKT_RETRY: + return &vec_pkt_type_retry; + case NGTCP2_PKT_1RTT: + return &vec_pkt_type_1rtt; case NGTCP2_PKT_VERSION_NEGOTIATION: return &vec_pkt_type_version_negotiation; case NGTCP2_PKT_STATELESS_RESET: return &vec_pkt_type_stateless_reset; - case NGTCP2_PKT_1RTT: - return &vec_pkt_type_1rtt; default: return &vec_pkt_type_unknown; } diff --git a/deps/npm/docs/content/commands/npm-audit.md b/deps/npm/docs/content/commands/npm-audit.md index f7d4e06718c96f..e0b80373ad181b 100644 --- a/deps/npm/docs/content/commands/npm-audit.md +++ b/deps/npm/docs/content/commands/npm-audit.md @@ -44,6 +44,16 @@ The `audit signatures` command will also verify the provenance attestations of d Because provenance attestations are such a new feature, security features may be added to (or changed in) the attestation format over time. To ensure that you're always able to verify attestation signatures check that you're running the latest version of the npm CLI. Please note this often means updating npm beyond the version that ships with Node.js. +To include the full sigstore attestation bundles in JSON output, use: + +```bash +$ npm audit signatures --json --include-attestations +``` + +This adds a `verified` array to the JSON output containing the attestation +bundles (DSSE envelopes, verification material, and transparency log entries) +for each verified package. + The npm CLI supports registry signatures and signing keys provided by any registry if the following conventions are followed: 1. Signatures are provided in the package's `packument` in each published version within the `dist` object: @@ -357,6 +367,18 @@ run any pre- or post-scripts. +#### `include-attestations` + +* Default: false +* Type: Boolean + +When used with `npm audit signatures --json`, includes the full sigstore +attestation bundles in the JSON output for each verified package. The +bundles contain DSSE envelopes, verification material, and transparency log +entries. + + + #### `workspace` * Default: diff --git a/deps/npm/docs/content/commands/npm-install-test.md b/deps/npm/docs/content/commands/npm-install-test.md index d576d335a66481..8291409edfb835 100644 --- a/deps/npm/docs/content/commands/npm-install-test.md +++ b/deps/npm/docs/content/commands/npm-install-test.md @@ -281,6 +281,8 @@ of a relative number of days. This config cannot be used with: `before` +This value is not exported to the environment for child processes. + #### `bin-links` * Default: true diff --git a/deps/npm/docs/content/commands/npm-install.md b/deps/npm/docs/content/commands/npm-install.md index 34c5ee6b1878da..77a34667725c3f 100644 --- a/deps/npm/docs/content/commands/npm-install.md +++ b/deps/npm/docs/content/commands/npm-install.md @@ -623,6 +623,8 @@ of a relative number of days. This config cannot be used with: `before` +This value is not exported to the environment for child processes. + #### `bin-links` * Default: true diff --git a/deps/npm/docs/content/commands/npm-ls.md b/deps/npm/docs/content/commands/npm-ls.md index c5b4388b3fb501..ddcb05d2c6ae5c 100644 --- a/deps/npm/docs/content/commands/npm-ls.md +++ b/deps/npm/docs/content/commands/npm-ls.md @@ -23,7 +23,7 @@ Note that nested packages will *also* show the paths to the specified packages. For example, running `npm ls promzard` in npm's source tree will show: ```bash -npm@11.11.1 /path/to/npm +npm@11.12.1 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 ``` diff --git a/deps/npm/docs/content/commands/npm-outdated.md b/deps/npm/docs/content/commands/npm-outdated.md index b05f8b5a5c0e1b..f635cb90565d2f 100644 --- a/deps/npm/docs/content/commands/npm-outdated.md +++ b/deps/npm/docs/content/commands/npm-outdated.md @@ -182,6 +182,8 @@ of a relative number of days. This config cannot be used with: `before` +This value is not exported to the environment for child processes. + ### See Also * [package spec](/using-npm/package-spec) diff --git a/deps/npm/docs/content/commands/npm-publish.md b/deps/npm/docs/content/commands/npm-publish.md index 95429d0bdc7518..c69e187429eabb 100644 --- a/deps/npm/docs/content/commands/npm-publish.md +++ b/deps/npm/docs/content/commands/npm-publish.md @@ -54,6 +54,8 @@ A `package` is interpreted the same way as other commands (like `npm install`) a * f) a `` that has a "latest" tag satisfying (e) * g) a `` that resolves to (a) +If either (a) or (b) is specified as a relative path, it should begin with an explicit `./` prefix. + The publish will fail if the package name and version combination already exists in the specified registry. Once a package is published with a given name and version, that specific name and version combination can never be used again, even if it is removed with [`npm unpublish`](/commands/npm-unpublish). diff --git a/deps/npm/docs/content/commands/npm-trust.md b/deps/npm/docs/content/commands/npm-trust.md index c26e47ad1271d9..e780330db878de 100644 --- a/deps/npm/docs/content/commands/npm-trust.md +++ b/deps/npm/docs/content/commands/npm-trust.md @@ -6,10 +6,6 @@ description: Manage trusted publishing relationships between packages and CI/CD ### Synopsis -```bash - -``` - Note: This command is unaware of workspaces. ### Prerequisites diff --git a/deps/npm/docs/content/commands/npm-update.md b/deps/npm/docs/content/commands/npm-update.md index 2011a1e235b2c5..60cb3c9ada4c2c 100644 --- a/deps/npm/docs/content/commands/npm-update.md +++ b/deps/npm/docs/content/commands/npm-update.md @@ -347,6 +347,8 @@ of a relative number of days. This config cannot be used with: `before` +This value is not exported to the environment for child processes. + #### `bin-links` * Default: true diff --git a/deps/npm/docs/content/commands/npm.md b/deps/npm/docs/content/commands/npm.md index b6dde2852ac661..2013ee7f8205f1 100644 --- a/deps/npm/docs/content/commands/npm.md +++ b/deps/npm/docs/content/commands/npm.md @@ -14,7 +14,7 @@ Note: This command is unaware of workspaces. ### Version -11.11.1 +11.12.1 ### Description diff --git a/deps/npm/docs/content/using-npm/config.md b/deps/npm/docs/content/using-npm/config.md index ff897d9f71b02f..96df0ae0058c69 100644 --- a/deps/npm/docs/content/using-npm/config.md +++ b/deps/npm/docs/content/using-npm/config.md @@ -770,6 +770,18 @@ the order in which omit/include are specified on the command-line. +#### `include-attestations` + +* Default: false +* Type: Boolean + +When used with `npm audit signatures --json`, includes the full sigstore +attestation bundles in the JSON output for each verified package. The +bundles contain DSSE envelopes, verification material, and transparency log +entries. + + + #### `include-staged` * Default: false @@ -1086,6 +1098,8 @@ of a relative number of days. This config cannot be used with: `before` +This value is not exported to the environment for child processes. + #### `name` * Default: null diff --git a/deps/npm/docs/lib/index.js b/deps/npm/docs/lib/index.js index 17f7bfca8fce90..d7a5e83ccf5062 100644 --- a/deps/npm/docs/lib/index.js +++ b/deps/npm/docs/lib/index.js @@ -104,25 +104,32 @@ const replaceUsage = (src, { path, commandLoader }) => { const replacer = assertPlaceholder(src, path, TAGS.USAGE) const { usage, name, workspaces } = getCommandByDoc(path, DOC_EXT, commandLoader) - const synopsis = ['```bash', usage] + const synopsis = [] - const cmdAliases = Object.keys(aliases).reduce((p, c) => { - if (aliases[c] === name) { - p.push(c) + if (usage) { + synopsis.push('```bash', usage) + + const cmdAliases = Object.keys(aliases).reduce((p, c) => { + if (aliases[c] === name) { + p.push(c) + } + return p + }, []) + + if (cmdAliases.length === 1) { + synopsis.push('', `alias: ${cmdAliases[0]}`) + } else if (cmdAliases.length > 1) { + synopsis.push('', `aliases: ${cmdAliases.join(', ')}`) } - return p - }, []) - if (cmdAliases.length === 1) { - synopsis.push('', `alias: ${cmdAliases[0]}`) - } else if (cmdAliases.length > 1) { - synopsis.push('', `aliases: ${cmdAliases.join(', ')}`) + synopsis.push('```') } - synopsis.push('```') - if (!workspaces) { - synopsis.push('', 'Note: This command is unaware of workspaces.') + if (synopsis.length) { + synopsis.push('') + } + synopsis.push('Note: This command is unaware of workspaces.') } return src.replace(replacer, synopsis.join('\n')) diff --git a/deps/npm/docs/output/commands/npm-access.html b/deps/npm/docs/output/commands/npm-access.html index 4eccf2281ade9c..fc29ba6a871af0 100644 --- a/deps/npm/docs/output/commands/npm-access.html +++ b/deps/npm/docs/output/commands/npm-access.html @@ -186,9 +186,9 @@
-

+

npm-access - @11.11.1 + @11.12.1

Set access level on published packages
diff --git a/deps/npm/docs/output/commands/npm-adduser.html b/deps/npm/docs/output/commands/npm-adduser.html index 8c1b0ba23ee659..b9fb04905a4df5 100644 --- a/deps/npm/docs/output/commands/npm-adduser.html +++ b/deps/npm/docs/output/commands/npm-adduser.html @@ -186,9 +186,9 @@
-

+

npm-adduser - @11.11.1 + @11.12.1

Add a registry user account
diff --git a/deps/npm/docs/output/commands/npm-audit.html b/deps/npm/docs/output/commands/npm-audit.html index 70be84e75b76be..9cd06e991464b3 100644 --- a/deps/npm/docs/output/commands/npm-audit.html +++ b/deps/npm/docs/output/commands/npm-audit.html @@ -186,16 +186,16 @@
-

+

npm-audit - @11.11.1 + @11.12.1

Run a security audit

Table of contents

- +

Synopsis

@@ -222,6 +222,12 @@

Audit Signatures

The audit signatures command will also verify the provenance attestations of downloaded packages. Because provenance attestations are such a new feature, security features may be added to (or changed in) the attestation format over time. To ensure that you're always able to verify attestation signatures check that you're running the latest version of the npm CLI. Please note this often means updating npm beyond the version that ships with Node.js.

+

To include the full sigstore attestation bundles in JSON output, use:

+
$ npm audit signatures --json --include-attestations
+
+

This adds a verified array to the JSON output containing the attestation +bundles (DSSE envelopes, verification material, and transparency log entries) +for each verified package.

The npm CLI supports registry signatures and signing keys provided by any registry if the following conventions are followed:

  1. Signatures are provided in the package's packument in each published version within the dist object:
  2. @@ -439,6 +445,15 @@

    ignore-scripts

    npm start, npm stop, npm restart, npm test, and npm run will still run their intended script if ignore-scripts is set, but they will not run any pre- or post-scripts.

    +

    include-attestations

    +
      +
    • Default: false
    • +
    • Type: Boolean
    • +
    +

    When used with npm audit signatures --json, includes the full sigstore +attestation bundles in the JSON output for each verified package. The +bundles contain DSSE envelopes, verification material, and transparency log +entries.

    workspace

    • Default:
    • diff --git a/deps/npm/docs/output/commands/npm-bugs.html b/deps/npm/docs/output/commands/npm-bugs.html index fa807fa6bfaed3..2b040aad7a9742 100644 --- a/deps/npm/docs/output/commands/npm-bugs.html +++ b/deps/npm/docs/output/commands/npm-bugs.html @@ -186,9 +186,9 @@
      -

      +

      npm-bugs - @11.11.1 + @11.12.1

      Report bugs for a package in a web browser
      diff --git a/deps/npm/docs/output/commands/npm-cache.html b/deps/npm/docs/output/commands/npm-cache.html index 9037645c51e382..59bc15e64b4f78 100644 --- a/deps/npm/docs/output/commands/npm-cache.html +++ b/deps/npm/docs/output/commands/npm-cache.html @@ -186,9 +186,9 @@
      -

      +

      npm-cache - @11.11.1 + @11.12.1

      Manipulates packages cache
      diff --git a/deps/npm/docs/output/commands/npm-ci.html b/deps/npm/docs/output/commands/npm-ci.html index 0816651531157b..e713c926853b7a 100644 --- a/deps/npm/docs/output/commands/npm-ci.html +++ b/deps/npm/docs/output/commands/npm-ci.html @@ -186,9 +186,9 @@
      -

      +

      npm-ci - @11.11.1 + @11.12.1

      Clean install a project
      diff --git a/deps/npm/docs/output/commands/npm-completion.html b/deps/npm/docs/output/commands/npm-completion.html index 2fe9944f7573ba..868929c5cf1294 100644 --- a/deps/npm/docs/output/commands/npm-completion.html +++ b/deps/npm/docs/output/commands/npm-completion.html @@ -186,9 +186,9 @@
      -

      +

      npm-completion - @11.11.1 + @11.12.1

      Tab Completion for npm
      diff --git a/deps/npm/docs/output/commands/npm-config.html b/deps/npm/docs/output/commands/npm-config.html index b8bfd67a530a11..5f5ace326516ec 100644 --- a/deps/npm/docs/output/commands/npm-config.html +++ b/deps/npm/docs/output/commands/npm-config.html @@ -186,9 +186,9 @@
      -

      +

      npm-config - @11.11.1 + @11.12.1

      Manage the npm configuration files
      diff --git a/deps/npm/docs/output/commands/npm-dedupe.html b/deps/npm/docs/output/commands/npm-dedupe.html index 195cd69f6731f8..21749194ae31bc 100644 --- a/deps/npm/docs/output/commands/npm-dedupe.html +++ b/deps/npm/docs/output/commands/npm-dedupe.html @@ -186,9 +186,9 @@
      -

      +

      npm-dedupe - @11.11.1 + @11.12.1

      Reduce duplication in the package tree
      diff --git a/deps/npm/docs/output/commands/npm-deprecate.html b/deps/npm/docs/output/commands/npm-deprecate.html index 1dd7caf57f1b49..0fb8cadb29d5ba 100644 --- a/deps/npm/docs/output/commands/npm-deprecate.html +++ b/deps/npm/docs/output/commands/npm-deprecate.html @@ -186,9 +186,9 @@
      -

      +

      npm-deprecate - @11.11.1 + @11.12.1

      Deprecate a version of a package
      diff --git a/deps/npm/docs/output/commands/npm-diff.html b/deps/npm/docs/output/commands/npm-diff.html index 28e6e2aff1e234..7ed4d107d8ae48 100644 --- a/deps/npm/docs/output/commands/npm-diff.html +++ b/deps/npm/docs/output/commands/npm-diff.html @@ -186,9 +186,9 @@
      -

      +

      npm-diff - @11.11.1 + @11.12.1

      The registry diff command
      diff --git a/deps/npm/docs/output/commands/npm-dist-tag.html b/deps/npm/docs/output/commands/npm-dist-tag.html index 5625e9d1613cc1..f4a5b6f8e5ca57 100644 --- a/deps/npm/docs/output/commands/npm-dist-tag.html +++ b/deps/npm/docs/output/commands/npm-dist-tag.html @@ -186,9 +186,9 @@
      -

      +

      npm-dist-tag - @11.11.1 + @11.12.1

      Modify package distribution tags
      diff --git a/deps/npm/docs/output/commands/npm-docs.html b/deps/npm/docs/output/commands/npm-docs.html index 9278b5b4073e66..17244edf83a6be 100644 --- a/deps/npm/docs/output/commands/npm-docs.html +++ b/deps/npm/docs/output/commands/npm-docs.html @@ -186,9 +186,9 @@
      -

      +

      npm-docs - @11.11.1 + @11.12.1

      Open documentation for a package in a web browser
      diff --git a/deps/npm/docs/output/commands/npm-doctor.html b/deps/npm/docs/output/commands/npm-doctor.html index 21565a2c12f6eb..eda670272c9296 100644 --- a/deps/npm/docs/output/commands/npm-doctor.html +++ b/deps/npm/docs/output/commands/npm-doctor.html @@ -186,9 +186,9 @@
      -

      +

      npm-doctor - @11.11.1 + @11.12.1

      Check the health of your npm environment
      diff --git a/deps/npm/docs/output/commands/npm-edit.html b/deps/npm/docs/output/commands/npm-edit.html index 5caff2aa9f861d..1f3ce25c4b064a 100644 --- a/deps/npm/docs/output/commands/npm-edit.html +++ b/deps/npm/docs/output/commands/npm-edit.html @@ -186,9 +186,9 @@
      -

      +

      npm-edit - @11.11.1 + @11.12.1

      Edit an installed package
      diff --git a/deps/npm/docs/output/commands/npm-exec.html b/deps/npm/docs/output/commands/npm-exec.html index 55518247acdd9f..4e6c048645c38a 100644 --- a/deps/npm/docs/output/commands/npm-exec.html +++ b/deps/npm/docs/output/commands/npm-exec.html @@ -186,9 +186,9 @@
      -

      +

      npm-exec - @11.11.1 + @11.12.1

      Run a command from a local or remote npm package
      diff --git a/deps/npm/docs/output/commands/npm-explain.html b/deps/npm/docs/output/commands/npm-explain.html index fd9b4fe19a5dd6..6bae2eef58312c 100644 --- a/deps/npm/docs/output/commands/npm-explain.html +++ b/deps/npm/docs/output/commands/npm-explain.html @@ -186,9 +186,9 @@
      -

      +

      npm-explain - @11.11.1 + @11.12.1

      Explain installed packages
      diff --git a/deps/npm/docs/output/commands/npm-explore.html b/deps/npm/docs/output/commands/npm-explore.html index 1936c345582111..554827e26da756 100644 --- a/deps/npm/docs/output/commands/npm-explore.html +++ b/deps/npm/docs/output/commands/npm-explore.html @@ -186,9 +186,9 @@
      -

      +

      npm-explore - @11.11.1 + @11.12.1

      Browse an installed package
      diff --git a/deps/npm/docs/output/commands/npm-find-dupes.html b/deps/npm/docs/output/commands/npm-find-dupes.html index 04370edd8ba11c..808a61c64167c1 100644 --- a/deps/npm/docs/output/commands/npm-find-dupes.html +++ b/deps/npm/docs/output/commands/npm-find-dupes.html @@ -186,9 +186,9 @@
      -

      +

      npm-find-dupes - @11.11.1 + @11.12.1

      Find duplication in the package tree
      diff --git a/deps/npm/docs/output/commands/npm-fund.html b/deps/npm/docs/output/commands/npm-fund.html index 2297635ae27b47..282ceffce8563f 100644 --- a/deps/npm/docs/output/commands/npm-fund.html +++ b/deps/npm/docs/output/commands/npm-fund.html @@ -186,9 +186,9 @@
      -

      +

      npm-fund - @11.11.1 + @11.12.1

      Retrieve funding information
      diff --git a/deps/npm/docs/output/commands/npm-get.html b/deps/npm/docs/output/commands/npm-get.html index 0b2f9dd572e3f2..3a3cf6c4a626b1 100644 --- a/deps/npm/docs/output/commands/npm-get.html +++ b/deps/npm/docs/output/commands/npm-get.html @@ -186,9 +186,9 @@
      -

      +

      npm-get - @11.11.1 + @11.12.1

      Get a value from the npm configuration
      diff --git a/deps/npm/docs/output/commands/npm-help-search.html b/deps/npm/docs/output/commands/npm-help-search.html index 66cdfe1fd090b3..bbfe72d189246a 100644 --- a/deps/npm/docs/output/commands/npm-help-search.html +++ b/deps/npm/docs/output/commands/npm-help-search.html @@ -186,9 +186,9 @@
      -

      +

      npm-help-search - @11.11.1 + @11.12.1

      Search npm help documentation
      diff --git a/deps/npm/docs/output/commands/npm-help.html b/deps/npm/docs/output/commands/npm-help.html index c4ea6e037d0afe..28af6a41d2c2fe 100644 --- a/deps/npm/docs/output/commands/npm-help.html +++ b/deps/npm/docs/output/commands/npm-help.html @@ -186,9 +186,9 @@
      -

      +

      npm-help - @11.11.1 + @11.12.1

      Get help on npm
      diff --git a/deps/npm/docs/output/commands/npm-init.html b/deps/npm/docs/output/commands/npm-init.html index b259127c68e1f4..0822f07ef6e0f8 100644 --- a/deps/npm/docs/output/commands/npm-init.html +++ b/deps/npm/docs/output/commands/npm-init.html @@ -186,9 +186,9 @@
      -

      +

      npm-init - @11.11.1 + @11.12.1

      Create a package.json file
      diff --git a/deps/npm/docs/output/commands/npm-install-ci-test.html b/deps/npm/docs/output/commands/npm-install-ci-test.html index 9177de9e34a1db..ac340b4f0af3fd 100644 --- a/deps/npm/docs/output/commands/npm-install-ci-test.html +++ b/deps/npm/docs/output/commands/npm-install-ci-test.html @@ -186,9 +186,9 @@
      -

      +

      npm-install-ci-test - @11.11.1 + @11.12.1

      Install a project with a clean slate and run tests
      diff --git a/deps/npm/docs/output/commands/npm-install-test.html b/deps/npm/docs/output/commands/npm-install-test.html index 0a6fe28ae8732f..4f3b150a4a1967 100644 --- a/deps/npm/docs/output/commands/npm-install-test.html +++ b/deps/npm/docs/output/commands/npm-install-test.html @@ -186,9 +186,9 @@
      -

      +

      npm-install-test - @11.11.1 + @11.12.1

      Install package(s) and run tests
      @@ -402,6 +402,7 @@

      min-release-age

      This flag is a complement to before, which accepts an exact date instead of a relative number of days.

      This config cannot be used with: before

      +

      This value is not exported to the environment for child processes.

      • Default: true
      • diff --git a/deps/npm/docs/output/commands/npm-install.html b/deps/npm/docs/output/commands/npm-install.html index 07b6acef4ce40b..af3fd1724aeac1 100644 --- a/deps/npm/docs/output/commands/npm-install.html +++ b/deps/npm/docs/output/commands/npm-install.html @@ -186,9 +186,9 @@
        -

        +

        npm-install - @11.11.1 + @11.12.1

        Install a package
        @@ -677,6 +677,7 @@

        min-release-age

        This flag is a complement to before, which accepts an exact date instead of a relative number of days.

        This config cannot be used with: before

        +

        This value is not exported to the environment for child processes.

        • Default: true
        • diff --git a/deps/npm/docs/output/commands/npm-link.html b/deps/npm/docs/output/commands/npm-link.html index 74c8d34a40290c..dce1cd126c74a0 100644 --- a/deps/npm/docs/output/commands/npm-link.html +++ b/deps/npm/docs/output/commands/npm-link.html @@ -186,9 +186,9 @@
          -

          +

          npm-link - @11.11.1 + @11.12.1

          Symlink a package folder
          diff --git a/deps/npm/docs/output/commands/npm-ll.html b/deps/npm/docs/output/commands/npm-ll.html index 2207540e832f4e..324f2fc7574735 100644 --- a/deps/npm/docs/output/commands/npm-ll.html +++ b/deps/npm/docs/output/commands/npm-ll.html @@ -186,9 +186,9 @@
          -

          +

          npm-ll - @11.11.1 + @11.12.1

          List installed packages
          diff --git a/deps/npm/docs/output/commands/npm-login.html b/deps/npm/docs/output/commands/npm-login.html index f8398bd514d915..3469d2b6432aad 100644 --- a/deps/npm/docs/output/commands/npm-login.html +++ b/deps/npm/docs/output/commands/npm-login.html @@ -186,9 +186,9 @@
          -

          +

          npm-login - @11.11.1 + @11.12.1

          Login to a registry user account
          diff --git a/deps/npm/docs/output/commands/npm-logout.html b/deps/npm/docs/output/commands/npm-logout.html index e90ec8738f1e51..75200db0db47e3 100644 --- a/deps/npm/docs/output/commands/npm-logout.html +++ b/deps/npm/docs/output/commands/npm-logout.html @@ -186,9 +186,9 @@
          -

          +

          npm-logout - @11.11.1 + @11.12.1

          Log out of the registry
          diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index 5c6d051f6da5d2..8e9edae452abe2 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -186,9 +186,9 @@
          -

          +

          npm-ls - @11.11.1 + @11.12.1

          List installed packages
          @@ -209,7 +209,7 @@

          Description

          Positional arguments are name@version-range identifiers, which will limit the results to only the paths to the packages named. Note that nested packages will also show the paths to the specified packages. For example, running npm ls promzard in npm's source tree will show:

          -
          npm@11.11.1 /path/to/npm
          +
          npm@11.12.1 /path/to/npm
           └─┬ init-package-json@0.0.4
             └── promzard@0.1.5
           
          diff --git a/deps/npm/docs/output/commands/npm-org.html b/deps/npm/docs/output/commands/npm-org.html index 640c87bf527128..22657f36b8727a 100644 --- a/deps/npm/docs/output/commands/npm-org.html +++ b/deps/npm/docs/output/commands/npm-org.html @@ -186,9 +186,9 @@
          -

          +

          npm-org - @11.11.1 + @11.12.1

          Manage orgs
          diff --git a/deps/npm/docs/output/commands/npm-outdated.html b/deps/npm/docs/output/commands/npm-outdated.html index 3469df56add2a9..c19a6b4900927c 100644 --- a/deps/npm/docs/output/commands/npm-outdated.html +++ b/deps/npm/docs/output/commands/npm-outdated.html @@ -186,9 +186,9 @@
          -

          +

          npm-outdated - @11.11.1 + @11.12.1

          Check for outdated packages
          @@ -341,6 +341,7 @@

          min-release-age

          This flag is a complement to before, which accepts an exact date instead of a relative number of days.

          This config cannot be used with: before

          +

          This value is not exported to the environment for child processes.

          See Also

          • package spec
          • diff --git a/deps/npm/docs/output/commands/npm-owner.html b/deps/npm/docs/output/commands/npm-owner.html index fd5291d69b506b..7712718fd66615 100644 --- a/deps/npm/docs/output/commands/npm-owner.html +++ b/deps/npm/docs/output/commands/npm-owner.html @@ -186,9 +186,9 @@
            -

            +

            npm-owner - @11.11.1 + @11.12.1

            Manage package owners
            diff --git a/deps/npm/docs/output/commands/npm-pack.html b/deps/npm/docs/output/commands/npm-pack.html index fba8289e0527e6..cdcd15b910c862 100644 --- a/deps/npm/docs/output/commands/npm-pack.html +++ b/deps/npm/docs/output/commands/npm-pack.html @@ -186,9 +186,9 @@
            -

            +

            npm-pack - @11.11.1 + @11.12.1

            Create a tarball from a package
            diff --git a/deps/npm/docs/output/commands/npm-ping.html b/deps/npm/docs/output/commands/npm-ping.html index f88d576d495ebc..db7b4741c06ba3 100644 --- a/deps/npm/docs/output/commands/npm-ping.html +++ b/deps/npm/docs/output/commands/npm-ping.html @@ -186,9 +186,9 @@
            -

            +

            npm-ping - @11.11.1 + @11.12.1

            Ping npm registry
            diff --git a/deps/npm/docs/output/commands/npm-pkg.html b/deps/npm/docs/output/commands/npm-pkg.html index 08173ffca459fa..13a3549b580c3d 100644 --- a/deps/npm/docs/output/commands/npm-pkg.html +++ b/deps/npm/docs/output/commands/npm-pkg.html @@ -186,9 +186,9 @@
            -

            +

            npm-pkg - @11.11.1 + @11.12.1

            Manages your package.json
            diff --git a/deps/npm/docs/output/commands/npm-prefix.html b/deps/npm/docs/output/commands/npm-prefix.html index 616cb1300bcf8a..8da182a2e749a7 100644 --- a/deps/npm/docs/output/commands/npm-prefix.html +++ b/deps/npm/docs/output/commands/npm-prefix.html @@ -186,9 +186,9 @@
            -

            +

            npm-prefix - @11.11.1 + @11.12.1

            Display prefix
            diff --git a/deps/npm/docs/output/commands/npm-profile.html b/deps/npm/docs/output/commands/npm-profile.html index 6bff0b4e6e10dd..d983bcdc4c2959 100644 --- a/deps/npm/docs/output/commands/npm-profile.html +++ b/deps/npm/docs/output/commands/npm-profile.html @@ -186,9 +186,9 @@
            -

            +

            npm-profile - @11.11.1 + @11.12.1

            Change settings on your registry profile
            diff --git a/deps/npm/docs/output/commands/npm-prune.html b/deps/npm/docs/output/commands/npm-prune.html index 86dd429a5ffd74..ca7b6c433add08 100644 --- a/deps/npm/docs/output/commands/npm-prune.html +++ b/deps/npm/docs/output/commands/npm-prune.html @@ -186,9 +186,9 @@
            -

            +

            npm-prune - @11.11.1 + @11.12.1

            Remove extraneous packages
            diff --git a/deps/npm/docs/output/commands/npm-publish.html b/deps/npm/docs/output/commands/npm-publish.html index a5c1cea63f28bb..17dc857fd2badc 100644 --- a/deps/npm/docs/output/commands/npm-publish.html +++ b/deps/npm/docs/output/commands/npm-publish.html @@ -186,9 +186,9 @@
            -

            +

            npm-publish - @11.11.1 + @11.12.1

            Publish a package
            @@ -228,6 +228,7 @@

            Examples

          • f) a <name> that has a "latest" tag satisfying (e)
          • g) a <git remote url> that resolves to (a)
          +

          If either (a) or (b) is specified as a relative path, it should begin with an explicit ./ prefix.

          The publish will fail if the package name and version combination already exists in the specified registry.

          Once a package is published with a given name and version, that specific name and version combination can never be used again, even if it is removed with npm unpublish.

          As of npm@5, both a sha1sum and an integrity field with a sha512sum of the tarball will be submitted to the registry during publication. diff --git a/deps/npm/docs/output/commands/npm-query.html b/deps/npm/docs/output/commands/npm-query.html index 3e1851a521b156..f1a1650a101967 100644 --- a/deps/npm/docs/output/commands/npm-query.html +++ b/deps/npm/docs/output/commands/npm-query.html @@ -186,9 +186,9 @@

          -

          +

          npm-query - @11.11.1 + @11.12.1

          Dependency selector query
          diff --git a/deps/npm/docs/output/commands/npm-rebuild.html b/deps/npm/docs/output/commands/npm-rebuild.html index 713867b0d0b28e..94969d6c331c7f 100644 --- a/deps/npm/docs/output/commands/npm-rebuild.html +++ b/deps/npm/docs/output/commands/npm-rebuild.html @@ -186,9 +186,9 @@
          -

          +

          npm-rebuild - @11.11.1 + @11.12.1

          Rebuild a package
          diff --git a/deps/npm/docs/output/commands/npm-repo.html b/deps/npm/docs/output/commands/npm-repo.html index f4f226ee6ca3a3..2c1243ef0247c1 100644 --- a/deps/npm/docs/output/commands/npm-repo.html +++ b/deps/npm/docs/output/commands/npm-repo.html @@ -186,9 +186,9 @@
          -

          +

          npm-repo - @11.11.1 + @11.12.1

          Open package repository page in the browser
          diff --git a/deps/npm/docs/output/commands/npm-restart.html b/deps/npm/docs/output/commands/npm-restart.html index 29bea2ad94fb0a..776d742edcb859 100644 --- a/deps/npm/docs/output/commands/npm-restart.html +++ b/deps/npm/docs/output/commands/npm-restart.html @@ -186,9 +186,9 @@
          -

          +

          npm-restart - @11.11.1 + @11.12.1

          Restart a package
          diff --git a/deps/npm/docs/output/commands/npm-root.html b/deps/npm/docs/output/commands/npm-root.html index 94db6ba66642a8..12d8e38c7414d7 100644 --- a/deps/npm/docs/output/commands/npm-root.html +++ b/deps/npm/docs/output/commands/npm-root.html @@ -186,9 +186,9 @@
          -

          +

          npm-root - @11.11.1 + @11.12.1

          Display npm root
          diff --git a/deps/npm/docs/output/commands/npm-run.html b/deps/npm/docs/output/commands/npm-run.html index a82a3da49388d5..ea63a7e17bb527 100644 --- a/deps/npm/docs/output/commands/npm-run.html +++ b/deps/npm/docs/output/commands/npm-run.html @@ -186,9 +186,9 @@
          -

          +

          npm-run - @11.11.1 + @11.12.1

          Run arbitrary package scripts
          diff --git a/deps/npm/docs/output/commands/npm-sbom.html b/deps/npm/docs/output/commands/npm-sbom.html index e5d680cc23c342..d656ee241b6a7b 100644 --- a/deps/npm/docs/output/commands/npm-sbom.html +++ b/deps/npm/docs/output/commands/npm-sbom.html @@ -186,9 +186,9 @@
          -

          +

          npm-sbom - @11.11.1 + @11.12.1

          Generate a Software Bill of Materials (SBOM)
          diff --git a/deps/npm/docs/output/commands/npm-search.html b/deps/npm/docs/output/commands/npm-search.html index 8b8b208cfec8e7..94f58f495a6629 100644 --- a/deps/npm/docs/output/commands/npm-search.html +++ b/deps/npm/docs/output/commands/npm-search.html @@ -186,9 +186,9 @@
          -

          +

          npm-search - @11.11.1 + @11.12.1

          Search for packages
          diff --git a/deps/npm/docs/output/commands/npm-set.html b/deps/npm/docs/output/commands/npm-set.html index 07ac21208aa929..363a444be92aa1 100644 --- a/deps/npm/docs/output/commands/npm-set.html +++ b/deps/npm/docs/output/commands/npm-set.html @@ -186,9 +186,9 @@
          -

          +

          npm-set - @11.11.1 + @11.12.1

          Set a value in the npm configuration
          diff --git a/deps/npm/docs/output/commands/npm-shrinkwrap.html b/deps/npm/docs/output/commands/npm-shrinkwrap.html index 28179afe5d77fb..f5cbb0c34edc42 100644 --- a/deps/npm/docs/output/commands/npm-shrinkwrap.html +++ b/deps/npm/docs/output/commands/npm-shrinkwrap.html @@ -186,9 +186,9 @@
          -

          +

          npm-shrinkwrap - @11.11.1 + @11.12.1

          Lock down dependency versions for publication
          diff --git a/deps/npm/docs/output/commands/npm-star.html b/deps/npm/docs/output/commands/npm-star.html index bed11d853274df..3533e05441276f 100644 --- a/deps/npm/docs/output/commands/npm-star.html +++ b/deps/npm/docs/output/commands/npm-star.html @@ -186,9 +186,9 @@
          -

          +

          npm-star - @11.11.1 + @11.12.1

          Mark your favorite packages
          diff --git a/deps/npm/docs/output/commands/npm-stars.html b/deps/npm/docs/output/commands/npm-stars.html index 1e46905bb1a2dd..0b34ca23a66c80 100644 --- a/deps/npm/docs/output/commands/npm-stars.html +++ b/deps/npm/docs/output/commands/npm-stars.html @@ -186,9 +186,9 @@
          -

          +

          npm-stars - @11.11.1 + @11.12.1

          View packages marked as favorites
          diff --git a/deps/npm/docs/output/commands/npm-start.html b/deps/npm/docs/output/commands/npm-start.html index 5a4ea33a60d388..d08c5474a3b97c 100644 --- a/deps/npm/docs/output/commands/npm-start.html +++ b/deps/npm/docs/output/commands/npm-start.html @@ -186,9 +186,9 @@
          -

          +

          npm-start - @11.11.1 + @11.12.1

          Start a package
          diff --git a/deps/npm/docs/output/commands/npm-stop.html b/deps/npm/docs/output/commands/npm-stop.html index 403b4df448f296..64b460da80b30e 100644 --- a/deps/npm/docs/output/commands/npm-stop.html +++ b/deps/npm/docs/output/commands/npm-stop.html @@ -186,9 +186,9 @@
          -

          +

          npm-stop - @11.11.1 + @11.12.1

          Stop a package
          diff --git a/deps/npm/docs/output/commands/npm-team.html b/deps/npm/docs/output/commands/npm-team.html index 2f5198c2082771..4a2891e78f6c68 100644 --- a/deps/npm/docs/output/commands/npm-team.html +++ b/deps/npm/docs/output/commands/npm-team.html @@ -186,9 +186,9 @@
          -

          +

          npm-team - @11.11.1 + @11.12.1

          Manage organization teams and team memberships
          diff --git a/deps/npm/docs/output/commands/npm-test.html b/deps/npm/docs/output/commands/npm-test.html index e6192317e09ef3..9a4d40757e343b 100644 --- a/deps/npm/docs/output/commands/npm-test.html +++ b/deps/npm/docs/output/commands/npm-test.html @@ -186,9 +186,9 @@
          -

          +

          npm-test - @11.11.1 + @11.12.1

          Test a package
          diff --git a/deps/npm/docs/output/commands/npm-token.html b/deps/npm/docs/output/commands/npm-token.html index ed4519a8d8b686..328d7aeb798b6f 100644 --- a/deps/npm/docs/output/commands/npm-token.html +++ b/deps/npm/docs/output/commands/npm-token.html @@ -186,9 +186,9 @@
          -

          +

          npm-token - @11.11.1 + @11.12.1

          Manage your authentication tokens
          diff --git a/deps/npm/docs/output/commands/npm-trust.html b/deps/npm/docs/output/commands/npm-trust.html index 8d9082f694c567..7814567feb0a1e 100644 --- a/deps/npm/docs/output/commands/npm-trust.html +++ b/deps/npm/docs/output/commands/npm-trust.html @@ -186,9 +186,9 @@
          -

          +

          npm-trust - @11.11.1 + @11.12.1

          Manage trusted publishing relationships between packages and CI/CD providers
          @@ -199,7 +199,6 @@

          Table of contents

          Synopsis

          -

          Note: This command is unaware of workspaces.

          Prerequisites

          Before using npm trust commands, ensure the following requirements are met:

          diff --git a/deps/npm/docs/output/commands/npm-undeprecate.html b/deps/npm/docs/output/commands/npm-undeprecate.html index 469f7ded0d83b8..f92ff69ee51fb6 100644 --- a/deps/npm/docs/output/commands/npm-undeprecate.html +++ b/deps/npm/docs/output/commands/npm-undeprecate.html @@ -186,9 +186,9 @@
          -

          +

          npm-undeprecate - @11.11.1 + @11.12.1

          Undeprecate a version of a package
          diff --git a/deps/npm/docs/output/commands/npm-uninstall.html b/deps/npm/docs/output/commands/npm-uninstall.html index a617d154dc525f..35e934b77ec199 100644 --- a/deps/npm/docs/output/commands/npm-uninstall.html +++ b/deps/npm/docs/output/commands/npm-uninstall.html @@ -186,9 +186,9 @@
          -

          +

          npm-uninstall - @11.11.1 + @11.12.1

          Remove a package
          diff --git a/deps/npm/docs/output/commands/npm-unpublish.html b/deps/npm/docs/output/commands/npm-unpublish.html index a728e0f4d56aa3..4091e5c782d264 100644 --- a/deps/npm/docs/output/commands/npm-unpublish.html +++ b/deps/npm/docs/output/commands/npm-unpublish.html @@ -186,9 +186,9 @@
          -

          +

          npm-unpublish - @11.11.1 + @11.12.1

          Remove a package from the registry
          diff --git a/deps/npm/docs/output/commands/npm-unstar.html b/deps/npm/docs/output/commands/npm-unstar.html index ec751ca17381fc..6be57361f2a502 100644 --- a/deps/npm/docs/output/commands/npm-unstar.html +++ b/deps/npm/docs/output/commands/npm-unstar.html @@ -186,9 +186,9 @@
          -

          +

          npm-unstar - @11.11.1 + @11.12.1

          Remove an item from your favorite packages
          diff --git a/deps/npm/docs/output/commands/npm-update.html b/deps/npm/docs/output/commands/npm-update.html index 8c64d8cb02a8fc..0a30a7356746f8 100644 --- a/deps/npm/docs/output/commands/npm-update.html +++ b/deps/npm/docs/output/commands/npm-update.html @@ -186,9 +186,9 @@
          -

          +

          npm-update - @11.11.1 + @11.12.1

          Update packages
          @@ -442,6 +442,7 @@

          min-release-age

          This flag is a complement to before, which accepts an exact date instead of a relative number of days.

          This config cannot be used with: before

          +

          This value is not exported to the environment for child processes.

          • Default: true
          • diff --git a/deps/npm/docs/output/commands/npm-version.html b/deps/npm/docs/output/commands/npm-version.html index fcf5f7f6bf1ab6..1d3e9cc5f74fe0 100644 --- a/deps/npm/docs/output/commands/npm-version.html +++ b/deps/npm/docs/output/commands/npm-version.html @@ -186,9 +186,9 @@
            -

            +

            npm-version - @11.11.1 + @11.12.1

            Bump a package version
            diff --git a/deps/npm/docs/output/commands/npm-view.html b/deps/npm/docs/output/commands/npm-view.html index 682902d4b79089..e93a7028ec7667 100644 --- a/deps/npm/docs/output/commands/npm-view.html +++ b/deps/npm/docs/output/commands/npm-view.html @@ -186,9 +186,9 @@
            -

            +

            npm-view - @11.11.1 + @11.12.1

            View registry info
            diff --git a/deps/npm/docs/output/commands/npm-whoami.html b/deps/npm/docs/output/commands/npm-whoami.html index 4daee589dd841a..ca4e63f8736443 100644 --- a/deps/npm/docs/output/commands/npm-whoami.html +++ b/deps/npm/docs/output/commands/npm-whoami.html @@ -186,9 +186,9 @@
            -

            +

            npm-whoami - @11.11.1 + @11.12.1

            Display npm username
            diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index e64366fc75a975..901b9d11514e3f 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -186,9 +186,9 @@
            -

            +

            npm - @11.11.1 + @11.12.1

            javascript package manager
            @@ -203,7 +203,7 @@

            Table of contents

          Note: This command is unaware of workspaces.

          Version

          -

          11.11.1

          +

          11.12.1

          Description

          npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency conflicts intelligently.

          diff --git a/deps/npm/docs/output/commands/npx.html b/deps/npm/docs/output/commands/npx.html index 08bc84e9953fad..00f5702e081fcc 100644 --- a/deps/npm/docs/output/commands/npx.html +++ b/deps/npm/docs/output/commands/npx.html @@ -186,9 +186,9 @@
          -

          +

          npx - @11.11.1 + @11.12.1

          Run a command from a local or remote npm package
          diff --git a/deps/npm/docs/output/configuring-npm/folders.html b/deps/npm/docs/output/configuring-npm/folders.html index c278f339ceaf04..974d42b2c656a9 100644 --- a/deps/npm/docs/output/configuring-npm/folders.html +++ b/deps/npm/docs/output/configuring-npm/folders.html @@ -186,9 +186,9 @@
          -

          +

          Folders - @11.11.1 + @11.12.1

          Folder structures used by npm
          diff --git a/deps/npm/docs/output/configuring-npm/install.html b/deps/npm/docs/output/configuring-npm/install.html index 0312f76a620994..0fda7ec593cefa 100644 --- a/deps/npm/docs/output/configuring-npm/install.html +++ b/deps/npm/docs/output/configuring-npm/install.html @@ -186,9 +186,9 @@
          -

          +

          Install - @11.11.1 + @11.12.1

          Download and install node and npm
          diff --git a/deps/npm/docs/output/configuring-npm/npm-global.html b/deps/npm/docs/output/configuring-npm/npm-global.html index c278f339ceaf04..974d42b2c656a9 100644 --- a/deps/npm/docs/output/configuring-npm/npm-global.html +++ b/deps/npm/docs/output/configuring-npm/npm-global.html @@ -186,9 +186,9 @@
          -

          +

          Folders - @11.11.1 + @11.12.1

          Folder structures used by npm
          diff --git a/deps/npm/docs/output/configuring-npm/npm-json.html b/deps/npm/docs/output/configuring-npm/npm-json.html index 64be225d20808e..07422781b58f05 100644 --- a/deps/npm/docs/output/configuring-npm/npm-json.html +++ b/deps/npm/docs/output/configuring-npm/npm-json.html @@ -186,9 +186,9 @@
          -

          +

          package.json - @11.11.1 + @11.12.1

          Specifics of npm's package.json handling
          diff --git a/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html b/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html index 97a6b0c9fb8d6f..0eea07f189f72c 100644 --- a/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html +++ b/deps/npm/docs/output/configuring-npm/npm-shrinkwrap-json.html @@ -186,9 +186,9 @@
          -

          +

          npm-shrinkwrap.json - @11.11.1 + @11.12.1

          A publishable lockfile
          diff --git a/deps/npm/docs/output/configuring-npm/npmrc.html b/deps/npm/docs/output/configuring-npm/npmrc.html index e1a3b32e5b8f30..1f91d117c07f84 100644 --- a/deps/npm/docs/output/configuring-npm/npmrc.html +++ b/deps/npm/docs/output/configuring-npm/npmrc.html @@ -186,9 +186,9 @@
          -

          +

          .npmrc - @11.11.1 + @11.12.1

          The npm config files
          diff --git a/deps/npm/docs/output/configuring-npm/package-json.html b/deps/npm/docs/output/configuring-npm/package-json.html index 64be225d20808e..07422781b58f05 100644 --- a/deps/npm/docs/output/configuring-npm/package-json.html +++ b/deps/npm/docs/output/configuring-npm/package-json.html @@ -186,9 +186,9 @@
          -

          +

          package.json - @11.11.1 + @11.12.1

          Specifics of npm's package.json handling
          diff --git a/deps/npm/docs/output/configuring-npm/package-lock-json.html b/deps/npm/docs/output/configuring-npm/package-lock-json.html index f11857e2c95c87..9eedd77093b0c3 100644 --- a/deps/npm/docs/output/configuring-npm/package-lock-json.html +++ b/deps/npm/docs/output/configuring-npm/package-lock-json.html @@ -186,9 +186,9 @@
          -

          +

          package-lock.json - @11.11.1 + @11.12.1

          A manifestation of the manifest
          diff --git a/deps/npm/docs/output/using-npm/config.html b/deps/npm/docs/output/using-npm/config.html index 12d8c8ae0e4e38..0d0ef99c6b6335 100644 --- a/deps/npm/docs/output/using-npm/config.html +++ b/deps/npm/docs/output/using-npm/config.html @@ -186,16 +186,16 @@
          -

          +

          Config - @11.11.1 + @11.12.1

          About npm configuration

          Table of contents

          -
          +

          Description

          @@ -756,6 +756,15 @@

          include

          This is the inverse of --omit=<type>.

          Dependency types specified in --include will not be omitted, regardless of the order in which omit/include are specified on the command-line.

          +

          include-attestations

          +
            +
          • Default: false
          • +
          • Type: Boolean
          • +
          +

          When used with npm audit signatures --json, includes the full sigstore +attestation bundles in the JSON output for each verified package. The +bundles contain DSSE envelopes, verification material, and transparency log +entries.

          include-staged

          • Default: false
          • @@ -983,6 +992,7 @@

            min-release-age

            This flag is a complement to before, which accepts an exact date instead of a relative number of days.

            This config cannot be used with: before

            +

            This value is not exported to the environment for child processes.

            name

            • Default: null
            • diff --git a/deps/npm/docs/output/using-npm/dependency-selectors.html b/deps/npm/docs/output/using-npm/dependency-selectors.html index 6473c4ac5e107f..98647db6cbde4d 100644 --- a/deps/npm/docs/output/using-npm/dependency-selectors.html +++ b/deps/npm/docs/output/using-npm/dependency-selectors.html @@ -186,9 +186,9 @@
              -

              +

              Dependency Selectors - @11.11.1 + @11.12.1

              Dependency Selector Syntax & Querying
              diff --git a/deps/npm/docs/output/using-npm/developers.html b/deps/npm/docs/output/using-npm/developers.html index 299b23a026de01..017ed86fef451d 100644 --- a/deps/npm/docs/output/using-npm/developers.html +++ b/deps/npm/docs/output/using-npm/developers.html @@ -186,9 +186,9 @@
              -

              +

              Developers - @11.11.1 + @11.12.1

              Developer guide
              diff --git a/deps/npm/docs/output/using-npm/logging.html b/deps/npm/docs/output/using-npm/logging.html index 5ad039009097ca..ba8b0b1132fd83 100644 --- a/deps/npm/docs/output/using-npm/logging.html +++ b/deps/npm/docs/output/using-npm/logging.html @@ -186,9 +186,9 @@
              -

              +

              Logging - @11.11.1 + @11.12.1

              Why, What & How we Log
              diff --git a/deps/npm/docs/output/using-npm/orgs.html b/deps/npm/docs/output/using-npm/orgs.html index ab24b5d19e3006..b761c39e1d4648 100644 --- a/deps/npm/docs/output/using-npm/orgs.html +++ b/deps/npm/docs/output/using-npm/orgs.html @@ -186,9 +186,9 @@
              -

              +

              Organizations - @11.11.1 + @11.12.1

              Working with teams & organizations
              diff --git a/deps/npm/docs/output/using-npm/package-spec.html b/deps/npm/docs/output/using-npm/package-spec.html index afd8988b733220..3459ab9906b253 100644 --- a/deps/npm/docs/output/using-npm/package-spec.html +++ b/deps/npm/docs/output/using-npm/package-spec.html @@ -186,9 +186,9 @@
              -

              +

              Package spec - @11.11.1 + @11.12.1

              Package name specifier
              diff --git a/deps/npm/docs/output/using-npm/registry.html b/deps/npm/docs/output/using-npm/registry.html index 67c15a687628a8..22a6220ca11145 100644 --- a/deps/npm/docs/output/using-npm/registry.html +++ b/deps/npm/docs/output/using-npm/registry.html @@ -186,9 +186,9 @@
              -

              +

              Registry - @11.11.1 + @11.12.1

              The JavaScript Package Registry
              diff --git a/deps/npm/docs/output/using-npm/removal.html b/deps/npm/docs/output/using-npm/removal.html index eb1dc4329d1490..0eab4f7bf7b098 100644 --- a/deps/npm/docs/output/using-npm/removal.html +++ b/deps/npm/docs/output/using-npm/removal.html @@ -186,9 +186,9 @@
              -

              +

              Removal - @11.11.1 + @11.12.1

              Cleaning the slate
              diff --git a/deps/npm/docs/output/using-npm/scope.html b/deps/npm/docs/output/using-npm/scope.html index 1ed3f678e8a598..7083e3a3e03e62 100644 --- a/deps/npm/docs/output/using-npm/scope.html +++ b/deps/npm/docs/output/using-npm/scope.html @@ -186,9 +186,9 @@
              -

              +

              Scope - @11.11.1 + @11.12.1

              Scoped packages
              diff --git a/deps/npm/docs/output/using-npm/scripts.html b/deps/npm/docs/output/using-npm/scripts.html index 3513f553e08b2c..710045adf98fa0 100644 --- a/deps/npm/docs/output/using-npm/scripts.html +++ b/deps/npm/docs/output/using-npm/scripts.html @@ -186,9 +186,9 @@
              -

              +

              Scripts - @11.11.1 + @11.12.1

              How npm handles the "scripts" field
              diff --git a/deps/npm/docs/output/using-npm/workspaces.html b/deps/npm/docs/output/using-npm/workspaces.html index 9c614074f63d85..be0ce097c25254 100644 --- a/deps/npm/docs/output/using-npm/workspaces.html +++ b/deps/npm/docs/output/using-npm/workspaces.html @@ -186,9 +186,9 @@
              -

              +

              Workspaces - @11.11.1 + @11.12.1

              Working with workspaces
              diff --git a/deps/npm/lib/commands/audit.js b/deps/npm/lib/commands/audit.js index c9ac3bac7d055e..39e3a599fc37e1 100644 --- a/deps/npm/lib/commands/audit.js +++ b/deps/npm/lib/commands/audit.js @@ -19,6 +19,7 @@ class Audit extends ArboristWorkspaceCmd { 'include', 'foreground-scripts', 'ignore-scripts', + 'include-attestations', ...super.params, ] diff --git a/deps/npm/lib/utils/explain-dep.js b/deps/npm/lib/utils/explain-dep.js index 4e9e93454e8a28..6c84aa4ebbc396 100644 --- a/deps/npm/lib/utils/explain-dep.js +++ b/deps/npm/lib/utils/explain-dep.js @@ -1,9 +1,9 @@ const { relative } = require('node:path') -const explainNode = (node, depth, chalk) => +const explainNode = (node, depth, chalk, seen = new Set()) => printNode(node, chalk) + - explainDependents(node, depth, chalk) + - explainLinksIn(node, depth, chalk) + explainDependents(node, depth, chalk, seen) + + explainLinksIn(node, depth, chalk, seen) const colorType = (type, chalk) => { const style = type === 'extraneous' ? chalk.red @@ -34,24 +34,24 @@ const printNode = (node, chalk) => { (node.location ? chalk.dim(`\n${node.location}`) : '') } -const explainLinksIn = ({ linksIn }, depth, chalk) => { +const explainLinksIn = ({ linksIn }, depth, chalk, seen) => { if (!linksIn || !linksIn.length || depth <= 0) { return '' } - const messages = linksIn.map(link => explainNode(link, depth - 1, chalk)) + const messages = linksIn.map(link => explainNode(link, depth - 1, chalk, seen)) const str = '\n' + messages.join('\n') return str.split('\n').join('\n ') } -const explainDependents = ({ dependents }, depth, chalk) => { +const explainDependents = ({ dependents }, depth, chalk, seen) => { if (!dependents || !dependents.length || depth <= 0) { return '' } const max = Math.ceil(depth / 2) const messages = dependents.slice(0, max) - .map(edge => explainEdge(edge, depth, chalk)) + .map(edge => explainEdge(edge, depth, chalk, seen)) // show just the names of the first 5 deps that overflowed the list if (dependents.length > max) { @@ -75,7 +75,10 @@ const explainDependents = ({ dependents }, depth, chalk) => { return str.split('\n').join('\n ') } -const explainEdge = ({ name, type, bundled, from, spec, rawSpec, overridden }, depth, chalk) => { +const explainEdge = ( + { name, type, bundled, from, spec, rawSpec, overridden }, + depth, chalk, seen = new Set() +) => { let dep = type === 'workspace' ? chalk.bold(relative(from.location, spec.slice('file:'.length))) : `${name}@"${spec}"` @@ -83,21 +86,31 @@ const explainEdge = ({ name, type, bundled, from, spec, rawSpec, overridden }, d dep = `${colorType('overridden', chalk)} ${dep} (was "${rawSpec}")` } - const fromMsg = ` from ${explainFrom(from, depth, chalk)}` + const fromMsg = ` from ${explainFrom(from, depth, chalk, seen)}` return (type === 'prod' ? '' : `${colorType(type, chalk)} `) + (bundled ? `${colorType('bundled', chalk)} ` : '') + `${dep}${fromMsg}` } -const explainFrom = (from, depth, chalk) => { +const explainFrom = (from, depth, chalk, seen) => { if (!from.name && !from.version) { return 'the root project' } - return printNode(from, chalk) + - explainDependents(from, depth - 1, chalk) + - explainLinksIn(from, depth - 1, chalk) + // Prevent infinite recursion from cycles in the dependency graph (e.g. linked strategy store nodes). Use stack-based tracking so diamond dependencies (same node reached via different paths) are still explained, but recursive cycles are broken. + const nodeId = `${from.name}@${from.version}:${from.location}` + if (seen.has(nodeId)) { + return printNode(from, chalk) + } + seen.add(nodeId) + + const result = printNode(from, chalk) + + explainDependents(from, depth - 1, chalk, seen) + + explainLinksIn(from, depth - 1, chalk, seen) + + seen.delete(nodeId) + return result } module.exports = { explainNode, printNode, explainEdge } diff --git a/deps/npm/lib/utils/verify-signatures.js b/deps/npm/lib/utils/verify-signatures.js index 9aef49e8f4785a..a4824d86d13ab6 100644 --- a/deps/npm/lib/utils/verify-signatures.js +++ b/deps/npm/lib/utils/verify-signatures.js @@ -17,6 +17,7 @@ class VerifySignatures { this.invalid = [] this.missing = [] this.checkedPackages = new Set() + this.verified = [] this.auditedWithKeysCount = 0 this.verifiedSignatureCount = 0 this.verifiedAttestationCount = 0 @@ -59,7 +60,11 @@ class VerifySignatures { } if (this.npm.config.get('json')) { - output.buffer({ invalid, missing }) + const result = { invalid, missing } + if (this.npm.config.get('include-attestations')) { + result.verified = this.verified + } + output.buffer(result) return } const end = process.hrtime.bigint() @@ -87,6 +92,9 @@ class VerifySignatures { } else { output.standard(`${this.verifiedAttestationCount} packages have ${verifiedBold} attestations`) } + if (!this.npm.config.get('include-attestations')) { + output.standard('(use --json --include-attestations to view attestation details)') + } output.standard() } @@ -288,6 +296,7 @@ class VerifySignatures { _integrity: integrity, _signatures, _attestations, + _attestationBundles, _resolved: resolved, } = await pacote.manifest(`${name}@${version}`, { verifySignatures: true, @@ -300,6 +309,7 @@ class VerifySignatures { integrity, signatures, attestations: _attestations, + attestationBundles: _attestationBundles, resolved, } return result @@ -324,9 +334,8 @@ class VerifySignatures { } try { - const { integrity, signatures, attestations, resolved } = await this.verifySignatures( - name, version, registry - ) + const { integrity, signatures, attestations, attestationBundles, resolved } = + await this.verifySignatures(name, version, registry) // Currently we only care about missing signatures on registries that provide a public key // We could make this configurable in the future with a strict/paranoid mode @@ -346,6 +355,16 @@ class VerifySignatures { // Track verified attestations separately to registry signatures, as all packages on registries with signing keys are expected to have registry signatures, but not all packages have provenance and publish attestations. if (attestations) { this.verifiedAttestationCount += 1 + if (this.npm.config.get('include-attestations')) { + this.verified.push({ + name, + version, + location, + registry, + attestations, + attestationBundles, + }) + } } } catch (e) { if (e.code === 'EINTEGRITYSIGNATURE' || e.code === 'EATTESTATIONVERIFY') { diff --git a/deps/npm/man/man1/npm-access.1 b/deps/npm/man/man1/npm-access.1 index efe5c38060c282..fe5817bd028550 100644 --- a/deps/npm/man/man1/npm-access.1 +++ b/deps/npm/man/man1/npm-access.1 @@ -1,4 +1,4 @@ -.TH "NPM-ACCESS" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-ACCESS" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-access\fR - Set access level on published packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-adduser.1 b/deps/npm/man/man1/npm-adduser.1 index 2fc10725efa4f6..ef295a68a0448e 100644 --- a/deps/npm/man/man1/npm-adduser.1 +++ b/deps/npm/man/man1/npm-adduser.1 @@ -1,4 +1,4 @@ -.TH "NPM-ADDUSER" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-ADDUSER" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-adduser\fR - Add a registry user account .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-audit.1 b/deps/npm/man/man1/npm-audit.1 index 1af936f51590d7..e2547bf58265bc 100644 --- a/deps/npm/man/man1/npm-audit.1 +++ b/deps/npm/man/man1/npm-audit.1 @@ -1,4 +1,4 @@ -.TH "NPM-AUDIT" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-AUDIT" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-audit\fR - Run a security audit .SS "Synopsis" @@ -34,6 +34,16 @@ $ npm audit signatures .P The \fBaudit signatures\fR command will also verify the provenance attestations of downloaded packages. Because provenance attestations are such a new feature, security features may be added to (or changed in) the attestation format over time. To ensure that you're always able to verify attestation signatures check that you're running the latest version of the npm CLI. Please note this often means updating npm beyond the version that ships with Node.js. .P +To include the full sigstore attestation bundles in JSON output, use: +.P +.RS 2 +.nf +$ npm audit signatures --json --include-attestations +.fi +.RE +.P +This adds a \fBverified\fR array to the JSON output containing the attestation bundles (DSSE envelopes, verification material, and transparency log entries) for each verified package. +.P The npm CLI supports registry signatures and signing keys provided by any registry if the following conventions are followed: .RS 0 .IP 1. 4 @@ -369,6 +379,16 @@ Type: Boolean If true, npm does not run scripts specified in package.json files. .P Note that commands explicitly intended to run a particular script, such as \fBnpm start\fR, \fBnpm stop\fR, \fBnpm restart\fR, \fBnpm test\fR, and \fBnpm run\fR will still run their intended script if \fBignore-scripts\fR is set, but they will \fInot\fR run any pre- or post-scripts. +.SS "\fBinclude-attestations\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +When used with \fBnpm audit signatures --json\fR, includes the full sigstore attestation bundles in the JSON output for each verified package. The bundles contain DSSE envelopes, verification material, and transparency log entries. .SS "\fBworkspace\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-bugs.1 b/deps/npm/man/man1/npm-bugs.1 index b846617fb7f68a..fa16e14c8d1419 100644 --- a/deps/npm/man/man1/npm-bugs.1 +++ b/deps/npm/man/man1/npm-bugs.1 @@ -1,4 +1,4 @@ -.TH "NPM-BUGS" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-BUGS" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-bugs\fR - Report bugs for a package in a web browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-cache.1 b/deps/npm/man/man1/npm-cache.1 index b89d619fa6ecee..4bbc439ed78a0a 100644 --- a/deps/npm/man/man1/npm-cache.1 +++ b/deps/npm/man/man1/npm-cache.1 @@ -1,4 +1,4 @@ -.TH "NPM-CACHE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-CACHE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-cache\fR - Manipulates packages cache .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ci.1 b/deps/npm/man/man1/npm-ci.1 index 7aa9667243491a..97528d31e77c07 100644 --- a/deps/npm/man/man1/npm-ci.1 +++ b/deps/npm/man/man1/npm-ci.1 @@ -1,4 +1,4 @@ -.TH "NPM-CI" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-CI" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-ci\fR - Clean install a project .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-completion.1 b/deps/npm/man/man1/npm-completion.1 index 2ec95a1400e6ab..7996ae51424e02 100644 --- a/deps/npm/man/man1/npm-completion.1 +++ b/deps/npm/man/man1/npm-completion.1 @@ -1,4 +1,4 @@ -.TH "NPM-COMPLETION" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-COMPLETION" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-completion\fR - Tab Completion for npm .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-config.1 b/deps/npm/man/man1/npm-config.1 index d0ffeff119855c..29621e37b88807 100644 --- a/deps/npm/man/man1/npm-config.1 +++ b/deps/npm/man/man1/npm-config.1 @@ -1,4 +1,4 @@ -.TH "NPM-CONFIG" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-CONFIG" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-config\fR - Manage the npm configuration files .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-dedupe.1 b/deps/npm/man/man1/npm-dedupe.1 index 1ed0a6c7e4a024..fbf53bfbedbc28 100644 --- a/deps/npm/man/man1/npm-dedupe.1 +++ b/deps/npm/man/man1/npm-dedupe.1 @@ -1,4 +1,4 @@ -.TH "NPM-DEDUPE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-DEDUPE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-dedupe\fR - Reduce duplication in the package tree .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-deprecate.1 b/deps/npm/man/man1/npm-deprecate.1 index f9270e2591003a..092e0270c8040e 100644 --- a/deps/npm/man/man1/npm-deprecate.1 +++ b/deps/npm/man/man1/npm-deprecate.1 @@ -1,4 +1,4 @@ -.TH "NPM-DEPRECATE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-DEPRECATE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-deprecate\fR - Deprecate a version of a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-diff.1 b/deps/npm/man/man1/npm-diff.1 index 36b28e8413d486..1dd968ae5d644c 100644 --- a/deps/npm/man/man1/npm-diff.1 +++ b/deps/npm/man/man1/npm-diff.1 @@ -1,4 +1,4 @@ -.TH "NPM-DIFF" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-DIFF" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-diff\fR - The registry diff command .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-dist-tag.1 b/deps/npm/man/man1/npm-dist-tag.1 index 46796c3a706dcd..6670e19b8a36f9 100644 --- a/deps/npm/man/man1/npm-dist-tag.1 +++ b/deps/npm/man/man1/npm-dist-tag.1 @@ -1,4 +1,4 @@ -.TH "NPM-DIST-TAG" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-DIST-TAG" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-dist-tag\fR - Modify package distribution tags .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-docs.1 b/deps/npm/man/man1/npm-docs.1 index 54bfe990744555..d8c56b505a97e1 100644 --- a/deps/npm/man/man1/npm-docs.1 +++ b/deps/npm/man/man1/npm-docs.1 @@ -1,4 +1,4 @@ -.TH "NPM-DOCS" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-DOCS" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-docs\fR - Open documentation for a package in a web browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-doctor.1 b/deps/npm/man/man1/npm-doctor.1 index d997f55d4d8f18..1467c59172d481 100644 --- a/deps/npm/man/man1/npm-doctor.1 +++ b/deps/npm/man/man1/npm-doctor.1 @@ -1,4 +1,4 @@ -.TH "NPM-DOCTOR" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-DOCTOR" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-doctor\fR - Check the health of your npm environment .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-edit.1 b/deps/npm/man/man1/npm-edit.1 index d2be7039d6e347..f13a69a4a7fb60 100644 --- a/deps/npm/man/man1/npm-edit.1 +++ b/deps/npm/man/man1/npm-edit.1 @@ -1,4 +1,4 @@ -.TH "NPM-EDIT" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-EDIT" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-edit\fR - Edit an installed package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-exec.1 b/deps/npm/man/man1/npm-exec.1 index af6f5a4915e536..267d42cd4c7979 100644 --- a/deps/npm/man/man1/npm-exec.1 +++ b/deps/npm/man/man1/npm-exec.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXEC" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-EXEC" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-exec\fR - Run a command from a local or remote npm package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-explain.1 b/deps/npm/man/man1/npm-explain.1 index 52b160d5d5e7df..ee9fae25c4f827 100644 --- a/deps/npm/man/man1/npm-explain.1 +++ b/deps/npm/man/man1/npm-explain.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXPLAIN" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-EXPLAIN" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-explain\fR - Explain installed packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-explore.1 b/deps/npm/man/man1/npm-explore.1 index 0e2656d7934374..39c3b059382285 100644 --- a/deps/npm/man/man1/npm-explore.1 +++ b/deps/npm/man/man1/npm-explore.1 @@ -1,4 +1,4 @@ -.TH "NPM-EXPLORE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-EXPLORE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-explore\fR - Browse an installed package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-find-dupes.1 b/deps/npm/man/man1/npm-find-dupes.1 index 408254ef442f9c..c607a2473d4869 100644 --- a/deps/npm/man/man1/npm-find-dupes.1 +++ b/deps/npm/man/man1/npm-find-dupes.1 @@ -1,4 +1,4 @@ -.TH "NPM-FIND-DUPES" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-FIND-DUPES" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-find-dupes\fR - Find duplication in the package tree .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-fund.1 b/deps/npm/man/man1/npm-fund.1 index 1af1d44feded9b..75120f042dfbb4 100644 --- a/deps/npm/man/man1/npm-fund.1 +++ b/deps/npm/man/man1/npm-fund.1 @@ -1,4 +1,4 @@ -.TH "NPM-FUND" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-FUND" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-fund\fR - Retrieve funding information .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-get.1 b/deps/npm/man/man1/npm-get.1 index c49c771c00177d..ed7e2133a83270 100644 --- a/deps/npm/man/man1/npm-get.1 +++ b/deps/npm/man/man1/npm-get.1 @@ -1,4 +1,4 @@ -.TH "NPM-GET" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-GET" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-get\fR - Get a value from the npm configuration .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-help-search.1 b/deps/npm/man/man1/npm-help-search.1 index fee7226ba3eb8f..171b3563193162 100644 --- a/deps/npm/man/man1/npm-help-search.1 +++ b/deps/npm/man/man1/npm-help-search.1 @@ -1,4 +1,4 @@ -.TH "NPM-HELP-SEARCH" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-HELP-SEARCH" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-help-search\fR - Search npm help documentation .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-help.1 b/deps/npm/man/man1/npm-help.1 index 366842b5da4b74..2dcdb3758e1189 100644 --- a/deps/npm/man/man1/npm-help.1 +++ b/deps/npm/man/man1/npm-help.1 @@ -1,4 +1,4 @@ -.TH "NPM-HELP" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-HELP" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-help\fR - Get help on npm .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-init.1 b/deps/npm/man/man1/npm-init.1 index 3efe21f259d863..101d656bfb5ea4 100644 --- a/deps/npm/man/man1/npm-init.1 +++ b/deps/npm/man/man1/npm-init.1 @@ -1,4 +1,4 @@ -.TH "NPM-INIT" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-INIT" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-init\fR - Create a package.json file .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-ci-test.1 b/deps/npm/man/man1/npm-install-ci-test.1 index 61100bcf94f4b8..c002a1303b98f3 100644 --- a/deps/npm/man/man1/npm-install-ci-test.1 +++ b/deps/npm/man/man1/npm-install-ci-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-CI-TEST" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-INSTALL-CI-TEST" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-install-ci-test\fR - Install a project with a clean slate and run tests .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-install-test.1 b/deps/npm/man/man1/npm-install-test.1 index 431a75d9ed797a..622c89df74af5d 100644 --- a/deps/npm/man/man1/npm-install-test.1 +++ b/deps/npm/man/man1/npm-install-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL-TEST" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-INSTALL-TEST" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-install-test\fR - Install package(s) and run tests .SS "Synopsis" @@ -243,6 +243,8 @@ If set, npm will build the npm tree such that only versions that were available This flag is a complement to \fBbefore\fR, which accepts an exact date instead of a relative number of days. .P This config cannot be used with: \fBbefore\fR +.P +This value is not exported to the environment for child processes. .SS "\fBbin-links\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-install.1 b/deps/npm/man/man1/npm-install.1 index 668a07c3c4f005..9221a74d6ec11c 100644 --- a/deps/npm/man/man1/npm-install.1 +++ b/deps/npm/man/man1/npm-install.1 @@ -1,4 +1,4 @@ -.TH "NPM-INSTALL" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-INSTALL" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-install\fR - Install a package .SS "Synopsis" @@ -633,6 +633,8 @@ If set, npm will build the npm tree such that only versions that were available This flag is a complement to \fBbefore\fR, which accepts an exact date instead of a relative number of days. .P This config cannot be used with: \fBbefore\fR +.P +This value is not exported to the environment for child processes. .SS "\fBbin-links\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-link.1 b/deps/npm/man/man1/npm-link.1 index ee3e84c814040e..855137368a7ec5 100644 --- a/deps/npm/man/man1/npm-link.1 +++ b/deps/npm/man/man1/npm-link.1 @@ -1,4 +1,4 @@ -.TH "NPM-LINK" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-LINK" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-link\fR - Symlink a package folder .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ll.1 b/deps/npm/man/man1/npm-ll.1 index 48109d800c5fe9..c0752d0dca049c 100644 --- a/deps/npm/man/man1/npm-ll.1 +++ b/deps/npm/man/man1/npm-ll.1 @@ -1,4 +1,4 @@ -.TH "NPM-LL" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-LL" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-ll\fR - List installed packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-login.1 b/deps/npm/man/man1/npm-login.1 index cf4f8826ec243a..59809a10c7217a 100644 --- a/deps/npm/man/man1/npm-login.1 +++ b/deps/npm/man/man1/npm-login.1 @@ -1,4 +1,4 @@ -.TH "NPM-LOGIN" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-LOGIN" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-login\fR - Login to a registry user account .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-logout.1 b/deps/npm/man/man1/npm-logout.1 index 7aeb68cc8f49da..530d8216fb6a24 100644 --- a/deps/npm/man/man1/npm-logout.1 +++ b/deps/npm/man/man1/npm-logout.1 @@ -1,4 +1,4 @@ -.TH "NPM-LOGOUT" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-LOGOUT" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-logout\fR - Log out of the registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1 index e7f020cfa3f80f..51afaf81e0f6c0 100644 --- a/deps/npm/man/man1/npm-ls.1 +++ b/deps/npm/man/man1/npm-ls.1 @@ -1,4 +1,4 @@ -.TH "NPM-LS" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-LS" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-ls\fR - List installed packages .SS "Synopsis" @@ -20,7 +20,7 @@ Positional arguments are \fBname@version-range\fR identifiers, which will limit .P .RS 2 .nf -npm@11.11.1 /path/to/npm +npm@11.12.1 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 .fi diff --git a/deps/npm/man/man1/npm-org.1 b/deps/npm/man/man1/npm-org.1 index 55dc1b791f079e..5aa0c33d6dad71 100644 --- a/deps/npm/man/man1/npm-org.1 +++ b/deps/npm/man/man1/npm-org.1 @@ -1,4 +1,4 @@ -.TH "NPM-ORG" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-ORG" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-org\fR - Manage orgs .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-outdated.1 b/deps/npm/man/man1/npm-outdated.1 index 07cf9e96940d78..adaf686880c67f 100644 --- a/deps/npm/man/man1/npm-outdated.1 +++ b/deps/npm/man/man1/npm-outdated.1 @@ -1,4 +1,4 @@ -.TH "NPM-OUTDATED" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-OUTDATED" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-outdated\fR - Check for outdated packages .SS "Synopsis" @@ -195,6 +195,8 @@ If set, npm will build the npm tree such that only versions that were available This flag is a complement to \fBbefore\fR, which accepts an exact date instead of a relative number of days. .P This config cannot be used with: \fBbefore\fR +.P +This value is not exported to the environment for child processes. .SS "See Also" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-owner.1 b/deps/npm/man/man1/npm-owner.1 index 017b53e96cdaa1..29e15fcd5cbc10 100644 --- a/deps/npm/man/man1/npm-owner.1 +++ b/deps/npm/man/man1/npm-owner.1 @@ -1,4 +1,4 @@ -.TH "NPM-OWNER" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-OWNER" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-owner\fR - Manage package owners .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-pack.1 b/deps/npm/man/man1/npm-pack.1 index 01b5b3095ca07c..7e9e44926960cc 100644 --- a/deps/npm/man/man1/npm-pack.1 +++ b/deps/npm/man/man1/npm-pack.1 @@ -1,4 +1,4 @@ -.TH "NPM-PACK" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-PACK" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-pack\fR - Create a tarball from a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-ping.1 b/deps/npm/man/man1/npm-ping.1 index 05e595ba54868e..616d928771c2e6 100644 --- a/deps/npm/man/man1/npm-ping.1 +++ b/deps/npm/man/man1/npm-ping.1 @@ -1,4 +1,4 @@ -.TH "NPM-PING" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-PING" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-ping\fR - Ping npm registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-pkg.1 b/deps/npm/man/man1/npm-pkg.1 index c992521223a5d0..0a7a4a2eda63ce 100644 --- a/deps/npm/man/man1/npm-pkg.1 +++ b/deps/npm/man/man1/npm-pkg.1 @@ -1,4 +1,4 @@ -.TH "NPM-PKG" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-PKG" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-pkg\fR - Manages your package.json .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-prefix.1 b/deps/npm/man/man1/npm-prefix.1 index d08388e3c67749..58fdc6e0637cf9 100644 --- a/deps/npm/man/man1/npm-prefix.1 +++ b/deps/npm/man/man1/npm-prefix.1 @@ -1,4 +1,4 @@ -.TH "NPM-PREFIX" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-PREFIX" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-prefix\fR - Display prefix .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-profile.1 b/deps/npm/man/man1/npm-profile.1 index 6f344d45540209..f4d456a011d2ed 100644 --- a/deps/npm/man/man1/npm-profile.1 +++ b/deps/npm/man/man1/npm-profile.1 @@ -1,4 +1,4 @@ -.TH "NPM-PROFILE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-PROFILE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-profile\fR - Change settings on your registry profile .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-prune.1 b/deps/npm/man/man1/npm-prune.1 index 0b892012f1126c..5cc3651dda1ead 100644 --- a/deps/npm/man/man1/npm-prune.1 +++ b/deps/npm/man/man1/npm-prune.1 @@ -1,4 +1,4 @@ -.TH "NPM-PRUNE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-PRUNE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-prune\fR - Remove extraneous packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-publish.1 b/deps/npm/man/man1/npm-publish.1 index 4161e191cd8723..305d75cb590968 100644 --- a/deps/npm/man/man1/npm-publish.1 +++ b/deps/npm/man/man1/npm-publish.1 @@ -1,4 +1,4 @@ -.TH "NPM-PUBLISH" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-PUBLISH" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-publish\fR - Publish a package .SS "Synopsis" @@ -65,6 +65,8 @@ f) a \fB\fR that has a "latest" tag satisfying (e) g) a \fB\fR that resolves to (a) .RE 0 +.P +If either (a) or (b) is specified as a relative path, it should begin with an explicit \fB./\fR prefix. .P The publish will fail if the package name and version combination already exists in the specified registry. .P diff --git a/deps/npm/man/man1/npm-query.1 b/deps/npm/man/man1/npm-query.1 index 95bba2f25cafe8..982c771d4c6624 100644 --- a/deps/npm/man/man1/npm-query.1 +++ b/deps/npm/man/man1/npm-query.1 @@ -1,4 +1,4 @@ -.TH "NPM-QUERY" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-QUERY" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-query\fR - Dependency selector query .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-rebuild.1 b/deps/npm/man/man1/npm-rebuild.1 index 5f4c5cd20e9d19..1542cb847a9fd5 100644 --- a/deps/npm/man/man1/npm-rebuild.1 +++ b/deps/npm/man/man1/npm-rebuild.1 @@ -1,4 +1,4 @@ -.TH "NPM-REBUILD" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-REBUILD" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-rebuild\fR - Rebuild a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-repo.1 b/deps/npm/man/man1/npm-repo.1 index 92dc03f4a2e1ba..d36c0ae61635b4 100644 --- a/deps/npm/man/man1/npm-repo.1 +++ b/deps/npm/man/man1/npm-repo.1 @@ -1,4 +1,4 @@ -.TH "NPM-REPO" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-REPO" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-repo\fR - Open package repository page in the browser .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-restart.1 b/deps/npm/man/man1/npm-restart.1 index 37a0f9b814013d..7c4eb5f61d8ef8 100644 --- a/deps/npm/man/man1/npm-restart.1 +++ b/deps/npm/man/man1/npm-restart.1 @@ -1,4 +1,4 @@ -.TH "NPM-RESTART" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-RESTART" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-restart\fR - Restart a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-root.1 b/deps/npm/man/man1/npm-root.1 index bb0d8d822367b8..dec2ae082ab0ea 100644 --- a/deps/npm/man/man1/npm-root.1 +++ b/deps/npm/man/man1/npm-root.1 @@ -1,4 +1,4 @@ -.TH "NPM-ROOT" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-ROOT" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-root\fR - Display npm root .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-run.1 b/deps/npm/man/man1/npm-run.1 index 956aa52fa4282e..226c791c846b26 100644 --- a/deps/npm/man/man1/npm-run.1 +++ b/deps/npm/man/man1/npm-run.1 @@ -1,4 +1,4 @@ -.TH "NPM-RUN" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-RUN" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-run\fR - Run arbitrary package scripts .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-sbom.1 b/deps/npm/man/man1/npm-sbom.1 index 8c939742ffd100..3c51502f529880 100644 --- a/deps/npm/man/man1/npm-sbom.1 +++ b/deps/npm/man/man1/npm-sbom.1 @@ -1,4 +1,4 @@ -.TH "NPM-SBOM" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-SBOM" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-sbom\fR - Generate a Software Bill of Materials (SBOM) .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-search.1 b/deps/npm/man/man1/npm-search.1 index c547fb7d6f439c..2481037a382c04 100644 --- a/deps/npm/man/man1/npm-search.1 +++ b/deps/npm/man/man1/npm-search.1 @@ -1,4 +1,4 @@ -.TH "NPM-SEARCH" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-SEARCH" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-search\fR - Search for packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-set.1 b/deps/npm/man/man1/npm-set.1 index b220a6c275f35b..b77a942abfa1f3 100644 --- a/deps/npm/man/man1/npm-set.1 +++ b/deps/npm/man/man1/npm-set.1 @@ -1,4 +1,4 @@ -.TH "NPM-SET" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-SET" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-set\fR - Set a value in the npm configuration .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-shrinkwrap.1 b/deps/npm/man/man1/npm-shrinkwrap.1 index d07105d3c01277..5f0ed829e939b9 100644 --- a/deps/npm/man/man1/npm-shrinkwrap.1 +++ b/deps/npm/man/man1/npm-shrinkwrap.1 @@ -1,4 +1,4 @@ -.TH "NPM-SHRINKWRAP" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-SHRINKWRAP" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-shrinkwrap\fR - Lock down dependency versions for publication .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-star.1 b/deps/npm/man/man1/npm-star.1 index 0fbec6948b308e..d70556d2c65c51 100644 --- a/deps/npm/man/man1/npm-star.1 +++ b/deps/npm/man/man1/npm-star.1 @@ -1,4 +1,4 @@ -.TH "NPM-STAR" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-STAR" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-star\fR - Mark your favorite packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stars.1 b/deps/npm/man/man1/npm-stars.1 index 52df512f34c2d2..11707121f7af14 100644 --- a/deps/npm/man/man1/npm-stars.1 +++ b/deps/npm/man/man1/npm-stars.1 @@ -1,4 +1,4 @@ -.TH "NPM-STARS" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-STARS" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-stars\fR - View packages marked as favorites .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-start.1 b/deps/npm/man/man1/npm-start.1 index 6af2ccc7c8cf02..4c138de64d3126 100644 --- a/deps/npm/man/man1/npm-start.1 +++ b/deps/npm/man/man1/npm-start.1 @@ -1,4 +1,4 @@ -.TH "NPM-START" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-START" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-start\fR - Start a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-stop.1 b/deps/npm/man/man1/npm-stop.1 index f1c705a06eb865..386f070a906553 100644 --- a/deps/npm/man/man1/npm-stop.1 +++ b/deps/npm/man/man1/npm-stop.1 @@ -1,4 +1,4 @@ -.TH "NPM-STOP" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-STOP" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-stop\fR - Stop a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-team.1 b/deps/npm/man/man1/npm-team.1 index 829de3ec393217..f6ed3de147b149 100644 --- a/deps/npm/man/man1/npm-team.1 +++ b/deps/npm/man/man1/npm-team.1 @@ -1,4 +1,4 @@ -.TH "NPM-TEAM" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-TEAM" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-team\fR - Manage organization teams and team memberships .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-test.1 b/deps/npm/man/man1/npm-test.1 index 4ee5931a296ffa..fe1c15b35895de 100644 --- a/deps/npm/man/man1/npm-test.1 +++ b/deps/npm/man/man1/npm-test.1 @@ -1,4 +1,4 @@ -.TH "NPM-TEST" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-TEST" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-test\fR - Test a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-token.1 b/deps/npm/man/man1/npm-token.1 index 62c1f741787b64..c1b0e1acb21157 100644 --- a/deps/npm/man/man1/npm-token.1 +++ b/deps/npm/man/man1/npm-token.1 @@ -1,4 +1,4 @@ -.TH "NPM-TOKEN" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-TOKEN" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-token\fR - Manage your authentication tokens .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-trust.1 b/deps/npm/man/man1/npm-trust.1 index 0b361c2fcb848f..81e67e7c17b800 100644 --- a/deps/npm/man/man1/npm-trust.1 +++ b/deps/npm/man/man1/npm-trust.1 @@ -1,14 +1,8 @@ -.TH "NPM-TRUST" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-TRUST" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-trust\fR - Manage trusted publishing relationships between packages and CI/CD providers .SS "Synopsis" .P -.RS 2 -.nf - -.fi -.RE -.P Note: This command is unaware of workspaces. .SS "Prerequisites" .P diff --git a/deps/npm/man/man1/npm-undeprecate.1 b/deps/npm/man/man1/npm-undeprecate.1 index 1908fbef2da9dd..7d2848a33ef9f7 100644 --- a/deps/npm/man/man1/npm-undeprecate.1 +++ b/deps/npm/man/man1/npm-undeprecate.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNDEPRECATE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-UNDEPRECATE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-undeprecate\fR - Undeprecate a version of a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-uninstall.1 b/deps/npm/man/man1/npm-uninstall.1 index 69b1a0e4254fec..5255c3104c7e1b 100644 --- a/deps/npm/man/man1/npm-uninstall.1 +++ b/deps/npm/man/man1/npm-uninstall.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNINSTALL" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-UNINSTALL" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-uninstall\fR - Remove a package .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-unpublish.1 b/deps/npm/man/man1/npm-unpublish.1 index 107cfef94e739d..a10179d140cb17 100644 --- a/deps/npm/man/man1/npm-unpublish.1 +++ b/deps/npm/man/man1/npm-unpublish.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNPUBLISH" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-UNPUBLISH" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-unpublish\fR - Remove a package from the registry .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-unstar.1 b/deps/npm/man/man1/npm-unstar.1 index de0398b60d84d2..21ddfa859aead3 100644 --- a/deps/npm/man/man1/npm-unstar.1 +++ b/deps/npm/man/man1/npm-unstar.1 @@ -1,4 +1,4 @@ -.TH "NPM-UNSTAR" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-UNSTAR" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-unstar\fR - Remove an item from your favorite packages .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-update.1 b/deps/npm/man/man1/npm-update.1 index 09fa88433aac0c..83acac8d81f052 100644 --- a/deps/npm/man/man1/npm-update.1 +++ b/deps/npm/man/man1/npm-update.1 @@ -1,4 +1,4 @@ -.TH "NPM-UPDATE" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-UPDATE" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-update\fR - Update packages .SS "Synopsis" @@ -315,6 +315,8 @@ If set, npm will build the npm tree such that only versions that were available This flag is a complement to \fBbefore\fR, which accepts an exact date instead of a relative number of days. .P This config cannot be used with: \fBbefore\fR +.P +This value is not exported to the environment for child processes. .SS "\fBbin-links\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man1/npm-version.1 b/deps/npm/man/man1/npm-version.1 index cd0ddfc1c473e3..709a8bfb00c34d 100644 --- a/deps/npm/man/man1/npm-version.1 +++ b/deps/npm/man/man1/npm-version.1 @@ -1,4 +1,4 @@ -.TH "NPM-VERSION" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-VERSION" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-version\fR - Bump a package version .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-view.1 b/deps/npm/man/man1/npm-view.1 index 8b3659188303d3..d45d8b38277c9d 100644 --- a/deps/npm/man/man1/npm-view.1 +++ b/deps/npm/man/man1/npm-view.1 @@ -1,4 +1,4 @@ -.TH "NPM-VIEW" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-VIEW" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-view\fR - View registry info .SS "Synopsis" diff --git a/deps/npm/man/man1/npm-whoami.1 b/deps/npm/man/man1/npm-whoami.1 index 92f4f5820566a5..f246bfc4781cb2 100644 --- a/deps/npm/man/man1/npm-whoami.1 +++ b/deps/npm/man/man1/npm-whoami.1 @@ -1,4 +1,4 @@ -.TH "NPM-WHOAMI" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM-WHOAMI" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-whoami\fR - Display npm username .SS "Synopsis" diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1 index 60a0cdb60876ac..a7ca1f48a0f4bc 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -1,4 +1,4 @@ -.TH "NPM" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPM" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm\fR - javascript package manager .SS "Synopsis" @@ -12,7 +12,7 @@ npm Note: This command is unaware of workspaces. .SS "Version" .P -11.11.1 +11.12.1 .SS "Description" .P npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency conflicts intelligently. diff --git a/deps/npm/man/man1/npx.1 b/deps/npm/man/man1/npx.1 index ec515d0d41969d..050afc7ab98e21 100644 --- a/deps/npm/man/man1/npx.1 +++ b/deps/npm/man/man1/npx.1 @@ -1,4 +1,4 @@ -.TH "NPX" "1" "March 2026" "NPM@11.11.1" "" +.TH "NPX" "1" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpx\fR - Run a command from a local or remote npm package .SS "Synopsis" diff --git a/deps/npm/man/man5/folders.5 b/deps/npm/man/man5/folders.5 index bf013eaf6aa8e5..6d8dbbe2d0930d 100644 --- a/deps/npm/man/man5/folders.5 +++ b/deps/npm/man/man5/folders.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "March 2026" "NPM@11.11.1" "" +.TH "FOLDERS" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBFolders\fR - Folder structures used by npm .SS "Description" diff --git a/deps/npm/man/man5/install.5 b/deps/npm/man/man5/install.5 index dcc1668bc5df71..8ebd081e06497d 100644 --- a/deps/npm/man/man5/install.5 +++ b/deps/npm/man/man5/install.5 @@ -1,4 +1,4 @@ -.TH "INSTALL" "5" "March 2026" "NPM@11.11.1" "" +.TH "INSTALL" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBInstall\fR - Download and install node and npm .SS "Description" diff --git a/deps/npm/man/man5/npm-global.5 b/deps/npm/man/man5/npm-global.5 index bf013eaf6aa8e5..6d8dbbe2d0930d 100644 --- a/deps/npm/man/man5/npm-global.5 +++ b/deps/npm/man/man5/npm-global.5 @@ -1,4 +1,4 @@ -.TH "FOLDERS" "5" "March 2026" "NPM@11.11.1" "" +.TH "FOLDERS" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBFolders\fR - Folder structures used by npm .SS "Description" diff --git a/deps/npm/man/man5/npm-json.5 b/deps/npm/man/man5/npm-json.5 index c736becf20b453..02241289fdd8a0 100644 --- a/deps/npm/man/man5/npm-json.5 +++ b/deps/npm/man/man5/npm-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE.JSON" "5" "March 2026" "NPM@11.11.1" "" +.TH "PACKAGE.JSON" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBpackage.json\fR - Specifics of npm's package.json handling .SS "Description" diff --git a/deps/npm/man/man5/npm-shrinkwrap-json.5 b/deps/npm/man/man5/npm-shrinkwrap-json.5 index e257df187cec9b..34361a3d7bc5e3 100644 --- a/deps/npm/man/man5/npm-shrinkwrap-json.5 +++ b/deps/npm/man/man5/npm-shrinkwrap-json.5 @@ -1,4 +1,4 @@ -.TH "NPM-SHRINKWRAP.JSON" "5" "March 2026" "NPM@11.11.1" "" +.TH "NPM-SHRINKWRAP.JSON" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBnpm-shrinkwrap.json\fR - A publishable lockfile .SS "Description" diff --git a/deps/npm/man/man5/npmrc.5 b/deps/npm/man/man5/npmrc.5 index 5d4dc6a0761202..bf27bcb5939f20 100644 --- a/deps/npm/man/man5/npmrc.5 +++ b/deps/npm/man/man5/npmrc.5 @@ -1,4 +1,4 @@ -.TH ".NPMRC" "5" "March 2026" "NPM@11.11.1" "" +.TH ".NPMRC" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fB.npmrc\fR - The npm config files .SS "Description" diff --git a/deps/npm/man/man5/package-json.5 b/deps/npm/man/man5/package-json.5 index c736becf20b453..02241289fdd8a0 100644 --- a/deps/npm/man/man5/package-json.5 +++ b/deps/npm/man/man5/package-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE.JSON" "5" "March 2026" "NPM@11.11.1" "" +.TH "PACKAGE.JSON" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBpackage.json\fR - Specifics of npm's package.json handling .SS "Description" diff --git a/deps/npm/man/man5/package-lock-json.5 b/deps/npm/man/man5/package-lock-json.5 index d904c97f93bd20..63ee956145c2a7 100644 --- a/deps/npm/man/man5/package-lock-json.5 +++ b/deps/npm/man/man5/package-lock-json.5 @@ -1,4 +1,4 @@ -.TH "PACKAGE-LOCK.JSON" "5" "March 2026" "NPM@11.11.1" "" +.TH "PACKAGE-LOCK.JSON" "5" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBpackage-lock.json\fR - A manifestation of the manifest .SS "Description" diff --git a/deps/npm/man/man7/config.7 b/deps/npm/man/man7/config.7 index 8920b050503512..8452593e01eb4b 100644 --- a/deps/npm/man/man7/config.7 +++ b/deps/npm/man/man7/config.7 @@ -1,4 +1,4 @@ -.TH "CONFIG" "7" "March 2026" "NPM@11.11.1" "" +.TH "CONFIG" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBConfig\fR - About npm configuration .SS "Description" @@ -801,6 +801,16 @@ Option that allows for defining which types of dependencies to install. This is the inverse of \fB--omit=\fR. .P Dependency types specified in \fB--include\fR will not be omitted, regardless of the order in which omit/include are specified on the command-line. +.SS "\fBinclude-attestations\fR" +.RS 0 +.IP \(bu 4 +Default: false +.IP \(bu 4 +Type: Boolean +.RE 0 + +.P +When used with \fBnpm audit signatures --json\fR, includes the full sigstore attestation bundles in the JSON output for each verified package. The bundles contain DSSE envelopes, verification material, and transparency log entries. .SS "\fBinclude-staged\fR" .RS 0 .IP \(bu 4 @@ -1109,6 +1119,8 @@ If set, npm will build the npm tree such that only versions that were available This flag is a complement to \fBbefore\fR, which accepts an exact date instead of a relative number of days. .P This config cannot be used with: \fBbefore\fR +.P +This value is not exported to the environment for child processes. .SS "\fBname\fR" .RS 0 .IP \(bu 4 diff --git a/deps/npm/man/man7/dependency-selectors.7 b/deps/npm/man/man7/dependency-selectors.7 index e78b647eec364f..9ad3c02c5cc23c 100644 --- a/deps/npm/man/man7/dependency-selectors.7 +++ b/deps/npm/man/man7/dependency-selectors.7 @@ -1,4 +1,4 @@ -.TH "SELECTORS" "7" "March 2026" "NPM@11.11.1" "" +.TH "SELECTORS" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBSelectors\fR - Dependency Selector Syntax & Querying .SS "Description" diff --git a/deps/npm/man/man7/developers.7 b/deps/npm/man/man7/developers.7 index 1e77c8a5a3b013..809ae04b8ab770 100644 --- a/deps/npm/man/man7/developers.7 +++ b/deps/npm/man/man7/developers.7 @@ -1,4 +1,4 @@ -.TH "DEVELOPERS" "7" "March 2026" "NPM@11.11.1" "" +.TH "DEVELOPERS" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBDevelopers\fR - Developer guide .SS "Description" diff --git a/deps/npm/man/man7/logging.7 b/deps/npm/man/man7/logging.7 index 58e9adca8469ad..434e1af696ceeb 100644 --- a/deps/npm/man/man7/logging.7 +++ b/deps/npm/man/man7/logging.7 @@ -1,4 +1,4 @@ -.TH "LOGGING" "7" "March 2026" "NPM@11.11.1" "" +.TH "LOGGING" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBLogging\fR - Why, What & How we Log .SS "Description" diff --git a/deps/npm/man/man7/orgs.7 b/deps/npm/man/man7/orgs.7 index 5883ba259fc68c..325210226876e5 100644 --- a/deps/npm/man/man7/orgs.7 +++ b/deps/npm/man/man7/orgs.7 @@ -1,4 +1,4 @@ -.TH "ORGANIZATIONS" "7" "March 2026" "NPM@11.11.1" "" +.TH "ORGANIZATIONS" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBOrganizations\fR - Working with teams & organizations .SS "Description" diff --git a/deps/npm/man/man7/package-spec.7 b/deps/npm/man/man7/package-spec.7 index ec00504169cfb9..fbcfeafd019d5d 100644 --- a/deps/npm/man/man7/package-spec.7 +++ b/deps/npm/man/man7/package-spec.7 @@ -1,4 +1,4 @@ -.TH "SPEC" "7" "March 2026" "NPM@11.11.1" "" +.TH "SPEC" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBspec\fR - Package name specifier .SS "Description" diff --git a/deps/npm/man/man7/registry.7 b/deps/npm/man/man7/registry.7 index 05de53d3513eb3..f2be8c14b15940 100644 --- a/deps/npm/man/man7/registry.7 +++ b/deps/npm/man/man7/registry.7 @@ -1,4 +1,4 @@ -.TH "REGISTRY" "7" "March 2026" "NPM@11.11.1" "" +.TH "REGISTRY" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBRegistry\fR - The JavaScript Package Registry .SS "Description" diff --git a/deps/npm/man/man7/removal.7 b/deps/npm/man/man7/removal.7 index b375f99e64187c..7b8773deb192ba 100644 --- a/deps/npm/man/man7/removal.7 +++ b/deps/npm/man/man7/removal.7 @@ -1,4 +1,4 @@ -.TH "REMOVAL" "7" "March 2026" "NPM@11.11.1" "" +.TH "REMOVAL" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBRemoval\fR - Cleaning the slate .SS "Synopsis" diff --git a/deps/npm/man/man7/scope.7 b/deps/npm/man/man7/scope.7 index 5e1e8414884bf5..f8b9d4a406532f 100644 --- a/deps/npm/man/man7/scope.7 +++ b/deps/npm/man/man7/scope.7 @@ -1,4 +1,4 @@ -.TH "SCOPE" "7" "March 2026" "NPM@11.11.1" "" +.TH "SCOPE" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBScope\fR - Scoped packages .SS "Description" diff --git a/deps/npm/man/man7/scripts.7 b/deps/npm/man/man7/scripts.7 index 809b611b147d4e..7244c685d5b83e 100644 --- a/deps/npm/man/man7/scripts.7 +++ b/deps/npm/man/man7/scripts.7 @@ -1,4 +1,4 @@ -.TH "SCRIPTS" "7" "March 2026" "NPM@11.11.1" "" +.TH "SCRIPTS" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBScripts\fR - How npm handles the "scripts" field .SS "Description" diff --git a/deps/npm/man/man7/workspaces.7 b/deps/npm/man/man7/workspaces.7 index 175fc466578ba4..4c4ed3324d997b 100644 --- a/deps/npm/man/man7/workspaces.7 +++ b/deps/npm/man/man7/workspaces.7 @@ -1,4 +1,4 @@ -.TH "WORKSPACES" "7" "March 2026" "NPM@11.11.1" "" +.TH "WORKSPACES" "7" "March 2026" "NPM@11.12.1" "" .SH "NAME" \fBWorkspaces\fR - Working with workspaces .SS "Description" diff --git a/deps/npm/node_modules/@gar/promise-retry/LICENSE b/deps/npm/node_modules/@gar/promise-retry/LICENSE index db5e914de1f585..581fd125674145 100644 --- a/deps/npm/node_modules/@gar/promise-retry/LICENSE +++ b/deps/npm/node_modules/@gar/promise-retry/LICENSE @@ -1,3 +1,4 @@ +Copyright (c) 2011 Tim Koschützki (tim@debuggable.com), Felix Geisendörfer (felix@debuggable.com) Copyright (c) 2014 IndigoUnited Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/deps/npm/node_modules/@gar/promise-retry/lib/index.js b/deps/npm/node_modules/@gar/promise-retry/lib/index.js index 9033419793aaf6..be4598498e4a00 100644 --- a/deps/npm/node_modules/@gar/promise-retry/lib/index.js +++ b/deps/npm/node_modules/@gar/promise-retry/lib/index.js @@ -1,9 +1,44 @@ -const retry = require('retry') +const { RetryOperation } = require('./retry') -const isRetryError = (err) => err?.code === 'EPROMISERETRY' && Object.hasOwn(err, 'retried') +const createTimeout = (attempt, opts) => Math.min(Math.round((1 + (opts.randomize ? Math.random() : 0)) * Math.max(opts.minTimeout, 1) * Math.pow(opts.factor, attempt)), opts.maxTimeout) +const isRetryError = err => err?.code === 'EPROMISERETRY' && Object.hasOwn(err, 'retried') -async function promiseRetry (fn, options = {}) { - const operation = retry.operation(options) +const promiseRetry = async (fn, options = {}) => { + let timeouts = [] + if (options instanceof Array) { + timeouts = [...options] + } else { + if (options.retries === Infinity) { + options.forever = true + delete options.retries + } + const opts = { + retries: 10, + factor: 2, + minTimeout: 1 * 1000, + maxTimeout: Infinity, + randomize: false, + ...options + } + if (opts.minTimeout > opts.maxTimeout) { + throw new Error('minTimeout is greater than maxTimeout') + } + if (opts.retries) { + for (let i = 0; i < opts.retries; i++) { + timeouts.push(createTimeout(i, opts)) + } + // sort the array numerically ascending (since the timeouts may be out of order at factor < 1) + timeouts.sort((a, b) => a - b) + } else if (options.forever) { + timeouts.push(createTimeout(0, opts)) + } + } + + const operation = new RetryOperation(timeouts, { + forever: options.forever, + unref: options.unref, + maxRetryTime: options.maxRetryTime + }) return new Promise(function (resolve, reject) { operation.attempt(async number => { @@ -13,13 +48,12 @@ async function promiseRetry (fn, options = {}) { }, number, operation) return resolve(result) } catch (err) { - if (isRetryError(err)) { - if (operation.retry(err.retried || new Error())) { - return - } + if (!isRetryError(err)) { + return reject(err) + } + if (!operation.retry(err.retried || new Error())) { return reject(err.retried) } - return reject(err) } }) }) diff --git a/deps/npm/node_modules/@gar/promise-retry/lib/retry.js b/deps/npm/node_modules/@gar/promise-retry/lib/retry.js new file mode 100644 index 00000000000000..e2d13dfaee5727 --- /dev/null +++ b/deps/npm/node_modules/@gar/promise-retry/lib/retry.js @@ -0,0 +1,109 @@ +class RetryOperation { + #attempts = 1 + #cachedTimeouts = null + #errors = [] + #fn = null + #maxRetryTime + #operationStart = null + #originalTimeouts + #timeouts + #timer = null + #unref + + constructor (timeouts, options = {}) { + this.#originalTimeouts = [...timeouts] + this.#timeouts = [...timeouts] + this.#unref = options.unref + this.#maxRetryTime = options.maxRetryTime || Infinity + if (options.forever) { + this.#cachedTimeouts = [...this.#timeouts] + } + } + + get timeouts () { + return [...this.#timeouts] + } + + get errors () { + return [...this.#errors] + } + + get attempts () { + return this.#attempts + } + + get mainError () { + let mainError = null + if (this.#errors.length) { + let mainErrorCount = 0 + const counts = {} + for (let i = 0; i < this.#errors.length; i++) { + const error = this.#errors[i] + const { message } = error + if (!counts[message]) { + counts[message] = 0 + } + counts[message]++ + + if (counts[message] >= mainErrorCount) { + mainError = error + mainErrorCount = counts[message] + } + } + } + return mainError + } + + reset () { + this.#attempts = 1 + this.#timeouts = [...this.#originalTimeouts] + } + + stop () { + if (this.#timer) { + clearTimeout(this.#timer) + } + + this.#timeouts = [] + this.#cachedTimeouts = null + } + + retry (err) { + this.#errors.push(err) + if (new Date().getTime() - this.#operationStart >= this.#maxRetryTime) { + // XXX This puts the timeout error first, meaning it will never show as mainError, there may be no way to ever see this + this.#errors.unshift(new Error('RetryOperation timeout occurred')) + return false + } + + let timeout = this.#timeouts.shift() + if (timeout === undefined) { + // We're out of timeouts, clear the last error and repeat the final timeout + if (this.#cachedTimeouts) { + this.#errors.pop() + timeout = this.#cachedTimeouts.at(-1) + } else { + return false + } + } + + // TODO what if there already is a timer? + this.#timer = setTimeout(() => { + this.#attempts++ + this.#fn(this.#attempts) + }, timeout) + + if (this.#unref) { + this.#timer.unref() + } + + return true + } + + attempt (fn) { + this.#fn = fn + this.#operationStart = new Date().getTime() + this.#fn(this.#attempts) + } +} +module.exports = { RetryOperation } diff --git a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/License b/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/License deleted file mode 100644 index 0b58de379fb308..00000000000000 --- a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/License +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2011: -Tim Koschützki (tim@debuggable.com) -Felix Geisendörfer (felix@debuggable.com) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. diff --git a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/example/dns.js b/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/example/dns.js deleted file mode 100644 index 446729b6f9af6b..00000000000000 --- a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/example/dns.js +++ /dev/null @@ -1,31 +0,0 @@ -var dns = require('dns'); -var retry = require('../lib/retry'); - -function faultTolerantResolve(address, cb) { - var opts = { - retries: 2, - factor: 2, - minTimeout: 1 * 1000, - maxTimeout: 2 * 1000, - randomize: true - }; - var operation = retry.operation(opts); - - operation.attempt(function(currentAttempt) { - dns.resolve(address, function(err, addresses) { - if (operation.retry(err)) { - return; - } - - cb(operation.mainError(), operation.errors(), addresses); - }); - }); -} - -faultTolerantResolve('nodejs.org', function(err, errors, addresses) { - console.warn('err:'); - console.log(err); - - console.warn('addresses:'); - console.log(addresses); -}); \ No newline at end of file diff --git a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/example/stop.js b/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/example/stop.js deleted file mode 100644 index e1ceafeebafc51..00000000000000 --- a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/example/stop.js +++ /dev/null @@ -1,40 +0,0 @@ -var retry = require('../lib/retry'); - -function attemptAsyncOperation(someInput, cb) { - var opts = { - retries: 2, - factor: 2, - minTimeout: 1 * 1000, - maxTimeout: 2 * 1000, - randomize: true - }; - var operation = retry.operation(opts); - - operation.attempt(function(currentAttempt) { - failingAsyncOperation(someInput, function(err, result) { - - if (err && err.message === 'A fatal error') { - operation.stop(); - return cb(err); - } - - if (operation.retry(err)) { - return; - } - - cb(operation.mainError(), operation.errors(), result); - }); - }); -} - -attemptAsyncOperation('test input', function(err, errors, result) { - console.warn('err:'); - console.log(err); - - console.warn('result:'); - console.log(result); -}); - -function failingAsyncOperation(input, cb) { - return setImmediate(cb.bind(null, new Error('A fatal error'))); -} diff --git a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/index.js b/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/index.js deleted file mode 100644 index ee62f3a112c28b..00000000000000 --- a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib/retry'); \ No newline at end of file diff --git a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/lib/retry.js b/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/lib/retry.js deleted file mode 100644 index 5e85e79197d36c..00000000000000 --- a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/lib/retry.js +++ /dev/null @@ -1,100 +0,0 @@ -var RetryOperation = require('./retry_operation'); - -exports.operation = function(options) { - var timeouts = exports.timeouts(options); - return new RetryOperation(timeouts, { - forever: options && (options.forever || options.retries === Infinity), - unref: options && options.unref, - maxRetryTime: options && options.maxRetryTime - }); -}; - -exports.timeouts = function(options) { - if (options instanceof Array) { - return [].concat(options); - } - - var opts = { - retries: 10, - factor: 2, - minTimeout: 1 * 1000, - maxTimeout: Infinity, - randomize: false - }; - for (var key in options) { - opts[key] = options[key]; - } - - if (opts.minTimeout > opts.maxTimeout) { - throw new Error('minTimeout is greater than maxTimeout'); - } - - var timeouts = []; - for (var i = 0; i < opts.retries; i++) { - timeouts.push(this.createTimeout(i, opts)); - } - - if (options && options.forever && !timeouts.length) { - timeouts.push(this.createTimeout(i, opts)); - } - - // sort the array numerically ascending - timeouts.sort(function(a,b) { - return a - b; - }); - - return timeouts; -}; - -exports.createTimeout = function(attempt, opts) { - var random = (opts.randomize) - ? (Math.random() + 1) - : 1; - - var timeout = Math.round(random * Math.max(opts.minTimeout, 1) * Math.pow(opts.factor, attempt)); - timeout = Math.min(timeout, opts.maxTimeout); - - return timeout; -}; - -exports.wrap = function(obj, options, methods) { - if (options instanceof Array) { - methods = options; - options = null; - } - - if (!methods) { - methods = []; - for (var key in obj) { - if (typeof obj[key] === 'function') { - methods.push(key); - } - } - } - - for (var i = 0; i < methods.length; i++) { - var method = methods[i]; - var original = obj[method]; - - obj[method] = function retryWrapper(original) { - var op = exports.operation(options); - var args = Array.prototype.slice.call(arguments, 1); - var callback = args.pop(); - - args.push(function(err) { - if (op.retry(err)) { - return; - } - if (err) { - arguments[0] = op.mainError(); - } - callback.apply(this, arguments); - }); - - op.attempt(function() { - original.apply(obj, args); - }); - }.bind(obj, original); - obj[method].options = options; - } -}; diff --git a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/lib/retry_operation.js b/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/lib/retry_operation.js deleted file mode 100644 index 105ce72b2be8e1..00000000000000 --- a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/lib/retry_operation.js +++ /dev/null @@ -1,162 +0,0 @@ -function RetryOperation(timeouts, options) { - // Compatibility for the old (timeouts, retryForever) signature - if (typeof options === 'boolean') { - options = { forever: options }; - } - - this._originalTimeouts = JSON.parse(JSON.stringify(timeouts)); - this._timeouts = timeouts; - this._options = options || {}; - this._maxRetryTime = options && options.maxRetryTime || Infinity; - this._fn = null; - this._errors = []; - this._attempts = 1; - this._operationTimeout = null; - this._operationTimeoutCb = null; - this._timeout = null; - this._operationStart = null; - this._timer = null; - - if (this._options.forever) { - this._cachedTimeouts = this._timeouts.slice(0); - } -} -module.exports = RetryOperation; - -RetryOperation.prototype.reset = function() { - this._attempts = 1; - this._timeouts = this._originalTimeouts.slice(0); -} - -RetryOperation.prototype.stop = function() { - if (this._timeout) { - clearTimeout(this._timeout); - } - if (this._timer) { - clearTimeout(this._timer); - } - - this._timeouts = []; - this._cachedTimeouts = null; -}; - -RetryOperation.prototype.retry = function(err) { - if (this._timeout) { - clearTimeout(this._timeout); - } - - if (!err) { - return false; - } - var currentTime = new Date().getTime(); - if (err && currentTime - this._operationStart >= this._maxRetryTime) { - this._errors.push(err); - this._errors.unshift(new Error('RetryOperation timeout occurred')); - return false; - } - - this._errors.push(err); - - var timeout = this._timeouts.shift(); - if (timeout === undefined) { - if (this._cachedTimeouts) { - // retry forever, only keep last error - this._errors.splice(0, this._errors.length - 1); - timeout = this._cachedTimeouts.slice(-1); - } else { - return false; - } - } - - var self = this; - this._timer = setTimeout(function() { - self._attempts++; - - if (self._operationTimeoutCb) { - self._timeout = setTimeout(function() { - self._operationTimeoutCb(self._attempts); - }, self._operationTimeout); - - if (self._options.unref) { - self._timeout.unref(); - } - } - - self._fn(self._attempts); - }, timeout); - - if (this._options.unref) { - this._timer.unref(); - } - - return true; -}; - -RetryOperation.prototype.attempt = function(fn, timeoutOps) { - this._fn = fn; - - if (timeoutOps) { - if (timeoutOps.timeout) { - this._operationTimeout = timeoutOps.timeout; - } - if (timeoutOps.cb) { - this._operationTimeoutCb = timeoutOps.cb; - } - } - - var self = this; - if (this._operationTimeoutCb) { - this._timeout = setTimeout(function() { - self._operationTimeoutCb(); - }, self._operationTimeout); - } - - this._operationStart = new Date().getTime(); - - this._fn(this._attempts); -}; - -RetryOperation.prototype.try = function(fn) { - console.log('Using RetryOperation.try() is deprecated'); - this.attempt(fn); -}; - -RetryOperation.prototype.start = function(fn) { - console.log('Using RetryOperation.start() is deprecated'); - this.attempt(fn); -}; - -RetryOperation.prototype.start = RetryOperation.prototype.try; - -RetryOperation.prototype.errors = function() { - return this._errors; -}; - -RetryOperation.prototype.attempts = function() { - return this._attempts; -}; - -RetryOperation.prototype.mainError = function() { - if (this._errors.length === 0) { - return null; - } - - var counts = {}; - var mainError = null; - var mainErrorCount = 0; - - for (var i = 0; i < this._errors.length; i++) { - var error = this._errors[i]; - var message = error.message; - var count = (counts[message] || 0) + 1; - - counts[message] = count; - - if (count >= mainErrorCount) { - mainError = error; - mainErrorCount = count; - } - } - - return mainError; -}; diff --git a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/package.json b/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/package.json deleted file mode 100644 index 48f35e8cff2859..00000000000000 --- a/deps/npm/node_modules/@gar/promise-retry/node_modules/retry/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "author": "Tim Koschützki (http://debuggable.com/)", - "name": "retry", - "description": "Abstraction for exponential and custom retry strategies for failed operations.", - "license": "MIT", - "version": "0.13.1", - "homepage": "https://github.com/tim-kos/node-retry", - "repository": { - "type": "git", - "url": "git://github.com/tim-kos/node-retry.git" - }, - "files": [ - "lib", - "example" - ], - "directories": { - "lib": "./lib" - }, - "main": "index.js", - "engines": { - "node": ">= 4" - }, - "dependencies": {}, - "devDependencies": { - "fake": "0.2.0", - "istanbul": "^0.4.5", - "tape": "^4.8.0" - }, - "scripts": { - "test": "./node_modules/.bin/istanbul cover ./node_modules/tape/bin/tape ./test/integration/*.js", - "release:major": "env SEMANTIC=major npm run release", - "release:minor": "env SEMANTIC=minor npm run release", - "release:patch": "env SEMANTIC=patch npm run release", - "release": "npm version ${SEMANTIC:-patch} -m \"Release %s\" && git push && git push --tags && npm publish" - } -} diff --git a/deps/npm/node_modules/@gar/promise-retry/package.json b/deps/npm/node_modules/@gar/promise-retry/package.json index 0bd8e31a2aa021..ab73b70283b311 100644 --- a/deps/npm/node_modules/@gar/promise-retry/package.json +++ b/deps/npm/node_modules/@gar/promise-retry/package.json @@ -1,6 +1,6 @@ { "name": "@gar/promise-retry", - "version": "1.0.2", + "version": "1.0.3", "description": "Retries a function that returns a promise, leveraging the power of the retry module.", "main": "./lib/index.js", "files": [ @@ -39,9 +39,6 @@ "replay" ], "license": "MIT", - "dependencies": { - "retry": "^0.13.1" - }, "engines": { "node": "^20.17.0 || >=22.9.0" } diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js index a20e936666cfad..44ac7cd34dcbc1 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js @@ -117,9 +117,11 @@ module.exports = cls => class Reifier extends cls { // of Node/Link trees log.warn('reify', 'The "linked" install strategy is EXPERIMENTAL and may contain bugs.') this.idealTree = await this.createIsolatedTree() - this.#linkedActualForDiff = this.#buildLinkedActualForDiff( - this.idealTree, this.actualTree - ) + if (this.actualTree) { + this.#linkedActualForDiff = this.#buildLinkedActualForDiff( + this.idealTree, this.actualTree + ) + } } await this[_diffTrees]() await this.#reifyPackages() @@ -815,6 +817,10 @@ module.exports = cls => class Reifier extends cls { if (combined.has(child.path) || !existsSync(child.path)) { continue } + // Skip store links whose ideal realpath doesn't exist on disk yet — the store hash changed and the symlink needs recreating via ADD. + if (child.isLink && child.resolved?.startsWith('file:.store/') && !existsSync(child.realpath)) { + continue + } let entry if (child.isLink) { entry = new IsolatedLink(child) diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/override-set.js b/deps/npm/node_modules/@npmcli/arborist/lib/override-set.js index b4a11ba589df77..ab597a7f33bd4a 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/override-set.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/override-set.js @@ -195,8 +195,29 @@ class OverrideSet { } } - // The override sets are incomparable. Neither one contains the other. - log.silly('Conflicting override sets', first, second) + // The override sets are incomparable (e.g. siblings like the "react" and "react-dom" children of the root override set). Check if they have semantically conflicting rules before treating this as an error. + if (this.haveConflictingRules(first, second)) { + log.silly('Conflicting override sets', first, second) + return undefined + } + + // The override sets are structurally incomparable but have compatible rules. Fall back to their nearest common ancestor so the node still has a valid override set. + return this.findCommonAncestor(first, second) + } + + static findCommonAncestor (first, second) { + const firstAncestors = [] + for (const ancestor of first.ancestry()) { + firstAncestors.push(ancestor) + } + for (const secondAnc of second.ancestry()) { + for (const firstAnc of firstAncestors) { + if (firstAnc.isEqual(secondAnc)) { + return firstAnc + } + } + } + return null } static doOverrideSetsConflict (first, second) { diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json index 6d480fad6ebc9b..58ddb99f8e3593 100644 --- a/deps/npm/node_modules/@npmcli/arborist/package.json +++ b/deps/npm/node_modules/@npmcli/arborist/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/arborist", - "version": "9.4.1", + "version": "9.4.2", "description": "Manage node_modules trees", "dependencies": { "@gar/promise-retry": "^1.0.0", diff --git a/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js b/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js index 0de8142d3ca77b..c3e5cd2b430189 100644 --- a/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js +++ b/deps/npm/node_modules/@npmcli/config/lib/definitions/definitions.js @@ -946,6 +946,17 @@ const definitions = { `, flatten, }), + 'include-attestations': new Definition('include-attestations', { + default: false, + type: Boolean, + description: ` + When used with \`npm audit signatures --json\`, includes the full + sigstore attestation bundles in the JSON output for each verified + package. The bundles contain DSSE envelopes, verification material, + and transparency log entries. + `, + flatten, + }), 'init-author-email': new Definition('init-author-email', { default: '', hint: '', @@ -1353,6 +1364,7 @@ const definitions = { hint: '', type: [null, Number], exclusive: ['before'], + envExport: false, description: ` If set, npm will build the npm tree such that only versions that were available more than the given number of days ago will be installed. If diff --git a/deps/npm/node_modules/@npmcli/config/lib/index.js b/deps/npm/node_modules/@npmcli/config/lib/index.js index 8520a02b6ed77c..a1acb7969b29f6 100644 --- a/deps/npm/node_modules/@npmcli/config/lib/index.js +++ b/deps/npm/node_modules/@npmcli/config/lib/index.js @@ -582,7 +582,7 @@ class Config { } } else { conf.raw = obj - for (const [key, value] of Object.entries(obj)) { + outer: for (const [key, value] of Object.entries(obj)) { const k = envReplace(key, this.env) const v = this.parseField(value, k) if (where !== 'default') { @@ -590,6 +590,13 @@ class Config { if (this.definitions[key]?.exclusive) { for (const exclusive of this.definitions[key].exclusive) { if (!this.isDefault(exclusive)) { + // when loading from env, skip only if sibling was explicitly set via CLI + if (where === 'env') { + const cliData = this.data.get('cli').data + if (Object.hasOwn(cliData, exclusive)) { + continue outer + } + } throw new TypeError(`--${key} cannot be provided when using --${exclusive}`) } } diff --git a/deps/npm/node_modules/@npmcli/config/package.json b/deps/npm/node_modules/@npmcli/config/package.json index 980a53d738b48d..5da16efc6cc4c3 100644 --- a/deps/npm/node_modules/@npmcli/config/package.json +++ b/deps/npm/node_modules/@npmcli/config/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/config", - "version": "10.7.1", + "version": "10.8.1", "files": [ "bin/", "lib/" diff --git a/deps/npm/node_modules/@sigstore/core/dist/crypto.js b/deps/npm/node_modules/@sigstore/core/dist/crypto.js index 296b5ba43e86a0..8d8110ee423476 100644 --- a/deps/npm/node_modules/@sigstore/core/dist/crypto.js +++ b/deps/npm/node_modules/@sigstore/core/dist/crypto.js @@ -25,7 +25,16 @@ limitations under the License. const crypto_1 = __importDefault(require("crypto")); function createPublicKey(key, type = 'spki') { if (typeof key === 'string') { - return crypto_1.default.createPublicKey(key); + if (key.startsWith('-----')) { + return crypto_1.default.createPublicKey(key); + } + else { + return crypto_1.default.createPublicKey({ + key: Buffer.from(key, 'base64'), + format: 'der', + type: type, + }); + } } else { return crypto_1.default.createPublicKey({ key, format: 'der', type: type }); diff --git a/deps/npm/node_modules/@sigstore/core/package.json b/deps/npm/node_modules/@sigstore/core/package.json index 8cd757103e7a11..0564a373c6fa31 100644 --- a/deps/npm/node_modules/@sigstore/core/package.json +++ b/deps/npm/node_modules/@sigstore/core/package.json @@ -1,6 +1,6 @@ { "name": "@sigstore/core", - "version": "3.1.0", + "version": "3.2.0", "description": "Base library for Sigstore", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/deps/npm/node_modules/@sigstore/sign/dist/external/fetch.js b/deps/npm/node_modules/@sigstore/sign/dist/external/fetch.js index 116090f3c641ef..9f65c8a7607fde 100644 --- a/deps/npm/node_modules/@sigstore/sign/dist/external/fetch.js +++ b/deps/npm/node_modules/@sigstore/sign/dist/external/fetch.js @@ -19,15 +19,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +const promise_retry_1 = require("@gar/promise-retry"); const http2_1 = require("http2"); const make_fetch_happen_1 = __importDefault(require("make-fetch-happen")); const proc_log_1 = require("proc-log"); -const promise_retry_1 = __importDefault(require("promise-retry")); const util_1 = require("../util"); const error_1 = require("./error"); const { HTTP2_HEADER_LOCATION, HTTP2_HEADER_CONTENT_TYPE, HTTP2_HEADER_USER_AGENT, HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_TOO_MANY_REQUESTS, HTTP_STATUS_REQUEST_TIMEOUT, } = http2_1.constants; async function fetchWithRetry(url, options) { - return (0, promise_retry_1.default)(async (retry, attemptNum) => { + return (0, promise_retry_1.promiseRetry)(async (retry, attemptNum) => { const method = options.method || 'POST'; const headers = { [HTTP2_HEADER_USER_AGENT]: util_1.ua.getUserAgent(), diff --git a/deps/npm/node_modules/@sigstore/sign/package.json b/deps/npm/node_modules/@sigstore/sign/package.json index 0f844a8735a891..c6b3f184a7bf1d 100644 --- a/deps/npm/node_modules/@sigstore/sign/package.json +++ b/deps/npm/node_modules/@sigstore/sign/package.json @@ -1,6 +1,6 @@ { "name": "@sigstore/sign", - "version": "4.1.0", + "version": "4.1.1", "description": "Sigstore signing library", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -27,18 +27,17 @@ }, "devDependencies": { "@sigstore/jest": "^0.0.0", - "@sigstore/mock": "^0.11.0", + "@sigstore/mock": "^0.12.0", "@sigstore/rekor-types": "^4.0.0", - "@types/make-fetch-happen": "^10.0.4", - "@types/promise-retry": "^1.1.6" + "@types/make-fetch-happen": "^10.0.4" }, "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.3", + "make-fetch-happen": "^15.0.4", "proc-log": "^6.1.0", - "promise-retry": "^2.0.1" + "@gar/promise-retry": "^1.0.2" }, "engines": { "node": "^20.17.0 || >=22.9.0" diff --git a/deps/npm/node_modules/@sigstore/tuf/package.json b/deps/npm/node_modules/@sigstore/tuf/package.json index a782796c45ed6c..b1fd9bebe135d0 100644 --- a/deps/npm/node_modules/@sigstore/tuf/package.json +++ b/deps/npm/node_modules/@sigstore/tuf/package.json @@ -1,6 +1,6 @@ { "name": "@sigstore/tuf", - "version": "4.0.1", + "version": "4.0.2", "description": "Client for the Sigstore TUF repository", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -28,7 +28,7 @@ }, "devDependencies": { "@sigstore/jest": "^0.0.0", - "@tufjs/repo-mock": "^4.0.0", + "@tufjs/repo-mock": "^4.0.1", "@types/make-fetch-happen": "^10.0.4" }, "dependencies": { diff --git a/deps/npm/node_modules/@sigstore/tuf/seeds.json b/deps/npm/node_modules/@sigstore/tuf/seeds.json index 54cb71f29ef0cf..2bd03b34f7db8d 100644 --- a/deps/npm/node_modules/@sigstore/tuf/seeds.json +++ b/deps/npm/node_modules/@sigstore/tuf/seeds.json @@ -1 +1 @@ -{"https://tuf-repo-cdn.sigstore.dev":{"root.json":"ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICJzaWciOiAiIgogIH0sCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGJiZGRkNDY0ZjgwNjZjZWI4OGJhNzg3Mzc1YzEyY2Q2MzMwNjgwZTA4YzI5MTA3MDNlNjUzOGM3MWNjNzlhZDIwMjIwNTE5MGIwNmU0NTM3ZmU5NjFiM2VmODFmZTY4ZWRjZDAwODljMTlmOTE5YWZlZDQyM2I5YWFmZDcwMDY0MTE1MyIKICB9LAogIHsKICAgImtleWlkIjogIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAic2lnIjogIjMwNDQwMjIwNjkzMDZjZDUyNTdmNzMyYTc0MGMxYWZlNjBhOGU0MzNjNWRlNThlYWZlYWRiZTk5YzMzNmM5YzcxZDE5OGNmODAyMjAwZDc3Mzk1M2FlN2RiYzQ4ZDNlNWJhZDlhNmY2NGJhZmZmMTk2YjdlMmFkNGE1MmExOTUxOTM2N2Q0N2RjMDQyIgogIH0sCiAgewogICAia2V5aWQiOiAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICJzaWciOiAiMzA0NDAyMjA0ZDIxYTJlYzgwZGY2NmU2MWY2ZmUyOTEyOTUxZGM0N2RmODM2MDM2ZjhjMGFiMTA4MTZkMzc1ZTcxZGJmNzllMDIyMDU0N2FkY2UxYWZkZjA0ZTY3OTRlZmEyMDNkZDUyNjRjNmY3ZTBlZjc4ZTU3ZmU5MzRiMGQyNmNiOTk0ZWVjNzYiCiAgfSwKICB7CiAgICJrZXlpZCI6ICJhNjg3ZTViZjRmYWI4MmIwZWU1OGQ0NmUwNWM5NTM1MTQ1YTJjOWFmYjQ1OGY0M2Q0MmI0NWNhMGZkY2UyYTcwIiwKICAgInNpZyI6ICIzMDQ1MDIyMDYwODI2NDk2NTU3MTQ0ZWIxNjQ5ODkzZWQ1ZjZmNGVhNTQ1MzZmZWIwY2E4MmY4Yjg5YWU2NDFiZTM5NzQzZTUwMjIxMDBhZDcxMThiNWU5ZDQ4MzczMjYyMDZlNDEyZmM2ZGEyOTk5OTI1ZDExMDMyOGE3YzE2NmIwNmM2MjQzMzZjOTNmIgogIH0sCiAgewogICAia2V5aWQiOiAiMTgzZTY0ZjM3NjcwZGMxM2NhMGQyODk5NWEzMDUzZjM3NDA5NTRkZGNlNDQzMjFhNDFlNDY1MzRjZjQ0ZTYzMiIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGQ4MTc5NDM5YzJlNzNlYjBjMTczM2FiZWU3ZmFmODMyZGNhZWE3MjYzZWRjYjQ5MTk4OTFjM2EyNDdmMDU5MjMwMjIxMDBlMWE0MzdlMDc5N2U4MDNmOWI3MmRjOWQyZDkyMTU1YjBhMjI3MGMyNGVmZGQ1ZjRiM2E1ZDhmMGIwZjQzMWE3IgogIH0KIF0sCiAic2lnbmVkIjogewogICJfdHlwZSI6ICJyb290IiwKICAiY29uc2lzdGVudF9zbmFwc2hvdCI6IHRydWUsCiAgImV4cGlyZXMiOiAiMjAyNi0wMS0yMlQxMzowNTo1OVoiLAogICJrZXlzIjogewogICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVdSaUdyNStqKzNKNVNzSCtadHI1bkUySDJ3TzdcbkJWK25PM3M5M2dMY2ExOHFUT3pIWTFvV3lBR0R5a01Tc0dUVUJTdDlEK0FuMEtmS3NEMm1mU000MlE9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1vbmxpbmUtdXJpIjogImdjcGttczpwcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wL2NyeXB0b0tleVZlcnNpb25zLzEiCiAgIH0sCiAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIjogewogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVNeHBQT0pDSVo1b3RHNDEwNmZHSnNlRVFpM1Y5XG5wa01ZUTR1eVY5VGoxTTdXSFhJeUxHK2prZnZ1RzBnbFExSlpiUlpaQlYzZ0FSNHNvamRHSElTZW93PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGxhbmNlIgogICB9LAogICAiMjJmNGNhZWM2ZDhlNmY5NTU1YWY2NmIzZDRjM2NiMDZhM2JiMjNmZGM3ZTM5YzkxNmM2MWY0NjJlNmY1MmIwNiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXpCelZPbUhDUG9qTVZMU0kzNjRXaWlWOE5QckRcbjZJZ1J4Vmxpc2t6L3YreTNKRVI1bWNWR2NPTmxpRGNXTUM1SjJsZkhtalBOUGhiNEg3eG04THpmU0E9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAc2FudGlhZ290b3JyZXMiCiAgIH0sCiAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaW5pa1NzQVFtWWtOZUg1ZVlxL0NuSXpMYWFjT1xueGxTYWF3UURPd3FLeS90Q3F4cTV4eFBTSmMyMUs0V0loczlHeU9rS2Z6dWVZM0dJTHpjTUpaNGNXdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBib2JjYWxsYXdheSIKICAgfSwKICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUwZ2hyaDkyTHcxWXIzaWRHVjVXcUN0TURCOEN4XG4rRDhoZEM0dzJaTE5JcGxWUm9WR0xza1lhM2doZU15T2ppSjhrUGkxNWFRMi8vN1Arb2o3VXZKUEd3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGpvc2h1YWdsIgogICB9LAogICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUVYc3ozU1pYRmI4ak1WNDJqNnBKbHlqYmpSOEtcbk4zQndvY2V4cTZMTUliNXFzV0tPUXZMTjE2TlVlZkxjNEhzd09vdW1Sc1ZWYWFqU3BRUzZmb2JrUnc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAbW5tNjc4IgogICB9CiAgfSwKICAicm9sZXMiOiB7CiAgICJyb290IjogewogICAgImtleWlkcyI6IFsKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICAgIjE4M2U2NGYzNzY3MGRjMTNjYTBkMjg5OTVhMzA1M2YzNzQwOTU0ZGRjZTQ0MzIxYTQxZTQ2NTM0Y2Y0NGU2MzIiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInNuYXBzaG90IjogewogICAgImtleWlkcyI6IFsKICAgICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMSwKICAgICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMzY1MCwKICAgICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDM2NQogICB9LAogICAidGFyZ2V0cyI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiLAogICAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAzCiAgIH0sCiAgICJ0aW1lc3RhbXAiOiB7CiAgICAia2V5aWRzIjogWwogICAgICIwYzg3NDMyYzNiZjA5ZmQ5OTE4OWZkYzMyZmE1ZWFlZGY0ZTRhNWZhYzdiYWI3M2ZhMDRhMmUwZmM2NGFmNmY1IgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiA3LAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogNgogICB9CiAgfSwKICAic3BlY192ZXJzaW9uIjogIjEuMCIsCiAgInZlcnNpb24iOiAxMywKICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDE5NywKICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0NgogfQp9","targets":{"trusted_root.json":"ewogICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRldi5zaWdzdG9yZS50cnVzdGVkcm9vdCtqc29uO3ZlcnNpb249MC4xIiwKICAidGxvZ3MiOiBbCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vcmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUyRzJZKzJ0YWJkVFY1QmNHaUJJeDBhOWZBRndya0JibUxTR3RrczRMM3FYNnlZWTB6dWZCbmhDOFVyL2l5NTVHaFdQLzlBL2JZMkxoQzMwTTkrUll0dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDEtMTJUMTE6NTM6MjdaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICJ3Tkk5YXRRR2x6K1ZXZk82TFJ5Z0g0UVVmWS84VzRSRndpVDVpNVdSZ0IwPSIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9sb2cyMDI1LTEucmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNQ293QlFZREsyVndBeUVBdDhybHAxa25Hd2pmYmNYQVlQWUFrbjBYaUx6MXg4TzR0MFlrRWhpZTI0ND0iLAogICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUQyNTUxOSIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjUtMDktMjNUMDA6MDA6MDBaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICJ6eEdaRlZ2ZDBGRW1qUjhXckZ3TWRjQUo5dnRhWS9RWGY0NFkxd1VlUDZBPSIKICAgICAgfQogICAgfQogIF0sCiAgImNlcnRpZmljYXRlQXV0aG9yaXRpZXMiOiBbCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAic2lnc3RvcmUuZGV2IiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJzaWdzdG9yZSIKICAgICAgfSwKICAgICAgInVyaSI6ICJodHRwczovL2Z1bGNpby5zaWdzdG9yZS5kZXYiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCK0RDQ0FYNmdBd0lCQWdJVE5Wa0Rab0Npb2ZQRHN5N2RmbTZnZUxidWh6QUtCZ2dxaGtqT1BRUURBekFxTVJVd0V3WURWUVFLRXd4emFXZHpkRzl5WlM1a1pYWXhFVEFQQmdOVkJBTVRDSE5wWjNOMGIzSmxNQjRYRFRJeE1ETXdOekF6TWpBeU9Wb1hEVE14TURJeU16QXpNakF5T1Zvd0tqRVZNQk1HQTFVRUNoTU1jMmxuYzNSdmNtVXVaR1YyTVJFd0R3WURWUVFERXdoemFXZHpkRzl5WlRCMk1CQUdCeXFHU000OUFnRUdCU3VCQkFBaUEySUFCTFN5QTdJaTVrK3BOTzhaRVdZMHlsZW1XRG93T2tOYTNrTCtHWkU1WjVHV2VoTDkvQTliUk5BM1JicnNaNWkwSmNhc3RhUkw3U3A1ZnAvakQ1ZHhxYy9VZFRWbmx2UzE2YW4rMllmc3dlL1F1TG9sUlVDcmNPRTIrMmlBNSt0emQ2Tm1NR1F3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUV3SFFZRFZSME9CQllFRk1qRkhRQkJtaVFwTWxFazZ3MnVTdTFLQnRQc01COEdBMVVkSXdRWU1CYUFGTWpGSFFCQm1pUXBNbEVrNncydVN1MUtCdFBzTUFvR0NDcUdTTTQ5QkFNREEyZ0FNR1VDTUg4bGlXSmZNdWk2dlhYQmhqRGdZNE13c2xtTi9USnhWZS84M1dyRm9td21OZjA1NnkxWDQ4RjljNG0zYTNvelhBSXhBS2pSYXk1L2FqL2pzS0tHSWttUWF0akk4dXVwSHIvK0N4RnZhSldtcFlxTmtMREdSVSs5b3J6aDVoSTJScmN1YVE9PSIKICAgICAgICAgIH0KICAgICAgICBdCiAgICAgIH0sCiAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAic3RhcnQiOiAiMjAyMS0wMy0wN1QwMzoyMDoyOVoiLAogICAgICAgICJlbmQiOiAiMjAyMi0xMi0zMVQyMzo1OTo1OS45OTlaIgogICAgICB9CiAgICB9LAogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly9mdWxjaW8uc2lnc3RvcmUuZGV2IiwKICAgICAgImNlcnRDaGFpbiI6IHsKICAgICAgICAiY2VydGlmaWNhdGVzIjogWwogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQ0dqQ0NBYUdnQXdJQkFnSVVBTG5WaVZmblUwYnJKYXNtUmtIcm4vVW5mYVF3Q2dZSUtvWkl6ajBFQXdNd0tqRVZNQk1HQTFVRUNoTU1jMmxuYzNSdmNtVXVaR1YyTVJFd0R3WURWUVFERXdoemFXZHpkRzl5WlRBZUZ3MHlNakEwTVRNeU1EQTJNVFZhRncwek1URXdNRFV4TXpVMk5UaGFNRGN4RlRBVEJnTlZCQW9UREhOcFozTjBiM0psTG1SbGRqRWVNQndHQTFVRUF4TVZjMmxuYzNSdmNtVXRhVzUwWlhKdFpXUnBZWFJsTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUU4UlZTL3lzSCtOT3Z1RFp5UEladGlsZ1VGOU5sYXJZcEFkOUhQMXZCQkgxVTVDVjc3TFNTN3MwWmlING5FN0h2N3B0UzZMdnZSL1NUazc5OExWZ016TGxKNEhlSWZGM3RIU2FleExjWXBTQVNyMWtTME4vUmdCSnovOWpXQ2lYbm8zc3dlVEFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd013RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVTM5UHB6MVlrRVpiNXFOanBLRldpeGk0WVpEOHdId1lEVlIwakJCZ3dGb0FVV01BZVg1RkZwV2FwZXN5UW9aTWkwQ3JGeGZvd0NnWUlLb1pJemowRUF3TURad0F3WkFJd1BDc1FLNERZaVpZRFBJYURpNUhGS25meFh4NkFTU1ZtRVJmc3luWUJpWDJYNlNKUm5aVTg0LzlEWmRuRnZ2eG1BakJPdDZRcEJsYzRKLzBEeHZrVENxcGNsdnppTDZCQ0NQbmpkbElCM1B1M0J4c1BteWdVWTdJaTJ6YmRDZGxpaW93PSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCOXpDQ0FYeWdBd0lCQWdJVUFMWk5BUEZkeEhQd2plRGxvRHd5WUNoQU8vNHdDZ1lJS29aSXpqMEVBd013S2pFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUkV3RHdZRFZRUURFd2h6YVdkemRHOXlaVEFlRncweU1URXdNRGN4TXpVMk5UbGFGdzB6TVRFd01EVXhNelUyTlRoYU1Db3hGVEFUQmdOVkJBb1RESE5wWjNOMGIzSmxMbVJsZGpFUk1BOEdBMVVFQXhNSWMybG5jM1J2Y21Vd2RqQVFCZ2NxaGtqT1BRSUJCZ1VyZ1FRQUlnTmlBQVQ3WGVGVDRyYjNQUUd3UzRJYWp0TGszL09sbnBnYW5nYUJjbFlwc1lCcjVpKzR5bkIwN2NlYjNMUDBPSU9aZHhleFg2OWM1aVZ1eUpSUStIejA1eWkrVUYzdUJXQWxIcGlTNXNoMCtIMkdIRTdTWHJrMUVDNW0xVHIxOUw5Z2c5MmpZekJoTUE0R0ExVWREd0VCL3dRRUF3SUJCakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlJZd0I1ZmtVV2xacWw2ekpDaGt5TFFLc1hGK2pBZkJnTlZIU01FR0RBV2dCUll3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYrakFLQmdncWhrak9QUVFEQXdOcEFEQm1BakVBajFuSGVYWnArMTNOV0JOYStFRHNEUDhHMVdXZzF0Q01XUC9XSFBxcGFWbzBqaHN3ZU5GWmdTczBlRTd3WUk0cUFqRUEyV0I5b3Q5OHNJa29GM3ZaWWRkMy9WdFdCNWI5VE5NZWE3SXgvc3RKNVRmY0xMZUFCTEU0Qk5KT3NRNHZuQkhKIgogICAgICAgICAgfQogICAgICAgIF0KICAgICAgfSwKICAgICAgInZhbGlkRm9yIjogewogICAgICAgICJzdGFydCI6ICIyMDIyLTA0LTEzVDIwOjA2OjE1WiIKICAgICAgfQogICAgfQogIF0sCiAgImN0bG9ncyI6IFsKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi90ZXN0IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUViZndSK1JKdWRYc2NnUkJScEtYMVhGRHkzUHl1ZER4ei9TZm5SaTFmVDhla3BmQmQyTzF1b3o3anIzWjhuS3p4QTY5RVVRK2VGQ0ZJM3pldWJQV1U3dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMTRUMDA6MDA6MDBaIiwKICAgICAgICAgICJlbmQiOiAiMjAyMi0xMC0zMVQyMzo1OTo1OS45OTlaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICJDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJPSIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi8yMDIyIiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpUFNsRmkwQ21GVGZFakNVcUY5SHVDRWNZWE5LQWFZYWxJSm1CWjh5eWV6UGpUcWh4cktCcE1uYW9jVnRMSkJJMWVNM3VYblF6UUdBSmRKNGdzOUZ5dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjItMTAtMjBUMDA6MDA6MDBaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICIzVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280PSIKICAgICAgfQogICAgfQogIF0sCiAgInRpbWVzdGFtcEF1dGhvcml0aWVzIjogWwogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUtdHNhLXNlbGZzaWduZWQiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly90aW1lc3RhbXAuc2lnc3RvcmUuZGV2L2FwaS92MS90aW1lc3RhbXAiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlDRURDQ0FaYWdBd0lCQWdJVU9oTlVMd3lRWWU2OHdVTXZ5NHFPaXlvaml3d3dDZ1lJS29aSXpqMEVBd013T1RFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNU0F3SGdZRFZRUURFeGR6YVdkemRHOXlaUzEwYzJFdGMyVnNabk5wWjI1bFpEQWVGdzB5TlRBME1EZ3dOalU1TkROYUZ3MHpOVEEwTURZd05qVTVORE5hTUM0eEZUQVRCZ05WQkFvVERITnBaM04wYjNKbExtUmxkakVWTUJNR0ExVUVBeE1NYzJsbmMzUnZjbVV0ZEhOaE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFNHJhMlo4aEtOaWcyVDlrRmpDQVRvR0czMGpreStXUXYzQnpMK21LdmgxU0tOUi9Vd3V3c2ZOQ2c0c3J5b1lBZDhFNmlzb3ZWQTNNNGFvTmRtOVFEaTUwWjhuVEV5dnFnZkRQdFRJd1hJdGZpVy9BRmYxVjd1d2tia0FvajB4eGNvMm93YURBT0JnTlZIUThCQWY4RUJBTUNCNEF3SFFZRFZSME9CQllFRkluOWVVT0h6OUJsUnNNQ1JzY3NjMXQ5dE9zRE1COEdBMVVkSXdRWU1CYUFGSmpzQWU5L3UxSC8xSlVlYjRxSW1GTUhpYzYvTUJZR0ExVWRKUUVCL3dRTU1Bb0dDQ3NHQVFVRkJ3TUlNQW9HQ0NxR1NNNDlCQU1EQTJnQU1HVUNNRHRwc1YvNkthTzBxeUYvVU1zWDJhU1VYS1FGZG9HVHB0UUdjMGZ0cTFjc3VsSFBHRzZkc215TU5kM0pCK0czRVFJeEFPYWp2QmNqcEptS2I0TnYrMlRhb2o4VWM1K2I2aWg2RlhDQ0tyYVNxdXBlMDd6cXN3TWNYSlRlMWNFeHZIdnZsdz09IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUI5ekNDQVh5Z0F3SUJBZ0lVVjdmMEdMRE9vRXpJaDhMWFNXODBPSmlVcDE0d0NnWUlLb1pJemowRUF3TXdPVEVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1TQXdIZ1lEVlFRREV4ZHphV2R6ZEc5eVpTMTBjMkV0YzJWc1puTnBaMjVsWkRBZUZ3MHlOVEEwTURnd05qVTVORE5hRncwek5UQTBNRFl3TmpVNU5ETmFNRGt4RlRBVEJnTlZCQW9UREhOcFozTjBiM0psTG1SbGRqRWdNQjRHQTFVRUF4TVhjMmxuYzNSdmNtVXRkSE5oTFhObGJHWnphV2R1WldRd2RqQVFCZ2NxaGtqT1BRSUJCZ1VyZ1FRQUlnTmlBQVFVUU50ZlJUL291M1lBVGE2d0Iva0tUZTcwY2ZKd3lSSUJvdk1udDhSY0pwaC9DT0U4MnV5UzZGbXBwTExMMVZCUEdjUGZwUVBZSk5Yeld3aThpY3doS1E2Vy9RZTJoM29lYkJiMkZIcHdOSkRxbytUTWFDL3RkZmt2L0VsSkI3MmpSVEJETUE0R0ExVWREd0VCL3dRRUF3SUJCakFTQmdOVkhSTUJBZjhFQ0RBR0FRSC9BZ0VBTUIwR0ExVWREZ1FXQkJTWTdBSHZmN3RSLzlTVkhtK0tpSmhUQjRuT3Z6QUtCZ2dxaGtqT1BRUURBd05wQURCbUFqRUF3R0VHcmZHWlIxY2VuMVI4L0RUVk1JOTQzTHNzWm1KUnREcC9pN1NmR0htR1JQNmdSYnVqOXZPSzNiNjdaMFFRQWpFQXVUMkg2NzNMUUVhSFRjeVFTWnJrcDRtWDdXd2ttRitzVmJrWVk1bVhOK1JNSDEzS1VFSEhPcUFTYWVtWVdLL0UiCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjUtMDctMDRUMDA6MDA6MDBaIgogICAgICB9CiAgICB9CiAgXQp9Cg==","registry.npmjs.org%2Fkeys.json":"ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTFPbGIzek1BRkZ4WEtIaUlrUU81Y0ozWWhsNWk2VVBwK0lodXRlQkpidUhjQTVVb2dLbzBFV3RsV3dXNktTYUtvVE5FWUw3SmxDUWlWbmtoQmt0VWdnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIxOTk5LTAxLTAxVDAwOjAwOjAwLjAwMFoiLAogICAgICAgICAgICAgICAgICAgICJlbmQiOiAiMjAyNS0wMS0yOVQwMDowMDowMC4wMDBaIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJrZXlJZCI6ICJTSEEyNTY6amwzYndzd3U4MFBqam9rQ2doMG8ydzVjMlU0TGhRQUU1N2dqOWN6MWt6QSIsCiAgICAgICAgICAgICJrZXlVc2FnZSI6ICJucG06YXR0ZXN0YXRpb25zIiwKICAgICAgICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxT2xiM3pNQUZGeFhLSGlJa1FPNWNKM1lobDVpNlVQcCtJaHV0ZUJKYnVIY0E1VW9nS28wRVd0bFd3VzZLU2FLb1RORVlMN0psQ1FpVm5raEJrdFVnZz09IiwKICAgICAgICAgICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwKICAgICAgICAgICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAiMjAyMi0xMi0wMVQwMDowMDowMC4wMDBaIiwKICAgICAgICAgICAgICAgICAgICAiZW5kIjogIjIwMjUtMDEtMjlUMDA6MDA6MDAuMDAwWiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OkRoUTh3UjVBUEJ2RkhMRi8rVGMrQVl2UE9kVHBjSURxT2h4c0JIUndDN1UiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImtleUlkIjogIlNIQTI1NjpEaFE4d1I1QVBCdkZITEYvK1RjK0FZdlBPZFRwY0lEcU9oeHNCSFJ3QzdVIiwKICAgICAgICAgICAgImtleVVzYWdlIjogIm5wbTphdHRlc3RhdGlvbnMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K"}}} +{"https://tuf-repo-cdn.sigstore.dev":{"root.json":"ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGUwNGM5NzA2Mjk5YmU1ZDhjMmIxNGZiNTBiY2Q1YjljMjQxZjEwNTk3MTUzZGZlMjJmOTQzZWZlODk2YjUxNTAwMjIxMDBjZmQ3YjlmMDZhNTkwMDc4NGUzMTJkMDJiOGUzMzZlZGJiM2IyZmFiNjFhYzE0NTUwYjMxMTJiNGY5ZTMzZGY0IgogIH0sCiAgewogICAia2V5aWQiOiAiMjJmNGNhZWM2ZDhlNmY5NTU1YWY2NmIzZDRjM2NiMDZhM2JiMjNmZGM3ZTM5YzkxNmM2MWY0NjJlNmY1MmIwNiIsCiAgICJzaWciOiAiIgogIH0sCiAgewogICAia2V5aWQiOiAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGNjMzA4YWU3ZDM5MGZhNzgyZWUzMzc2ZGRmYWE5Mjk4MzUwMTZlODZkYWQ4MWY2OWUyZGU3ZWMxZTE3NDQzMmUwMjIwNWZiMTk5MDZhMzFjY2UxNDZjMjk2MjQ0NDNjMGQwYzJmMzNlZTgwZGFjMzlkNzIxMTRmOTM5NjA3Y2MyMjkzNyIKICB9LAogIHsKICAgImtleWlkIjogImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiLAogICAic2lnIjogIjMwNDUwMjIwM2Y4YWZmN2EzMGUwNWE4YzNkOTA0YjY3MWFiMWE2ZTRlOGE2ZjUwOGI3Y2ZhMGM3ODBlNzI5NzZiZWU3YTIyNzAyMjEwMGY2NGM5Yjc2NTUyNmYzNGQ5ZWExNjMzOWNmMjM4ODkzZTFjMzM2OGI0ZjA5MTBhNjFhMWFmMjdkZGEwMWViYjkiCiAgfSwKICB7CiAgICJrZXlpZCI6ICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIiwKICAgInNpZyI6ICIzMDQ1MDIyMDIzNjNjYTI0OWFlZmE2ZDVmNjFjNDA4YTMyY2RkMDc5YjAzNGE3ODg4ZGRmMjEzNmRjNDUxNWVkNGE3Mjg0MTgwMjIxMDBiMDRlY2E0MmJjNTEwY2NiYmY1ZDMwNzgzYWFhOTM2YjFmMTM3Y2E3YTAxN2VlOWQ5MGQzNzEwNDMyZGEwNDI3IgogIH0KIF0sCiAic2lnbmVkIjogewogICJfdHlwZSI6ICJyb290IiwKICAiY29uc2lzdGVudF9zbmFwc2hvdCI6IHRydWUsCiAgImV4cGlyZXMiOiAiMjAyNi0wNi0yMlQxMzoyNzowMVoiLAogICJrZXlzIjogewogICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVdSaUdyNStqKzNKNVNzSCtadHI1bkUySDJ3TzdcbkJWK25PM3M5M2dMY2ExOHFUT3pIWTFvV3lBR0R5a01Tc0dUVUJTdDlEK0FuMEtmS3NEMm1mU000MlE9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1vbmxpbmUtdXJpIjogImdjcGttczpwcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wL2NyeXB0b0tleVZlcnNpb25zLzEiCiAgIH0sCiAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIjogewogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVNeHBQT0pDSVo1b3RHNDEwNmZHSnNlRVFpM1Y5XG5wa01ZUTR1eVY5VGoxTTdXSFhJeUxHK2prZnZ1RzBnbFExSlpiUlpaQlYzZ0FSNHNvamRHSElTZW93PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGxhbmNlIgogICB9LAogICAiMjJmNGNhZWM2ZDhlNmY5NTU1YWY2NmIzZDRjM2NiMDZhM2JiMjNmZGM3ZTM5YzkxNmM2MWY0NjJlNmY1MmIwNiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXpCelZPbUhDUG9qTVZMU0kzNjRXaWlWOE5QckRcbjZJZ1J4Vmxpc2t6L3YreTNKRVI1bWNWR2NPTmxpRGNXTUM1SjJsZkhtalBOUGhiNEg3eG04THpmU0E9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAc2FudGlhZ290b3JyZXMiCiAgIH0sCiAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaW5pa1NzQVFtWWtOZUg1ZVlxL0NuSXpMYWFjT1xueGxTYWF3UURPd3FLeS90Q3F4cTV4eFBTSmMyMUs0V0loczlHeU9rS2Z6dWVZM0dJTHpjTUpaNGNXdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBib2JjYWxsYXdheSIKICAgfSwKICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUwZ2hyaDkyTHcxWXIzaWRHVjVXcUN0TURCOEN4XG4rRDhoZEM0dzJaTE5JcGxWUm9WR0xza1lhM2doZU15T2ppSjhrUGkxNWFRMi8vN1Arb2o3VXZKUEd3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGpvc2h1YWdsIgogICB9LAogICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUVYc3ozU1pYRmI4ak1WNDJqNnBKbHlqYmpSOEtcbk4zQndvY2V4cTZMTUliNXFzV0tPUXZMTjE2TlVlZkxjNEhzd09vdW1Sc1ZWYWFqU3BRUzZmb2JrUnc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAbW5tNjc4IgogICB9CiAgfSwKICAicm9sZXMiOiB7CiAgICJyb290IjogewogICAgImtleWlkcyI6IFsKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICAgIjE4M2U2NGYzNzY3MGRjMTNjYTBkMjg5OTVhMzA1M2YzNzQwOTU0ZGRjZTQ0MzIxYTQxZTQ2NTM0Y2Y0NGU2MzIiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInNuYXBzaG90IjogewogICAgImtleWlkcyI6IFsKICAgICAiMGM4NzQzMmMzYmYwOWZkOTkxODlmZGMzMmZhNWVhZWRmNGU0YTVmYWM3YmFiNzNmYTA0YTJlMGZjNjRhZjZmNSIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMSwKICAgICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMzY1MCwKICAgICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDM2NQogICB9LAogICAidGFyZ2V0cyI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiLAogICAgICIxODNlNjRmMzc2NzBkYzEzY2EwZDI4OTk1YTMwNTNmMzc0MDk1NGRkY2U0NDMyMWE0MWU0NjUzNGNmNDRlNjMyIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAzCiAgIH0sCiAgICJ0aW1lc3RhbXAiOiB7CiAgICAia2V5aWRzIjogWwogICAgICIwYzg3NDMyYzNiZjA5ZmQ5OTE4OWZkYzMyZmE1ZWFlZGY0ZTRhNWZhYzdiYWI3M2ZhMDRhMmUwZmM2NGFmNmY1IgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiA3LAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogNgogICB9CiAgfSwKICAic3BlY192ZXJzaW9uIjogIjEuMCIsCiAgInZlcnNpb24iOiAxNCwKICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDE5NywKICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0NgogfQp9","targets":{"trusted_root.json":"ewogICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRldi5zaWdzdG9yZS50cnVzdGVkcm9vdCtqc29uO3ZlcnNpb249MC4xIiwKICAidGxvZ3MiOiBbCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vcmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUyRzJZKzJ0YWJkVFY1QmNHaUJJeDBhOWZBRndya0JibUxTR3RrczRMM3FYNnlZWTB6dWZCbmhDOFVyL2l5NTVHaFdQLzlBL2JZMkxoQzMwTTkrUll0dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDEtMTJUMTE6NTM6MjdaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICJ3Tkk5YXRRR2x6K1ZXZk82TFJ5Z0g0UVVmWS84VzRSRndpVDVpNVdSZ0IwPSIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9sb2cyMDI1LTEucmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNQ293QlFZREsyVndBeUVBdDhybHAxa25Hd2pmYmNYQVlQWUFrbjBYaUx6MXg4TzR0MFlrRWhpZTI0ND0iLAogICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUQyNTUxOSIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjUtMDktMjNUMDA6MDA6MDBaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICJ6eEdaRlZ2ZDBGRW1qUjhXckZ3TWRjQUo5dnRhWS9RWGY0NFkxd1VlUDZBPSIKICAgICAgfQogICAgfQogIF0sCiAgImNlcnRpZmljYXRlQXV0aG9yaXRpZXMiOiBbCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAic2lnc3RvcmUuZGV2IiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJzaWdzdG9yZSIKICAgICAgfSwKICAgICAgInVyaSI6ICJodHRwczovL2Z1bGNpby5zaWdzdG9yZS5kZXYiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCK0RDQ0FYNmdBd0lCQWdJVE5Wa0Rab0Npb2ZQRHN5N2RmbTZnZUxidWh6QUtCZ2dxaGtqT1BRUURBekFxTVJVd0V3WURWUVFLRXd4emFXZHpkRzl5WlM1a1pYWXhFVEFQQmdOVkJBTVRDSE5wWjNOMGIzSmxNQjRYRFRJeE1ETXdOekF6TWpBeU9Wb1hEVE14TURJeU16QXpNakF5T1Zvd0tqRVZNQk1HQTFVRUNoTU1jMmxuYzNSdmNtVXVaR1YyTVJFd0R3WURWUVFERXdoemFXZHpkRzl5WlRCMk1CQUdCeXFHU000OUFnRUdCU3VCQkFBaUEySUFCTFN5QTdJaTVrK3BOTzhaRVdZMHlsZW1XRG93T2tOYTNrTCtHWkU1WjVHV2VoTDkvQTliUk5BM1JicnNaNWkwSmNhc3RhUkw3U3A1ZnAvakQ1ZHhxYy9VZFRWbmx2UzE2YW4rMllmc3dlL1F1TG9sUlVDcmNPRTIrMmlBNSt0emQ2Tm1NR1F3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUV3SFFZRFZSME9CQllFRk1qRkhRQkJtaVFwTWxFazZ3MnVTdTFLQnRQc01COEdBMVVkSXdRWU1CYUFGTWpGSFFCQm1pUXBNbEVrNncydVN1MUtCdFBzTUFvR0NDcUdTTTQ5QkFNREEyZ0FNR1VDTUg4bGlXSmZNdWk2dlhYQmhqRGdZNE13c2xtTi9USnhWZS84M1dyRm9td21OZjA1NnkxWDQ4RjljNG0zYTNvelhBSXhBS2pSYXk1L2FqL2pzS0tHSWttUWF0akk4dXVwSHIvK0N4RnZhSldtcFlxTmtMREdSVSs5b3J6aDVoSTJScmN1YVE9PSIKICAgICAgICAgIH0KICAgICAgICBdCiAgICAgIH0sCiAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAic3RhcnQiOiAiMjAyMS0wMy0wN1QwMzoyMDoyOVoiLAogICAgICAgICJlbmQiOiAiMjAyMi0xMi0zMVQyMzo1OTo1OS45OTlaIgogICAgICB9CiAgICB9LAogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly9mdWxjaW8uc2lnc3RvcmUuZGV2IiwKICAgICAgImNlcnRDaGFpbiI6IHsKICAgICAgICAiY2VydGlmaWNhdGVzIjogWwogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQ0dqQ0NBYUdnQXdJQkFnSVVBTG5WaVZmblUwYnJKYXNtUmtIcm4vVW5mYVF3Q2dZSUtvWkl6ajBFQXdNd0tqRVZNQk1HQTFVRUNoTU1jMmxuYzNSdmNtVXVaR1YyTVJFd0R3WURWUVFERXdoemFXZHpkRzl5WlRBZUZ3MHlNakEwTVRNeU1EQTJNVFZhRncwek1URXdNRFV4TXpVMk5UaGFNRGN4RlRBVEJnTlZCQW9UREhOcFozTjBiM0psTG1SbGRqRWVNQndHQTFVRUF4TVZjMmxuYzNSdmNtVXRhVzUwWlhKdFpXUnBZWFJsTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUU4UlZTL3lzSCtOT3Z1RFp5UEladGlsZ1VGOU5sYXJZcEFkOUhQMXZCQkgxVTVDVjc3TFNTN3MwWmlING5FN0h2N3B0UzZMdnZSL1NUazc5OExWZ016TGxKNEhlSWZGM3RIU2FleExjWXBTQVNyMWtTME4vUmdCSnovOWpXQ2lYbm8zc3dlVEFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd013RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVTM5UHB6MVlrRVpiNXFOanBLRldpeGk0WVpEOHdId1lEVlIwakJCZ3dGb0FVV01BZVg1RkZwV2FwZXN5UW9aTWkwQ3JGeGZvd0NnWUlLb1pJemowRUF3TURad0F3WkFJd1BDc1FLNERZaVpZRFBJYURpNUhGS25meFh4NkFTU1ZtRVJmc3luWUJpWDJYNlNKUm5aVTg0LzlEWmRuRnZ2eG1BakJPdDZRcEJsYzRKLzBEeHZrVENxcGNsdnppTDZCQ0NQbmpkbElCM1B1M0J4c1BteWdVWTdJaTJ6YmRDZGxpaW93PSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCOXpDQ0FYeWdBd0lCQWdJVUFMWk5BUEZkeEhQd2plRGxvRHd5WUNoQU8vNHdDZ1lJS29aSXpqMEVBd013S2pFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUkV3RHdZRFZRUURFd2h6YVdkemRHOXlaVEFlRncweU1URXdNRGN4TXpVMk5UbGFGdzB6TVRFd01EVXhNelUyTlRoYU1Db3hGVEFUQmdOVkJBb1RESE5wWjNOMGIzSmxMbVJsZGpFUk1BOEdBMVVFQXhNSWMybG5jM1J2Y21Vd2RqQVFCZ2NxaGtqT1BRSUJCZ1VyZ1FRQUlnTmlBQVQ3WGVGVDRyYjNQUUd3UzRJYWp0TGszL09sbnBnYW5nYUJjbFlwc1lCcjVpKzR5bkIwN2NlYjNMUDBPSU9aZHhleFg2OWM1aVZ1eUpSUStIejA1eWkrVUYzdUJXQWxIcGlTNXNoMCtIMkdIRTdTWHJrMUVDNW0xVHIxOUw5Z2c5MmpZekJoTUE0R0ExVWREd0VCL3dRRUF3SUJCakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlJZd0I1ZmtVV2xacWw2ekpDaGt5TFFLc1hGK2pBZkJnTlZIU01FR0RBV2dCUll3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYrakFLQmdncWhrak9QUVFEQXdOcEFEQm1BakVBajFuSGVYWnArMTNOV0JOYStFRHNEUDhHMVdXZzF0Q01XUC9XSFBxcGFWbzBqaHN3ZU5GWmdTczBlRTd3WUk0cUFqRUEyV0I5b3Q5OHNJa29GM3ZaWWRkMy9WdFdCNWI5VE5NZWE3SXgvc3RKNVRmY0xMZUFCTEU0Qk5KT3NRNHZuQkhKIgogICAgICAgICAgfQogICAgICAgIF0KICAgICAgfSwKICAgICAgInZhbGlkRm9yIjogewogICAgICAgICJzdGFydCI6ICIyMDIyLTA0LTEzVDIwOjA2OjE1WiIKICAgICAgfQogICAgfQogIF0sCiAgImN0bG9ncyI6IFsKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi90ZXN0IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUViZndSK1JKdWRYc2NnUkJScEtYMVhGRHkzUHl1ZER4ei9TZm5SaTFmVDhla3BmQmQyTzF1b3o3anIzWjhuS3p4QTY5RVVRK2VGQ0ZJM3pldWJQV1U3dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMTRUMDA6MDA6MDBaIiwKICAgICAgICAgICJlbmQiOiAiMjAyMi0xMC0zMVQyMzo1OTo1OS45OTlaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICJDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJPSIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi8yMDIyIiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpUFNsRmkwQ21GVGZFakNVcUY5SHVDRWNZWE5LQWFZYWxJSm1CWjh5eWV6UGpUcWh4cktCcE1uYW9jVnRMSkJJMWVNM3VYblF6UUdBSmRKNGdzOUZ5dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjItMTAtMjBUMDA6MDA6MDBaIgogICAgICAgIH0KICAgICAgfSwKICAgICAgImxvZ0lkIjogewogICAgICAgICJrZXlJZCI6ICIzVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280PSIKICAgICAgfQogICAgfQogIF0sCiAgInRpbWVzdGFtcEF1dGhvcml0aWVzIjogWwogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUtdHNhLXNlbGZzaWduZWQiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly90aW1lc3RhbXAuc2lnc3RvcmUuZGV2L2FwaS92MS90aW1lc3RhbXAiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlDRURDQ0FaYWdBd0lCQWdJVU9oTlVMd3lRWWU2OHdVTXZ5NHFPaXlvaml3d3dDZ1lJS29aSXpqMEVBd013T1RFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNU0F3SGdZRFZRUURFeGR6YVdkemRHOXlaUzEwYzJFdGMyVnNabk5wWjI1bFpEQWVGdzB5TlRBME1EZ3dOalU1TkROYUZ3MHpOVEEwTURZd05qVTVORE5hTUM0eEZUQVRCZ05WQkFvVERITnBaM04wYjNKbExtUmxkakVWTUJNR0ExVUVBeE1NYzJsbmMzUnZjbVV0ZEhOaE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFNHJhMlo4aEtOaWcyVDlrRmpDQVRvR0czMGpreStXUXYzQnpMK21LdmgxU0tOUi9Vd3V3c2ZOQ2c0c3J5b1lBZDhFNmlzb3ZWQTNNNGFvTmRtOVFEaTUwWjhuVEV5dnFnZkRQdFRJd1hJdGZpVy9BRmYxVjd1d2tia0FvajB4eGNvMm93YURBT0JnTlZIUThCQWY4RUJBTUNCNEF3SFFZRFZSME9CQllFRkluOWVVT0h6OUJsUnNNQ1JzY3NjMXQ5dE9zRE1COEdBMVVkSXdRWU1CYUFGSmpzQWU5L3UxSC8xSlVlYjRxSW1GTUhpYzYvTUJZR0ExVWRKUUVCL3dRTU1Bb0dDQ3NHQVFVRkJ3TUlNQW9HQ0NxR1NNNDlCQU1EQTJnQU1HVUNNRHRwc1YvNkthTzBxeUYvVU1zWDJhU1VYS1FGZG9HVHB0UUdjMGZ0cTFjc3VsSFBHRzZkc215TU5kM0pCK0czRVFJeEFPYWp2QmNqcEptS2I0TnYrMlRhb2o4VWM1K2I2aWg2RlhDQ0tyYVNxdXBlMDd6cXN3TWNYSlRlMWNFeHZIdnZsdz09IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUI5ekNDQVh5Z0F3SUJBZ0lVVjdmMEdMRE9vRXpJaDhMWFNXODBPSmlVcDE0d0NnWUlLb1pJemowRUF3TXdPVEVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1TQXdIZ1lEVlFRREV4ZHphV2R6ZEc5eVpTMTBjMkV0YzJWc1puTnBaMjVsWkRBZUZ3MHlOVEEwTURnd05qVTVORE5hRncwek5UQTBNRFl3TmpVNU5ETmFNRGt4RlRBVEJnTlZCQW9UREhOcFozTjBiM0psTG1SbGRqRWdNQjRHQTFVRUF4TVhjMmxuYzNSdmNtVXRkSE5oTFhObGJHWnphV2R1WldRd2RqQVFCZ2NxaGtqT1BRSUJCZ1VyZ1FRQUlnTmlBQVFVUU50ZlJUL291M1lBVGE2d0Iva0tUZTcwY2ZKd3lSSUJvdk1udDhSY0pwaC9DT0U4MnV5UzZGbXBwTExMMVZCUEdjUGZwUVBZSk5Yeld3aThpY3doS1E2Vy9RZTJoM29lYkJiMkZIcHdOSkRxbytUTWFDL3RkZmt2L0VsSkI3MmpSVEJETUE0R0ExVWREd0VCL3dRRUF3SUJCakFTQmdOVkhSTUJBZjhFQ0RBR0FRSC9BZ0VBTUIwR0ExVWREZ1FXQkJTWTdBSHZmN3RSLzlTVkhtK0tpSmhUQjRuT3Z6QUtCZ2dxaGtqT1BRUURBd05wQURCbUFqRUF3R0VHcmZHWlIxY2VuMVI4L0RUVk1JOTQzTHNzWm1KUnREcC9pN1NmR0htR1JQNmdSYnVqOXZPSzNiNjdaMFFRQWpFQXVUMkg2NzNMUUVhSFRjeVFTWnJrcDRtWDdXd2ttRitzVmJrWVk1bVhOK1JNSDEzS1VFSEhPcUFTYWVtWVdLL0UiCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjUtMDctMDRUMDA6MDA6MDBaIgogICAgICB9CiAgICB9CiAgXQp9Cg==","registry.npmjs.org%2Fkeys.json":"ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTFPbGIzek1BRkZ4WEtIaUlrUU81Y0ozWWhsNWk2VVBwK0lodXRlQkpidUhjQTVVb2dLbzBFV3RsV3dXNktTYUtvVE5FWUw3SmxDUWlWbmtoQmt0VWdnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIxOTk5LTAxLTAxVDAwOjAwOjAwLjAwMFoiLAogICAgICAgICAgICAgICAgICAgICJlbmQiOiAiMjAyNS0wMS0yOVQwMDowMDowMC4wMDBaIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJrZXlJZCI6ICJTSEEyNTY6amwzYndzd3U4MFBqam9rQ2doMG8ydzVjMlU0TGhRQUU1N2dqOWN6MWt6QSIsCiAgICAgICAgICAgICJrZXlVc2FnZSI6ICJucG06YXR0ZXN0YXRpb25zIiwKICAgICAgICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxT2xiM3pNQUZGeFhLSGlJa1FPNWNKM1lobDVpNlVQcCtJaHV0ZUJKYnVIY0E1VW9nS28wRVd0bFd3VzZLU2FLb1RORVlMN0psQ1FpVm5raEJrdFVnZz09IiwKICAgICAgICAgICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwKICAgICAgICAgICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAiMjAyMi0xMi0wMVQwMDowMDowMC4wMDBaIiwKICAgICAgICAgICAgICAgICAgICAiZW5kIjogIjIwMjUtMDEtMjlUMDA6MDA6MDAuMDAwWiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OkRoUTh3UjVBUEJ2RkhMRi8rVGMrQVl2UE9kVHBjSURxT2h4c0JIUndDN1UiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImtleUlkIjogIlNIQTI1NjpEaFE4d1I1QVBCdkZITEYvK1RjK0FZdlBPZFRwY0lEcU9oeHNCSFJ3QzdVIiwKICAgICAgICAgICAgImtleVVzYWdlIjogIm5wbTphdHRlc3RhdGlvbnMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K"}}} diff --git a/deps/npm/node_modules/cacache/lib/content/write.js b/deps/npm/node_modules/cacache/lib/content/write.js index e7187abca8788a..8deb91c066a964 100644 --- a/deps/npm/node_modules/cacache/lib/content/write.js +++ b/deps/npm/node_modules/cacache/lib/content/write.js @@ -10,7 +10,7 @@ const Pipeline = require('minipass-pipeline') const Flush = require('minipass-flush') const path = require('path') const ssri = require('ssri') -const uniqueFilename = require('unique-filename') +const { tmpName } = require('../util/tmp') const fsm = require('fs-minipass') module.exports = write @@ -152,7 +152,7 @@ async function pipeToTmp (inputStream, cache, tmpTarget, opts) { } async function makeTmp (cache, opts) { - const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix) + const tmpTarget = tmpName(cache, opts.tmpPrefix) await fs.mkdir(path.dirname(tmpTarget), { recursive: true }) return { target: tmpTarget, diff --git a/deps/npm/node_modules/cacache/lib/entry-index.js b/deps/npm/node_modules/cacache/lib/entry-index.js index 0e09b10818d097..a8fdf93a03e424 100644 --- a/deps/npm/node_modules/cacache/lib/entry-index.js +++ b/deps/npm/node_modules/cacache/lib/entry-index.js @@ -12,7 +12,7 @@ const { const { Minipass } = require('minipass') const path = require('path') const ssri = require('ssri') -const uniqueFilename = require('unique-filename') +const { tmpName } = require('./util/tmp') const contentPath = require('./content/path') const hashToSegments = require('./util/hash-to-segments') @@ -69,7 +69,7 @@ async function compact (cache, key, matchFn, opts = {}) { }).join('\n') const setup = async () => { - const target = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix) + const target = tmpName(cache, opts.tmpPrefix) await mkdir(path.dirname(target), { recursive: true }) return { target, diff --git a/deps/npm/node_modules/cacache/lib/util/tmp.js b/deps/npm/node_modules/cacache/lib/util/tmp.js index 0bf5302136ebeb..d5c088e1e6de01 100644 --- a/deps/npm/node_modules/cacache/lib/util/tmp.js +++ b/deps/npm/node_modules/cacache/lib/util/tmp.js @@ -1,11 +1,17 @@ 'use strict' +const crypto = require('crypto') const { withTempDir } = require('@npmcli/fs') const fs = require('fs/promises') const path = require('path') module.exports.mkdir = mktmpdir +module.exports.tmpName = function tmpName (cache, tmpPrefix) { + const id = crypto.randomUUID() + return path.join(cache, 'tmp', tmpPrefix ? `${tmpPrefix}-${id}` : id) +} + async function mktmpdir (cache, opts = {}) { const { tmpPrefix } = opts const tmpDir = path.join(cache, 'tmp') diff --git a/deps/npm/node_modules/cacache/package.json b/deps/npm/node_modules/cacache/package.json index fb8eb5f66edf93..170e43646fa2b1 100644 --- a/deps/npm/node_modules/cacache/package.json +++ b/deps/npm/node_modules/cacache/package.json @@ -1,6 +1,6 @@ { "name": "cacache", - "version": "20.0.3", + "version": "20.0.4", "cache-version": { "content": "2", "index": "5" @@ -55,12 +55,11 @@ "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^13.0.0", - "unique-filename": "^5.0.0" + "ssri": "^13.0.0" }, "devDependencies": { "@npmcli/eslint-config": "^6.0.1", - "@npmcli/template-oss": "4.28.0", + "@npmcli/template-oss": "4.29.0", "tap": "^16.0.0" }, "engines": { @@ -69,7 +68,7 @@ "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", "windowsCI": false, - "version": "4.28.0", + "version": "4.29.0", "publish": "true" }, "author": "GitHub Inc.", diff --git a/deps/npm/node_modules/err-code/bower.json b/deps/npm/node_modules/err-code/bower.json deleted file mode 100644 index a39cb702cedb21..00000000000000 --- a/deps/npm/node_modules/err-code/bower.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "err-code", - "version": "1.1.1", - "description": "Create new error instances with a code and additional properties", - "main": "index.umd.js", - "homepage": "https://github.com/IndigoUnited/js-err-code", - "authors": [ - "IndigoUnited (http://indigounited.com)" - ], - "moduleType": [ - "amd", - "globals", - "node" - ], - "keywords": [ - "error", - "err", - "code", - "properties", - "property" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] -} diff --git a/deps/npm/node_modules/err-code/index.js b/deps/npm/node_modules/err-code/index.js deleted file mode 100644 index 9ff3e9c5de4c2c..00000000000000 --- a/deps/npm/node_modules/err-code/index.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -function assign(obj, props) { - for (const key in props) { - Object.defineProperty(obj, key, { - value: props[key], - enumerable: true, - configurable: true, - }); - } - - return obj; -} - -function createError(err, code, props) { - if (!err || typeof err === 'string') { - throw new TypeError('Please pass an Error to err-code'); - } - - if (!props) { - props = {}; - } - - if (typeof code === 'object') { - props = code; - code = undefined; - } - - if (code != null) { - props.code = code; - } - - try { - return assign(err, props); - } catch (_) { - props.message = err.message; - props.stack = err.stack; - - const ErrClass = function () {}; - - ErrClass.prototype = Object.create(Object.getPrototypeOf(err)); - - return assign(new ErrClass(), props); - } -} - -module.exports = createError; diff --git a/deps/npm/node_modules/err-code/index.umd.js b/deps/npm/node_modules/err-code/index.umd.js deleted file mode 100644 index 41007269d3d039..00000000000000 --- a/deps/npm/node_modules/err-code/index.umd.js +++ /dev/null @@ -1,51 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.errCode = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i index.umd.js" - }, - "bugs": { - "url": "https://github.com/IndigoUnited/js-err-code/issues/" - }, - "repository": { - "type": "git", - "url": "git://github.com/IndigoUnited/js-err-code.git" - }, - "keywords": [ - "error", - "err", - "code", - "properties", - "property" - ], - "author": "IndigoUnited (http://indigounited.com)", - "license": "MIT", - "devDependencies": { - "@satazor/eslint-config": "^3.0.0", - "browserify": "^16.5.1", - "eslint": "^7.2.0", - "expect.js": "^0.3.1", - "mocha": "^8.0.1" - } -} diff --git a/deps/npm/node_modules/err-code/test/test.js b/deps/npm/node_modules/err-code/test/test.js deleted file mode 100644 index 22ba0a8a1a8c1f..00000000000000 --- a/deps/npm/node_modules/err-code/test/test.js +++ /dev/null @@ -1,159 +0,0 @@ -'use strict'; - -const errcode = require('../index'); -const expect = require('expect.js'); - -describe('errcode', () => { - describe('string as first argument', () => { - it('should throw an error', () => { - expect(() => { errcode('my message'); }).to.throwError((err) => { - expect(err).to.be.a(TypeError); - }); - }); - }); - - describe('error as first argument', () => { - it('should accept an error and do nothing', () => { - const myErr = new Error('my message'); - const err = errcode(myErr); - - expect(err).to.be(myErr); - expect(err.hasOwnProperty(err.code)).to.be(false); - }); - - it('should accept an error and add a code', () => { - const myErr = new Error('my message'); - const err = errcode(myErr, 'ESOME'); - - expect(err).to.be(myErr); - expect(err.code).to.be('ESOME'); - }); - - it('should accept an error object and add code & properties', () => { - const myErr = new Error('my message'); - const err = errcode(myErr, 'ESOME', { foo: 'bar', bar: 'foo' }); - - expect(err).to.be.an(Error); - expect(err.code).to.be('ESOME'); - expect(err.foo).to.be('bar'); - expect(err.bar).to.be('foo'); - }); - - it('should create an error object without code but with properties', () => { - const myErr = new Error('my message'); - const err = errcode(myErr, { foo: 'bar', bar: 'foo' }); - - expect(err).to.be.an(Error); - expect(err.code).to.be(undefined); - expect(err.foo).to.be('bar'); - expect(err.bar).to.be('foo'); - }); - - it('should set a non-writable field', () => { - const myErr = new Error('my message'); - - Object.defineProperty(myErr, 'code', { - value: 'derp', - writable: false, - }); - const err = errcode(myErr, 'ERR_WAT'); - - expect(err).to.be.an(Error); - expect(err.stack).to.equal(myErr.stack); - expect(err.code).to.be('ERR_WAT'); - }); - - it('should add a code to frozen object', () => { - const myErr = new Error('my message'); - const err = errcode(Object.freeze(myErr), 'ERR_WAT'); - - expect(err).to.be.an(Error); - expect(err.stack).to.equal(myErr.stack); - expect(err.code).to.be('ERR_WAT'); - }); - - it('should to set a field that throws at assignment time', () => { - const myErr = new Error('my message'); - - Object.defineProperty(myErr, 'code', { - enumerable: true, - set() { - throw new Error('Nope!'); - }, - get() { - return 'derp'; - }, - }); - const err = errcode(myErr, 'ERR_WAT'); - - expect(err).to.be.an(Error); - expect(err.stack).to.equal(myErr.stack); - expect(err.code).to.be('ERR_WAT'); - }); - - it('should retain error type', () => { - const myErr = new TypeError('my message'); - - Object.defineProperty(myErr, 'code', { - value: 'derp', - writable: false, - }); - const err = errcode(myErr, 'ERR_WAT'); - - expect(err).to.be.a(TypeError); - expect(err.stack).to.equal(myErr.stack); - expect(err.code).to.be('ERR_WAT'); - }); - - it('should add a code to a class that extends Error', () => { - class CustomError extends Error { - set code(val) { - throw new Error('Nope!'); - } - } - - const myErr = new CustomError('my message'); - - Object.defineProperty(myErr, 'code', { - value: 'derp', - writable: false, - configurable: false, - }); - const err = errcode(myErr, 'ERR_WAT'); - - expect(err).to.be.a(CustomError); - expect(err.stack).to.equal(myErr.stack); - expect(err.code).to.be('ERR_WAT'); - - // original prototype chain should be intact - expect(() => { - const otherErr = new CustomError('my message'); - - otherErr.code = 'derp'; - }).to.throwError(); - }); - - it('should support errors that are not Errors', () => { - const err = errcode({ - message: 'Oh noes!', - }, 'ERR_WAT'); - - expect(err.message).to.be('Oh noes!'); - expect(err.code).to.be('ERR_WAT'); - }); - }); - - describe('falsy first arguments', () => { - it('should not allow passing null as the first argument', () => { - expect(() => { errcode(null); }).to.throwError((err) => { - expect(err).to.be.a(TypeError); - }); - }); - - it('should not allow passing undefined as the first argument', () => { - expect(() => { errcode(undefined); }).to.throwError((err) => { - expect(err).to.be.a(TypeError); - }); - }); - }); -}); diff --git a/deps/npm/node_modules/imurmurhash/imurmurhash.js b/deps/npm/node_modules/imurmurhash/imurmurhash.js deleted file mode 100644 index e63146a2b7e70b..00000000000000 --- a/deps/npm/node_modules/imurmurhash/imurmurhash.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @preserve - * JS Implementation of incremental MurmurHash3 (r150) (as of May 10, 2013) - * - * @author Jens Taylor - * @see http://github.com/homebrewing/brauhaus-diff - * @author Gary Court - * @see http://github.com/garycourt/murmurhash-js - * @author Austin Appleby - * @see http://sites.google.com/site/murmurhash/ - */ -(function(){ - var cache; - - // Call this function without `new` to use the cached object (good for - // single-threaded environments), or with `new` to create a new object. - // - // @param {string} key A UTF-16 or ASCII string - // @param {number} seed An optional positive integer - // @return {object} A MurmurHash3 object for incremental hashing - function MurmurHash3(key, seed) { - var m = this instanceof MurmurHash3 ? this : cache; - m.reset(seed) - if (typeof key === 'string' && key.length > 0) { - m.hash(key); - } - - if (m !== this) { - return m; - } - }; - - // Incrementally add a string to this hash - // - // @param {string} key A UTF-16 or ASCII string - // @return {object} this - MurmurHash3.prototype.hash = function(key) { - var h1, k1, i, top, len; - - len = key.length; - this.len += len; - - k1 = this.k1; - i = 0; - switch (this.rem) { - case 0: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) : 0; - case 1: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) << 8 : 0; - case 2: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) << 16 : 0; - case 3: - k1 ^= len > i ? (key.charCodeAt(i) & 0xff) << 24 : 0; - k1 ^= len > i ? (key.charCodeAt(i++) & 0xff00) >> 8 : 0; - } - - this.rem = (len + this.rem) & 3; // & 3 is same as % 4 - len -= this.rem; - if (len > 0) { - h1 = this.h1; - while (1) { - k1 = (k1 * 0x2d51 + (k1 & 0xffff) * 0xcc9e0000) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = (k1 * 0x3593 + (k1 & 0xffff) * 0x1b870000) & 0xffffffff; - - h1 ^= k1; - h1 = (h1 << 13) | (h1 >>> 19); - h1 = (h1 * 5 + 0xe6546b64) & 0xffffffff; - - if (i >= len) { - break; - } - - k1 = ((key.charCodeAt(i++) & 0xffff)) ^ - ((key.charCodeAt(i++) & 0xffff) << 8) ^ - ((key.charCodeAt(i++) & 0xffff) << 16); - top = key.charCodeAt(i++); - k1 ^= ((top & 0xff) << 24) ^ - ((top & 0xff00) >> 8); - } - - k1 = 0; - switch (this.rem) { - case 3: k1 ^= (key.charCodeAt(i + 2) & 0xffff) << 16; - case 2: k1 ^= (key.charCodeAt(i + 1) & 0xffff) << 8; - case 1: k1 ^= (key.charCodeAt(i) & 0xffff); - } - - this.h1 = h1; - } - - this.k1 = k1; - return this; - }; - - // Get the result of this hash - // - // @return {number} The 32-bit hash - MurmurHash3.prototype.result = function() { - var k1, h1; - - k1 = this.k1; - h1 = this.h1; - - if (k1 > 0) { - k1 = (k1 * 0x2d51 + (k1 & 0xffff) * 0xcc9e0000) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = (k1 * 0x3593 + (k1 & 0xffff) * 0x1b870000) & 0xffffffff; - h1 ^= k1; - } - - h1 ^= this.len; - - h1 ^= h1 >>> 16; - h1 = (h1 * 0xca6b + (h1 & 0xffff) * 0x85eb0000) & 0xffffffff; - h1 ^= h1 >>> 13; - h1 = (h1 * 0xae35 + (h1 & 0xffff) * 0xc2b20000) & 0xffffffff; - h1 ^= h1 >>> 16; - - return h1 >>> 0; - }; - - // Reset the hash object for reuse - // - // @param {number} seed An optional positive integer - MurmurHash3.prototype.reset = function(seed) { - this.h1 = typeof seed === 'number' ? seed : 0; - this.rem = this.k1 = this.len = 0; - return this; - }; - - // A cached object to use. This can be safely used if you're in a single- - // threaded environment, otherwise you need to create new hashes to use. - cache = new MurmurHash3(); - - if (typeof(module) != 'undefined') { - module.exports = MurmurHash3; - } else { - this.MurmurHash3 = MurmurHash3; - } -}()); diff --git a/deps/npm/node_modules/imurmurhash/imurmurhash.min.js b/deps/npm/node_modules/imurmurhash/imurmurhash.min.js deleted file mode 100644 index dc0ee88d6b69c9..00000000000000 --- a/deps/npm/node_modules/imurmurhash/imurmurhash.min.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @preserve - * JS Implementation of incremental MurmurHash3 (r150) (as of May 10, 2013) - * - * @author Jens Taylor - * @see http://github.com/homebrewing/brauhaus-diff - * @author Gary Court - * @see http://github.com/garycourt/murmurhash-js - * @author Austin Appleby - * @see http://sites.google.com/site/murmurhash/ - */ -!function(){function t(h,r){var s=this instanceof t?this:e;return s.reset(r),"string"==typeof h&&h.length>0&&s.hash(h),s!==this?s:void 0}var e;t.prototype.hash=function(t){var e,h,r,s,i;switch(i=t.length,this.len+=i,h=this.k1,r=0,this.rem){case 0:h^=i>r?65535&t.charCodeAt(r++):0;case 1:h^=i>r?(65535&t.charCodeAt(r++))<<8:0;case 2:h^=i>r?(65535&t.charCodeAt(r++))<<16:0;case 3:h^=i>r?(255&t.charCodeAt(r))<<24:0,h^=i>r?(65280&t.charCodeAt(r++))>>8:0}if(this.rem=3&i+this.rem,i-=this.rem,i>0){for(e=this.h1;;){if(h=4294967295&11601*h+3432906752*(65535&h),h=h<<15|h>>>17,h=4294967295&13715*h+461832192*(65535&h),e^=h,e=e<<13|e>>>19,e=4294967295&5*e+3864292196,r>=i)break;h=65535&t.charCodeAt(r++)^(65535&t.charCodeAt(r++))<<8^(65535&t.charCodeAt(r++))<<16,s=t.charCodeAt(r++),h^=(255&s)<<24^(65280&s)>>8}switch(h=0,this.rem){case 3:h^=(65535&t.charCodeAt(r+2))<<16;case 2:h^=(65535&t.charCodeAt(r+1))<<8;case 1:h^=65535&t.charCodeAt(r)}this.h1=e}return this.k1=h,this},t.prototype.result=function(){var t,e;return t=this.k1,e=this.h1,t>0&&(t=4294967295&11601*t+3432906752*(65535&t),t=t<<15|t>>>17,t=4294967295&13715*t+461832192*(65535&t),e^=t),e^=this.len,e^=e>>>16,e=4294967295&51819*e+2246770688*(65535&e),e^=e>>>13,e=4294967295&44597*e+3266445312*(65535&e),e^=e>>>16,e>>>0},t.prototype.reset=function(t){return this.h1="number"==typeof t?t:0,this.rem=this.k1=this.len=0,this},e=new t,"undefined"!=typeof module?module.exports=t:this.MurmurHash3=t}(); \ No newline at end of file diff --git a/deps/npm/node_modules/imurmurhash/package.json b/deps/npm/node_modules/imurmurhash/package.json deleted file mode 100644 index 8a93edb55a2245..00000000000000 --- a/deps/npm/node_modules/imurmurhash/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "imurmurhash", - "version": "0.1.4", - "description": "An incremental implementation of MurmurHash3", - "homepage": "https://github.com/jensyt/imurmurhash-js", - "main": "imurmurhash.js", - "files": [ - "imurmurhash.js", - "imurmurhash.min.js", - "package.json", - "README.md" - ], - "repository": { - "type": "git", - "url": "https://github.com/jensyt/imurmurhash-js" - }, - "bugs": { - "url": "https://github.com/jensyt/imurmurhash-js/issues" - }, - "keywords": [ - "murmur", - "murmurhash", - "murmurhash3", - "hash", - "incremental" - ], - "author": { - "name": "Jens Taylor", - "email": "jensyt@gmail.com", - "url": "https://github.com/homebrewing" - }, - "license": "MIT", - "dependencies": { - }, - "devDependencies": { - }, - "engines": { - "node": ">=0.8.19" - } -} diff --git a/deps/npm/node_modules/libnpmdiff/package.json b/deps/npm/node_modules/libnpmdiff/package.json index dd268cd3b7b0f7..943fa219aeff65 100644 --- a/deps/npm/node_modules/libnpmdiff/package.json +++ b/deps/npm/node_modules/libnpmdiff/package.json @@ -1,6 +1,6 @@ { "name": "libnpmdiff", - "version": "8.1.4", + "version": "8.1.5", "description": "The registry diff", "repository": { "type": "git", @@ -47,7 +47,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.4.1", + "@npmcli/arborist": "^9.4.2", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", diff --git a/deps/npm/node_modules/libnpmexec/package.json b/deps/npm/node_modules/libnpmexec/package.json index d6654212a4f063..1338bc4073236d 100644 --- a/deps/npm/node_modules/libnpmexec/package.json +++ b/deps/npm/node_modules/libnpmexec/package.json @@ -1,6 +1,6 @@ { "name": "libnpmexec", - "version": "10.2.4", + "version": "10.2.5", "files": [ "bin/", "lib/" @@ -61,7 +61,7 @@ }, "dependencies": { "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.4.1", + "@npmcli/arborist": "^9.4.2", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", diff --git a/deps/npm/node_modules/libnpmfund/package.json b/deps/npm/node_modules/libnpmfund/package.json index b59fb249ad8015..3ab87f9c7a394d 100644 --- a/deps/npm/node_modules/libnpmfund/package.json +++ b/deps/npm/node_modules/libnpmfund/package.json @@ -1,6 +1,6 @@ { "name": "libnpmfund", - "version": "7.0.18", + "version": "7.0.19", "main": "lib/index.js", "files": [ "bin/", @@ -46,7 +46,7 @@ "tap": "^16.3.8" }, "dependencies": { - "@npmcli/arborist": "^9.4.1" + "@npmcli/arborist": "^9.4.2" }, "engines": { "node": "^20.17.0 || >=22.9.0" diff --git a/deps/npm/node_modules/libnpmpack/package.json b/deps/npm/node_modules/libnpmpack/package.json index ffd3bfd612a640..ad76fc3b746010 100644 --- a/deps/npm/node_modules/libnpmpack/package.json +++ b/deps/npm/node_modules/libnpmpack/package.json @@ -1,6 +1,6 @@ { "name": "libnpmpack", - "version": "9.1.4", + "version": "9.1.5", "description": "Programmatic API for the bits behind npm pack", "author": "GitHub Inc.", "main": "lib/index.js", @@ -37,7 +37,7 @@ "bugs": "https://github.com/npm/libnpmpack/issues", "homepage": "https://npmjs.com/package/libnpmpack", "dependencies": { - "@npmcli/arborist": "^9.4.1", + "@npmcli/arborist": "^9.4.2", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" diff --git a/deps/npm/node_modules/lru-cache/dist/commonjs/index.js b/deps/npm/node_modules/lru-cache/dist/commonjs/index.js index 03473f2cc06ac3..a97b9f7ecc5301 100644 --- a/deps/npm/node_modules/lru-cache/dist/commonjs/index.js +++ b/deps/npm/node_modules/lru-cache/dist/commonjs/index.js @@ -452,31 +452,38 @@ class LRUCache { this.#setItemTTL = (index, ttl, start = this.#perf.now()) => { starts[index] = ttl !== 0 ? start : 0; ttls[index] = ttl; - // clear out the purge timer if we're setting TTL to 0, and - // previously had a ttl purge timer running, so it doesn't - // fire unnecessarily. - if (purgeTimers?.[index]) { - clearTimeout(purgeTimers[index]); - purgeTimers[index] = undefined; - } - if (ttl !== 0 && purgeTimers) { - const t = setTimeout(() => { - if (this.#isStale(index)) { - this.#delete(this.#keyList[index], 'expire'); - } - }, ttl + 1); - // unref() not supported on all platforms - /* c8 ignore start */ - if (t.unref) { - t.unref(); - } - /* c8 ignore stop */ - purgeTimers[index] = t; - } + setPurgetTimer(index, ttl); }; this.#updateItemAge = index => { starts[index] = ttls[index] !== 0 ? this.#perf.now() : 0; + setPurgetTimer(index, ttls[index]); }; + // clear out the purge timer if we're setting TTL to 0, and + // previously had a ttl purge timer running, so it doesn't + // fire unnecessarily. Don't need to do this if we're not doing + // autopurge. + const setPurgetTimer = !this.ttlAutopurge ? + () => { } + : (index, ttl) => { + if (purgeTimers?.[index]) { + clearTimeout(purgeTimers[index]); + purgeTimers[index] = undefined; + } + if (ttl && ttl !== 0 && purgeTimers) { + const t = setTimeout(() => { + if (this.#isStale(index)) { + this.#delete(this.#keyList[index], 'expire'); + } + }, ttl + 1); + // unref() not supported on all platforms + /* c8 ignore start */ + if (t.unref) { + t.unref(); + } + /* c8 ignore stop */ + purgeTimers[index] = t; + } + }; this.#statusTTL = (status, index) => { if (ttls[index]) { const ttl = ttls[index]; @@ -1219,8 +1226,7 @@ class LRUCache { if (this.#valList[index] === p) { // if we allow stale on fetch rejections, then we need to ensure that // the stale value is not removed from the cache when the fetch fails. - const del = !noDelete || - !proceed && bf.__staleWhileFetching === undefined; + const del = !noDelete || (!proceed && bf.__staleWhileFetching === undefined); if (del) { this.#delete(k, 'fetch'); } diff --git a/deps/npm/node_modules/lru-cache/dist/commonjs/index.min.js b/deps/npm/node_modules/lru-cache/dist/commonjs/index.min.js index 2be540a202670b..5a001c2d833615 100644 --- a/deps/npm/node_modules/lru-cache/dist/commonjs/index.min.js +++ b/deps/npm/node_modules/lru-cache/dist/commonjs/index.min.js @@ -1,2 +1,2 @@ -"use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports.LRUCache=void 0;var x=typeof performance=="object"&&performance&&typeof performance.now=="function"?performance:Date,U=new Set,R=typeof process=="object"&&process?process:{},I=(a,t,e,i)=>{typeof R.emitWarning=="function"?R.emitWarning(a,t,e,i):console.error(`[${e}] ${t}: ${a}`)},C=globalThis.AbortController,L=globalThis.AbortSignal;if(typeof C>"u"){L=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(i,s){this._onabort.push(s)}},C=class{constructor(){t()}signal=new L;abort(i){if(!this.signal.aborted){this.signal.reason=i,this.signal.aborted=!0;for(let s of this.signal._onabort)s(i);this.signal.onabort?.(i)}}};let a=R.env?.LRU_CACHE_IGNORE_AC_WARNING!=="1",t=()=>{a&&(a=!1,I("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}var G=a=>!U.has(a),H=Symbol("type"),y=a=>a&&a===Math.floor(a)&&a>0&&isFinite(a),M=a=>y(a)?a<=Math.pow(2,8)?Uint8Array:a<=Math.pow(2,16)?Uint16Array:a<=Math.pow(2,32)?Uint32Array:a<=Number.MAX_SAFE_INTEGER?z:null:null,z=class extends Array{constructor(t){super(t),this.fill(0)}},W=class a{heap;length;static#o=!1;static create(t){let e=M(t);if(!e)return[];a.#o=!0;let i=new a(t,e);return a.#o=!1,i}constructor(t,e){if(!a.#o)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new e(t),this.length=0}push(t){this.heap[this.length++]=t}pop(){return this.heap[--this.length]}},D=class a{#o;#c;#w;#C;#S;#L;#U;#m;get perf(){return this.#m}ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#n;#_;#s;#i;#t;#a;#u;#l;#h;#b;#r;#y;#A;#d;#g;#T;#v;#f;#I;static unsafeExposeInternals(t){return{starts:t.#A,ttls:t.#d,autopurgeTimers:t.#g,sizes:t.#y,keyMap:t.#s,keyList:t.#i,valList:t.#t,next:t.#a,prev:t.#u,get head(){return t.#l},get tail(){return t.#h},free:t.#b,isBackgroundFetch:e=>t.#e(e),backgroundFetch:(e,i,s,h)=>t.#G(e,i,s,h),moveToTail:e=>t.#D(e),indexes:e=>t.#F(e),rindexes:e=>t.#O(e),isStale:e=>t.#p(e)}}get max(){return this.#o}get maxSize(){return this.#c}get calculatedSize(){return this.#_}get size(){return this.#n}get fetchMethod(){return this.#L}get memoMethod(){return this.#U}get dispose(){return this.#w}get onInsert(){return this.#C}get disposeAfter(){return this.#S}constructor(t){let{max:e=0,ttl:i,ttlResolution:s=1,ttlAutopurge:h,updateAgeOnGet:n,updateAgeOnHas:o,allowStale:r,dispose:f,onInsert:m,disposeAfter:c,noDisposeOnSet:d,noUpdateTTL:g,maxSize:A=0,maxEntrySize:p=0,sizeCalculation:_,fetchMethod:l,memoMethod:w,noDeleteOnFetchRejection:b,noDeleteOnStaleGet:S,allowStaleOnFetchRejection:u,allowStaleOnFetchAbort:T,ignoreFetchAbort:F,perf:v}=t;if(v!==void 0&&typeof v?.now!="function")throw new TypeError("perf option must have a now() method if specified");if(this.#m=v??x,e!==0&&!y(e))throw new TypeError("max option must be a nonnegative integer");let O=e?M(e):Array;if(!O)throw new Error("invalid max value: "+e);if(this.#o=e,this.#c=A,this.maxEntrySize=p||this.#c,this.sizeCalculation=_,this.sizeCalculation){if(!this.#c&&!this.maxEntrySize)throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");if(typeof this.sizeCalculation!="function")throw new TypeError("sizeCalculation set to non-function")}if(w!==void 0&&typeof w!="function")throw new TypeError("memoMethod must be a function if defined");if(this.#U=w,l!==void 0&&typeof l!="function")throw new TypeError("fetchMethod must be a function if specified");if(this.#L=l,this.#v=!!l,this.#s=new Map,this.#i=new Array(e).fill(void 0),this.#t=new Array(e).fill(void 0),this.#a=new O(e),this.#u=new O(e),this.#l=0,this.#h=0,this.#b=W.create(e),this.#n=0,this.#_=0,typeof f=="function"&&(this.#w=f),typeof m=="function"&&(this.#C=m),typeof c=="function"?(this.#S=c,this.#r=[]):(this.#S=void 0,this.#r=void 0),this.#T=!!this.#w,this.#I=!!this.#C,this.#f=!!this.#S,this.noDisposeOnSet=!!d,this.noUpdateTTL=!!g,this.noDeleteOnFetchRejection=!!b,this.allowStaleOnFetchRejection=!!u,this.allowStaleOnFetchAbort=!!T,this.ignoreFetchAbort=!!F,this.maxEntrySize!==0){if(this.#c!==0&&!y(this.#c))throw new TypeError("maxSize must be a positive integer if specified");if(!y(this.maxEntrySize))throw new TypeError("maxEntrySize must be a positive integer if specified");this.#B()}if(this.allowStale=!!r,this.noDeleteOnStaleGet=!!S,this.updateAgeOnGet=!!n,this.updateAgeOnHas=!!o,this.ttlResolution=y(s)||s===0?s:1,this.ttlAutopurge=!!h,this.ttl=i||0,this.ttl){if(!y(this.ttl))throw new TypeError("ttl must be a positive integer if specified");this.#j()}if(this.#o===0&&this.ttl===0&&this.#c===0)throw new TypeError("At least one of max, maxSize, or ttl is required");if(!this.ttlAutopurge&&!this.#o&&!this.#c){let E="LRU_CACHE_UNBOUNDED";G(E)&&(U.add(E),I("TTL caching without ttlAutopurge, max, or maxSize can result in unbounded memory consumption.","UnboundedCacheWarning",E,a))}}getRemainingTTL(t){return this.#s.has(t)?1/0:0}#j(){let t=new z(this.#o),e=new z(this.#o);this.#d=t,this.#A=e;let i=this.ttlAutopurge?new Array(this.#o):void 0;this.#g=i,this.#N=(n,o,r=this.#m.now())=>{if(e[n]=o!==0?r:0,t[n]=o,i?.[n]&&(clearTimeout(i[n]),i[n]=void 0),o!==0&&i){let f=setTimeout(()=>{this.#p(n)&&this.#E(this.#i[n],"expire")},o+1);f.unref&&f.unref(),i[n]=f}},this.#R=n=>{e[n]=t[n]!==0?this.#m.now():0},this.#z=(n,o)=>{if(t[o]){let r=t[o],f=e[o];if(!r||!f)return;n.ttl=r,n.start=f,n.now=s||h();let m=n.now-f;n.remainingTTL=r-m}};let s=0,h=()=>{let n=this.#m.now();if(this.ttlResolution>0){s=n;let o=setTimeout(()=>s=0,this.ttlResolution);o.unref&&o.unref()}return n};this.getRemainingTTL=n=>{let o=this.#s.get(n);if(o===void 0)return 0;let r=t[o],f=e[o];if(!r||!f)return 1/0;let m=(s||h())-f;return r-m},this.#p=n=>{let o=e[n],r=t[n];return!!r&&!!o&&(s||h())-o>r}}#R=()=>{};#z=()=>{};#N=()=>{};#p=()=>!1;#B(){let t=new z(this.#o);this.#_=0,this.#y=t,this.#W=e=>{this.#_-=t[e],t[e]=0},this.#P=(e,i,s,h)=>{if(this.#e(i))return 0;if(!y(s))if(h){if(typeof h!="function")throw new TypeError("sizeCalculation must be a function");if(s=h(i,e),!y(s))throw new TypeError("sizeCalculation return invalid (expect positive integer)")}else throw new TypeError("invalid size value (must be positive integer). When maxSize or maxEntrySize is used, sizeCalculation or size must be set.");return s},this.#M=(e,i,s)=>{if(t[e]=i,this.#c){let h=this.#c-t[e];for(;this.#_>h;)this.#x(!0)}this.#_+=t[e],s&&(s.entrySize=i,s.totalCalculatedSize=this.#_)}}#W=t=>{};#M=(t,e,i)=>{};#P=(t,e,i,s)=>{if(i||s)throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");return 0};*#F({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#h;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#l));)e=this.#u[e]}*#O({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#l;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#h));)e=this.#a[e]}#H(t){return t!==void 0&&this.#s.get(this.#i[t])===t}*entries(){for(let t of this.#F())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*rentries(){for(let t of this.#O())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*keys(){for(let t of this.#F()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*rkeys(){for(let t of this.#O()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*values(){for(let t of this.#F())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}*rvalues(){for(let t of this.#O())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}[Symbol.iterator](){return this.entries()}[Symbol.toStringTag]="LRUCache";find(t,e={}){for(let i of this.#F()){let s=this.#t[i],h=this.#e(s)?s.__staleWhileFetching:s;if(h!==void 0&&t(h,this.#i[i],this))return this.get(this.#i[i],e)}}forEach(t,e=this){for(let i of this.#F()){let s=this.#t[i],h=this.#e(s)?s.__staleWhileFetching:s;h!==void 0&&t.call(e,h,this.#i[i],this)}}rforEach(t,e=this){for(let i of this.#O()){let s=this.#t[i],h=this.#e(s)?s.__staleWhileFetching:s;h!==void 0&&t.call(e,h,this.#i[i],this)}}purgeStale(){let t=!1;for(let e of this.#O({allowStale:!0}))this.#p(e)&&(this.#E(this.#i[e],"expire"),t=!0);return t}info(t){let e=this.#s.get(t);if(e===void 0)return;let i=this.#t[e],s=this.#e(i)?i.__staleWhileFetching:i;if(s===void 0)return;let h={value:s};if(this.#d&&this.#A){let n=this.#d[e],o=this.#A[e];if(n&&o){let r=n-(this.#m.now()-o);h.ttl=r,h.start=Date.now()}}return this.#y&&(h.size=this.#y[e]),h}dump(){let t=[];for(let e of this.#F({allowStale:!0})){let i=this.#i[e],s=this.#t[e],h=this.#e(s)?s.__staleWhileFetching:s;if(h===void 0||i===void 0)continue;let n={value:h};if(this.#d&&this.#A){n.ttl=this.#d[e];let o=this.#m.now()-this.#A[e];n.start=Math.floor(Date.now()-o)}this.#y&&(n.size=this.#y[e]),t.unshift([i,n])}return t}load(t){this.clear();for(let[e,i]of t){if(i.start){let s=Date.now()-i.start;i.start=this.#m.now()-s}this.set(e,i.value,i)}}set(t,e,i={}){if(e===void 0)return this.delete(t),this;let{ttl:s=this.ttl,start:h,noDisposeOnSet:n=this.noDisposeOnSet,sizeCalculation:o=this.sizeCalculation,status:r}=i,{noUpdateTTL:f=this.noUpdateTTL}=i,m=this.#P(t,e,i.size||0,o);if(this.maxEntrySize&&m>this.maxEntrySize)return r&&(r.set="miss",r.maxEntrySizeExceeded=!0),this.#E(t,"set"),this;let c=this.#n===0?void 0:this.#s.get(t);if(c===void 0)c=this.#n===0?this.#h:this.#b.length!==0?this.#b.pop():this.#n===this.#o?this.#x(!1):this.#n,this.#i[c]=t,this.#t[c]=e,this.#s.set(t,c),this.#a[this.#h]=c,this.#u[c]=this.#h,this.#h=c,this.#n++,this.#M(c,m,r),r&&(r.set="add"),f=!1,this.#I&&this.#C?.(e,t,"add");else{this.#D(c);let d=this.#t[c];if(e!==d){if(this.#v&&this.#e(d)){d.__abortController.abort(new Error("replaced"));let{__staleWhileFetching:g}=d;g!==void 0&&!n&&(this.#T&&this.#w?.(g,t,"set"),this.#f&&this.#r?.push([g,t,"set"]))}else n||(this.#T&&this.#w?.(d,t,"set"),this.#f&&this.#r?.push([d,t,"set"]));if(this.#W(c),this.#M(c,m,r),this.#t[c]=e,r){r.set="replace";let g=d&&this.#e(d)?d.__staleWhileFetching:d;g!==void 0&&(r.oldValue=g)}}else r&&(r.set="update");this.#I&&this.onInsert?.(e,t,e===d?"update":"replace")}if(s!==0&&!this.#d&&this.#j(),this.#d&&(f||this.#N(c,s,h),r&&this.#z(r,c)),!n&&this.#f&&this.#r){let d=this.#r,g;for(;g=d?.shift();)this.#S?.(...g)}return this}pop(){try{for(;this.#n;){let t=this.#t[this.#l];if(this.#x(!0),this.#e(t)){if(t.__staleWhileFetching)return t.__staleWhileFetching}else if(t!==void 0)return t}}finally{if(this.#f&&this.#r){let t=this.#r,e;for(;e=t?.shift();)this.#S?.(...e)}}}#x(t){let e=this.#l,i=this.#i[e],s=this.#t[e];return this.#v&&this.#e(s)?s.__abortController.abort(new Error("evicted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(s,i,"evict"),this.#f&&this.#r?.push([s,i,"evict"])),this.#W(e),this.#g?.[e]&&(clearTimeout(this.#g[e]),this.#g[e]=void 0),t&&(this.#i[e]=void 0,this.#t[e]=void 0,this.#b.push(e)),this.#n===1?(this.#l=this.#h=0,this.#b.length=0):this.#l=this.#a[e],this.#s.delete(i),this.#n--,e}has(t,e={}){let{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=e,h=this.#s.get(t);if(h!==void 0){let n=this.#t[h];if(this.#e(n)&&n.__staleWhileFetching===void 0)return!1;if(this.#p(h))s&&(s.has="stale",this.#z(s,h));else return i&&this.#R(h),s&&(s.has="hit",this.#z(s,h)),!0}else s&&(s.has="miss");return!1}peek(t,e={}){let{allowStale:i=this.allowStale}=e,s=this.#s.get(t);if(s===void 0||!i&&this.#p(s))return;let h=this.#t[s];return this.#e(h)?h.__staleWhileFetching:h}#G(t,e,i,s){let h=e===void 0?void 0:this.#t[e];if(this.#e(h))return h;let n=new C,{signal:o}=i;o?.addEventListener("abort",()=>n.abort(o.reason),{signal:n.signal});let r={signal:n.signal,options:i,context:s},f=(p,_=!1)=>{let{aborted:l}=n.signal,w=i.ignoreFetchAbort&&p!==void 0,b=i.ignoreFetchAbort||!!(i.allowStaleOnFetchAbort&&p!==void 0);if(i.status&&(l&&!_?(i.status.fetchAborted=!0,i.status.fetchError=n.signal.reason,w&&(i.status.fetchAbortIgnored=!0)):i.status.fetchResolved=!0),l&&!w&&!_)return c(n.signal.reason,b);let S=g,u=this.#t[e];return(u===g||w&&_&&u===void 0)&&(p===void 0?S.__staleWhileFetching!==void 0?this.#t[e]=S.__staleWhileFetching:this.#E(t,"fetch"):(i.status&&(i.status.fetchUpdated=!0),this.set(t,p,r.options))),p},m=p=>(i.status&&(i.status.fetchRejected=!0,i.status.fetchError=p),c(p,!1)),c=(p,_)=>{let{aborted:l}=n.signal,w=l&&i.allowStaleOnFetchAbort,b=w||i.allowStaleOnFetchRejection,S=b||i.noDeleteOnFetchRejection,u=g;if(this.#t[e]===g&&(!S||!_&&u.__staleWhileFetching===void 0?this.#E(t,"fetch"):w||(this.#t[e]=u.__staleWhileFetching)),b)return i.status&&u.__staleWhileFetching!==void 0&&(i.status.returnedStale=!0),u.__staleWhileFetching;if(u.__returned===u)throw p},d=(p,_)=>{let l=this.#L?.(t,h,r);l&&l instanceof Promise&&l.then(w=>p(w===void 0?void 0:w),_),n.signal.addEventListener("abort",()=>{(!i.ignoreFetchAbort||i.allowStaleOnFetchAbort)&&(p(void 0),i.allowStaleOnFetchAbort&&(p=w=>f(w,!0)))})};i.status&&(i.status.fetchDispatched=!0);let g=new Promise(d).then(f,m),A=Object.assign(g,{__abortController:n,__staleWhileFetching:h,__returned:void 0});return e===void 0?(this.set(t,A,{...r.options,status:void 0}),e=this.#s.get(t)):this.#t[e]=A,A}#e(t){if(!this.#v)return!1;let e=t;return!!e&&e instanceof Promise&&e.hasOwnProperty("__staleWhileFetching")&&e.__abortController instanceof C}async fetch(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:h=this.noDeleteOnStaleGet,ttl:n=this.ttl,noDisposeOnSet:o=this.noDisposeOnSet,size:r=0,sizeCalculation:f=this.sizeCalculation,noUpdateTTL:m=this.noUpdateTTL,noDeleteOnFetchRejection:c=this.noDeleteOnFetchRejection,allowStaleOnFetchRejection:d=this.allowStaleOnFetchRejection,ignoreFetchAbort:g=this.ignoreFetchAbort,allowStaleOnFetchAbort:A=this.allowStaleOnFetchAbort,context:p,forceRefresh:_=!1,status:l,signal:w}=e;if(!this.#v)return l&&(l.fetch="get"),this.get(t,{allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:h,status:l});let b={allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:h,ttl:n,noDisposeOnSet:o,size:r,sizeCalculation:f,noUpdateTTL:m,noDeleteOnFetchRejection:c,allowStaleOnFetchRejection:d,allowStaleOnFetchAbort:A,ignoreFetchAbort:g,status:l,signal:w},S=this.#s.get(t);if(S===void 0){l&&(l.fetch="miss");let u=this.#G(t,S,b,p);return u.__returned=u}else{let u=this.#t[S];if(this.#e(u)){let E=i&&u.__staleWhileFetching!==void 0;return l&&(l.fetch="inflight",E&&(l.returnedStale=!0)),E?u.__staleWhileFetching:u.__returned=u}let T=this.#p(S);if(!_&&!T)return l&&(l.fetch="hit"),this.#D(S),s&&this.#R(S),l&&this.#z(l,S),u;let F=this.#G(t,S,b,p),O=F.__staleWhileFetching!==void 0&&i;return l&&(l.fetch=T?"stale":"refresh",O&&T&&(l.returnedStale=!0)),O?F.__staleWhileFetching:F.__returned=F}}async forceFetch(t,e={}){let i=await this.fetch(t,e);if(i===void 0)throw new Error("fetch() returned undefined");return i}memo(t,e={}){let i=this.#U;if(!i)throw new Error("no memoMethod provided to constructor");let{context:s,forceRefresh:h,...n}=e,o=this.get(t,n);if(!h&&o!==void 0)return o;let r=i(t,o,{options:n,context:s});return this.set(t,r,n),r}get(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:h=this.noDeleteOnStaleGet,status:n}=e,o=this.#s.get(t);if(o!==void 0){let r=this.#t[o],f=this.#e(r);return n&&this.#z(n,o),this.#p(o)?(n&&(n.get="stale"),f?(n&&i&&r.__staleWhileFetching!==void 0&&(n.returnedStale=!0),i?r.__staleWhileFetching:void 0):(h||this.#E(t,"expire"),n&&i&&(n.returnedStale=!0),i?r:void 0)):(n&&(n.get="hit"),f?r.__staleWhileFetching:(this.#D(o),s&&this.#R(o),r))}else n&&(n.get="miss")}#k(t,e){this.#u[e]=t,this.#a[t]=e}#D(t){t!==this.#h&&(t===this.#l?this.#l=this.#a[t]:this.#k(this.#u[t],this.#a[t]),this.#k(this.#h,t),this.#h=t)}delete(t){return this.#E(t,"delete")}#E(t,e){let i=!1;if(this.#n!==0){let s=this.#s.get(t);if(s!==void 0)if(this.#g?.[s]&&(clearTimeout(this.#g?.[s]),this.#g[s]=void 0),i=!0,this.#n===1)this.#V(e);else{this.#W(s);let h=this.#t[s];if(this.#e(h)?h.__abortController.abort(new Error("deleted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(h,t,e),this.#f&&this.#r?.push([h,t,e])),this.#s.delete(t),this.#i[s]=void 0,this.#t[s]=void 0,s===this.#h)this.#h=this.#u[s];else if(s===this.#l)this.#l=this.#a[s];else{let n=this.#u[s];this.#a[n]=this.#a[s];let o=this.#a[s];this.#u[o]=this.#u[s]}this.#n--,this.#b.push(s)}}if(this.#f&&this.#r?.length){let s=this.#r,h;for(;h=s?.shift();)this.#S?.(...h)}return i}clear(){return this.#V("delete")}#V(t){for(let e of this.#O({allowStale:!0})){let i=this.#t[e];if(this.#e(i))i.__abortController.abort(new Error("deleted"));else{let s=this.#i[e];this.#T&&this.#w?.(i,s,t),this.#f&&this.#r?.push([i,s,t])}}if(this.#s.clear(),this.#t.fill(void 0),this.#i.fill(void 0),this.#d&&this.#A){this.#d.fill(0),this.#A.fill(0);for(let e of this.#g??[])e!==void 0&&clearTimeout(e);this.#g?.fill(void 0)}if(this.#y&&this.#y.fill(0),this.#l=0,this.#h=0,this.#b.length=0,this.#_=0,this.#n=0,this.#f&&this.#r){let e=this.#r,i;for(;i=e?.shift();)this.#S?.(...i)}}};exports.LRUCache=D; +"use strict";Object.defineProperty(exports,"__esModule",{value:!0});exports.LRUCache=void 0;var G=typeof performance=="object"&&performance&&typeof performance.now=="function"?performance:Date,U=new Set,R=typeof process=="object"&&process?process:{},I=(c,t,e,i)=>{typeof R.emitWarning=="function"?R.emitWarning(c,t,e,i):console.error(`[${e}] ${t}: ${c}`)},C=globalThis.AbortController,L=globalThis.AbortSignal;if(typeof C>"u"){L=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(i,s){this._onabort.push(s)}},C=class{constructor(){t()}signal=new L;abort(i){if(!this.signal.aborted){this.signal.reason=i,this.signal.aborted=!0;for(let s of this.signal._onabort)s(i);this.signal.onabort?.(i)}}};let c=R.env?.LRU_CACHE_IGNORE_AC_WARNING!=="1",t=()=>{c&&(c=!1,I("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}var x=c=>!U.has(c),H=Symbol("type"),y=c=>c&&c===Math.floor(c)&&c>0&&isFinite(c),M=c=>y(c)?c<=Math.pow(2,8)?Uint8Array:c<=Math.pow(2,16)?Uint16Array:c<=Math.pow(2,32)?Uint32Array:c<=Number.MAX_SAFE_INTEGER?z:null:null,z=class extends Array{constructor(t){super(t),this.fill(0)}},W=class c{heap;length;static#o=!1;static create(t){let e=M(t);if(!e)return[];c.#o=!0;let i=new c(t,e);return c.#o=!1,i}constructor(t,e){if(!c.#o)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new e(t),this.length=0}push(t){this.heap[this.length++]=t}pop(){return this.heap[--this.length]}},D=class c{#o;#c;#w;#C;#S;#L;#U;#m;get perf(){return this.#m}ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#n;#_;#s;#i;#t;#a;#u;#l;#h;#b;#r;#y;#A;#d;#g;#T;#v;#f;#I;static unsafeExposeInternals(t){return{starts:t.#A,ttls:t.#d,autopurgeTimers:t.#g,sizes:t.#y,keyMap:t.#s,keyList:t.#i,valList:t.#t,next:t.#a,prev:t.#u,get head(){return t.#l},get tail(){return t.#h},free:t.#b,isBackgroundFetch:e=>t.#e(e),backgroundFetch:(e,i,s,n)=>t.#x(e,i,s,n),moveToTail:e=>t.#D(e),indexes:e=>t.#F(e),rindexes:e=>t.#O(e),isStale:e=>t.#p(e)}}get max(){return this.#o}get maxSize(){return this.#c}get calculatedSize(){return this.#_}get size(){return this.#n}get fetchMethod(){return this.#L}get memoMethod(){return this.#U}get dispose(){return this.#w}get onInsert(){return this.#C}get disposeAfter(){return this.#S}constructor(t){let{max:e=0,ttl:i,ttlResolution:s=1,ttlAutopurge:n,updateAgeOnGet:o,updateAgeOnHas:h,allowStale:r,dispose:a,onInsert:w,disposeAfter:f,noDisposeOnSet:d,noUpdateTTL:g,maxSize:A=0,maxEntrySize:p=0,sizeCalculation:_,fetchMethod:l,memoMethod:S,noDeleteOnFetchRejection:b,noDeleteOnStaleGet:m,allowStaleOnFetchRejection:u,allowStaleOnFetchAbort:T,ignoreFetchAbort:F,perf:v}=t;if(v!==void 0&&typeof v?.now!="function")throw new TypeError("perf option must have a now() method if specified");if(this.#m=v??G,e!==0&&!y(e))throw new TypeError("max option must be a nonnegative integer");let O=e?M(e):Array;if(!O)throw new Error("invalid max value: "+e);if(this.#o=e,this.#c=A,this.maxEntrySize=p||this.#c,this.sizeCalculation=_,this.sizeCalculation){if(!this.#c&&!this.maxEntrySize)throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");if(typeof this.sizeCalculation!="function")throw new TypeError("sizeCalculation set to non-function")}if(S!==void 0&&typeof S!="function")throw new TypeError("memoMethod must be a function if defined");if(this.#U=S,l!==void 0&&typeof l!="function")throw new TypeError("fetchMethod must be a function if specified");if(this.#L=l,this.#v=!!l,this.#s=new Map,this.#i=new Array(e).fill(void 0),this.#t=new Array(e).fill(void 0),this.#a=new O(e),this.#u=new O(e),this.#l=0,this.#h=0,this.#b=W.create(e),this.#n=0,this.#_=0,typeof a=="function"&&(this.#w=a),typeof w=="function"&&(this.#C=w),typeof f=="function"?(this.#S=f,this.#r=[]):(this.#S=void 0,this.#r=void 0),this.#T=!!this.#w,this.#I=!!this.#C,this.#f=!!this.#S,this.noDisposeOnSet=!!d,this.noUpdateTTL=!!g,this.noDeleteOnFetchRejection=!!b,this.allowStaleOnFetchRejection=!!u,this.allowStaleOnFetchAbort=!!T,this.ignoreFetchAbort=!!F,this.maxEntrySize!==0){if(this.#c!==0&&!y(this.#c))throw new TypeError("maxSize must be a positive integer if specified");if(!y(this.maxEntrySize))throw new TypeError("maxEntrySize must be a positive integer if specified");this.#B()}if(this.allowStale=!!r,this.noDeleteOnStaleGet=!!m,this.updateAgeOnGet=!!o,this.updateAgeOnHas=!!h,this.ttlResolution=y(s)||s===0?s:1,this.ttlAutopurge=!!n,this.ttl=i||0,this.ttl){if(!y(this.ttl))throw new TypeError("ttl must be a positive integer if specified");this.#j()}if(this.#o===0&&this.ttl===0&&this.#c===0)throw new TypeError("At least one of max, maxSize, or ttl is required");if(!this.ttlAutopurge&&!this.#o&&!this.#c){let E="LRU_CACHE_UNBOUNDED";x(E)&&(U.add(E),I("TTL caching without ttlAutopurge, max, or maxSize can result in unbounded memory consumption.","UnboundedCacheWarning",E,c))}}getRemainingTTL(t){return this.#s.has(t)?1/0:0}#j(){let t=new z(this.#o),e=new z(this.#o);this.#d=t,this.#A=e;let i=this.ttlAutopurge?new Array(this.#o):void 0;this.#g=i,this.#N=(h,r,a=this.#m.now())=>{e[h]=r!==0?a:0,t[h]=r,s(h,r)},this.#R=h=>{e[h]=t[h]!==0?this.#m.now():0,s(h,t[h])};let s=this.ttlAutopurge?(h,r)=>{if(i?.[h]&&(clearTimeout(i[h]),i[h]=void 0),r&&r!==0&&i){let a=setTimeout(()=>{this.#p(h)&&this.#E(this.#i[h],"expire")},r+1);a.unref&&a.unref(),i[h]=a}}:()=>{};this.#z=(h,r)=>{if(t[r]){let a=t[r],w=e[r];if(!a||!w)return;h.ttl=a,h.start=w,h.now=n||o();let f=h.now-w;h.remainingTTL=a-f}};let n=0,o=()=>{let h=this.#m.now();if(this.ttlResolution>0){n=h;let r=setTimeout(()=>n=0,this.ttlResolution);r.unref&&r.unref()}return h};this.getRemainingTTL=h=>{let r=this.#s.get(h);if(r===void 0)return 0;let a=t[r],w=e[r];if(!a||!w)return 1/0;let f=(n||o())-w;return a-f},this.#p=h=>{let r=e[h],a=t[h];return!!a&&!!r&&(n||o())-r>a}}#R=()=>{};#z=()=>{};#N=()=>{};#p=()=>!1;#B(){let t=new z(this.#o);this.#_=0,this.#y=t,this.#W=e=>{this.#_-=t[e],t[e]=0},this.#P=(e,i,s,n)=>{if(this.#e(i))return 0;if(!y(s))if(n){if(typeof n!="function")throw new TypeError("sizeCalculation must be a function");if(s=n(i,e),!y(s))throw new TypeError("sizeCalculation return invalid (expect positive integer)")}else throw new TypeError("invalid size value (must be positive integer). When maxSize or maxEntrySize is used, sizeCalculation or size must be set.");return s},this.#M=(e,i,s)=>{if(t[e]=i,this.#c){let n=this.#c-t[e];for(;this.#_>n;)this.#G(!0)}this.#_+=t[e],s&&(s.entrySize=i,s.totalCalculatedSize=this.#_)}}#W=t=>{};#M=(t,e,i)=>{};#P=(t,e,i,s)=>{if(i||s)throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");return 0};*#F({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#h;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#l));)e=this.#u[e]}*#O({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#l;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#h));)e=this.#a[e]}#H(t){return t!==void 0&&this.#s.get(this.#i[t])===t}*entries(){for(let t of this.#F())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*rentries(){for(let t of this.#O())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*keys(){for(let t of this.#F()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*rkeys(){for(let t of this.#O()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*values(){for(let t of this.#F())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}*rvalues(){for(let t of this.#O())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}[Symbol.iterator](){return this.entries()}[Symbol.toStringTag]="LRUCache";find(t,e={}){for(let i of this.#F()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;if(n!==void 0&&t(n,this.#i[i],this))return this.get(this.#i[i],e)}}forEach(t,e=this){for(let i of this.#F()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;n!==void 0&&t.call(e,n,this.#i[i],this)}}rforEach(t,e=this){for(let i of this.#O()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;n!==void 0&&t.call(e,n,this.#i[i],this)}}purgeStale(){let t=!1;for(let e of this.#O({allowStale:!0}))this.#p(e)&&(this.#E(this.#i[e],"expire"),t=!0);return t}info(t){let e=this.#s.get(t);if(e===void 0)return;let i=this.#t[e],s=this.#e(i)?i.__staleWhileFetching:i;if(s===void 0)return;let n={value:s};if(this.#d&&this.#A){let o=this.#d[e],h=this.#A[e];if(o&&h){let r=o-(this.#m.now()-h);n.ttl=r,n.start=Date.now()}}return this.#y&&(n.size=this.#y[e]),n}dump(){let t=[];for(let e of this.#F({allowStale:!0})){let i=this.#i[e],s=this.#t[e],n=this.#e(s)?s.__staleWhileFetching:s;if(n===void 0||i===void 0)continue;let o={value:n};if(this.#d&&this.#A){o.ttl=this.#d[e];let h=this.#m.now()-this.#A[e];o.start=Math.floor(Date.now()-h)}this.#y&&(o.size=this.#y[e]),t.unshift([i,o])}return t}load(t){this.clear();for(let[e,i]of t){if(i.start){let s=Date.now()-i.start;i.start=this.#m.now()-s}this.set(e,i.value,i)}}set(t,e,i={}){if(e===void 0)return this.delete(t),this;let{ttl:s=this.ttl,start:n,noDisposeOnSet:o=this.noDisposeOnSet,sizeCalculation:h=this.sizeCalculation,status:r}=i,{noUpdateTTL:a=this.noUpdateTTL}=i,w=this.#P(t,e,i.size||0,h);if(this.maxEntrySize&&w>this.maxEntrySize)return r&&(r.set="miss",r.maxEntrySizeExceeded=!0),this.#E(t,"set"),this;let f=this.#n===0?void 0:this.#s.get(t);if(f===void 0)f=this.#n===0?this.#h:this.#b.length!==0?this.#b.pop():this.#n===this.#o?this.#G(!1):this.#n,this.#i[f]=t,this.#t[f]=e,this.#s.set(t,f),this.#a[this.#h]=f,this.#u[f]=this.#h,this.#h=f,this.#n++,this.#M(f,w,r),r&&(r.set="add"),a=!1,this.#I&&this.#C?.(e,t,"add");else{this.#D(f);let d=this.#t[f];if(e!==d){if(this.#v&&this.#e(d)){d.__abortController.abort(new Error("replaced"));let{__staleWhileFetching:g}=d;g!==void 0&&!o&&(this.#T&&this.#w?.(g,t,"set"),this.#f&&this.#r?.push([g,t,"set"]))}else o||(this.#T&&this.#w?.(d,t,"set"),this.#f&&this.#r?.push([d,t,"set"]));if(this.#W(f),this.#M(f,w,r),this.#t[f]=e,r){r.set="replace";let g=d&&this.#e(d)?d.__staleWhileFetching:d;g!==void 0&&(r.oldValue=g)}}else r&&(r.set="update");this.#I&&this.onInsert?.(e,t,e===d?"update":"replace")}if(s!==0&&!this.#d&&this.#j(),this.#d&&(a||this.#N(f,s,n),r&&this.#z(r,f)),!o&&this.#f&&this.#r){let d=this.#r,g;for(;g=d?.shift();)this.#S?.(...g)}return this}pop(){try{for(;this.#n;){let t=this.#t[this.#l];if(this.#G(!0),this.#e(t)){if(t.__staleWhileFetching)return t.__staleWhileFetching}else if(t!==void 0)return t}}finally{if(this.#f&&this.#r){let t=this.#r,e;for(;e=t?.shift();)this.#S?.(...e)}}}#G(t){let e=this.#l,i=this.#i[e],s=this.#t[e];return this.#v&&this.#e(s)?s.__abortController.abort(new Error("evicted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(s,i,"evict"),this.#f&&this.#r?.push([s,i,"evict"])),this.#W(e),this.#g?.[e]&&(clearTimeout(this.#g[e]),this.#g[e]=void 0),t&&(this.#i[e]=void 0,this.#t[e]=void 0,this.#b.push(e)),this.#n===1?(this.#l=this.#h=0,this.#b.length=0):this.#l=this.#a[e],this.#s.delete(i),this.#n--,e}has(t,e={}){let{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=e,n=this.#s.get(t);if(n!==void 0){let o=this.#t[n];if(this.#e(o)&&o.__staleWhileFetching===void 0)return!1;if(this.#p(n))s&&(s.has="stale",this.#z(s,n));else return i&&this.#R(n),s&&(s.has="hit",this.#z(s,n)),!0}else s&&(s.has="miss");return!1}peek(t,e={}){let{allowStale:i=this.allowStale}=e,s=this.#s.get(t);if(s===void 0||!i&&this.#p(s))return;let n=this.#t[s];return this.#e(n)?n.__staleWhileFetching:n}#x(t,e,i,s){let n=e===void 0?void 0:this.#t[e];if(this.#e(n))return n;let o=new C,{signal:h}=i;h?.addEventListener("abort",()=>o.abort(h.reason),{signal:o.signal});let r={signal:o.signal,options:i,context:s},a=(p,_=!1)=>{let{aborted:l}=o.signal,S=i.ignoreFetchAbort&&p!==void 0,b=i.ignoreFetchAbort||!!(i.allowStaleOnFetchAbort&&p!==void 0);if(i.status&&(l&&!_?(i.status.fetchAborted=!0,i.status.fetchError=o.signal.reason,S&&(i.status.fetchAbortIgnored=!0)):i.status.fetchResolved=!0),l&&!S&&!_)return f(o.signal.reason,b);let m=g,u=this.#t[e];return(u===g||S&&_&&u===void 0)&&(p===void 0?m.__staleWhileFetching!==void 0?this.#t[e]=m.__staleWhileFetching:this.#E(t,"fetch"):(i.status&&(i.status.fetchUpdated=!0),this.set(t,p,r.options))),p},w=p=>(i.status&&(i.status.fetchRejected=!0,i.status.fetchError=p),f(p,!1)),f=(p,_)=>{let{aborted:l}=o.signal,S=l&&i.allowStaleOnFetchAbort,b=S||i.allowStaleOnFetchRejection,m=b||i.noDeleteOnFetchRejection,u=g;if(this.#t[e]===g&&(!m||!_&&u.__staleWhileFetching===void 0?this.#E(t,"fetch"):S||(this.#t[e]=u.__staleWhileFetching)),b)return i.status&&u.__staleWhileFetching!==void 0&&(i.status.returnedStale=!0),u.__staleWhileFetching;if(u.__returned===u)throw p},d=(p,_)=>{let l=this.#L?.(t,n,r);l&&l instanceof Promise&&l.then(S=>p(S===void 0?void 0:S),_),o.signal.addEventListener("abort",()=>{(!i.ignoreFetchAbort||i.allowStaleOnFetchAbort)&&(p(void 0),i.allowStaleOnFetchAbort&&(p=S=>a(S,!0)))})};i.status&&(i.status.fetchDispatched=!0);let g=new Promise(d).then(a,w),A=Object.assign(g,{__abortController:o,__staleWhileFetching:n,__returned:void 0});return e===void 0?(this.set(t,A,{...r.options,status:void 0}),e=this.#s.get(t)):this.#t[e]=A,A}#e(t){if(!this.#v)return!1;let e=t;return!!e&&e instanceof Promise&&e.hasOwnProperty("__staleWhileFetching")&&e.__abortController instanceof C}async fetch(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:n=this.noDeleteOnStaleGet,ttl:o=this.ttl,noDisposeOnSet:h=this.noDisposeOnSet,size:r=0,sizeCalculation:a=this.sizeCalculation,noUpdateTTL:w=this.noUpdateTTL,noDeleteOnFetchRejection:f=this.noDeleteOnFetchRejection,allowStaleOnFetchRejection:d=this.allowStaleOnFetchRejection,ignoreFetchAbort:g=this.ignoreFetchAbort,allowStaleOnFetchAbort:A=this.allowStaleOnFetchAbort,context:p,forceRefresh:_=!1,status:l,signal:S}=e;if(!this.#v)return l&&(l.fetch="get"),this.get(t,{allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:n,status:l});let b={allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:n,ttl:o,noDisposeOnSet:h,size:r,sizeCalculation:a,noUpdateTTL:w,noDeleteOnFetchRejection:f,allowStaleOnFetchRejection:d,allowStaleOnFetchAbort:A,ignoreFetchAbort:g,status:l,signal:S},m=this.#s.get(t);if(m===void 0){l&&(l.fetch="miss");let u=this.#x(t,m,b,p);return u.__returned=u}else{let u=this.#t[m];if(this.#e(u)){let E=i&&u.__staleWhileFetching!==void 0;return l&&(l.fetch="inflight",E&&(l.returnedStale=!0)),E?u.__staleWhileFetching:u.__returned=u}let T=this.#p(m);if(!_&&!T)return l&&(l.fetch="hit"),this.#D(m),s&&this.#R(m),l&&this.#z(l,m),u;let F=this.#x(t,m,b,p),O=F.__staleWhileFetching!==void 0&&i;return l&&(l.fetch=T?"stale":"refresh",O&&T&&(l.returnedStale=!0)),O?F.__staleWhileFetching:F.__returned=F}}async forceFetch(t,e={}){let i=await this.fetch(t,e);if(i===void 0)throw new Error("fetch() returned undefined");return i}memo(t,e={}){let i=this.#U;if(!i)throw new Error("no memoMethod provided to constructor");let{context:s,forceRefresh:n,...o}=e,h=this.get(t,o);if(!n&&h!==void 0)return h;let r=i(t,h,{options:o,context:s});return this.set(t,r,o),r}get(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:n=this.noDeleteOnStaleGet,status:o}=e,h=this.#s.get(t);if(h!==void 0){let r=this.#t[h],a=this.#e(r);return o&&this.#z(o,h),this.#p(h)?(o&&(o.get="stale"),a?(o&&i&&r.__staleWhileFetching!==void 0&&(o.returnedStale=!0),i?r.__staleWhileFetching:void 0):(n||this.#E(t,"expire"),o&&i&&(o.returnedStale=!0),i?r:void 0)):(o&&(o.get="hit"),a?r.__staleWhileFetching:(this.#D(h),s&&this.#R(h),r))}else o&&(o.get="miss")}#k(t,e){this.#u[e]=t,this.#a[t]=e}#D(t){t!==this.#h&&(t===this.#l?this.#l=this.#a[t]:this.#k(this.#u[t],this.#a[t]),this.#k(this.#h,t),this.#h=t)}delete(t){return this.#E(t,"delete")}#E(t,e){let i=!1;if(this.#n!==0){let s=this.#s.get(t);if(s!==void 0)if(this.#g?.[s]&&(clearTimeout(this.#g?.[s]),this.#g[s]=void 0),i=!0,this.#n===1)this.#V(e);else{this.#W(s);let n=this.#t[s];if(this.#e(n)?n.__abortController.abort(new Error("deleted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(n,t,e),this.#f&&this.#r?.push([n,t,e])),this.#s.delete(t),this.#i[s]=void 0,this.#t[s]=void 0,s===this.#h)this.#h=this.#u[s];else if(s===this.#l)this.#l=this.#a[s];else{let o=this.#u[s];this.#a[o]=this.#a[s];let h=this.#a[s];this.#u[h]=this.#u[s]}this.#n--,this.#b.push(s)}}if(this.#f&&this.#r?.length){let s=this.#r,n;for(;n=s?.shift();)this.#S?.(...n)}return i}clear(){return this.#V("delete")}#V(t){for(let e of this.#O({allowStale:!0})){let i=this.#t[e];if(this.#e(i))i.__abortController.abort(new Error("deleted"));else{let s=this.#i[e];this.#T&&this.#w?.(i,s,t),this.#f&&this.#r?.push([i,s,t])}}if(this.#s.clear(),this.#t.fill(void 0),this.#i.fill(void 0),this.#d&&this.#A){this.#d.fill(0),this.#A.fill(0);for(let e of this.#g??[])e!==void 0&&clearTimeout(e);this.#g?.fill(void 0)}if(this.#y&&this.#y.fill(0),this.#l=0,this.#h=0,this.#b.length=0,this.#_=0,this.#n=0,this.#f&&this.#r){let e=this.#r,i;for(;i=e?.shift();)this.#S?.(...i)}}};exports.LRUCache=D; //# sourceMappingURL=index.min.js.map diff --git a/deps/npm/node_modules/lru-cache/dist/esm/index.js b/deps/npm/node_modules/lru-cache/dist/esm/index.js index c66c4b9e31a3ad..882d85dd71e51f 100644 --- a/deps/npm/node_modules/lru-cache/dist/esm/index.js +++ b/deps/npm/node_modules/lru-cache/dist/esm/index.js @@ -449,31 +449,38 @@ export class LRUCache { this.#setItemTTL = (index, ttl, start = this.#perf.now()) => { starts[index] = ttl !== 0 ? start : 0; ttls[index] = ttl; - // clear out the purge timer if we're setting TTL to 0, and - // previously had a ttl purge timer running, so it doesn't - // fire unnecessarily. - if (purgeTimers?.[index]) { - clearTimeout(purgeTimers[index]); - purgeTimers[index] = undefined; - } - if (ttl !== 0 && purgeTimers) { - const t = setTimeout(() => { - if (this.#isStale(index)) { - this.#delete(this.#keyList[index], 'expire'); - } - }, ttl + 1); - // unref() not supported on all platforms - /* c8 ignore start */ - if (t.unref) { - t.unref(); - } - /* c8 ignore stop */ - purgeTimers[index] = t; - } + setPurgetTimer(index, ttl); }; this.#updateItemAge = index => { starts[index] = ttls[index] !== 0 ? this.#perf.now() : 0; + setPurgetTimer(index, ttls[index]); }; + // clear out the purge timer if we're setting TTL to 0, and + // previously had a ttl purge timer running, so it doesn't + // fire unnecessarily. Don't need to do this if we're not doing + // autopurge. + const setPurgetTimer = !this.ttlAutopurge ? + () => { } + : (index, ttl) => { + if (purgeTimers?.[index]) { + clearTimeout(purgeTimers[index]); + purgeTimers[index] = undefined; + } + if (ttl && ttl !== 0 && purgeTimers) { + const t = setTimeout(() => { + if (this.#isStale(index)) { + this.#delete(this.#keyList[index], 'expire'); + } + }, ttl + 1); + // unref() not supported on all platforms + /* c8 ignore start */ + if (t.unref) { + t.unref(); + } + /* c8 ignore stop */ + purgeTimers[index] = t; + } + }; this.#statusTTL = (status, index) => { if (ttls[index]) { const ttl = ttls[index]; @@ -1216,8 +1223,7 @@ export class LRUCache { if (this.#valList[index] === p) { // if we allow stale on fetch rejections, then we need to ensure that // the stale value is not removed from the cache when the fetch fails. - const del = !noDelete || - !proceed && bf.__staleWhileFetching === undefined; + const del = !noDelete || (!proceed && bf.__staleWhileFetching === undefined); if (del) { this.#delete(k, 'fetch'); } diff --git a/deps/npm/node_modules/lru-cache/dist/esm/index.min.js b/deps/npm/node_modules/lru-cache/dist/esm/index.min.js index 81f29da1dea9f1..bb72f37e7d0643 100644 --- a/deps/npm/node_modules/lru-cache/dist/esm/index.min.js +++ b/deps/npm/node_modules/lru-cache/dist/esm/index.min.js @@ -1,2 +1,2 @@ -var M=typeof performance=="object"&&performance&&typeof performance.now=="function"?performance:Date,I=new Set,R=typeof process=="object"&&process?process:{},x=(a,t,e,i)=>{typeof R.emitWarning=="function"?R.emitWarning(a,t,e,i):console.error(`[${e}] ${t}: ${a}`)},C=globalThis.AbortController,D=globalThis.AbortSignal;if(typeof C>"u"){D=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(i,s){this._onabort.push(s)}},C=class{constructor(){t()}signal=new D;abort(i){if(!this.signal.aborted){this.signal.reason=i,this.signal.aborted=!0;for(let s of this.signal._onabort)s(i);this.signal.onabort?.(i)}}};let a=R.env?.LRU_CACHE_IGNORE_AC_WARNING!=="1",t=()=>{a&&(a=!1,x("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}var G=a=>!I.has(a),H=Symbol("type"),y=a=>a&&a===Math.floor(a)&&a>0&&isFinite(a),U=a=>y(a)?a<=Math.pow(2,8)?Uint8Array:a<=Math.pow(2,16)?Uint16Array:a<=Math.pow(2,32)?Uint32Array:a<=Number.MAX_SAFE_INTEGER?z:null:null,z=class extends Array{constructor(t){super(t),this.fill(0)}},W=class a{heap;length;static#o=!1;static create(t){let e=U(t);if(!e)return[];a.#o=!0;let i=new a(t,e);return a.#o=!1,i}constructor(t,e){if(!a.#o)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new e(t),this.length=0}push(t){this.heap[this.length++]=t}pop(){return this.heap[--this.length]}},L=class a{#o;#c;#w;#C;#S;#L;#I;#m;get perf(){return this.#m}ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#n;#_;#s;#i;#t;#a;#u;#l;#h;#b;#r;#y;#A;#d;#g;#T;#v;#f;#x;static unsafeExposeInternals(t){return{starts:t.#A,ttls:t.#d,autopurgeTimers:t.#g,sizes:t.#y,keyMap:t.#s,keyList:t.#i,valList:t.#t,next:t.#a,prev:t.#u,get head(){return t.#l},get tail(){return t.#h},free:t.#b,isBackgroundFetch:e=>t.#e(e),backgroundFetch:(e,i,s,h)=>t.#G(e,i,s,h),moveToTail:e=>t.#D(e),indexes:e=>t.#F(e),rindexes:e=>t.#O(e),isStale:e=>t.#p(e)}}get max(){return this.#o}get maxSize(){return this.#c}get calculatedSize(){return this.#_}get size(){return this.#n}get fetchMethod(){return this.#L}get memoMethod(){return this.#I}get dispose(){return this.#w}get onInsert(){return this.#C}get disposeAfter(){return this.#S}constructor(t){let{max:e=0,ttl:i,ttlResolution:s=1,ttlAutopurge:h,updateAgeOnGet:n,updateAgeOnHas:o,allowStale:r,dispose:f,onInsert:m,disposeAfter:c,noDisposeOnSet:d,noUpdateTTL:g,maxSize:A=0,maxEntrySize:p=0,sizeCalculation:_,fetchMethod:l,memoMethod:w,noDeleteOnFetchRejection:b,noDeleteOnStaleGet:S,allowStaleOnFetchRejection:u,allowStaleOnFetchAbort:T,ignoreFetchAbort:F,perf:v}=t;if(v!==void 0&&typeof v?.now!="function")throw new TypeError("perf option must have a now() method if specified");if(this.#m=v??M,e!==0&&!y(e))throw new TypeError("max option must be a nonnegative integer");let O=e?U(e):Array;if(!O)throw new Error("invalid max value: "+e);if(this.#o=e,this.#c=A,this.maxEntrySize=p||this.#c,this.sizeCalculation=_,this.sizeCalculation){if(!this.#c&&!this.maxEntrySize)throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");if(typeof this.sizeCalculation!="function")throw new TypeError("sizeCalculation set to non-function")}if(w!==void 0&&typeof w!="function")throw new TypeError("memoMethod must be a function if defined");if(this.#I=w,l!==void 0&&typeof l!="function")throw new TypeError("fetchMethod must be a function if specified");if(this.#L=l,this.#v=!!l,this.#s=new Map,this.#i=new Array(e).fill(void 0),this.#t=new Array(e).fill(void 0),this.#a=new O(e),this.#u=new O(e),this.#l=0,this.#h=0,this.#b=W.create(e),this.#n=0,this.#_=0,typeof f=="function"&&(this.#w=f),typeof m=="function"&&(this.#C=m),typeof c=="function"?(this.#S=c,this.#r=[]):(this.#S=void 0,this.#r=void 0),this.#T=!!this.#w,this.#x=!!this.#C,this.#f=!!this.#S,this.noDisposeOnSet=!!d,this.noUpdateTTL=!!g,this.noDeleteOnFetchRejection=!!b,this.allowStaleOnFetchRejection=!!u,this.allowStaleOnFetchAbort=!!T,this.ignoreFetchAbort=!!F,this.maxEntrySize!==0){if(this.#c!==0&&!y(this.#c))throw new TypeError("maxSize must be a positive integer if specified");if(!y(this.maxEntrySize))throw new TypeError("maxEntrySize must be a positive integer if specified");this.#B()}if(this.allowStale=!!r,this.noDeleteOnStaleGet=!!S,this.updateAgeOnGet=!!n,this.updateAgeOnHas=!!o,this.ttlResolution=y(s)||s===0?s:1,this.ttlAutopurge=!!h,this.ttl=i||0,this.ttl){if(!y(this.ttl))throw new TypeError("ttl must be a positive integer if specified");this.#j()}if(this.#o===0&&this.ttl===0&&this.#c===0)throw new TypeError("At least one of max, maxSize, or ttl is required");if(!this.ttlAutopurge&&!this.#o&&!this.#c){let E="LRU_CACHE_UNBOUNDED";G(E)&&(I.add(E),x("TTL caching without ttlAutopurge, max, or maxSize can result in unbounded memory consumption.","UnboundedCacheWarning",E,a))}}getRemainingTTL(t){return this.#s.has(t)?1/0:0}#j(){let t=new z(this.#o),e=new z(this.#o);this.#d=t,this.#A=e;let i=this.ttlAutopurge?new Array(this.#o):void 0;this.#g=i,this.#N=(n,o,r=this.#m.now())=>{if(e[n]=o!==0?r:0,t[n]=o,i?.[n]&&(clearTimeout(i[n]),i[n]=void 0),o!==0&&i){let f=setTimeout(()=>{this.#p(n)&&this.#E(this.#i[n],"expire")},o+1);f.unref&&f.unref(),i[n]=f}},this.#R=n=>{e[n]=t[n]!==0?this.#m.now():0},this.#z=(n,o)=>{if(t[o]){let r=t[o],f=e[o];if(!r||!f)return;n.ttl=r,n.start=f,n.now=s||h();let m=n.now-f;n.remainingTTL=r-m}};let s=0,h=()=>{let n=this.#m.now();if(this.ttlResolution>0){s=n;let o=setTimeout(()=>s=0,this.ttlResolution);o.unref&&o.unref()}return n};this.getRemainingTTL=n=>{let o=this.#s.get(n);if(o===void 0)return 0;let r=t[o],f=e[o];if(!r||!f)return 1/0;let m=(s||h())-f;return r-m},this.#p=n=>{let o=e[n],r=t[n];return!!r&&!!o&&(s||h())-o>r}}#R=()=>{};#z=()=>{};#N=()=>{};#p=()=>!1;#B(){let t=new z(this.#o);this.#_=0,this.#y=t,this.#W=e=>{this.#_-=t[e],t[e]=0},this.#P=(e,i,s,h)=>{if(this.#e(i))return 0;if(!y(s))if(h){if(typeof h!="function")throw new TypeError("sizeCalculation must be a function");if(s=h(i,e),!y(s))throw new TypeError("sizeCalculation return invalid (expect positive integer)")}else throw new TypeError("invalid size value (must be positive integer). When maxSize or maxEntrySize is used, sizeCalculation or size must be set.");return s},this.#U=(e,i,s)=>{if(t[e]=i,this.#c){let h=this.#c-t[e];for(;this.#_>h;)this.#M(!0)}this.#_+=t[e],s&&(s.entrySize=i,s.totalCalculatedSize=this.#_)}}#W=t=>{};#U=(t,e,i)=>{};#P=(t,e,i,s)=>{if(i||s)throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");return 0};*#F({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#h;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#l));)e=this.#u[e]}*#O({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#l;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#h));)e=this.#a[e]}#H(t){return t!==void 0&&this.#s.get(this.#i[t])===t}*entries(){for(let t of this.#F())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*rentries(){for(let t of this.#O())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*keys(){for(let t of this.#F()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*rkeys(){for(let t of this.#O()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*values(){for(let t of this.#F())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}*rvalues(){for(let t of this.#O())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}[Symbol.iterator](){return this.entries()}[Symbol.toStringTag]="LRUCache";find(t,e={}){for(let i of this.#F()){let s=this.#t[i],h=this.#e(s)?s.__staleWhileFetching:s;if(h!==void 0&&t(h,this.#i[i],this))return this.get(this.#i[i],e)}}forEach(t,e=this){for(let i of this.#F()){let s=this.#t[i],h=this.#e(s)?s.__staleWhileFetching:s;h!==void 0&&t.call(e,h,this.#i[i],this)}}rforEach(t,e=this){for(let i of this.#O()){let s=this.#t[i],h=this.#e(s)?s.__staleWhileFetching:s;h!==void 0&&t.call(e,h,this.#i[i],this)}}purgeStale(){let t=!1;for(let e of this.#O({allowStale:!0}))this.#p(e)&&(this.#E(this.#i[e],"expire"),t=!0);return t}info(t){let e=this.#s.get(t);if(e===void 0)return;let i=this.#t[e],s=this.#e(i)?i.__staleWhileFetching:i;if(s===void 0)return;let h={value:s};if(this.#d&&this.#A){let n=this.#d[e],o=this.#A[e];if(n&&o){let r=n-(this.#m.now()-o);h.ttl=r,h.start=Date.now()}}return this.#y&&(h.size=this.#y[e]),h}dump(){let t=[];for(let e of this.#F({allowStale:!0})){let i=this.#i[e],s=this.#t[e],h=this.#e(s)?s.__staleWhileFetching:s;if(h===void 0||i===void 0)continue;let n={value:h};if(this.#d&&this.#A){n.ttl=this.#d[e];let o=this.#m.now()-this.#A[e];n.start=Math.floor(Date.now()-o)}this.#y&&(n.size=this.#y[e]),t.unshift([i,n])}return t}load(t){this.clear();for(let[e,i]of t){if(i.start){let s=Date.now()-i.start;i.start=this.#m.now()-s}this.set(e,i.value,i)}}set(t,e,i={}){if(e===void 0)return this.delete(t),this;let{ttl:s=this.ttl,start:h,noDisposeOnSet:n=this.noDisposeOnSet,sizeCalculation:o=this.sizeCalculation,status:r}=i,{noUpdateTTL:f=this.noUpdateTTL}=i,m=this.#P(t,e,i.size||0,o);if(this.maxEntrySize&&m>this.maxEntrySize)return r&&(r.set="miss",r.maxEntrySizeExceeded=!0),this.#E(t,"set"),this;let c=this.#n===0?void 0:this.#s.get(t);if(c===void 0)c=this.#n===0?this.#h:this.#b.length!==0?this.#b.pop():this.#n===this.#o?this.#M(!1):this.#n,this.#i[c]=t,this.#t[c]=e,this.#s.set(t,c),this.#a[this.#h]=c,this.#u[c]=this.#h,this.#h=c,this.#n++,this.#U(c,m,r),r&&(r.set="add"),f=!1,this.#x&&this.#C?.(e,t,"add");else{this.#D(c);let d=this.#t[c];if(e!==d){if(this.#v&&this.#e(d)){d.__abortController.abort(new Error("replaced"));let{__staleWhileFetching:g}=d;g!==void 0&&!n&&(this.#T&&this.#w?.(g,t,"set"),this.#f&&this.#r?.push([g,t,"set"]))}else n||(this.#T&&this.#w?.(d,t,"set"),this.#f&&this.#r?.push([d,t,"set"]));if(this.#W(c),this.#U(c,m,r),this.#t[c]=e,r){r.set="replace";let g=d&&this.#e(d)?d.__staleWhileFetching:d;g!==void 0&&(r.oldValue=g)}}else r&&(r.set="update");this.#x&&this.onInsert?.(e,t,e===d?"update":"replace")}if(s!==0&&!this.#d&&this.#j(),this.#d&&(f||this.#N(c,s,h),r&&this.#z(r,c)),!n&&this.#f&&this.#r){let d=this.#r,g;for(;g=d?.shift();)this.#S?.(...g)}return this}pop(){try{for(;this.#n;){let t=this.#t[this.#l];if(this.#M(!0),this.#e(t)){if(t.__staleWhileFetching)return t.__staleWhileFetching}else if(t!==void 0)return t}}finally{if(this.#f&&this.#r){let t=this.#r,e;for(;e=t?.shift();)this.#S?.(...e)}}}#M(t){let e=this.#l,i=this.#i[e],s=this.#t[e];return this.#v&&this.#e(s)?s.__abortController.abort(new Error("evicted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(s,i,"evict"),this.#f&&this.#r?.push([s,i,"evict"])),this.#W(e),this.#g?.[e]&&(clearTimeout(this.#g[e]),this.#g[e]=void 0),t&&(this.#i[e]=void 0,this.#t[e]=void 0,this.#b.push(e)),this.#n===1?(this.#l=this.#h=0,this.#b.length=0):this.#l=this.#a[e],this.#s.delete(i),this.#n--,e}has(t,e={}){let{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=e,h=this.#s.get(t);if(h!==void 0){let n=this.#t[h];if(this.#e(n)&&n.__staleWhileFetching===void 0)return!1;if(this.#p(h))s&&(s.has="stale",this.#z(s,h));else return i&&this.#R(h),s&&(s.has="hit",this.#z(s,h)),!0}else s&&(s.has="miss");return!1}peek(t,e={}){let{allowStale:i=this.allowStale}=e,s=this.#s.get(t);if(s===void 0||!i&&this.#p(s))return;let h=this.#t[s];return this.#e(h)?h.__staleWhileFetching:h}#G(t,e,i,s){let h=e===void 0?void 0:this.#t[e];if(this.#e(h))return h;let n=new C,{signal:o}=i;o?.addEventListener("abort",()=>n.abort(o.reason),{signal:n.signal});let r={signal:n.signal,options:i,context:s},f=(p,_=!1)=>{let{aborted:l}=n.signal,w=i.ignoreFetchAbort&&p!==void 0,b=i.ignoreFetchAbort||!!(i.allowStaleOnFetchAbort&&p!==void 0);if(i.status&&(l&&!_?(i.status.fetchAborted=!0,i.status.fetchError=n.signal.reason,w&&(i.status.fetchAbortIgnored=!0)):i.status.fetchResolved=!0),l&&!w&&!_)return c(n.signal.reason,b);let S=g,u=this.#t[e];return(u===g||w&&_&&u===void 0)&&(p===void 0?S.__staleWhileFetching!==void 0?this.#t[e]=S.__staleWhileFetching:this.#E(t,"fetch"):(i.status&&(i.status.fetchUpdated=!0),this.set(t,p,r.options))),p},m=p=>(i.status&&(i.status.fetchRejected=!0,i.status.fetchError=p),c(p,!1)),c=(p,_)=>{let{aborted:l}=n.signal,w=l&&i.allowStaleOnFetchAbort,b=w||i.allowStaleOnFetchRejection,S=b||i.noDeleteOnFetchRejection,u=g;if(this.#t[e]===g&&(!S||!_&&u.__staleWhileFetching===void 0?this.#E(t,"fetch"):w||(this.#t[e]=u.__staleWhileFetching)),b)return i.status&&u.__staleWhileFetching!==void 0&&(i.status.returnedStale=!0),u.__staleWhileFetching;if(u.__returned===u)throw p},d=(p,_)=>{let l=this.#L?.(t,h,r);l&&l instanceof Promise&&l.then(w=>p(w===void 0?void 0:w),_),n.signal.addEventListener("abort",()=>{(!i.ignoreFetchAbort||i.allowStaleOnFetchAbort)&&(p(void 0),i.allowStaleOnFetchAbort&&(p=w=>f(w,!0)))})};i.status&&(i.status.fetchDispatched=!0);let g=new Promise(d).then(f,m),A=Object.assign(g,{__abortController:n,__staleWhileFetching:h,__returned:void 0});return e===void 0?(this.set(t,A,{...r.options,status:void 0}),e=this.#s.get(t)):this.#t[e]=A,A}#e(t){if(!this.#v)return!1;let e=t;return!!e&&e instanceof Promise&&e.hasOwnProperty("__staleWhileFetching")&&e.__abortController instanceof C}async fetch(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:h=this.noDeleteOnStaleGet,ttl:n=this.ttl,noDisposeOnSet:o=this.noDisposeOnSet,size:r=0,sizeCalculation:f=this.sizeCalculation,noUpdateTTL:m=this.noUpdateTTL,noDeleteOnFetchRejection:c=this.noDeleteOnFetchRejection,allowStaleOnFetchRejection:d=this.allowStaleOnFetchRejection,ignoreFetchAbort:g=this.ignoreFetchAbort,allowStaleOnFetchAbort:A=this.allowStaleOnFetchAbort,context:p,forceRefresh:_=!1,status:l,signal:w}=e;if(!this.#v)return l&&(l.fetch="get"),this.get(t,{allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:h,status:l});let b={allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:h,ttl:n,noDisposeOnSet:o,size:r,sizeCalculation:f,noUpdateTTL:m,noDeleteOnFetchRejection:c,allowStaleOnFetchRejection:d,allowStaleOnFetchAbort:A,ignoreFetchAbort:g,status:l,signal:w},S=this.#s.get(t);if(S===void 0){l&&(l.fetch="miss");let u=this.#G(t,S,b,p);return u.__returned=u}else{let u=this.#t[S];if(this.#e(u)){let E=i&&u.__staleWhileFetching!==void 0;return l&&(l.fetch="inflight",E&&(l.returnedStale=!0)),E?u.__staleWhileFetching:u.__returned=u}let T=this.#p(S);if(!_&&!T)return l&&(l.fetch="hit"),this.#D(S),s&&this.#R(S),l&&this.#z(l,S),u;let F=this.#G(t,S,b,p),O=F.__staleWhileFetching!==void 0&&i;return l&&(l.fetch=T?"stale":"refresh",O&&T&&(l.returnedStale=!0)),O?F.__staleWhileFetching:F.__returned=F}}async forceFetch(t,e={}){let i=await this.fetch(t,e);if(i===void 0)throw new Error("fetch() returned undefined");return i}memo(t,e={}){let i=this.#I;if(!i)throw new Error("no memoMethod provided to constructor");let{context:s,forceRefresh:h,...n}=e,o=this.get(t,n);if(!h&&o!==void 0)return o;let r=i(t,o,{options:n,context:s});return this.set(t,r,n),r}get(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:h=this.noDeleteOnStaleGet,status:n}=e,o=this.#s.get(t);if(o!==void 0){let r=this.#t[o],f=this.#e(r);return n&&this.#z(n,o),this.#p(o)?(n&&(n.get="stale"),f?(n&&i&&r.__staleWhileFetching!==void 0&&(n.returnedStale=!0),i?r.__staleWhileFetching:void 0):(h||this.#E(t,"expire"),n&&i&&(n.returnedStale=!0),i?r:void 0)):(n&&(n.get="hit"),f?r.__staleWhileFetching:(this.#D(o),s&&this.#R(o),r))}else n&&(n.get="miss")}#k(t,e){this.#u[e]=t,this.#a[t]=e}#D(t){t!==this.#h&&(t===this.#l?this.#l=this.#a[t]:this.#k(this.#u[t],this.#a[t]),this.#k(this.#h,t),this.#h=t)}delete(t){return this.#E(t,"delete")}#E(t,e){let i=!1;if(this.#n!==0){let s=this.#s.get(t);if(s!==void 0)if(this.#g?.[s]&&(clearTimeout(this.#g?.[s]),this.#g[s]=void 0),i=!0,this.#n===1)this.#V(e);else{this.#W(s);let h=this.#t[s];if(this.#e(h)?h.__abortController.abort(new Error("deleted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(h,t,e),this.#f&&this.#r?.push([h,t,e])),this.#s.delete(t),this.#i[s]=void 0,this.#t[s]=void 0,s===this.#h)this.#h=this.#u[s];else if(s===this.#l)this.#l=this.#a[s];else{let n=this.#u[s];this.#a[n]=this.#a[s];let o=this.#a[s];this.#u[o]=this.#u[s]}this.#n--,this.#b.push(s)}}if(this.#f&&this.#r?.length){let s=this.#r,h;for(;h=s?.shift();)this.#S?.(...h)}return i}clear(){return this.#V("delete")}#V(t){for(let e of this.#O({allowStale:!0})){let i=this.#t[e];if(this.#e(i))i.__abortController.abort(new Error("deleted"));else{let s=this.#i[e];this.#T&&this.#w?.(i,s,t),this.#f&&this.#r?.push([i,s,t])}}if(this.#s.clear(),this.#t.fill(void 0),this.#i.fill(void 0),this.#d&&this.#A){this.#d.fill(0),this.#A.fill(0);for(let e of this.#g??[])e!==void 0&&clearTimeout(e);this.#g?.fill(void 0)}if(this.#y&&this.#y.fill(0),this.#l=0,this.#h=0,this.#b.length=0,this.#_=0,this.#n=0,this.#f&&this.#r){let e=this.#r,i;for(;i=e?.shift();)this.#S?.(...i)}}};export{L as LRUCache}; +var x=typeof performance=="object"&&performance&&typeof performance.now=="function"?performance:Date,I=new Set,R=typeof process=="object"&&process?process:{},U=(c,t,e,i)=>{typeof R.emitWarning=="function"?R.emitWarning(c,t,e,i):console.error(`[${e}] ${t}: ${c}`)},C=globalThis.AbortController,D=globalThis.AbortSignal;if(typeof C>"u"){D=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(i,s){this._onabort.push(s)}},C=class{constructor(){t()}signal=new D;abort(i){if(!this.signal.aborted){this.signal.reason=i,this.signal.aborted=!0;for(let s of this.signal._onabort)s(i);this.signal.onabort?.(i)}}};let c=R.env?.LRU_CACHE_IGNORE_AC_WARNING!=="1",t=()=>{c&&(c=!1,U("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}var G=c=>!I.has(c),H=Symbol("type"),y=c=>c&&c===Math.floor(c)&&c>0&&isFinite(c),M=c=>y(c)?c<=Math.pow(2,8)?Uint8Array:c<=Math.pow(2,16)?Uint16Array:c<=Math.pow(2,32)?Uint32Array:c<=Number.MAX_SAFE_INTEGER?z:null:null,z=class extends Array{constructor(t){super(t),this.fill(0)}},W=class c{heap;length;static#o=!1;static create(t){let e=M(t);if(!e)return[];c.#o=!0;let i=new c(t,e);return c.#o=!1,i}constructor(t,e){if(!c.#o)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new e(t),this.length=0}push(t){this.heap[this.length++]=t}pop(){return this.heap[--this.length]}},L=class c{#o;#c;#w;#C;#S;#L;#I;#m;get perf(){return this.#m}ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#n;#_;#s;#i;#t;#a;#u;#l;#h;#b;#r;#y;#A;#d;#g;#T;#v;#f;#U;static unsafeExposeInternals(t){return{starts:t.#A,ttls:t.#d,autopurgeTimers:t.#g,sizes:t.#y,keyMap:t.#s,keyList:t.#i,valList:t.#t,next:t.#a,prev:t.#u,get head(){return t.#l},get tail(){return t.#h},free:t.#b,isBackgroundFetch:e=>t.#e(e),backgroundFetch:(e,i,s,n)=>t.#G(e,i,s,n),moveToTail:e=>t.#D(e),indexes:e=>t.#F(e),rindexes:e=>t.#O(e),isStale:e=>t.#p(e)}}get max(){return this.#o}get maxSize(){return this.#c}get calculatedSize(){return this.#_}get size(){return this.#n}get fetchMethod(){return this.#L}get memoMethod(){return this.#I}get dispose(){return this.#w}get onInsert(){return this.#C}get disposeAfter(){return this.#S}constructor(t){let{max:e=0,ttl:i,ttlResolution:s=1,ttlAutopurge:n,updateAgeOnGet:o,updateAgeOnHas:h,allowStale:r,dispose:a,onInsert:w,disposeAfter:f,noDisposeOnSet:d,noUpdateTTL:g,maxSize:A=0,maxEntrySize:p=0,sizeCalculation:_,fetchMethod:l,memoMethod:S,noDeleteOnFetchRejection:b,noDeleteOnStaleGet:m,allowStaleOnFetchRejection:u,allowStaleOnFetchAbort:T,ignoreFetchAbort:F,perf:v}=t;if(v!==void 0&&typeof v?.now!="function")throw new TypeError("perf option must have a now() method if specified");if(this.#m=v??x,e!==0&&!y(e))throw new TypeError("max option must be a nonnegative integer");let O=e?M(e):Array;if(!O)throw new Error("invalid max value: "+e);if(this.#o=e,this.#c=A,this.maxEntrySize=p||this.#c,this.sizeCalculation=_,this.sizeCalculation){if(!this.#c&&!this.maxEntrySize)throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");if(typeof this.sizeCalculation!="function")throw new TypeError("sizeCalculation set to non-function")}if(S!==void 0&&typeof S!="function")throw new TypeError("memoMethod must be a function if defined");if(this.#I=S,l!==void 0&&typeof l!="function")throw new TypeError("fetchMethod must be a function if specified");if(this.#L=l,this.#v=!!l,this.#s=new Map,this.#i=new Array(e).fill(void 0),this.#t=new Array(e).fill(void 0),this.#a=new O(e),this.#u=new O(e),this.#l=0,this.#h=0,this.#b=W.create(e),this.#n=0,this.#_=0,typeof a=="function"&&(this.#w=a),typeof w=="function"&&(this.#C=w),typeof f=="function"?(this.#S=f,this.#r=[]):(this.#S=void 0,this.#r=void 0),this.#T=!!this.#w,this.#U=!!this.#C,this.#f=!!this.#S,this.noDisposeOnSet=!!d,this.noUpdateTTL=!!g,this.noDeleteOnFetchRejection=!!b,this.allowStaleOnFetchRejection=!!u,this.allowStaleOnFetchAbort=!!T,this.ignoreFetchAbort=!!F,this.maxEntrySize!==0){if(this.#c!==0&&!y(this.#c))throw new TypeError("maxSize must be a positive integer if specified");if(!y(this.maxEntrySize))throw new TypeError("maxEntrySize must be a positive integer if specified");this.#B()}if(this.allowStale=!!r,this.noDeleteOnStaleGet=!!m,this.updateAgeOnGet=!!o,this.updateAgeOnHas=!!h,this.ttlResolution=y(s)||s===0?s:1,this.ttlAutopurge=!!n,this.ttl=i||0,this.ttl){if(!y(this.ttl))throw new TypeError("ttl must be a positive integer if specified");this.#j()}if(this.#o===0&&this.ttl===0&&this.#c===0)throw new TypeError("At least one of max, maxSize, or ttl is required");if(!this.ttlAutopurge&&!this.#o&&!this.#c){let E="LRU_CACHE_UNBOUNDED";G(E)&&(I.add(E),U("TTL caching without ttlAutopurge, max, or maxSize can result in unbounded memory consumption.","UnboundedCacheWarning",E,c))}}getRemainingTTL(t){return this.#s.has(t)?1/0:0}#j(){let t=new z(this.#o),e=new z(this.#o);this.#d=t,this.#A=e;let i=this.ttlAutopurge?new Array(this.#o):void 0;this.#g=i,this.#N=(h,r,a=this.#m.now())=>{e[h]=r!==0?a:0,t[h]=r,s(h,r)},this.#R=h=>{e[h]=t[h]!==0?this.#m.now():0,s(h,t[h])};let s=this.ttlAutopurge?(h,r)=>{if(i?.[h]&&(clearTimeout(i[h]),i[h]=void 0),r&&r!==0&&i){let a=setTimeout(()=>{this.#p(h)&&this.#E(this.#i[h],"expire")},r+1);a.unref&&a.unref(),i[h]=a}}:()=>{};this.#z=(h,r)=>{if(t[r]){let a=t[r],w=e[r];if(!a||!w)return;h.ttl=a,h.start=w,h.now=n||o();let f=h.now-w;h.remainingTTL=a-f}};let n=0,o=()=>{let h=this.#m.now();if(this.ttlResolution>0){n=h;let r=setTimeout(()=>n=0,this.ttlResolution);r.unref&&r.unref()}return h};this.getRemainingTTL=h=>{let r=this.#s.get(h);if(r===void 0)return 0;let a=t[r],w=e[r];if(!a||!w)return 1/0;let f=(n||o())-w;return a-f},this.#p=h=>{let r=e[h],a=t[h];return!!a&&!!r&&(n||o())-r>a}}#R=()=>{};#z=()=>{};#N=()=>{};#p=()=>!1;#B(){let t=new z(this.#o);this.#_=0,this.#y=t,this.#W=e=>{this.#_-=t[e],t[e]=0},this.#P=(e,i,s,n)=>{if(this.#e(i))return 0;if(!y(s))if(n){if(typeof n!="function")throw new TypeError("sizeCalculation must be a function");if(s=n(i,e),!y(s))throw new TypeError("sizeCalculation return invalid (expect positive integer)")}else throw new TypeError("invalid size value (must be positive integer). When maxSize or maxEntrySize is used, sizeCalculation or size must be set.");return s},this.#M=(e,i,s)=>{if(t[e]=i,this.#c){let n=this.#c-t[e];for(;this.#_>n;)this.#x(!0)}this.#_+=t[e],s&&(s.entrySize=i,s.totalCalculatedSize=this.#_)}}#W=t=>{};#M=(t,e,i)=>{};#P=(t,e,i,s)=>{if(i||s)throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");return 0};*#F({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#h;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#l));)e=this.#u[e]}*#O({allowStale:t=this.allowStale}={}){if(this.#n)for(let e=this.#l;!(!this.#H(e)||((t||!this.#p(e))&&(yield e),e===this.#h));)e=this.#a[e]}#H(t){return t!==void 0&&this.#s.get(this.#i[t])===t}*entries(){for(let t of this.#F())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*rentries(){for(let t of this.#O())this.#t[t]!==void 0&&this.#i[t]!==void 0&&!this.#e(this.#t[t])&&(yield[this.#i[t],this.#t[t]])}*keys(){for(let t of this.#F()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*rkeys(){for(let t of this.#O()){let e=this.#i[t];e!==void 0&&!this.#e(this.#t[t])&&(yield e)}}*values(){for(let t of this.#F())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}*rvalues(){for(let t of this.#O())this.#t[t]!==void 0&&!this.#e(this.#t[t])&&(yield this.#t[t])}[Symbol.iterator](){return this.entries()}[Symbol.toStringTag]="LRUCache";find(t,e={}){for(let i of this.#F()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;if(n!==void 0&&t(n,this.#i[i],this))return this.get(this.#i[i],e)}}forEach(t,e=this){for(let i of this.#F()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;n!==void 0&&t.call(e,n,this.#i[i],this)}}rforEach(t,e=this){for(let i of this.#O()){let s=this.#t[i],n=this.#e(s)?s.__staleWhileFetching:s;n!==void 0&&t.call(e,n,this.#i[i],this)}}purgeStale(){let t=!1;for(let e of this.#O({allowStale:!0}))this.#p(e)&&(this.#E(this.#i[e],"expire"),t=!0);return t}info(t){let e=this.#s.get(t);if(e===void 0)return;let i=this.#t[e],s=this.#e(i)?i.__staleWhileFetching:i;if(s===void 0)return;let n={value:s};if(this.#d&&this.#A){let o=this.#d[e],h=this.#A[e];if(o&&h){let r=o-(this.#m.now()-h);n.ttl=r,n.start=Date.now()}}return this.#y&&(n.size=this.#y[e]),n}dump(){let t=[];for(let e of this.#F({allowStale:!0})){let i=this.#i[e],s=this.#t[e],n=this.#e(s)?s.__staleWhileFetching:s;if(n===void 0||i===void 0)continue;let o={value:n};if(this.#d&&this.#A){o.ttl=this.#d[e];let h=this.#m.now()-this.#A[e];o.start=Math.floor(Date.now()-h)}this.#y&&(o.size=this.#y[e]),t.unshift([i,o])}return t}load(t){this.clear();for(let[e,i]of t){if(i.start){let s=Date.now()-i.start;i.start=this.#m.now()-s}this.set(e,i.value,i)}}set(t,e,i={}){if(e===void 0)return this.delete(t),this;let{ttl:s=this.ttl,start:n,noDisposeOnSet:o=this.noDisposeOnSet,sizeCalculation:h=this.sizeCalculation,status:r}=i,{noUpdateTTL:a=this.noUpdateTTL}=i,w=this.#P(t,e,i.size||0,h);if(this.maxEntrySize&&w>this.maxEntrySize)return r&&(r.set="miss",r.maxEntrySizeExceeded=!0),this.#E(t,"set"),this;let f=this.#n===0?void 0:this.#s.get(t);if(f===void 0)f=this.#n===0?this.#h:this.#b.length!==0?this.#b.pop():this.#n===this.#o?this.#x(!1):this.#n,this.#i[f]=t,this.#t[f]=e,this.#s.set(t,f),this.#a[this.#h]=f,this.#u[f]=this.#h,this.#h=f,this.#n++,this.#M(f,w,r),r&&(r.set="add"),a=!1,this.#U&&this.#C?.(e,t,"add");else{this.#D(f);let d=this.#t[f];if(e!==d){if(this.#v&&this.#e(d)){d.__abortController.abort(new Error("replaced"));let{__staleWhileFetching:g}=d;g!==void 0&&!o&&(this.#T&&this.#w?.(g,t,"set"),this.#f&&this.#r?.push([g,t,"set"]))}else o||(this.#T&&this.#w?.(d,t,"set"),this.#f&&this.#r?.push([d,t,"set"]));if(this.#W(f),this.#M(f,w,r),this.#t[f]=e,r){r.set="replace";let g=d&&this.#e(d)?d.__staleWhileFetching:d;g!==void 0&&(r.oldValue=g)}}else r&&(r.set="update");this.#U&&this.onInsert?.(e,t,e===d?"update":"replace")}if(s!==0&&!this.#d&&this.#j(),this.#d&&(a||this.#N(f,s,n),r&&this.#z(r,f)),!o&&this.#f&&this.#r){let d=this.#r,g;for(;g=d?.shift();)this.#S?.(...g)}return this}pop(){try{for(;this.#n;){let t=this.#t[this.#l];if(this.#x(!0),this.#e(t)){if(t.__staleWhileFetching)return t.__staleWhileFetching}else if(t!==void 0)return t}}finally{if(this.#f&&this.#r){let t=this.#r,e;for(;e=t?.shift();)this.#S?.(...e)}}}#x(t){let e=this.#l,i=this.#i[e],s=this.#t[e];return this.#v&&this.#e(s)?s.__abortController.abort(new Error("evicted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(s,i,"evict"),this.#f&&this.#r?.push([s,i,"evict"])),this.#W(e),this.#g?.[e]&&(clearTimeout(this.#g[e]),this.#g[e]=void 0),t&&(this.#i[e]=void 0,this.#t[e]=void 0,this.#b.push(e)),this.#n===1?(this.#l=this.#h=0,this.#b.length=0):this.#l=this.#a[e],this.#s.delete(i),this.#n--,e}has(t,e={}){let{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=e,n=this.#s.get(t);if(n!==void 0){let o=this.#t[n];if(this.#e(o)&&o.__staleWhileFetching===void 0)return!1;if(this.#p(n))s&&(s.has="stale",this.#z(s,n));else return i&&this.#R(n),s&&(s.has="hit",this.#z(s,n)),!0}else s&&(s.has="miss");return!1}peek(t,e={}){let{allowStale:i=this.allowStale}=e,s=this.#s.get(t);if(s===void 0||!i&&this.#p(s))return;let n=this.#t[s];return this.#e(n)?n.__staleWhileFetching:n}#G(t,e,i,s){let n=e===void 0?void 0:this.#t[e];if(this.#e(n))return n;let o=new C,{signal:h}=i;h?.addEventListener("abort",()=>o.abort(h.reason),{signal:o.signal});let r={signal:o.signal,options:i,context:s},a=(p,_=!1)=>{let{aborted:l}=o.signal,S=i.ignoreFetchAbort&&p!==void 0,b=i.ignoreFetchAbort||!!(i.allowStaleOnFetchAbort&&p!==void 0);if(i.status&&(l&&!_?(i.status.fetchAborted=!0,i.status.fetchError=o.signal.reason,S&&(i.status.fetchAbortIgnored=!0)):i.status.fetchResolved=!0),l&&!S&&!_)return f(o.signal.reason,b);let m=g,u=this.#t[e];return(u===g||S&&_&&u===void 0)&&(p===void 0?m.__staleWhileFetching!==void 0?this.#t[e]=m.__staleWhileFetching:this.#E(t,"fetch"):(i.status&&(i.status.fetchUpdated=!0),this.set(t,p,r.options))),p},w=p=>(i.status&&(i.status.fetchRejected=!0,i.status.fetchError=p),f(p,!1)),f=(p,_)=>{let{aborted:l}=o.signal,S=l&&i.allowStaleOnFetchAbort,b=S||i.allowStaleOnFetchRejection,m=b||i.noDeleteOnFetchRejection,u=g;if(this.#t[e]===g&&(!m||!_&&u.__staleWhileFetching===void 0?this.#E(t,"fetch"):S||(this.#t[e]=u.__staleWhileFetching)),b)return i.status&&u.__staleWhileFetching!==void 0&&(i.status.returnedStale=!0),u.__staleWhileFetching;if(u.__returned===u)throw p},d=(p,_)=>{let l=this.#L?.(t,n,r);l&&l instanceof Promise&&l.then(S=>p(S===void 0?void 0:S),_),o.signal.addEventListener("abort",()=>{(!i.ignoreFetchAbort||i.allowStaleOnFetchAbort)&&(p(void 0),i.allowStaleOnFetchAbort&&(p=S=>a(S,!0)))})};i.status&&(i.status.fetchDispatched=!0);let g=new Promise(d).then(a,w),A=Object.assign(g,{__abortController:o,__staleWhileFetching:n,__returned:void 0});return e===void 0?(this.set(t,A,{...r.options,status:void 0}),e=this.#s.get(t)):this.#t[e]=A,A}#e(t){if(!this.#v)return!1;let e=t;return!!e&&e instanceof Promise&&e.hasOwnProperty("__staleWhileFetching")&&e.__abortController instanceof C}async fetch(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:n=this.noDeleteOnStaleGet,ttl:o=this.ttl,noDisposeOnSet:h=this.noDisposeOnSet,size:r=0,sizeCalculation:a=this.sizeCalculation,noUpdateTTL:w=this.noUpdateTTL,noDeleteOnFetchRejection:f=this.noDeleteOnFetchRejection,allowStaleOnFetchRejection:d=this.allowStaleOnFetchRejection,ignoreFetchAbort:g=this.ignoreFetchAbort,allowStaleOnFetchAbort:A=this.allowStaleOnFetchAbort,context:p,forceRefresh:_=!1,status:l,signal:S}=e;if(!this.#v)return l&&(l.fetch="get"),this.get(t,{allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:n,status:l});let b={allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:n,ttl:o,noDisposeOnSet:h,size:r,sizeCalculation:a,noUpdateTTL:w,noDeleteOnFetchRejection:f,allowStaleOnFetchRejection:d,allowStaleOnFetchAbort:A,ignoreFetchAbort:g,status:l,signal:S},m=this.#s.get(t);if(m===void 0){l&&(l.fetch="miss");let u=this.#G(t,m,b,p);return u.__returned=u}else{let u=this.#t[m];if(this.#e(u)){let E=i&&u.__staleWhileFetching!==void 0;return l&&(l.fetch="inflight",E&&(l.returnedStale=!0)),E?u.__staleWhileFetching:u.__returned=u}let T=this.#p(m);if(!_&&!T)return l&&(l.fetch="hit"),this.#D(m),s&&this.#R(m),l&&this.#z(l,m),u;let F=this.#G(t,m,b,p),O=F.__staleWhileFetching!==void 0&&i;return l&&(l.fetch=T?"stale":"refresh",O&&T&&(l.returnedStale=!0)),O?F.__staleWhileFetching:F.__returned=F}}async forceFetch(t,e={}){let i=await this.fetch(t,e);if(i===void 0)throw new Error("fetch() returned undefined");return i}memo(t,e={}){let i=this.#I;if(!i)throw new Error("no memoMethod provided to constructor");let{context:s,forceRefresh:n,...o}=e,h=this.get(t,o);if(!n&&h!==void 0)return h;let r=i(t,h,{options:o,context:s});return this.set(t,r,o),r}get(t,e={}){let{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:n=this.noDeleteOnStaleGet,status:o}=e,h=this.#s.get(t);if(h!==void 0){let r=this.#t[h],a=this.#e(r);return o&&this.#z(o,h),this.#p(h)?(o&&(o.get="stale"),a?(o&&i&&r.__staleWhileFetching!==void 0&&(o.returnedStale=!0),i?r.__staleWhileFetching:void 0):(n||this.#E(t,"expire"),o&&i&&(o.returnedStale=!0),i?r:void 0)):(o&&(o.get="hit"),a?r.__staleWhileFetching:(this.#D(h),s&&this.#R(h),r))}else o&&(o.get="miss")}#k(t,e){this.#u[e]=t,this.#a[t]=e}#D(t){t!==this.#h&&(t===this.#l?this.#l=this.#a[t]:this.#k(this.#u[t],this.#a[t]),this.#k(this.#h,t),this.#h=t)}delete(t){return this.#E(t,"delete")}#E(t,e){let i=!1;if(this.#n!==0){let s=this.#s.get(t);if(s!==void 0)if(this.#g?.[s]&&(clearTimeout(this.#g?.[s]),this.#g[s]=void 0),i=!0,this.#n===1)this.#V(e);else{this.#W(s);let n=this.#t[s];if(this.#e(n)?n.__abortController.abort(new Error("deleted")):(this.#T||this.#f)&&(this.#T&&this.#w?.(n,t,e),this.#f&&this.#r?.push([n,t,e])),this.#s.delete(t),this.#i[s]=void 0,this.#t[s]=void 0,s===this.#h)this.#h=this.#u[s];else if(s===this.#l)this.#l=this.#a[s];else{let o=this.#u[s];this.#a[o]=this.#a[s];let h=this.#a[s];this.#u[h]=this.#u[s]}this.#n--,this.#b.push(s)}}if(this.#f&&this.#r?.length){let s=this.#r,n;for(;n=s?.shift();)this.#S?.(...n)}return i}clear(){return this.#V("delete")}#V(t){for(let e of this.#O({allowStale:!0})){let i=this.#t[e];if(this.#e(i))i.__abortController.abort(new Error("deleted"));else{let s=this.#i[e];this.#T&&this.#w?.(i,s,t),this.#f&&this.#r?.push([i,s,t])}}if(this.#s.clear(),this.#t.fill(void 0),this.#i.fill(void 0),this.#d&&this.#A){this.#d.fill(0),this.#A.fill(0);for(let e of this.#g??[])e!==void 0&&clearTimeout(e);this.#g?.fill(void 0)}if(this.#y&&this.#y.fill(0),this.#l=0,this.#h=0,this.#b.length=0,this.#_=0,this.#n=0,this.#f&&this.#r){let e=this.#r,i;for(;i=e?.shift();)this.#S?.(...i)}}};export{L as LRUCache}; //# sourceMappingURL=index.min.js.map diff --git a/deps/npm/node_modules/lru-cache/package.json b/deps/npm/node_modules/lru-cache/package.json index 1554184f053da4..aac56efd1920ed 100644 --- a/deps/npm/node_modules/lru-cache/package.json +++ b/deps/npm/node_modules/lru-cache/package.json @@ -1,7 +1,7 @@ { "name": "lru-cache", "description": "A cache object that deletes the least-recently-used items.", - "version": "11.2.6", + "version": "11.2.7", "author": "Isaac Z. Schlueter ", "keywords": [ "mru", @@ -66,14 +66,6 @@ "engines": { "node": "20 || >=22" }, - "tap": { - "node-arg": [ - "--expose-gc" - ], - "plugin": [ - "@tapjs/clock" - ] - }, "exports": { "./raw": { "import": { diff --git a/deps/npm/node_modules/make-fetch-happen/lib/remote.js b/deps/npm/node_modules/make-fetch-happen/lib/remote.js index 5dd17c58b28a22..061759638f05cb 100644 --- a/deps/npm/node_modules/make-fetch-happen/lib/remote.js +++ b/deps/npm/node_modules/make-fetch-happen/lib/remote.js @@ -3,6 +3,7 @@ const fetch = require('minipass-fetch') const { promiseRetry } = require('@gar/promise-retry') const ssri = require('ssri') const { log } = require('proc-log') +const { redact: cleanUrl } = require('@npmcli/redact') const CachingMinipassPipeline = require('./pipeline.js') const { getAgent } = require('@npmcli/agent') @@ -55,6 +56,7 @@ const remoteFetch = (request, options) => { return promiseRetry(async (retryHandler, attemptNum) => { const req = new fetch.Request(request, _opts) + const url = cleanUrl(req.url) try { let res = await fetch(req, _opts) if (_opts.integrity && res.status === 200) { @@ -92,7 +94,7 @@ const remoteFetch = (request, options) => { } /* eslint-disable-next-line max-len */ - log.http('fetch', `${req.method} ${req.url} attempt ${attemptNum} failed with ${res.status}`) + log.http('fetch', `${req.method} ${url} attempt ${attemptNum} failed with ${res.status}`) return retryHandler(res) } @@ -116,7 +118,7 @@ const remoteFetch = (request, options) => { options.onRetry(err) } - log.http('fetch', `${req.method} ${req.url} attempt ${attemptNum} failed with ${err.code}`) + log.http('fetch', `${req.method} ${url} attempt ${attemptNum} failed with ${err.code}`) return retryHandler(err) } }, options.retry).catch((err) => { diff --git a/deps/npm/node_modules/make-fetch-happen/package.json b/deps/npm/node_modules/make-fetch-happen/package.json index 5090b5042756fe..1d06ac4889c3e3 100644 --- a/deps/npm/node_modules/make-fetch-happen/package.json +++ b/deps/npm/node_modules/make-fetch-happen/package.json @@ -1,6 +1,6 @@ { "name": "make-fetch-happen", - "version": "15.0.4", + "version": "15.0.5", "description": "Opinionated, caching, retrying fetch client", "main": "lib/index.js", "files": [ @@ -35,6 +35,7 @@ "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", diff --git a/deps/npm/node_modules/promise-retry/LICENSE b/deps/npm/node_modules/promise-retry/LICENSE deleted file mode 100644 index db5e914de1f585..00000000000000 --- a/deps/npm/node_modules/promise-retry/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2014 IndigoUnited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/deps/npm/node_modules/promise-retry/index.js b/deps/npm/node_modules/promise-retry/index.js deleted file mode 100644 index 5df48ae91602d6..00000000000000 --- a/deps/npm/node_modules/promise-retry/index.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -var errcode = require('err-code'); -var retry = require('retry'); - -var hasOwn = Object.prototype.hasOwnProperty; - -function isRetryError(err) { - return err && err.code === 'EPROMISERETRY' && hasOwn.call(err, 'retried'); -} - -function promiseRetry(fn, options) { - var temp; - var operation; - - if (typeof fn === 'object' && typeof options === 'function') { - // Swap options and fn when using alternate signature (options, fn) - temp = options; - options = fn; - fn = temp; - } - - operation = retry.operation(options); - - return new Promise(function (resolve, reject) { - operation.attempt(function (number) { - Promise.resolve() - .then(function () { - return fn(function (err) { - if (isRetryError(err)) { - err = err.retried; - } - - throw errcode(new Error('Retrying'), 'EPROMISERETRY', { retried: err }); - }, number); - }) - .then(resolve, function (err) { - if (isRetryError(err)) { - err = err.retried; - - if (operation.retry(err || new Error())) { - return; - } - } - - reject(err); - }); - }); - }); -} - -module.exports = promiseRetry; diff --git a/deps/npm/node_modules/promise-retry/package.json b/deps/npm/node_modules/promise-retry/package.json deleted file mode 100644 index 6842de823fd198..00000000000000 --- a/deps/npm/node_modules/promise-retry/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "promise-retry", - "version": "2.0.1", - "description": "Retries a function that returns a promise, leveraging the power of the retry module.", - "main": "index.js", - "scripts": { - "test": "mocha --bail -t 10000" - }, - "bugs": { - "url": "https://github.com/IndigoUnited/node-promise-retry/issues/" - }, - "repository": { - "type": "git", - "url": "git://github.com/IndigoUnited/node-promise-retry.git" - }, - "keywords": [ - "retry", - "promise", - "backoff", - "repeat", - "replay" - ], - "author": "IndigoUnited (http://indigounited.com)", - "license": "MIT", - "devDependencies": { - "expect.js": "^0.3.1", - "mocha": "^8.0.1", - "sleep-promise": "^8.0.1" - }, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } -} diff --git a/deps/npm/node_modules/promise-retry/test/test.js b/deps/npm/node_modules/promise-retry/test/test.js deleted file mode 100644 index 466b0991e0f558..00000000000000 --- a/deps/npm/node_modules/promise-retry/test/test.js +++ /dev/null @@ -1,263 +0,0 @@ -'use strict'; - -var expect = require('expect.js'); -var promiseRetry = require('../'); -var promiseDelay = require('sleep-promise'); - -describe('promise-retry', function () { - it('should call fn again if retry was called', function () { - var count = 0; - - return promiseRetry(function (retry) { - count += 1; - - return promiseDelay(10) - .then(function () { - if (count <= 2) { - retry(new Error('foo')); - } - - return 'final'; - }); - }, { factor: 1 }) - .then(function (value) { - expect(value).to.be('final'); - expect(count).to.be(3); - }, function () { - throw new Error('should not fail'); - }); - }); - - it('should call fn with the attempt number', function () { - var count = 0; - - return promiseRetry(function (retry, number) { - count += 1; - expect(count).to.equal(number); - - return promiseDelay(10) - .then(function () { - if (count <= 2) { - retry(new Error('foo')); - } - - return 'final'; - }); - }, { factor: 1 }) - .then(function (value) { - expect(value).to.be('final'); - expect(count).to.be(3); - }, function () { - throw new Error('should not fail'); - }); - }); - - it('should not retry on fulfillment if retry was not called', function () { - var count = 0; - - return promiseRetry(function () { - count += 1; - - return promiseDelay(10) - .then(function () { - return 'final'; - }); - }) - .then(function (value) { - expect(value).to.be('final'); - expect(count).to.be(1); - }, function () { - throw new Error('should not fail'); - }); - }); - - it('should not retry on rejection if retry was not called', function () { - var count = 0; - - return promiseRetry(function () { - count += 1; - - return promiseDelay(10) - .then(function () { - throw new Error('foo'); - }); - }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err.message).to.be('foo'); - expect(count).to.be(1); - }); - }); - - it('should not retry on rejection if nr of retries is 0', function () { - var count = 0; - - return promiseRetry(function (retry) { - count += 1; - - return promiseDelay(10) - .then(function () { - throw new Error('foo'); - }) - .catch(retry); - }, { retries : 0 }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err.message).to.be('foo'); - expect(count).to.be(1); - }); - }); - - it('should reject the promise if the retries were exceeded', function () { - var count = 0; - - return promiseRetry(function (retry) { - count += 1; - - return promiseDelay(10) - .then(function () { - throw new Error('foo'); - }) - .catch(retry); - }, { retries: 2, factor: 1 }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err.message).to.be('foo'); - expect(count).to.be(3); - }); - }); - - it('should pass options to the underlying retry module', function () { - var count = 0; - - return promiseRetry(function (retry) { - return promiseDelay(10) - .then(function () { - if (count < 2) { - count += 1; - retry(new Error('foo')); - } - - return 'final'; - }); - }, { retries: 1, factor: 1 }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err.message).to.be('foo'); - }); - }); - - it('should convert direct fulfillments into promises', function () { - return promiseRetry(function () { - return 'final'; - }, { factor: 1 }) - .then(function (value) { - expect(value).to.be('final'); - }, function () { - throw new Error('should not fail'); - }); - }); - - it('should convert direct rejections into promises', function () { - promiseRetry(function () { - throw new Error('foo'); - }, { retries: 1, factor: 1 }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err.message).to.be('foo'); - }); - }); - - it('should not crash on undefined rejections', function () { - return promiseRetry(function () { - throw undefined; - }, { retries: 1, factor: 1 }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err).to.be(undefined); - }) - .then(function () { - return promiseRetry(function (retry) { - retry(); - }, { retries: 1, factor: 1 }); - }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err).to.be(undefined); - }); - }); - - it('should retry if retry() was called with undefined', function () { - var count = 0; - - return promiseRetry(function (retry) { - count += 1; - - return promiseDelay(10) - .then(function () { - if (count <= 2) { - retry(); - } - - return 'final'; - }); - }, { factor: 1 }) - .then(function (value) { - expect(value).to.be('final'); - expect(count).to.be(3); - }, function () { - throw new Error('should not fail'); - }); - }); - - it('should work with several retries in the same chain', function () { - var count = 0; - - return promiseRetry(function (retry) { - count += 1; - - return promiseDelay(10) - .then(function () { - retry(new Error('foo')); - }) - .catch(function (err) { - retry(err); - }); - }, { retries: 1, factor: 1 }) - .then(function () { - throw new Error('should not succeed'); - }, function (err) { - expect(err.message).to.be('foo'); - expect(count).to.be(2); - }); - }); - - it('should allow options to be passed first', function () { - var count = 0; - - return promiseRetry({ factor: 1 }, function (retry) { - count += 1; - - return promiseDelay(10) - .then(function () { - if (count <= 2) { - retry(new Error('foo')); - } - - return 'final'; - }); - }).then(function (value) { - expect(value).to.be('final'); - expect(count).to.be(3); - }, function () { - throw new Error('should not fail'); - }); - }); -}); diff --git a/deps/npm/node_modules/retry/License b/deps/npm/node_modules/retry/License deleted file mode 100644 index 0b58de379fb308..00000000000000 --- a/deps/npm/node_modules/retry/License +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2011: -Tim Koschützki (tim@debuggable.com) -Felix Geisendörfer (felix@debuggable.com) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. diff --git a/deps/npm/node_modules/retry/Makefile b/deps/npm/node_modules/retry/Makefile deleted file mode 100644 index 1968d8ff8b07bc..00000000000000 --- a/deps/npm/node_modules/retry/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -SHELL := /bin/bash - -release-major: test - npm version major -m "Release %s" - git push - npm publish - -release-minor: test - npm version minor -m "Release %s" - git push - npm publish - -release-patch: test - npm version patch -m "Release %s" - git push - npm publish - -.PHONY: test release-major release-minor release-patch diff --git a/deps/npm/node_modules/retry/equation.gif b/deps/npm/node_modules/retry/equation.gif deleted file mode 100644 index 97107237ba19f5..00000000000000 Binary files a/deps/npm/node_modules/retry/equation.gif and /dev/null differ diff --git a/deps/npm/node_modules/retry/example/dns.js b/deps/npm/node_modules/retry/example/dns.js deleted file mode 100644 index 446729b6f9af6b..00000000000000 --- a/deps/npm/node_modules/retry/example/dns.js +++ /dev/null @@ -1,31 +0,0 @@ -var dns = require('dns'); -var retry = require('../lib/retry'); - -function faultTolerantResolve(address, cb) { - var opts = { - retries: 2, - factor: 2, - minTimeout: 1 * 1000, - maxTimeout: 2 * 1000, - randomize: true - }; - var operation = retry.operation(opts); - - operation.attempt(function(currentAttempt) { - dns.resolve(address, function(err, addresses) { - if (operation.retry(err)) { - return; - } - - cb(operation.mainError(), operation.errors(), addresses); - }); - }); -} - -faultTolerantResolve('nodejs.org', function(err, errors, addresses) { - console.warn('err:'); - console.log(err); - - console.warn('addresses:'); - console.log(addresses); -}); \ No newline at end of file diff --git a/deps/npm/node_modules/retry/example/stop.js b/deps/npm/node_modules/retry/example/stop.js deleted file mode 100644 index e1ceafeebafc51..00000000000000 --- a/deps/npm/node_modules/retry/example/stop.js +++ /dev/null @@ -1,40 +0,0 @@ -var retry = require('../lib/retry'); - -function attemptAsyncOperation(someInput, cb) { - var opts = { - retries: 2, - factor: 2, - minTimeout: 1 * 1000, - maxTimeout: 2 * 1000, - randomize: true - }; - var operation = retry.operation(opts); - - operation.attempt(function(currentAttempt) { - failingAsyncOperation(someInput, function(err, result) { - - if (err && err.message === 'A fatal error') { - operation.stop(); - return cb(err); - } - - if (operation.retry(err)) { - return; - } - - cb(operation.mainError(), operation.errors(), result); - }); - }); -} - -attemptAsyncOperation('test input', function(err, errors, result) { - console.warn('err:'); - console.log(err); - - console.warn('result:'); - console.log(result); -}); - -function failingAsyncOperation(input, cb) { - return setImmediate(cb.bind(null, new Error('A fatal error'))); -} diff --git a/deps/npm/node_modules/retry/index.js b/deps/npm/node_modules/retry/index.js deleted file mode 100644 index ee62f3a112c28b..00000000000000 --- a/deps/npm/node_modules/retry/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib/retry'); \ No newline at end of file diff --git a/deps/npm/node_modules/retry/lib/retry.js b/deps/npm/node_modules/retry/lib/retry.js deleted file mode 100644 index dcb57680727948..00000000000000 --- a/deps/npm/node_modules/retry/lib/retry.js +++ /dev/null @@ -1,100 +0,0 @@ -var RetryOperation = require('./retry_operation'); - -exports.operation = function(options) { - var timeouts = exports.timeouts(options); - return new RetryOperation(timeouts, { - forever: options && options.forever, - unref: options && options.unref, - maxRetryTime: options && options.maxRetryTime - }); -}; - -exports.timeouts = function(options) { - if (options instanceof Array) { - return [].concat(options); - } - - var opts = { - retries: 10, - factor: 2, - minTimeout: 1 * 1000, - maxTimeout: Infinity, - randomize: false - }; - for (var key in options) { - opts[key] = options[key]; - } - - if (opts.minTimeout > opts.maxTimeout) { - throw new Error('minTimeout is greater than maxTimeout'); - } - - var timeouts = []; - for (var i = 0; i < opts.retries; i++) { - timeouts.push(this.createTimeout(i, opts)); - } - - if (options && options.forever && !timeouts.length) { - timeouts.push(this.createTimeout(i, opts)); - } - - // sort the array numerically ascending - timeouts.sort(function(a,b) { - return a - b; - }); - - return timeouts; -}; - -exports.createTimeout = function(attempt, opts) { - var random = (opts.randomize) - ? (Math.random() + 1) - : 1; - - var timeout = Math.round(random * opts.minTimeout * Math.pow(opts.factor, attempt)); - timeout = Math.min(timeout, opts.maxTimeout); - - return timeout; -}; - -exports.wrap = function(obj, options, methods) { - if (options instanceof Array) { - methods = options; - options = null; - } - - if (!methods) { - methods = []; - for (var key in obj) { - if (typeof obj[key] === 'function') { - methods.push(key); - } - } - } - - for (var i = 0; i < methods.length; i++) { - var method = methods[i]; - var original = obj[method]; - - obj[method] = function retryWrapper(original) { - var op = exports.operation(options); - var args = Array.prototype.slice.call(arguments, 1); - var callback = args.pop(); - - args.push(function(err) { - if (op.retry(err)) { - return; - } - if (err) { - arguments[0] = op.mainError(); - } - callback.apply(this, arguments); - }); - - op.attempt(function() { - original.apply(obj, args); - }); - }.bind(obj, original); - obj[method].options = options; - } -}; diff --git a/deps/npm/node_modules/retry/lib/retry_operation.js b/deps/npm/node_modules/retry/lib/retry_operation.js deleted file mode 100644 index 1e564696fe7e07..00000000000000 --- a/deps/npm/node_modules/retry/lib/retry_operation.js +++ /dev/null @@ -1,158 +0,0 @@ -function RetryOperation(timeouts, options) { - // Compatibility for the old (timeouts, retryForever) signature - if (typeof options === 'boolean') { - options = { forever: options }; - } - - this._originalTimeouts = JSON.parse(JSON.stringify(timeouts)); - this._timeouts = timeouts; - this._options = options || {}; - this._maxRetryTime = options && options.maxRetryTime || Infinity; - this._fn = null; - this._errors = []; - this._attempts = 1; - this._operationTimeout = null; - this._operationTimeoutCb = null; - this._timeout = null; - this._operationStart = null; - - if (this._options.forever) { - this._cachedTimeouts = this._timeouts.slice(0); - } -} -module.exports = RetryOperation; - -RetryOperation.prototype.reset = function() { - this._attempts = 1; - this._timeouts = this._originalTimeouts; -} - -RetryOperation.prototype.stop = function() { - if (this._timeout) { - clearTimeout(this._timeout); - } - - this._timeouts = []; - this._cachedTimeouts = null; -}; - -RetryOperation.prototype.retry = function(err) { - if (this._timeout) { - clearTimeout(this._timeout); - } - - if (!err) { - return false; - } - var currentTime = new Date().getTime(); - if (err && currentTime - this._operationStart >= this._maxRetryTime) { - this._errors.unshift(new Error('RetryOperation timeout occurred')); - return false; - } - - this._errors.push(err); - - var timeout = this._timeouts.shift(); - if (timeout === undefined) { - if (this._cachedTimeouts) { - // retry forever, only keep last error - this._errors.splice(this._errors.length - 1, this._errors.length); - this._timeouts = this._cachedTimeouts.slice(0); - timeout = this._timeouts.shift(); - } else { - return false; - } - } - - var self = this; - var timer = setTimeout(function() { - self._attempts++; - - if (self._operationTimeoutCb) { - self._timeout = setTimeout(function() { - self._operationTimeoutCb(self._attempts); - }, self._operationTimeout); - - if (self._options.unref) { - self._timeout.unref(); - } - } - - self._fn(self._attempts); - }, timeout); - - if (this._options.unref) { - timer.unref(); - } - - return true; -}; - -RetryOperation.prototype.attempt = function(fn, timeoutOps) { - this._fn = fn; - - if (timeoutOps) { - if (timeoutOps.timeout) { - this._operationTimeout = timeoutOps.timeout; - } - if (timeoutOps.cb) { - this._operationTimeoutCb = timeoutOps.cb; - } - } - - var self = this; - if (this._operationTimeoutCb) { - this._timeout = setTimeout(function() { - self._operationTimeoutCb(); - }, self._operationTimeout); - } - - this._operationStart = new Date().getTime(); - - this._fn(this._attempts); -}; - -RetryOperation.prototype.try = function(fn) { - console.log('Using RetryOperation.try() is deprecated'); - this.attempt(fn); -}; - -RetryOperation.prototype.start = function(fn) { - console.log('Using RetryOperation.start() is deprecated'); - this.attempt(fn); -}; - -RetryOperation.prototype.start = RetryOperation.prototype.try; - -RetryOperation.prototype.errors = function() { - return this._errors; -}; - -RetryOperation.prototype.attempts = function() { - return this._attempts; -}; - -RetryOperation.prototype.mainError = function() { - if (this._errors.length === 0) { - return null; - } - - var counts = {}; - var mainError = null; - var mainErrorCount = 0; - - for (var i = 0; i < this._errors.length; i++) { - var error = this._errors[i]; - var message = error.message; - var count = (counts[message] || 0) + 1; - - counts[message] = count; - - if (count >= mainErrorCount) { - mainError = error; - mainErrorCount = count; - } - } - - return mainError; -}; diff --git a/deps/npm/node_modules/retry/package.json b/deps/npm/node_modules/retry/package.json deleted file mode 100644 index 73c7259707aeef..00000000000000 --- a/deps/npm/node_modules/retry/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "author": "Tim Koschützki (http://debuggable.com/)", - "name": "retry", - "description": "Abstraction for exponential and custom retry strategies for failed operations.", - "license": "MIT", - "version": "0.12.0", - "homepage": "https://github.com/tim-kos/node-retry", - "repository": { - "type": "git", - "url": "git://github.com/tim-kos/node-retry.git" - }, - "directories": { - "lib": "./lib" - }, - "main": "index", - "engines": { - "node": ">= 4" - }, - "dependencies": {}, - "devDependencies": { - "fake": "0.2.0", - "istanbul": "^0.4.5", - "tape": "^4.8.0" - }, - "scripts": { - "test": "./node_modules/.bin/istanbul cover ./node_modules/tape/bin/tape ./test/integration/*.js", - "release:major": "env SEMANTIC=major npm run release", - "release:minor": "env SEMANTIC=minor npm run release", - "release:patch": "env SEMANTIC=patch npm run release", - "release": "npm version ${SEMANTIC:-patch} -m \"Release %s\" && git push && git push --tags && npm publish" - } -} diff --git a/deps/npm/node_modules/retry/test/common.js b/deps/npm/node_modules/retry/test/common.js deleted file mode 100644 index 224720696ebac8..00000000000000 --- a/deps/npm/node_modules/retry/test/common.js +++ /dev/null @@ -1,10 +0,0 @@ -var common = module.exports; -var path = require('path'); - -var rootDir = path.join(__dirname, '..'); -common.dir = { - lib: rootDir + '/lib' -}; - -common.assert = require('assert'); -common.fake = require('fake'); \ No newline at end of file diff --git a/deps/npm/node_modules/retry/test/integration/test-forever.js b/deps/npm/node_modules/retry/test/integration/test-forever.js deleted file mode 100644 index b41307cb529f12..00000000000000 --- a/deps/npm/node_modules/retry/test/integration/test-forever.js +++ /dev/null @@ -1,24 +0,0 @@ -var common = require('../common'); -var assert = common.assert; -var retry = require(common.dir.lib + '/retry'); - -(function testForeverUsesFirstTimeout() { - var operation = retry.operation({ - retries: 0, - minTimeout: 100, - maxTimeout: 100, - forever: true - }); - - operation.attempt(function(numAttempt) { - console.log('>numAttempt', numAttempt); - var err = new Error("foo"); - if (numAttempt == 10) { - operation.stop(); - } - - if (operation.retry(err)) { - return; - } - }); -})(); diff --git a/deps/npm/node_modules/retry/test/integration/test-retry-operation.js b/deps/npm/node_modules/retry/test/integration/test-retry-operation.js deleted file mode 100644 index e351bb683ed449..00000000000000 --- a/deps/npm/node_modules/retry/test/integration/test-retry-operation.js +++ /dev/null @@ -1,258 +0,0 @@ -var common = require('../common'); -var assert = common.assert; -var fake = common.fake.create(); -var retry = require(common.dir.lib + '/retry'); - -(function testReset() { - var error = new Error('some error'); - var operation = retry.operation([1, 2, 3]); - var attempts = 0; - - var finalCallback = fake.callback('finalCallback'); - fake.expectAnytime(finalCallback); - - var expectedFinishes = 1; - var finishes = 0; - - var fn = function() { - operation.attempt(function(currentAttempt) { - attempts++; - assert.equal(currentAttempt, attempts); - if (operation.retry(error)) { - return; - } - - finishes++ - assert.equal(expectedFinishes, finishes); - assert.strictEqual(attempts, 4); - assert.strictEqual(operation.attempts(), attempts); - assert.strictEqual(operation.mainError(), error); - - if (finishes < 2) { - attempts = 0; - expectedFinishes++; - operation.reset(); - fn() - } else { - finalCallback(); - } - }); - }; - - fn(); -})(); - -(function testErrors() { - var operation = retry.operation(); - - var error = new Error('some error'); - var error2 = new Error('some other error'); - operation._errors.push(error); - operation._errors.push(error2); - - assert.deepEqual(operation.errors(), [error, error2]); -})(); - -(function testMainErrorReturnsMostFrequentError() { - var operation = retry.operation(); - var error = new Error('some error'); - var error2 = new Error('some other error'); - - operation._errors.push(error); - operation._errors.push(error2); - operation._errors.push(error); - - assert.strictEqual(operation.mainError(), error); -})(); - -(function testMainErrorReturnsLastErrorOnEqualCount() { - var operation = retry.operation(); - var error = new Error('some error'); - var error2 = new Error('some other error'); - - operation._errors.push(error); - operation._errors.push(error2); - - assert.strictEqual(operation.mainError(), error2); -})(); - -(function testAttempt() { - var operation = retry.operation(); - var fn = new Function(); - - var timeoutOpts = { - timeout: 1, - cb: function() {} - }; - operation.attempt(fn, timeoutOpts); - - assert.strictEqual(fn, operation._fn); - assert.strictEqual(timeoutOpts.timeout, operation._operationTimeout); - assert.strictEqual(timeoutOpts.cb, operation._operationTimeoutCb); -})(); - -(function testRetry() { - var error = new Error('some error'); - var operation = retry.operation([1, 2, 3]); - var attempts = 0; - - var finalCallback = fake.callback('finalCallback'); - fake.expectAnytime(finalCallback); - - var fn = function() { - operation.attempt(function(currentAttempt) { - attempts++; - assert.equal(currentAttempt, attempts); - if (operation.retry(error)) { - return; - } - - assert.strictEqual(attempts, 4); - assert.strictEqual(operation.attempts(), attempts); - assert.strictEqual(operation.mainError(), error); - finalCallback(); - }); - }; - - fn(); -})(); - -(function testRetryForever() { - var error = new Error('some error'); - var operation = retry.operation({ retries: 3, forever: true }); - var attempts = 0; - - var finalCallback = fake.callback('finalCallback'); - fake.expectAnytime(finalCallback); - - var fn = function() { - operation.attempt(function(currentAttempt) { - attempts++; - assert.equal(currentAttempt, attempts); - if (attempts !== 6 && operation.retry(error)) { - return; - } - - assert.strictEqual(attempts, 6); - assert.strictEqual(operation.attempts(), attempts); - assert.strictEqual(operation.mainError(), error); - finalCallback(); - }); - }; - - fn(); -})(); - -(function testRetryForeverNoRetries() { - var error = new Error('some error'); - var delay = 50 - var operation = retry.operation({ - retries: null, - forever: true, - minTimeout: delay, - maxTimeout: delay - }); - - var attempts = 0; - var startTime = new Date().getTime(); - - var finalCallback = fake.callback('finalCallback'); - fake.expectAnytime(finalCallback); - - var fn = function() { - operation.attempt(function(currentAttempt) { - attempts++; - assert.equal(currentAttempt, attempts); - if (attempts !== 4 && operation.retry(error)) { - return; - } - - var endTime = new Date().getTime(); - var minTime = startTime + (delay * 3); - var maxTime = minTime + 20 // add a little headroom for code execution time - assert(endTime >= minTime) - assert(endTime < maxTime) - assert.strictEqual(attempts, 4); - assert.strictEqual(operation.attempts(), attempts); - assert.strictEqual(operation.mainError(), error); - finalCallback(); - }); - }; - - fn(); -})(); - -(function testStop() { - var error = new Error('some error'); - var operation = retry.operation([1, 2, 3]); - var attempts = 0; - - var finalCallback = fake.callback('finalCallback'); - fake.expectAnytime(finalCallback); - - var fn = function() { - operation.attempt(function(currentAttempt) { - attempts++; - assert.equal(currentAttempt, attempts); - - if (attempts === 2) { - operation.stop(); - - assert.strictEqual(attempts, 2); - assert.strictEqual(operation.attempts(), attempts); - assert.strictEqual(operation.mainError(), error); - finalCallback(); - } - - if (operation.retry(error)) { - return; - } - }); - }; - - fn(); -})(); - -(function testMaxRetryTime() { - var error = new Error('some error'); - var maxRetryTime = 30; - var operation = retry.operation({ - minTimeout: 1, - maxRetryTime: maxRetryTime - }); - var attempts = 0; - - var finalCallback = fake.callback('finalCallback'); - fake.expectAnytime(finalCallback); - - var longAsyncFunction = function (wait, callback){ - setTimeout(callback, wait); - }; - - var fn = function() { - var startTime = new Date().getTime(); - operation.attempt(function(currentAttempt) { - attempts++; - assert.equal(currentAttempt, attempts); - - if (attempts !== 2) { - if (operation.retry(error)) { - return; - } - } else { - var curTime = new Date().getTime(); - longAsyncFunction(maxRetryTime - (curTime - startTime - 1), function(){ - if (operation.retry(error)) { - assert.fail('timeout should be occurred'); - return; - } - - assert.strictEqual(operation.mainError(), error); - finalCallback(); - }); - } - }); - }; - - fn(); -})(); diff --git a/deps/npm/node_modules/retry/test/integration/test-retry-wrap.js b/deps/npm/node_modules/retry/test/integration/test-retry-wrap.js deleted file mode 100644 index 3d2b6bfa6436d2..00000000000000 --- a/deps/npm/node_modules/retry/test/integration/test-retry-wrap.js +++ /dev/null @@ -1,101 +0,0 @@ -var common = require('../common'); -var assert = common.assert; -var fake = common.fake.create(); -var retry = require(common.dir.lib + '/retry'); - -function getLib() { - return { - fn1: function() {}, - fn2: function() {}, - fn3: function() {} - }; -} - -(function wrapAll() { - var lib = getLib(); - retry.wrap(lib); - assert.equal(lib.fn1.name, 'bound retryWrapper'); - assert.equal(lib.fn2.name, 'bound retryWrapper'); - assert.equal(lib.fn3.name, 'bound retryWrapper'); -}()); - -(function wrapAllPassOptions() { - var lib = getLib(); - retry.wrap(lib, {retries: 2}); - assert.equal(lib.fn1.name, 'bound retryWrapper'); - assert.equal(lib.fn2.name, 'bound retryWrapper'); - assert.equal(lib.fn3.name, 'bound retryWrapper'); - assert.equal(lib.fn1.options.retries, 2); - assert.equal(lib.fn2.options.retries, 2); - assert.equal(lib.fn3.options.retries, 2); -}()); - -(function wrapDefined() { - var lib = getLib(); - retry.wrap(lib, ['fn2', 'fn3']); - assert.notEqual(lib.fn1.name, 'bound retryWrapper'); - assert.equal(lib.fn2.name, 'bound retryWrapper'); - assert.equal(lib.fn3.name, 'bound retryWrapper'); -}()); - -(function wrapDefinedAndPassOptions() { - var lib = getLib(); - retry.wrap(lib, {retries: 2}, ['fn2', 'fn3']); - assert.notEqual(lib.fn1.name, 'bound retryWrapper'); - assert.equal(lib.fn2.name, 'bound retryWrapper'); - assert.equal(lib.fn3.name, 'bound retryWrapper'); - assert.equal(lib.fn2.options.retries, 2); - assert.equal(lib.fn3.options.retries, 2); -}()); - -(function runWrappedWithoutError() { - var callbackCalled; - var lib = {method: function(a, b, callback) { - assert.equal(a, 1); - assert.equal(b, 2); - assert.equal(typeof callback, 'function'); - callback(); - }}; - retry.wrap(lib); - lib.method(1, 2, function() { - callbackCalled = true; - }); - assert.ok(callbackCalled); -}()); - -(function runWrappedSeveralWithoutError() { - var callbacksCalled = 0; - var lib = { - fn1: function (a, callback) { - assert.equal(a, 1); - assert.equal(typeof callback, 'function'); - callback(); - }, - fn2: function (a, callback) { - assert.equal(a, 2); - assert.equal(typeof callback, 'function'); - callback(); - } - }; - retry.wrap(lib, {}, ['fn1', 'fn2']); - lib.fn1(1, function() { - callbacksCalled++; - }); - lib.fn2(2, function() { - callbacksCalled++; - }); - assert.equal(callbacksCalled, 2); -}()); - -(function runWrappedWithError() { - var callbackCalled; - var lib = {method: function(callback) { - callback(new Error('Some error')); - }}; - retry.wrap(lib, {retries: 1}); - lib.method(function(err) { - callbackCalled = true; - assert.ok(err instanceof Error); - }); - assert.ok(!callbackCalled); -}()); diff --git a/deps/npm/node_modules/retry/test/integration/test-timeouts.js b/deps/npm/node_modules/retry/test/integration/test-timeouts.js deleted file mode 100644 index 7206b0fb0b01d0..00000000000000 --- a/deps/npm/node_modules/retry/test/integration/test-timeouts.js +++ /dev/null @@ -1,69 +0,0 @@ -var common = require('../common'); -var assert = common.assert; -var retry = require(common.dir.lib + '/retry'); - -(function testDefaultValues() { - var timeouts = retry.timeouts(); - - assert.equal(timeouts.length, 10); - assert.equal(timeouts[0], 1000); - assert.equal(timeouts[1], 2000); - assert.equal(timeouts[2], 4000); -})(); - -(function testDefaultValuesWithRandomize() { - var minTimeout = 5000; - var timeouts = retry.timeouts({ - minTimeout: minTimeout, - randomize: true - }); - - assert.equal(timeouts.length, 10); - assert.ok(timeouts[0] > minTimeout); - assert.ok(timeouts[1] > timeouts[0]); - assert.ok(timeouts[2] > timeouts[1]); -})(); - -(function testPassedTimeoutsAreUsed() { - var timeoutsArray = [1000, 2000, 3000]; - var timeouts = retry.timeouts(timeoutsArray); - assert.deepEqual(timeouts, timeoutsArray); - assert.notStrictEqual(timeouts, timeoutsArray); -})(); - -(function testTimeoutsAreWithinBoundaries() { - var minTimeout = 1000; - var maxTimeout = 10000; - var timeouts = retry.timeouts({ - minTimeout: minTimeout, - maxTimeout: maxTimeout - }); - for (var i = 0; i < timeouts; i++) { - assert.ok(timeouts[i] >= minTimeout); - assert.ok(timeouts[i] <= maxTimeout); - } -})(); - -(function testTimeoutsAreIncremental() { - var timeouts = retry.timeouts(); - var lastTimeout = timeouts[0]; - for (var i = 0; i < timeouts; i++) { - assert.ok(timeouts[i] > lastTimeout); - lastTimeout = timeouts[i]; - } -})(); - -(function testTimeoutsAreIncrementalForFactorsLessThanOne() { - var timeouts = retry.timeouts({ - retries: 3, - factor: 0.5 - }); - - var expected = [250, 500, 1000]; - assert.deepEqual(expected, timeouts); -})(); - -(function testRetries() { - var timeouts = retry.timeouts({retries: 2}); - assert.strictEqual(timeouts.length, 2); -})(); diff --git a/deps/npm/node_modules/unique-filename/LICENSE b/deps/npm/node_modules/unique-filename/LICENSE deleted file mode 100644 index 69619c125ea7ef..00000000000000 --- a/deps/npm/node_modules/unique-filename/LICENSE +++ /dev/null @@ -1,5 +0,0 @@ -Copyright npm, Inc - -Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/deps/npm/node_modules/unique-filename/lib/index.js b/deps/npm/node_modules/unique-filename/lib/index.js deleted file mode 100644 index d067d2e709809a..00000000000000 --- a/deps/npm/node_modules/unique-filename/lib/index.js +++ /dev/null @@ -1,7 +0,0 @@ -var path = require('path') - -var uniqueSlug = require('unique-slug') - -module.exports = function (filepath, prefix, uniq) { - return path.join(filepath, (prefix ? prefix + '-' : '') + uniqueSlug(uniq)) -} diff --git a/deps/npm/node_modules/unique-filename/package.json b/deps/npm/node_modules/unique-filename/package.json deleted file mode 100644 index 57892db1fc2c6c..00000000000000 --- a/deps/npm/node_modules/unique-filename/package.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "unique-filename", - "version": "5.0.0", - "description": "Generate a unique filename for use in temporary directories or caches.", - "main": "lib/index.js", - "scripts": { - "test": "tap", - "lint": "npm run eslint", - "postlint": "template-oss-check", - "template-oss-apply": "template-oss-apply --force", - "lintfix": "npm run eslint -- --fix", - "snap": "tap", - "posttest": "npm run lint", - "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/npm/unique-filename.git" - }, - "keywords": [], - "author": "GitHub Inc.", - "license": "ISC", - "bugs": { - "url": "https://github.com/iarna/unique-filename/issues" - }, - "homepage": "https://github.com/iarna/unique-filename", - "devDependencies": { - "@npmcli/eslint-config": "^5.0.0", - "@npmcli/template-oss": "4.27.1", - "tap": "^16.3.0" - }, - "dependencies": { - "unique-slug": "^6.0.0" - }, - "files": [ - "bin/", - "lib/" - ], - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "templateOSS": { - "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.27.1", - "publish": true - }, - "tap": { - "nyc-arg": [ - "--exclude", - "tap-snapshots/**" - ] - } -} diff --git a/deps/npm/node_modules/unique-slug/LICENSE b/deps/npm/node_modules/unique-slug/LICENSE deleted file mode 100644 index 7953647e7760b8..00000000000000 --- a/deps/npm/node_modules/unique-slug/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -The ISC License - -Copyright npm, Inc - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/deps/npm/node_modules/unique-slug/lib/index.js b/deps/npm/node_modules/unique-slug/lib/index.js deleted file mode 100644 index 1bac84d95d7307..00000000000000 --- a/deps/npm/node_modules/unique-slug/lib/index.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' -var MurmurHash3 = require('imurmurhash') - -module.exports = function (uniq) { - if (uniq) { - var hash = new MurmurHash3(uniq) - return ('00000000' + hash.result().toString(16)).slice(-8) - } else { - return (Math.random().toString(16) + '0000000').slice(2, 10) - } -} diff --git a/deps/npm/node_modules/unique-slug/package.json b/deps/npm/node_modules/unique-slug/package.json deleted file mode 100644 index 88df517c0335e6..00000000000000 --- a/deps/npm/node_modules/unique-slug/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "unique-slug", - "version": "6.0.0", - "description": "Generate a unique character string suitible for use in files and URLs.", - "main": "lib/index.js", - "scripts": { - "test": "tap", - "lint": "npm run eslint", - "postlint": "template-oss-check", - "template-oss-apply": "template-oss-apply --force", - "lintfix": "npm run eslint -- --fix", - "snap": "tap", - "posttest": "npm run lint", - "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"" - }, - "keywords": [], - "author": "GitHub Inc.", - "license": "ISC", - "devDependencies": { - "@npmcli/eslint-config": "^5.0.0", - "@npmcli/template-oss": "4.27.1", - "tap": "^16.3.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/npm/unique-slug.git" - }, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "files": [ - "bin/", - "lib/" - ], - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "templateOSS": { - "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.27.1", - "publish": true - }, - "tap": { - "nyc-arg": [ - "--exclude", - "tap-snapshots/**" - ] - } -} diff --git a/deps/npm/package.json b/deps/npm/package.json index 45209901d7928f..022dfee6f4bd91 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "11.11.1", + "version": "11.12.1", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -52,8 +52,8 @@ }, "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.4.1", - "@npmcli/config": "^10.7.1", + "@npmcli/arborist": "^9.4.2", + "@npmcli/config": "^10.8.1", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", @@ -61,10 +61,10 @@ "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.4", - "@sigstore/tuf": "^4.0.1", + "@sigstore/tuf": "^4.0.2", "abbrev": "^4.0.0", "archy": "~1.0.0", - "cacache": "^20.0.3", + "cacache": "^20.0.4", "chalk": "^5.6.2", "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", @@ -77,16 +77,16 @@ "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.4", - "libnpmexec": "^10.2.4", - "libnpmfund": "^7.0.18", + "libnpmdiff": "^8.1.5", + "libnpmexec": "^10.2.5", + "libnpmfund": "^7.0.19", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.4", + "libnpmpack": "^9.1.5", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", - "make-fetch-happen": "^15.0.4", + "make-fetch-happen": "^15.0.5", "minimatch": "^10.2.4", "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", diff --git a/deps/npm/tap-snapshots/test/lib/commands/audit.js.test.cjs b/deps/npm/tap-snapshots/test/lib/commands/audit.js.test.cjs index 843cf7c8dd3708..3a4cec0d6cead6 100644 --- a/deps/npm/tap-snapshots/test/lib/commands/audit.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/commands/audit.js.test.cjs @@ -305,6 +305,7 @@ audited 1 package in xxx 1 package has a verified registry signature 1 package has a verified attestation +(use --json --include-attestations to view attestation details) ` exports[`test/lib/commands/audit.js TAP audit signatures with valid signatures > must match snapshot 1`] = ` diff --git a/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs b/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs index bae0cc8ede3947..6617b3a0827f76 100644 --- a/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/commands/config.js.test.cjs @@ -72,6 +72,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "include": [], "include-staged": false, "include-workspace-root": false, + "include-attestations": false, "init-author-email": "", "init-author-name": "", "init-author-url": "", @@ -248,6 +249,7 @@ https-proxy = null if-present = false ignore-scripts = false include = [] +include-attestations = false include-staged = false include-workspace-root = false init-author-email = "" diff --git a/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs b/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs index f04c0868ce8823..dbdcba2d7c3218 100644 --- a/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/docs.js.test.cjs @@ -821,6 +821,18 @@ the order in which omit/include are specified on the command-line. +#### \`include-attestations\` + +* Default: false +* Type: Boolean + +When used with \`npm audit signatures --json\`, includes the full sigstore +attestation bundles in the JSON output for each verified package. The +bundles contain DSSE envelopes, verification material, and transparency log +entries. + + + #### \`include-staged\` * Default: false @@ -1137,6 +1149,8 @@ of a relative number of days. This config cannot be used with: \`before\` +This value is not exported to the environment for child processes. + #### \`name\` * Default: null @@ -2302,6 +2316,7 @@ Array [ "include", "include-staged", "include-workspace-root", + "include-attestations", "init-author-email", "init-author-name", "init-author-url", @@ -2476,6 +2491,7 @@ Array [ "include", "include-staged", "include-workspace-root", + "include-attestations", "init-private", "install-links", "install-strategy", @@ -2643,6 +2659,7 @@ Object { "httpsProxy": null, "ifPresent": false, "ignoreScripts": false, + "includeAttestations": false, "includeStaged": false, "includeWorkspaceRoot": false, "initPrivate": false, @@ -2869,7 +2886,7 @@ Options: [--json] [--package-lock-only] [--no-package-lock] [--omit [--omit ...]] [--include [--include ...]] -[--foreground-scripts] [--ignore-scripts] +[--foreground-scripts] [--ignore-scripts] [--include-attestations] [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--install-links] @@ -2903,6 +2920,9 @@ Options: --ignore-scripts If true, npm does not run scripts specified in package.json files. + --include-attestations + When used with \`npm audit signatures --json\`, includes the full + -w|--workspace Enable running a command in the context of the configured workspaces of the @@ -2932,6 +2952,7 @@ npm audit [fix|signatures] #### \`include\` #### \`foreground-scripts\` #### \`ignore-scripts\` +#### \`include-attestations\` #### \`workspace\` #### \`workspaces\` #### \`include-workspace-root\` @@ -5808,10 +5829,6 @@ Run "npm trust --help" for more info on a subcommand. Run "npm help trust" for more info -\`\`\`bash - -\`\`\` - Note: This command is unaware of workspaces. #### Synopsis diff --git a/deps/npm/tap-snapshots/test/lib/utils/explain-dep.js.test.cjs b/deps/npm/tap-snapshots/test/lib/utils/explain-dep.js.test.cjs index 34620d5c749bc0..60fca466bb43f2 100644 --- a/deps/npm/tap-snapshots/test/lib/utils/explain-dep.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/utils/explain-dep.js.test.cjs @@ -5,6 +5,28 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' +exports[`test/lib/utils/explain-dep.js TAP basic > circular dependency does not recurse infinitely 1`] = ` +cycle-a@1.0.0 +node_modules/cycle-a + cycle-a@"1.x" from cycle-b@2.0.0 + node_modules/cycle-b + cycle-b@"2.x" from cycle-a@1.0.0 + node_modules/cycle-a + cycle-a@"1.x" from cycle-b@2.0.0 + node_modules/cycle-b +` + +exports[`test/lib/utils/explain-dep.js TAP basic > circular dependency from other side 1`] = ` +cycle-b@2.0.0 +node_modules/cycle-b + cycle-b@"2.x" from cycle-a@1.0.0 + node_modules/cycle-a + cycle-a@"1.x" from cycle-b@2.0.0 + node_modules/cycle-b + cycle-b@"2.x" from cycle-a@1.0.0 + node_modules/cycle-a +` + exports[`test/lib/utils/explain-dep.js TAP basic > ellipses test one 1`] = ` manydep@1.0.0 manydep@"1.0.0" from prod-dep@1.2.3 @@ -21,6 +43,11 @@ manydep@1.0.0 6 more (optdep, extra-neos, deep-dev, peer, the root project, a package with a pretty long name) ` +exports[`test/lib/utils/explain-dep.js TAP basic > explainEdge without seen parameter 1`] = ` +some-dep@"1.x" from parent-pkg@2.0.0 +node_modules/parent-pkg +` + exports[`test/lib/utils/explain-dep.js TAP basic bundled > explain color deep 1`] = ` bundle-of-joy@1.0.0 bundled node_modules/bundle-of-joy diff --git a/deps/npm/test/lib/commands/audit.js b/deps/npm/test/lib/commands/audit.js index 4103ff8dec8c16..04d09f1aee4695 100644 --- a/deps/npm/test/lib/commands/audit.js +++ b/deps/npm/test/lib/commands/audit.js @@ -1850,9 +1850,99 @@ t.test('audit signatures', async t => { t.notOk(process.exitCode, 'should exit successfully') t.match(joinedOutput(), /1 package has a verified attestation/) + t.match(joinedOutput(), /use --json --include-attestations to view attestation details/) t.matchSnapshot(joinedOutput()) }) + t.test('with valid attestations and --include-attestations (human-readable)', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithValidAttestations, + config: { + 'include-attestations': true, + }, + mocks: { + pacote: t.mock('pacote', { + sigstore: { verify: async () => true }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + const fixture = fs.readFileSync( + path.resolve(__dirname, '../../fixtures/sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture) + mockTUF({ npm, target: TUF_VALID_KEYS_TARGET }) + + await npm.exec('audit', ['signatures']) + + t.notOk(process.exitCode, 'should exit successfully') + t.match(joinedOutput(), /1 package has a verified attestation/) + t.notMatch(joinedOutput(), /use --json --include-attestations to view attestation details/) + }) + + t.test('with valid attestations --json --include-attestations', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithValidAttestations, + config: { + json: true, + 'include-attestations': true, + }, + mocks: { + pacote: t.mock('pacote', { + sigstore: { verify: async () => true }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + const fixture = fs.readFileSync( + path.resolve(__dirname, '../../fixtures/sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture) + mockTUF({ npm, target: TUF_VALID_KEYS_TARGET }) + + await npm.exec('audit', ['signatures']) + + t.notOk(process.exitCode, 'should exit successfully') + const jsonOutput = JSON.parse(joinedOutput()) + t.ok(jsonOutput.verified, 'should include verified array') + t.equal(jsonOutput.verified.length, 1, 'should have one verified package') + t.equal(jsonOutput.verified[0].name, 'sigstore', 'should have correct package name') + t.equal(jsonOutput.verified[0].version, '1.0.0', 'should have correct version') + t.ok(jsonOutput.verified[0].attestations, 'should include attestations') + }) + + t.test('with valid attestations --json without --include-attestations', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithValidAttestations, + config: { + json: true, + }, + mocks: { + pacote: t.mock('pacote', { + sigstore: { verify: async () => true }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + await manifestWithValidAttestations({ registry }) + const fixture = fs.readFileSync( + path.resolve(__dirname, '../../fixtures/sigstore/valid-sigstore-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, fixture) + mockTUF({ npm, target: TUF_VALID_KEYS_TARGET }) + + await npm.exec('audit', ['signatures']) + + t.notOk(process.exitCode, 'should exit successfully') + const jsonOutput = JSON.parse(joinedOutput()) + t.notOk(jsonOutput.verified, 'should not include verified array') + }) + t.test('with keyless attestations and no registry keys', async t => { const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: installWithValidAttestations, diff --git a/deps/npm/test/lib/utils/explain-dep.js b/deps/npm/test/lib/utils/explain-dep.js index a90c0e90d5da63..2a9a93f2b529e3 100644 --- a/deps/npm/test/lib/utils/explain-dep.js +++ b/deps/npm/test/lib/utils/explain-dep.js @@ -1,6 +1,6 @@ const { resolve } = require('node:path') const t = require('tap') -const { explainNode, printNode } = require('../../../lib/utils/explain-dep.js') +const { explainNode, printNode, explainEdge } = require('../../../lib/utils/explain-dep.js') const { cleanCwd } = require('../../fixtures/clean-snapshot') t.cleanSnapshot = (str) => cleanCwd(str) @@ -268,6 +268,47 @@ t.test('basic', async t => { }) } + // Regression test for https://github.com/npm/cli/issues/9109 + // Circular dependency graphs (common in linked strategy store nodes) should not cause infinite recursion in explainNode. + const cycleA = { + name: 'cycle-a', + version: '1.0.0', + location: 'node_modules/cycle-a', + dependents: [], + } + const cycleB = { + name: 'cycle-b', + version: '2.0.0', + location: 'node_modules/cycle-b', + dependents: [{ + type: 'prod', + name: 'cycle-b', + spec: '2.x', + from: cycleA, + }], + } + cycleA.dependents = [{ + type: 'prod', + name: 'cycle-a', + spec: '1.x', + from: cycleB, + }] + t.matchSnapshot(explainNode(cycleA, Infinity, noColor), 'circular dependency does not recurse infinitely') + t.matchSnapshot(explainNode(cycleB, Infinity, noColor), 'circular dependency from other side') + + // explainEdge called without seen parameter (covers default seen = new Set() branch on explainEdge and explainFrom) + t.matchSnapshot(explainEdge({ + type: 'prod', + name: 'some-dep', + spec: '1.x', + from: { + name: 'parent-pkg', + version: '2.0.0', + location: 'node_modules/parent-pkg', + dependents: [], + }, + }, 2, noColor), 'explainEdge without seen parameter') + // make sure that we show the last one if it's the only one that would // hit the ... cases.manyDeps.dependents.pop() diff --git a/deps/simdjson/simdjson.cpp b/deps/simdjson/simdjson.cpp index 0374b89f39c2d3..8f3305987681e6 100644 --- a/deps/simdjson/simdjson.cpp +++ b/deps/simdjson/simdjson.cpp @@ -1,4 +1,4 @@ -/* auto-generated on 2026-02-20 16:16:37 -0500. version 4.3.1 Do not edit! */ +/* auto-generated on 2026-03-25 17:25:37 -0400. version 4.5.0 Do not edit! */ /* including simdjson.cpp: */ /* begin file simdjson.cpp */ #define SIMDJSON_SRC_SIMDJSON_CPP @@ -146,7 +146,6 @@ #define SIMDJSON_CONSTEVAL 0 #endif // defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L #endif // !defined(SIMDJSON_CONSTEVAL) - #endif // SIMDJSON_COMPILER_CHECK_H /* end file simdjson/compiler_check.h */ /* including simdjson/portability.h: #include "simdjson/portability.h" */ @@ -219,6 +218,25 @@ using std::size_t; #define SIMDJSON_IS_LASX 1 // We can always run both #elif defined(__loongarch_sx) #define SIMDJSON_IS_LSX 1 + +// Adjust for runtime dispatching support. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__NVCOMPILER) +#if __GNUC__ > 15 || (__GNUC__ == 15 && __GNUC_MINOR__ >= 0) + // We are ok, we will support runtime dispatch for LASX. +#else + // We disable runtime dispatch for LASX, which means that we will not be able to use LASX + // even if it is supported by the hardware. + // Loongson users should update to GCC 15 or better. + #define SIMDJSON_IMPLEMENTATION_LASX 0 +#endif +#else + // We are not using GCC, so we assume that we can support runtime dispatch for LASX. + // https://godbolt.org/z/jcMnrjYhs + #define SIMDJSON_IMPLEMENTATION_LASX 0 +#endif + + + #endif #elif defined(__PPC64__) || defined(_M_PPC64) #define SIMDJSON_IS_PPC64 1 @@ -7290,6 +7308,12 @@ class dom_parser_implementation { */ size_t _max_depth{0}; +public: + /** Whether to store big integers as strings instead of returning BIGINT_ERROR */ + bool _number_as_string{false}; + +protected: + // Declaring these so that subclasses can use them to implement their constructors. simdjson_inline dom_parser_implementation() noexcept; simdjson_inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; @@ -7865,7 +7889,8 @@ enum class tape_type { DOUBLE = 'd', TRUE_VALUE = 't', FALSE_VALUE = 'f', - NULL_VALUE = 'n' + NULL_VALUE = 'n', + BIGINT = 'Z' // Big integer stored as string in string buffer }; // enum class tape_type } // namespace internal @@ -14160,6 +14185,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -14243,6 +14271,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace arm64 @@ -15180,7 +15220,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -20521,6 +20577,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -20604,6 +20663,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace haswell @@ -21541,7 +21612,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -26877,6 +26964,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -26960,6 +27050,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace icelake @@ -27897,7 +27999,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -33504,6 +33622,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -33587,6 +33708,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace ppc64 @@ -34524,7 +34657,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -40693,6 +40842,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -40776,6 +40928,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace westmere @@ -41713,7 +41877,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -46913,6 +47093,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -46996,6 +47179,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace lasx @@ -47933,7 +48128,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -53037,6 +53248,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -53120,6 +53334,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace lsx @@ -54057,7 +54283,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -59580,6 +59822,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -59663,6 +59908,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace rvv_vls @@ -60600,7 +60857,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { @@ -60938,6 +61211,19 @@ simdjson_inline int leading_zeroes(uint64_t input_num) { #endif// _MSC_VER } +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#ifdef _MSC_VER + unsigned long trailing_zero = 0; + // Search the mask data from least significant bit (LSB) + // to most significant bit (MSB) for a set bit (1). + if (_BitScanForward64(&trailing_zero, input_num)) + return (int)trailing_zero; + else return 64; +#else + return __builtin_ctzll(input_num); +#endif// _MSC_VER +} + } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -63169,6 +63455,19 @@ simdjson_inline int leading_zeroes(uint64_t input_num) { #endif// _MSC_VER } +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#ifdef _MSC_VER + unsigned long trailing_zero = 0; + // Search the mask data from least significant bit (LSB) + // to most significant bit (MSB) for a set bit (1). + if (_BitScanForward64(&trailing_zero, input_num)) + return (int)trailing_zero; + else return 64; +#else + return __builtin_ctzll(input_num); +#endif// _MSC_VER +} + } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -64145,6 +64444,9 @@ struct tape_writer { /** Write a double value to tape. */ simdjson_inline void append_double(double value) noexcept; + /** Write a big integer (as string) to tape. src points to first digit, len is byte count. */ + simdjson_inline void append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept; + /** * Append a tape entry (an 8-bit type,and 56 bits worth of value). */ @@ -64228,6 +64530,18 @@ simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, intern tape_loc = val | ((uint64_t(char(t))) << 56); } +simdjson_inline void tape_writer::append_bigint(const uint8_t *src, size_t len, uint8_t *&string_buf) noexcept { + // Write to string buffer: [4-byte LE length][digits][null] + uint32_t str_len = uint32_t(len); + memcpy(string_buf, &str_len, sizeof(uint32_t)); + memcpy(string_buf + sizeof(uint32_t), src, len); + string_buf[sizeof(uint32_t) + len] = 0; + // Tape entry: offset into string buffer + // The caller must set the offset relative to doc.string_buf base + append(0, internal::tape_type::BIGINT); // placeholder offset, caller patches + string_buf += sizeof(uint32_t) + len + 1; +} + } // namespace stage2 } // unnamed namespace } // namespace fallback @@ -64411,7 +64725,23 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string( simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { iter.log_value("number"); - return numberparsing::parse_number(value, tape); + error_code err = numberparsing::parse_number(value, tape); + if (simdjson_unlikely(err == BIGINT_ERROR && + iter.dom_parser._number_as_string)) { + // Write big integer to string buffer using the same format as strings. + // Scan digits the same way parse_number does (skip optional '-', then digits). + const uint8_t *p = value; + if (*p == '-') p++; + while (numberparsing::is_digit(*p)) p++; + size_t len = size_t(p - value); + tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::BIGINT); + uint8_t *dst = current_string_buf_loc + sizeof(uint32_t); + memcpy(dst, value, len); + dst += len; + on_end_string(dst); + return SUCCESS; + } + return err; } simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { diff --git a/deps/simdjson/simdjson.h b/deps/simdjson/simdjson.h index cf2658af259b79..3a413a7d1e046e 100644 --- a/deps/simdjson/simdjson.h +++ b/deps/simdjson/simdjson.h @@ -1,4 +1,4 @@ -/* auto-generated on 2026-02-20 16:16:37 -0500. version 4.3.1 Do not edit! */ +/* auto-generated on 2026-03-25 17:25:37 -0400. version 4.5.0 Do not edit! */ /* including simdjson.h: */ /* begin file simdjson.h */ #ifndef SIMDJSON_H @@ -166,7 +166,6 @@ #define SIMDJSON_CONSTEVAL 0 #endif // defined(__cpp_consteval) && __cpp_consteval >= 201811L && defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L #endif // !defined(SIMDJSON_CONSTEVAL) - #endif // SIMDJSON_COMPILER_CHECK_H /* end file simdjson/compiler_check.h */ /* including simdjson/portability.h: #include "simdjson/portability.h" */ @@ -239,6 +238,25 @@ using std::size_t; #define SIMDJSON_IS_LASX 1 // We can always run both #elif defined(__loongarch_sx) #define SIMDJSON_IS_LSX 1 + +// Adjust for runtime dispatching support. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__NVCOMPILER) +#if __GNUC__ > 15 || (__GNUC__ == 15 && __GNUC_MINOR__ >= 0) + // We are ok, we will support runtime dispatch for LASX. +#else + // We disable runtime dispatch for LASX, which means that we will not be able to use LASX + // even if it is supported by the hardware. + // Loongson users should update to GCC 15 or better. + #define SIMDJSON_IMPLEMENTATION_LASX 0 +#endif +#else + // We are not using GCC, so we assume that we can support runtime dispatch for LASX. + // https://godbolt.org/z/jcMnrjYhs + #define SIMDJSON_IMPLEMENTATION_LASX 0 +#endif + + + #endif #elif defined(__PPC64__) || defined(_M_PPC64) #define SIMDJSON_IS_PPC64 1 @@ -2520,7 +2538,7 @@ namespace std { #define SIMDJSON_SIMDJSON_VERSION_H /** The version of simdjson being used (major.minor.revision) */ -#define SIMDJSON_VERSION "4.3.1" +#define SIMDJSON_VERSION "4.5.0" namespace simdjson { enum { @@ -2531,11 +2549,11 @@ enum { /** * The minor version (major.MINOR.revision) of simdjson being used. */ - SIMDJSON_VERSION_MINOR = 3, + SIMDJSON_VERSION_MINOR = 5, /** * The revision (major.minor.REVISION) of simdjson being used. */ - SIMDJSON_VERSION_REVISION = 1 + SIMDJSON_VERSION_REVISION = 0 }; } // namespace simdjson @@ -3774,6 +3792,12 @@ class dom_parser_implementation { */ size_t _max_depth{0}; +public: + /** Whether to store big integers as strings instead of returning BIGINT_ERROR */ + bool _number_as_string{false}; + +protected: + // Declaring these so that subclasses can use them to implement their constructors. simdjson_inline dom_parser_implementation() noexcept; simdjson_inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; @@ -4326,6 +4350,62 @@ inline std::ostream& operator<<(std::ostream& out, const padded_string& s) { ret inline std::ostream& operator<<(std::ostream& out, simdjson_result &s) noexcept(false) { return out << s.value(); } #endif + +#ifndef _WIN32 +/** + * A class representing a memory-mapped file with padding. + * It is only available on non-Windows platforms, as Windows has different APIs for memory mapping. + */ +class padded_memory_map { +public: + /** + * Create a new padded memory map for the given file. + * After creating the memory map, you can call view() to get a padded_string_view of the file content. + * The memory map will be automatically released when the padded_memory_map instance is destroyed. + * Note that the file content is not copied, so this is efficient for large files. However, + * the file must remain unchanged while the memory map is in use. In case of error (e.g., file not found, + * permission denied, etc.), the memory map will be invalid and view() will return an empty view. + * You can check if the memory map is valid by calling is_valid() before using view(). + * + * @param filename the path to the file to memory-map. + */ + simdjson_inline padded_memory_map(const char *filename) noexcept; + /** + * Destroy the padded memory map and release any resources. + */ + simdjson_inline ~padded_memory_map() noexcept; + + // lifetime of the view is tied to the memory map, so we can return a view + // directly + /** + * Get a view of the memory-mapped file. It always succeeds, but the view may be empty + * if the memory map is invalid (e.g., due to file not found, permission denied, etc.). + * You can check if the memory map is valid by calling is_valid() before using the view. + * + * Lifetime of the view is tied to the memory map, so the view should not be used after the + * padded_memory_map instance is destroyed. + * + * @return a padded_string_view representing the memory-mapped file, or an empty view if the memory map is invalid. + */ + simdjson_inline simdjson::padded_string_view view() const noexcept simdjson_lifetime_bound; + /** + * Check if the memory map is valid. + * + * @return true if the memory map is valid, false otherwise. + */ + simdjson_inline bool is_valid() const noexcept; + +private: + padded_memory_map() = delete; + padded_memory_map(const padded_memory_map &) = delete; + padded_memory_map &operator=(const padded_memory_map &) = delete; + const char *data{nullptr}; + size_t size{0}; +}; +#endif // _WIN32 + + + } // namespace simdjson // This is deliberately outside of simdjson so that people get it without having to use the namespace @@ -4404,7 +4484,7 @@ namespace simdjson { */ class padded_string_view : public std::string_view { private: - size_t _capacity; + size_t _capacity{0}; public: /** Create an empty padded_string_view. */ @@ -4608,6 +4688,14 @@ inline padded_string_view pad_with_reserve(std::string& s) noexcept { #include #include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#endif + namespace simdjson { namespace internal { @@ -4964,6 +5052,57 @@ inline bool padded_string_builder::reserve(size_t additional) noexcept { return true; } + +#ifndef _WIN32 +simdjson_inline padded_memory_map::padded_memory_map(const char *filename) noexcept { + + int fd = open(filename, O_RDONLY); + if (fd == -1) { + return; // file not found or cannot be opened, data will be nullptr + } + struct stat st; + if (fstat(fd, &st) == -1) { + close(fd); + return; // failed to get file size, data will be nullptr + } + size = static_cast(st.st_size); + size_t total_size = size + simdjson::SIMDJSON_PADDING; + void *anon_map = + mmap(NULL, total_size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (anon_map == MAP_FAILED) { + close(fd); + return; // failed to create anonymous mapping, data will be nullptr + } + void *file_map = + mmap(anon_map, size, PROT_READ, MAP_SHARED | MAP_FIXED, fd, 0); + if (file_map == MAP_FAILED) { + munmap(anon_map, total_size); + close(fd); + return; // failed to mmap file, data will be nullptr + } + data = static_cast(file_map); + close(fd); // no longer needed after mapping +} + +simdjson_inline padded_memory_map::~padded_memory_map() noexcept { + if (data != nullptr) { + munmap(const_cast(data), size + simdjson::SIMDJSON_PADDING); + } +} + + +simdjson_inline simdjson::padded_string_view padded_memory_map::view() const noexcept simdjson_lifetime_bound { + if(!is_valid()) { + return simdjson::padded_string_view(); // return an empty view if mapping failed + } + return simdjson::padded_string_view(data, size, size + simdjson::SIMDJSON_PADDING); +} + +simdjson_inline bool padded_memory_map::is_valid() const noexcept { + return data != nullptr; +} +#endif // _WIN32 + } // namespace simdjson inline simdjson::padded_string operator ""_padded(const char *str, size_t len) { @@ -4974,6 +5113,7 @@ inline simdjson::padded_string operator ""_padded(const char8_t *str, size_t len return simdjson::padded_string(reinterpret_cast(str), len); } #endif + #endif // SIMDJSON_PADDED_STRING_INL_H /* end file simdjson/padded_string-inl.h */ /* skipped duplicate #include "simdjson/padded_string_view.h" */ @@ -6030,6 +6170,13 @@ class parser { inline bool dump_raw_tape(std::ostream &os) const noexcept; + /** + * When enabled, big integers (exceeding uint64 range) are stored as strings + * in the tape instead of returning BIGINT_ERROR. Default: false. + */ + inline void number_as_string(bool enabled) noexcept { _number_as_string = enabled; } + inline bool number_as_string() const noexcept { return _number_as_string; } + private: /** * The maximum document length this parser will automatically support. @@ -6038,6 +6185,9 @@ class parser { */ size_t _max_capacity; + /** Whether to store big integers as strings instead of returning BIGINT_ERROR */ + bool _number_as_string{false}; + /** * The loaded buffer (reused each time load() is called) */ @@ -6431,7 +6581,8 @@ enum class element_type { DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double. STRING = '"', ///< std::string_view BOOL = 't', ///< bool - NULL_VALUE = 'n' ///< null + NULL_VALUE = 'n', ///< null + BIGINT = 'Z' ///< std::string_view: big integer stored as raw digit string }; /** @@ -6530,6 +6681,14 @@ class element { */ inline simdjson_result get_bool() const noexcept; + /** + * Read this element as a big integer (raw digit string). + * + * @returns A string_view of the raw digits, or: + * INCORRECT_TYPE if the JSON element is not a big integer. + */ + inline simdjson_result get_bigint() const noexcept; + /** * Whether this element is a json array. * @@ -6585,6 +6744,11 @@ class element { */ inline bool is_null() const noexcept; + /** + * Whether this element is a big integer (number exceeding 64-bit range). + */ + inline bool is_bigint() const noexcept; + /** * Tell whether the value can be cast to provided type (T). * @@ -6943,6 +7107,7 @@ struct simdjson_result : public internal::simdjson_result_base get_uint64() const noexcept; simdjson_inline simdjson_result get_double() const noexcept; simdjson_inline simdjson_result get_bool() const noexcept; + simdjson_inline simdjson_result get_bigint() const noexcept; simdjson_inline bool is_array() const noexcept; simdjson_inline bool is_object() const noexcept; @@ -6953,6 +7118,7 @@ struct simdjson_result : public internal::simdjson_result_base operator[](std::string_view key) const noexcept; simdjson_inline simdjson_result operator[](const char *key) const noexcept; @@ -7943,7 +8109,8 @@ enum class tape_type { DOUBLE = 'd', TRUE_VALUE = 't', FALSE_VALUE = 'f', - NULL_VALUE = 'n' + NULL_VALUE = 'n', + BIGINT = 'Z' // Big integer stored as string in string buffer }; // enum class tape_type } // namespace internal @@ -8817,6 +8984,10 @@ simdjson_inline simdjson_result simdjson_result::get_bool() if (error()) { return error(); } return first.get_bool(); } +simdjson_inline simdjson_result simdjson_result::get_bigint() const noexcept { + if (error()) { return error(); } + return first.get_bigint(); +} simdjson_inline bool simdjson_result::is_array() const noexcept { return !error() && first.is_array(); @@ -8846,6 +9017,9 @@ simdjson_inline bool simdjson_result::is_bool() const noexcept { simdjson_inline bool simdjson_result::is_null() const noexcept { return !error() && first.is_null(); } +simdjson_inline bool simdjson_result::is_bigint() const noexcept { + return !error() && first.is_bigint(); +} simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) const noexcept { if (error()) { return error(); } @@ -8954,6 +9128,15 @@ inline simdjson_result element::get_bool() const noexcept { } return INCORRECT_TYPE; } +inline simdjson_result element::get_bigint() const noexcept { + SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); + switch (tape.tape_ref_type()) { + case internal::tape_type::BIGINT: + return tape.get_string_view(); + default: + return INCORRECT_TYPE; + } +} inline simdjson_result element::get_c_str() const noexcept { SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 switch (tape.tape_ref_type()) { @@ -9096,6 +9279,10 @@ inline bool element::is_null() const noexcept { return tape.is_null_on_tape(); } +inline bool element::is_bigint() const noexcept { + return tape.tape_ref_type() == internal::tape_type::BIGINT; +} + #if SIMDJSON_EXCEPTIONS inline element::operator bool() const noexcept(false) { return get(); } @@ -9228,6 +9415,8 @@ inline std::ostream& operator<<(std::ostream& out, element_type type) { return out << "bool"; case element_type::NULL_VALUE: return out << "null"; + case element_type::BIGINT: + return out << "bigint"; default: return out << "unexpected content!!!"; // abort() usage is forbidden in the library } @@ -9395,6 +9584,7 @@ inline simdjson_result parser::parse_into_document(document& provided_d buf += 3; len -= 3; } + implementation->_number_as_string = _number_as_string; _error = implementation->parse(buf, len, provided_doc); if (_error) { return _error; } @@ -10073,6 +10263,15 @@ inline bool document::dump_raw_tape(std::ostream &os) const noexcept { case 'r': // we start and end with the root node // should we be hitting the root node? return false; + case 'Z': // we have a big integer + os << "bigint "; + std::memcpy(&string_length, string_buf.get() + payload, sizeof(uint32_t)); + os << std::string_view( + reinterpret_cast(string_buf.get() + payload + sizeof(uint32_t)), + string_length + ); + os << '\n'; + break; default: return false; } @@ -10558,6 +10757,12 @@ inline void string_builder::append(simdjson::dom::element value) { case tape_type::STRING: format.string(iter.get_string_view()); break; + case tape_type::BIGINT: { + // Big integer stored as string — output raw digits (no quotes) + auto sv = iter.get_string_view(); + format.chars(sv.data(), sv.data() + sv.size()); + break; + } case tape_type::INT64: format.number(iter.next_tape_value()); iter.json_index++; // numbers take up 2 spots, so we need to increment @@ -16498,6 +16703,19 @@ simdjson_inline int leading_zeroes(uint64_t input_num) { #endif// _MSC_VER } +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#ifdef _MSC_VER + unsigned long trailing_zero = 0; + // Search the mask data from least significant bit (LSB) + // to most significant bit (MSB) for a set bit (1). + if (_BitScanForward64(&trailing_zero, input_num)) + return (int)trailing_zero; + else return 64; +#else + return __builtin_ctzll(input_num); +#endif// _MSC_VER +} + } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -39592,6 +39810,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -39604,6 +39838,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace arm64 { @@ -39704,6 +39954,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -39747,28 +40026,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -39806,13 +40069,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -40506,6 +40884,19 @@ simdjson_inline int leading_zeroes(uint64_t input_num) { #endif// _MSC_VER } +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#ifdef _MSC_VER + unsigned long trailing_zero = 0; + // Search the mask data from least significant bit (LSB) + // to most significant bit (MSB) for a set bit (1). + if (_BitScanForward64(&trailing_zero, input_num)) + return (int)trailing_zero; + else return 64; +#else + return __builtin_ctzll(input_num); +#endif// _MSC_VER +} + } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -41503,6 +41894,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -41515,6 +41922,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace fallback { @@ -41615,6 +42038,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -41658,28 +42110,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -41717,13 +42153,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -43914,6 +44465,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -43926,6 +44493,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace haswell { @@ -44026,6 +44609,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -44069,28 +44681,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -44128,13 +44724,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -46325,6 +47036,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -46337,6 +47064,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace icelake { @@ -46437,6 +47180,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -46480,28 +47252,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -46539,13 +47295,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -48851,6 +49722,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -48863,6 +49750,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace ppc64 { @@ -48963,6 +49866,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -49006,28 +49938,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -49065,13 +49981,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -51694,6 +52725,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -51706,6 +52753,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace westmere { @@ -51806,6 +52869,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -51849,28 +52941,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -51908,13 +52984,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -54011,6 +55202,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -54023,6 +55230,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace lsx { @@ -54123,6 +55346,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -54166,28 +55418,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -54225,13 +55461,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -56351,6 +57702,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -56363,6 +57730,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace lasx { @@ -56463,6 +57846,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -56506,28 +57918,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -56565,13 +57961,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -58695,6 +60206,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif +#if defined(__loongarch_sx) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX +#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 +#endif +#endif +#if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ + defined(__riscv_vector) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV +#define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 +#endif +#endif +#if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) +#ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 +#endif +#endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER @@ -58707,6 +60234,22 @@ simdjson_warn_unused simdjson_result extract_fractured_json( #include #endif #endif +#if SIMDJSON_EXPERIMENTAL_HAS_LSX +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_RVV +#include +#endif +#if SIMDJSON_EXPERIMENTAL_HAS_PPC64 +#include +#ifdef bool +#undef bool +#endif +#ifdef vector +#undef vector +#endif +#endif + namespace simdjson { namespace rvv_vls { @@ -58807,6 +60350,35 @@ simdjson_inline bool fast_needs_escaping(std::string_view view) { } return _mm_movemask_epi8(running) != 0; } +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline bool fast_needs_escaping(std::string_view view) { + if (view.size() < 16) { + return simple_needs_escaping(view); + } + size_t i = 0; + __vector unsigned char running = vec_splats((unsigned char)0); + __vector unsigned char v34 = vec_splats((unsigned char)34); + __vector unsigned char v92 = vec_splats((unsigned char)92); + __vector unsigned char v32 = vec_splats((unsigned char)32); + + for (; i + 15 < view.size(); i += 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(view.data() + i)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + if (i < view.size()) { + __vector unsigned char word = vec_vsx_ld( + 0, reinterpret_cast(view.data() + view.length() - 16)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); + running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); + running = vec_or(running, + (__vector unsigned char)vec_cmplt(word, v32)); + } + return !vec_all_eq(running, vec_splats((unsigned char)0)); +} #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); @@ -58850,28 +60422,12 @@ find_next_json_quotable_character(const std::string_view view, needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - if (vmaxvq_u32(vreinterpretq_u32_u8(needs_escape)) != 0) { - // Found quotable character - extract exact byte position using ctz - uint64x2_t as64 = vreinterpretq_u64_u8(needs_escape); - uint64_t lo = vgetq_lane_u64(as64, 0); - uint64_t hi = vgetq_lane_u64(as64, 1); + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - if (lo != 0) { - _BitScanForward64(&trailing_zero, lo); - return offset + trailing_zero / 8; - } else { - _BitScanForward64(&trailing_zero, hi); - return offset + 8 + trailing_zero / 8; - } -#else - if (lo != 0) { - return offset + __builtin_ctzll(lo) / 8; - } else { - return offset + 8 + __builtin_ctzll(hi) / 8; - } -#endif + auto trailing_zero = trailing_zeroes(mask); + return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; @@ -58909,13 +60465,128 @@ find_next_json_quotable_character(const std::string_view view, if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); -#ifdef _MSC_VER - unsigned long trailing_zero = 0; - _BitScanForward(&trailing_zero, mask); - return offset + trailing_zero; + return offset + trailing_zeroes(mask); + } + ptr += 16; + remaining -= 16; + } + + // Scalar fallback for remaining bytes + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_LSX +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + //SIMD constants for characters requiring escape + __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' + __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' + __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold + + while (remaining >= 16){ + __m128i word = __lsx_vld(ptr, 0); + + //Check for the quotable characters: '"', '\\', or control char (<32) + __m128i needs_escape = __lsx_vseq_b(word, v34); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); + needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); + + if (!__lsx_bz_v(needs_escape)){ + + //Found quotable character - extract exact byte position + uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); + uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); + size_t offset = ptr - reinterpret_cast(view.data()); + if ( lo != 0) { + return offset + trailing_zeroes(lo) / 8; + } else { + return offset + 8 + trailing_zeroes(hi) / 8; + } + } + ptr += 16; + remaining -= 16; + } + size_t current = len - remaining; + return find_next_json_quotable_character_scalar(view, current); +} +#elif SIMDJSON_EXPERIMENTAL_HAS_RVV +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + while (remaining > 0) { + size_t vl = __riscv_vsetvl_e8m1(remaining); + vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmseq(word, (uint8_t)92, vl), vl); + needs_escape = __riscv_vmor(needs_escape, + __riscv_vmsltu(word, (uint8_t)32, vl), vl); + + long first = __riscv_vfirst(needs_escape, vl); + if (first >= 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + first; + } + ptr += vl; + remaining -= vl; + } + + return len; +} +#elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 +simdjson_inline size_t +find_next_json_quotable_character(const std::string_view view, + size_t location) noexcept { + const size_t len = view.size(); + const uint8_t *ptr = + reinterpret_cast(view.data()) + location; + size_t remaining = len - location; + + // SIMD constants for characters requiring escape + __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' + __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' + __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold + + // Bitmask for vec_vbpermq to extract one bit per byte + const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, + 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, + 0x18, 0x10, 0x08, 0x00}; + + while (remaining >= 16) { + __vector unsigned char word = + vec_vsx_ld(0, reinterpret_cast(ptr)); + + // Check for quotable characters: '"', '\\', or control chars (< 32) + __vector unsigned char needs_escape = + (__vector unsigned char)vec_cmpeq(word, v34); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmpeq(word, v92)); + needs_escape = vec_or(needs_escape, + (__vector unsigned char)vec_cmplt(word, v32)); + + __vector unsigned long long result = + (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); +#ifdef __LITTLE_ENDIAN__ + unsigned int mask = static_cast(result[1]); #else - return offset + __builtin_ctz(mask); + unsigned int mask = static_cast(result[0]); #endif + if (mask != 0) { + size_t offset = ptr - reinterpret_cast(view.data()); + return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; @@ -73462,6 +75133,19 @@ simdjson_inline int leading_zeroes(uint64_t input_num) { #endif// _MSC_VER } +simdjson_inline int trailing_zeroes(uint64_t input_num) { +#ifdef _MSC_VER + unsigned long trailing_zero = 0; + // Search the mask data from least significant bit (LSB) + // to most significant bit (MSB) for a set bit (1). + if (_BitScanForward64(&trailing_zero, input_num)) + return (int)trailing_zero; + else return 64; +#else + return __builtin_ctzll(input_num); +#endif// _MSC_VER +} + } // unnamed namespace } // namespace fallback } // namespace simdjson diff --git a/deps/sqlite/sqlite3.c b/deps/sqlite/sqlite3.c index 76d2887bf034c6..851a2504cd0bde 100644 --- a/deps/sqlite/sqlite3.c +++ b/deps/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.52.0. By combining all the individual C code files into this +** version 3.51.3. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -18,7 +18,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** 557aeb43869d3585137b17690cb3b64f7de6 with changes in files: +** 737ae4a34738ffa0c3ff7f9bb18df914dd1c with changes in files: ** ** */ @@ -467,12 +467,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.52.0" -#define SQLITE_VERSION_NUMBER 3052000 -#define SQLITE_SOURCE_ID "2026-03-06 16:01:44 557aeb43869d3585137b17690cb3b64f7de6921774daae9e56403c3717dceab6" -#define SQLITE_SCM_BRANCH "trunk" -#define SQLITE_SCM_TAGS "release major-release version-3.52.0" -#define SQLITE_SCM_DATETIME "2026-03-06T16:01:44.367Z" +#define SQLITE_VERSION "3.51.3" +#define SQLITE_VERSION_NUMBER 3051003 +#define SQLITE_SOURCE_ID "2026-03-13 10:38:09 737ae4a34738ffa0c3ff7f9bb18df914dd1cad163f28fd6b6e114a344fe6d618" +#define SQLITE_SCM_BRANCH "branch-3.51" +#define SQLITE_SCM_TAGS "release version-3.51.3" +#define SQLITE_SCM_DATETIME "2026-03-13T10:38:09.694Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -1811,7 +1811,7 @@ typedef const char *sqlite3_filename; ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** -** ^The xSetSystemCall(), xGetSystemCall(), and xNextSystemCall() interfaces +** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided ** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can @@ -2888,15 +2888,12 @@ struct sqlite3_mem_methods { ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] **
              SQLITE_DBCONFIG_STMT_SCANSTATUS
              **
              The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in -** [SQLITE_ENABLE_STMT_SCANSTATUS] builds. In this case, it sets or clears -** a flag that enables collection of run-time performance statistics -** used by [sqlite3_stmt_scanstatus_v2()] and the [nexec and ncycle] -** columns of the [bytecode virtual table]. -** For statistics to be collected, the flag must be set on -** the database handle both when the SQL statement is -** [sqlite3_prepare|prepared] and when it is [sqlite3_step|stepped]. -** The flag is set (collection of statistics is enabled) by default. -**

              This option takes two arguments: an integer and a pointer to +** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears +** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() +** statistics. For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is prepared and when it +** is stepped. The flag is set (collection of statistics is enabled) +** by default.

              This option takes two arguments: an integer and a pointer to ** an integer. The first argument is 1, 0, or -1 to enable, disable, or ** leave unchanged the statement scanstatus option. If the second argument ** is not NULL, then the value of the statement scanstatus setting after @@ -2969,22 +2966,6 @@ struct sqlite3_mem_methods { ** comments are allowed in SQL text after processing the first argument. **

              ** -** [[SQLITE_DBCONFIG_FP_DIGITS]] -**
              SQLITE_DBCONFIG_FP_DIGITS
              -**
              The SQLITE_DBCONFIG_FP_DIGITS setting is a small integer that determines -** the number of significant digits that SQLite will attempt to preserve when -** converting floating point numbers (IEEE 754 "doubles") into text. The -** default value 17, as of SQLite version 3.52.0. The value was 15 in all -** prior versions.

              -** This option takes two arguments which are an integer and a pointer -** to an integer. The first argument is a small integer, between 3 and 23, or -** zero. The FP_DIGITS setting is changed to that small integer, or left -** altered if the first argument is zero or out of range. The second argument -** is a pointer to an integer. If the pointer is not NULL, then the value of -** the FP_DIGITS setting, after possibly being modified by the first -** arguments, is written into the integer to which the second argument points. -**

              -** ** ** ** [[DBCONFIG arguments]]

              Arguments To SQLITE_DBCONFIG Options

              @@ -3002,10 +2983,9 @@ struct sqlite3_mem_methods { ** the first argument. ** **

              While most SQLITE_DBCONFIG options use the argument format -** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME], -** [SQLITE_DBCONFIG_LOOKASIDE], and [SQLITE_DBCONFIG_FP_DIGITS] options -** are different. See the documentation of those exceptional options for -** details. +** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME] +** and [SQLITE_DBCONFIG_LOOKASIDE] options are different. See the +** documentation of those exceptional options for details. */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ @@ -3030,8 +3010,7 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */ -#define SQLITE_DBCONFIG_FP_DIGITS 1023 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1023 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -4513,7 +4492,6 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename); **

            • sqlite3_errmsg() **
            • sqlite3_errmsg16() **
            • sqlite3_error_offset() -**
            • sqlite3_db_handle() **
            ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -4560,7 +4538,7 @@ SQLITE_API const char *sqlite3_errstr(int); SQLITE_API int sqlite3_error_offset(sqlite3 *db); /* -** CAPI3REF: Set Error Code And Message +** CAPI3REF: Set Error Codes And Message ** METHOD: sqlite3 ** ** Set the error code of the database handle passed as the first argument @@ -4679,10 +4657,6 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
            SQLITE_LIMIT_EXPR_DEPTH
            **
            The maximum depth of the parse tree on any expression.
            )^ ** -** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
            SQLITE_LIMIT_PARSER_DEPTH
            -**
            The maximum depth of the LALR(1) parser stack used to analyze -** input SQL statements.
            )^ -** ** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(
            SQLITE_LIMIT_COMPOUND_SELECT
            **
            The maximum number of terms in a compound SELECT statement.
            )^ ** @@ -4727,7 +4701,6 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 -#define SQLITE_LIMIT_PARSER_DEPTH 12 /* ** CAPI3REF: Prepare Flags @@ -4772,29 +4745,12 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** fails, the sqlite3_prepare_v3() call returns the same error indications ** with or without this flag; it just omits the call to [sqlite3_log()] that ** logs the error. -** -** [[SQLITE_PREPARE_FROM_DDL]]
            SQLITE_PREPARE_FROM_DDL
            -**
            The SQLITE_PREPARE_FROM_DDL flag causes the SQL compiler to enforce -** security constraints that would otherwise only be enforced when parsing -** the database schema. In other words, the SQLITE_PREPARE_FROM_DDL flag -** causes the SQL compiler to treat the SQL statement being prepared as if -** it had come from an attacker. When SQLITE_PREPARE_FROM_DDL is used and -** [SQLITE_DBCONFIG_TRUSTED_SCHEMA] is off, SQL functions may only be called -** if they are tagged with [SQLITE_INNOCUOUS] and virtual tables may only -** be used if they are tagged with [SQLITE_VTAB_INNOCUOUS]. Best practice -** is to use the SQLITE_PREPARE_FROM_DDL option when preparing any SQL that -** is derived from parts of the database schema. In particular, virtual -** table implementations that run SQL statements that are derived from -** arguments to their CREATE VIRTUAL TABLE statement should always use -** [sqlite3_prepare_v3()] and set the SQLITE_PREPARE_FROM_DDL flag to -** prevent bypass of the [SQLITE_DBCONFIG_TRUSTED_SCHEMA] security checks. ** */ #define SQLITE_PREPARE_PERSISTENT 0x01 #define SQLITE_PREPARE_NORMALIZE 0x02 #define SQLITE_PREPARE_NO_VTAB 0x04 #define SQLITE_PREPARE_DONT_LOG 0x10 -#define SQLITE_PREPARE_FROM_DDL 0x20 /* ** CAPI3REF: Compiling An SQL Statement @@ -4808,9 +4764,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** The preferred routine to use is [sqlite3_prepare_v2()]. The ** [sqlite3_prepare()] interface is legacy and should be avoided. -** [sqlite3_prepare_v3()] has an extra -** [SQLITE_PREPARE_FROM_DDL|"prepFlags" option] that is some times -** needed for special purpose or to pass along security restrictions. +** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used +** for special purposes. ** ** The use of the UTF-8 interfaces is preferred, as SQLite currently ** does all parsing using UTF-8. The UTF-16 interfaces are provided @@ -5215,8 +5170,8 @@ typedef struct sqlite3_context sqlite3_context; ** it should be a pointer to well-formed UTF16 text. ** ^If the third parameter to sqlite3_bind_text64() is not NULL, then ** it should be a pointer to a well-formed unicode string that is -** either UTF8 if the sixth parameter is SQLITE_UTF8 or SQLITE_UTF8_ZT, -** or UTF16 otherwise. +** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16 +** otherwise. ** ** [[byte-order determination rules]] ^The byte-order of ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) @@ -5262,15 +5217,10 @@ typedef struct sqlite3_context sqlite3_context; ** object and pointer to it must remain valid until then. ^SQLite will then ** manage the lifetime of its private copy. ** -** ^The sixth argument (the E argument) -** to sqlite3_bind_text64(S,K,Z,N,D,E) must be one of -** [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], -** or [SQLITE_UTF16LE] to specify the encoding of the text in the -** third parameter, Z. The special value [SQLITE_UTF8_ZT] means that the -** string argument is both UTF-8 encoded and is zero-terminated. In other -** words, SQLITE_UTF8_ZT means that the Z array is allocated to hold at -** least N+1 bytes and that the Z[N] byte is zero. If -** the E argument to sqlite3_bind_text64(S,K,Z,N,D,E) is not one of the +** ^The sixth argument to sqlite3_bind_text64() must be one of +** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] +** to specify the encoding of the text in the third parameter. If +** the sixth argument to sqlite3_bind_text64() is not one of the ** allowed values shown above, or if the text encoding is different ** from the encoding specified by the sixth parameter, then the behavior ** is undefined. @@ -6137,51 +6087,6 @@ SQLITE_API int sqlite3_create_window_function( ** ** These constants define integer codes that represent the various ** text encodings supported by SQLite. -** -**
            -** [[SQLITE_UTF8]]
            SQLITE_UTF8
            Text is encoding as UTF-8
            -** -** [[SQLITE_UTF16LE]]
            SQLITE_UTF16LE
            Text is encoding as UTF-16 -** with each code point being expressed "little endian" - the least significant -** byte first. This is the usual encoding, for example on Windows.
            -** -** [[SQLITE_UTF16BE]]
            SQLITE_UTF16BE
            Text is encoding as UTF-16 -** with each code point being expressed "big endian" - the most significant -** byte first. This encoding is less common, but is still sometimes seen, -** specially on older systems. -** -** [[SQLITE_UTF16]]
            SQLITE_UTF16
            Text is encoding as UTF-16 -** with each code point being expressed either little endian or as big -** endian, according to the native endianness of the host computer. -** -** [[SQLITE_ANY]]
            SQLITE_ANY
            This encoding value may only be used -** to declare the preferred text for [application-defined SQL functions] -** created using [sqlite3_create_function()] and similar. If the preferred -** encoding (the 4th parameter to sqlite3_create_function() - the eTextRep -** parameter) is SQLITE_ANY, that indicates that the function does not have -** a preference regarding the text encoding of its parameters and can take -** any text encoding that the SQLite core find convenient to supply. This -** option is deprecated. Please do not use it in new applications. -** -** [[SQLITE_UTF16_ALIGNED]]
            SQLITE_UTF16_ALIGNED
            This encoding -** value may be used as the 3rd parameter (the eTextRep parameter) to -** [sqlite3_create_collation()] and similar. This encoding value means -** that the application-defined collating sequence created expects its -** input strings to be in UTF16 in native byte order, and that the start -** of the strings must be aligned to a 2-byte boundary. -** -** [[SQLITE_UTF8_ZT]]
            SQLITE_UTF8_ZT
            This option can only be -** used to specify the text encoding to strings input to [sqlite3_result_text64()] -** and [sqlite3_bind_text64()]. It means that the input string (call it "z") -** is UTF-8 encoded and that it is zero-terminated. If the length parameter -** (call it "n") is non-negative, this encoding option means that the caller -** guarantees that z array contains at least n+1 bytes and that the z[n] -** byte has a value of zero. -** This option gives the same output as SQLITE_UTF8, but can be more efficient -** by avoiding the need to make a copy of the input string, in some cases. -** However, if z is allocated to hold fewer than n+1 bytes or if the -** z[n] byte is not zero, undefined behavior may result. -**
            */ #define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ #define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ @@ -6189,7 +6094,6 @@ SQLITE_API int sqlite3_create_window_function( #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ -#define SQLITE_UTF8_ZT 16 /* Zero-terminated UTF8 */ /* ** CAPI3REF: Function Flags @@ -6695,14 +6599,10 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi ** ** There is no limit (other than available memory) on the number of different ** client data pointers (with different names) that can be attached to a -** single database connection. However, the current implementation stores -** the content on a linked list. Insert and retrieval performance will -** be proportional to the number of entries. The design use case, and -** the use case for which the implementation is optimized, is -** that an application will store only small number of client data names, -** typically just one or two. This interface is not intended to be a -** generalized key/value store for thousands or millions of keys. It -** will work for that, but performance might be disappointing. +** single database connection. However, the implementation is optimized +** for the case of having only one or two different client data names. +** Applications and wrapper libraries are discouraged from using more than +** one client data name each. ** ** There is no way to enumerate the client data pointers ** associated with a database connection. The N parameter can be thought @@ -6810,14 +6710,10 @@ typedef void (*sqlite3_destructor_type)(void*); ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. -** ^The sqlite3_result_text64(C,Z,N,D,E) interface sets the return value of an +** ^The sqlite3_result_text64() interface sets the return value of an ** application-defined function to be a text string in an encoding -** specified the E parameter, which must be one -** of [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], -** or [SQLITE_UTF16LE]. ^The special value [SQLITE_UTF8_ZT] means that -** the result text is both UTF-8 and zero-terminated. In other words, -** SQLITE_UTF8_ZT means that the Z array holds at least N+1 byes and that -** the Z[N] is zero. +** specified by the fifth (and last) parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces @@ -6904,7 +6800,7 @@ SQLITE_API void sqlite3_result_int(sqlite3_context*, int); SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); SQLITE_API void sqlite3_result_null(sqlite3_context*); SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char *z, sqlite3_uint64 n, +SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, void(*)(void*), unsigned char encoding); SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); @@ -7843,7 +7739,7 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The sqlite3_load_extension() interface attempts to load an ** [SQLite extension] library contained in the file zFile. If ** the file cannot be loaded directly, attempts are made to load -** with various operating-system specific filename extensions added. +** with various operating-system specific extensions added. ** So for example, if "samplelib" cannot be loaded, then names like ** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might ** be tried also. @@ -7851,10 +7747,10 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it tries names of the form "sqlite3_X_init" -** where X consists of the lower-case equivalent of all ASCII alphabetic -** characters or all ASCII alphanumeric characters in the filename from -** the last "/" to the first following "." and omitting any initial "lib".)^ +** If that does not work, it constructs a name "sqlite3_X_init" where +** X consists of the lower-case equivalent of all ASCII alphabetic +** characters in the filename from the last "/" to the first following +** "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the @@ -9147,22 +9043,17 @@ SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3*); ** pass the returned value to [sqlite3_free()] to avoid a memory leak. ** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any ** errors were encountered during construction of the string. ^The -** [sqlite3_str_finish(X)] interface might also return a NULL pointer if the +** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the ** string in [sqlite3_str] object X is zero bytes long. -** -** ^The [sqlite3_str_free(X)] interface destroys both the sqlite3_str object -** X and the string content it contains. Calling sqlite3_str_free(X) is -** the equivalent of calling [sqlite3_free](sqlite3_str_finish(X)). */ SQLITE_API char *sqlite3_str_finish(sqlite3_str*); -SQLITE_API void sqlite3_str_free(sqlite3_str*); /* ** CAPI3REF: Add Content To A Dynamic String ** METHOD: sqlite3_str ** -** These interfaces add or remove content to an sqlite3_str object -** previously obtained from [sqlite3_str_new()]. +** These interfaces add content to an sqlite3_str object previously obtained +** from [sqlite3_str_new()]. ** ** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] @@ -9185,10 +9076,6 @@ SQLITE_API void sqlite3_str_free(sqlite3_str*); ** ^The [sqlite3_str_reset(X)] method resets the string under construction ** inside [sqlite3_str] object X back to zero bytes in length. ** -** ^The [sqlite3_str_truncate(X,N)] method changes the length of the string -** under construction to be N bytes are less. This routine is a no-op if -** N is negative or if the string is already N bytes or smaller in size. -** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a ** subsequent call to [sqlite3_str_errcode(X)]. @@ -9199,7 +9086,6 @@ SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn); SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C); SQLITE_API void sqlite3_str_reset(sqlite3_str*); -SQLITE_API void sqlite3_str_truncate(sqlite3_str*,int N); /* ** CAPI3REF: Status Of A Dynamic String @@ -11033,9 +10919,9 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value ** ** a variable pointed to by the "pOut" parameter. ** ** The "flags" parameter must be passed a mask of flags. At present only -** one flag is defined - [SQLITE_SCANSTAT_COMPLEX]. If SQLITE_SCANSTAT_COMPLEX +** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX ** is specified, then status information is available for all elements -** of a query plan that are reported by "[EXPLAIN QUERY PLAN]" output. If +** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If ** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements ** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of ** the EXPLAIN QUERY PLAN output) are available. Invoking API @@ -11049,8 +10935,7 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value ** ** elements used to implement the statement - a non-zero value is returned and ** the variable that pOut points to is unchanged. ** -** See also: [sqlite3_stmt_scanstatus_reset()] and the -** [nexec and ncycle] columnes of the [bytecode virtual table]. +** See also: [sqlite3_stmt_scanstatus_reset()] */ SQLITE_API int sqlite3_stmt_scanstatus( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ @@ -11592,41 +11477,19 @@ SQLITE_API int sqlite3_deserialize( /* ** CAPI3REF: Bind array values to the CARRAY table-valued function ** -** The sqlite3_carray_bind_v2(S,I,P,N,F,X,D) interface binds an array value to -** parameter that is the first argument of the [carray() table-valued function]. -** The S parameter is a pointer to the [prepared statement] that uses the carray() -** functions. I is the parameter index to be bound. I must be the index of the -** parameter that is the first argument to the carray() table-valued function. -** P is a pointer to the array to be bound, and N is the number of elements in -** the array. The F argument is one of constants [SQLITE_CARRAY_INT32], -** [SQLITE_CARRAY_INT64], [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], -** or [SQLITE_CARRAY_BLOB] to indicate the datatype of the array P. -** -** If the X argument is not a NULL pointer or one of the special -** values [SQLITE_STATIC] or [SQLITE_TRANSIENT], then SQLite will invoke -** the function X with argument D when it is finished using the data in P. -** The call to X(D) is a destructor for the array P. The destructor X(D) -** is invoked even if the call to sqlite3_carray_bind() fails. If the X -** parameter is the special-case value [SQLITE_STATIC], then SQLite assumes -** that the data static and the destructor is never invoked. If the X -** parameter is the special-case value [SQLITE_TRANSIENT], then -** sqlite3_carray_bind_v2() makes its own private copy of the data prior -** to returning and never invokes the destructor X. -** -** The sqlite3_carray_bind() function works the same as sqlite_carray_bind_v2() -** with a D parameter set to P. In other words, -** sqlite3_carray_bind(S,I,P,N,F,X) is same as -** sqlite3_carray_bind(S,I,P,N,F,X,P). -*/ -SQLITE_API int sqlite3_carray_bind_v2( - sqlite3_stmt *pStmt, /* Statement to be bound */ - int i, /* Parameter index */ - void *aData, /* Pointer to array data */ - int nData, /* Number of data elements */ - int mFlags, /* CARRAY flags */ - void (*xDel)(void*), /* Destructor for aData */ - void *pDel /* Optional argument to xDel() */ -); +** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to +** one of the first argument of the [carray() table-valued function]. The +** S parameter is a pointer to the [prepared statement] that uses the carray() +** functions. I is the parameter index to be bound. P is a pointer to the +** array to be bound, and N is the number of eements in the array. The +** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to +** indicate the datatype of the array being bound. The X argument is not a +** NULL pointer, then SQLite will invoke the function X on the P parameter +** after it has finished using P, even if the call to +** sqlite3_carray_bind() fails. The special-case finalizer +** SQLITE_TRANSIENT has no effect here. +*/ SQLITE_API int sqlite3_carray_bind( sqlite3_stmt *pStmt, /* Statement to be bound */ int i, /* Parameter index */ @@ -14527,42 +14390,21 @@ struct fts5_api { ** It used to be the case that setting this value to zero would ** turn the limit off. That is no longer true. It is not possible ** to turn this limit off. -** -** The hard limit is the largest possible 32-bit signed integer less -** 1024, or 2147482624. */ #ifndef SQLITE_MAX_SQL_LENGTH # define SQLITE_MAX_SQL_LENGTH 1000000000 #endif /* -** The maximum depth of an expression tree. The expression tree depth -** is also limited indirectly by SQLITE_MAX_SQL_LENGTH and by -** SQLITE_MAX_PARSER_DEPTH. Reducing the maximum complexity of -** expressions can help prevent excess memory usage by hostile SQL. -** -** A value of 0 for this compile-time option causes all expression -** depth limiting code to be omitted. +** The maximum depth of an expression tree. This is limited to +** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might +** want to place more severe limits on the complexity of an +** expression. A value of 0 means that there is no limit. */ #ifndef SQLITE_MAX_EXPR_DEPTH # define SQLITE_MAX_EXPR_DEPTH 1000 #endif -/* -** The maximum depth of the LALR(1) stack used in the parser that -** interprets SQL inputs. The parser stack depth can also be limited -** indirectly by SQLITE_MAX_SQL_LENGTH. Limiting the parser stack -** depth can help prevent excess memory usage and excess CPU stack -** usage when processing hostile SQL. -** -** Prior to version 3.45.0 (2024-01-15), the parser stack was -** hard-coded to 100 entries, and that worked fine for almost all -** applications. So the upper bound on this limit need not be large. -*/ -#ifndef SQLITE_MAX_PARSER_DEPTH -# define SQLITE_MAX_PARSER_DEPTH 2500 -#endif - /* ** The maximum number of terms in a compound SELECT statement. ** The code generator for compound SELECT statements does one @@ -15448,7 +15290,6 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); # define float sqlite_int64 # define fabs(X) ((X)<0?-(X):(X)) # define sqlite3IsOverflow(X) 0 -# define INFINITY (9223372036854775807LL) # ifndef SQLITE_BIG_DBL # define SQLITE_BIG_DBL (((sqlite3_int64)1)<<50) # endif @@ -15858,7 +15699,6 @@ typedef INT16_TYPE LogEst; #else # define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) #endif -#define TWO_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&1)==0) /* ** Disable MMAP on platforms where it is known to not work @@ -17671,7 +17511,7 @@ typedef struct VdbeOpList VdbeOpList; ** Additional non-public SQLITE_PREPARE_* flags */ #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ -#define SQLITE_PREPARE_MASK 0x3f /* Mask of public flags */ +#define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */ /* ** Prototypes for the VDBE interface. See comments on the implementation @@ -17953,10 +17793,10 @@ struct PgHdr { PCache *pCache; /* PRIVATE: Cache that owns this page */ PgHdr *pDirty; /* Transient list of dirty sorted by pgno */ Pager *pPager; /* The pager this page is part of */ + Pgno pgno; /* Page number for this page */ #ifdef SQLITE_CHECK_PAGES - u64 pageHash; /* Hash of page content */ + u32 pageHash; /* Hash of page content */ #endif - Pgno pgno; /* Page number for this page */ u16 flags; /* PGHDR flags defined below */ /********************************************************************** @@ -18296,7 +18136,7 @@ struct Schema { ** The number of different kinds of things that can be limited ** using the sqlite3_limit() interface. */ -#define SQLITE_N_LIMIT (SQLITE_LIMIT_PARSER_DEPTH+1) +#define SQLITE_N_LIMIT (SQLITE_LIMIT_WORKER_THREADS+1) /* ** Lookaside malloc is a set of fixed-size buffers that can be used @@ -18450,7 +18290,6 @@ struct sqlite3 { u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ u8 eOpenState; /* Current condition of the connection */ - u8 nFpDigit; /* Significant digits to keep on double->text */ int nextPagesize; /* Pagesize after VACUUM if >0 */ i64 nChange; /* Value returned by sqlite3_changes() */ i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ @@ -20345,6 +20184,19 @@ struct Upsert { /* ** An instance of the following structure contains all information ** needed to generate code for a single SELECT statement. +** +** See the header comment on the computeLimitRegisters() routine for a +** detailed description of the meaning of the iLimit and iOffset fields. +** +** addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes. +** These addresses must be stored so that we can go back and fill in +** the P4_KEYINFO and P2 parameters later. Neither the KeyInfo nor +** the number of columns in P2 can be computed at the same time +** as the OP_OpenEphm instruction is coded because not +** enough information about the compound query is known at that point. +** The KeyInfo for addrOpenTran[0] and [1] contains collating sequences +** for the result set. The KeyInfo for addrOpenEphm[2] contains collating +** sequences for the ORDER BY clause. */ struct Select { u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */ @@ -20352,6 +20204,7 @@ struct Select { u32 selFlags; /* Various SF_* values */ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ u32 selId; /* Unique identifier number for this SELECT */ + int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ ExprList *pEList; /* The fields of the result */ SrcList *pSrc; /* The FROM clause */ Expr *pWhere; /* The WHERE clause */ @@ -20383,7 +20236,7 @@ struct Select { #define SF_Resolved 0x0000004 /* Identifiers have been resolved */ #define SF_Aggregate 0x0000008 /* Contains agg functions or a GROUP BY */ #define SF_HasAgg 0x0000010 /* Contains aggregate functions */ -#define SF_ClonedRhsIn 0x0000020 /* Cloned RHS of an IN operator */ +#define SF_UsesEphemeral 0x0000020 /* Uses the OpenEphemeral opcode */ #define SF_Expanded 0x0000040 /* sqlite3SelectExpand() called on this */ #define SF_HasTypeInfo 0x0000080 /* FROM subqueries have Table metadata */ #define SF_Compound 0x0000100 /* Part of a compound query */ @@ -20393,14 +20246,14 @@ struct Select { #define SF_MinMaxAgg 0x0001000 /* Aggregate containing min() or max() */ #define SF_Recursive 0x0002000 /* The recursive part of a recursive CTE */ #define SF_FixedLimit 0x0004000 /* nSelectRow set by a constant LIMIT */ -/* 0x0008000 // available for reuse */ +#define SF_MaybeConvert 0x0008000 /* Need convertCompoundSelectToSubquery() */ #define SF_Converted 0x0010000 /* By convertCompoundSelectToSubquery() */ #define SF_IncludeHidden 0x0020000 /* Include hidden columns in output */ #define SF_ComplexResult 0x0040000 /* Result contains subquery or function */ #define SF_WhereBegin 0x0080000 /* Really a WhereBegin() call. Debug Only */ #define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ #define SF_View 0x0200000 /* SELECT statement is a view */ -/* 0x0400000 // available for reuse */ +#define SF_NoopOrderBy 0x0400000 /* ORDER BY is ignored for this query */ #define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ #define SF_PushDown 0x1000000 /* Modified by WHERE-clause push-down opt */ #define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ @@ -20420,6 +20273,11 @@ struct Select { ** by one of the following macros. The "SRT" prefix means "SELECT Result ** Type". ** +** SRT_Union Store results as a key in a temporary index +** identified by pDest->iSDParm. +** +** SRT_Except Remove results from the temporary index pDest->iSDParm. +** ** SRT_Exists Store a 1 in memory cell pDest->iSDParm if the result ** set is not empty. ** @@ -20483,28 +20341,30 @@ struct Select { ** table. (pDest->iSDParm) is the number of key columns in ** each index record in this case. */ -#define SRT_Exists 1 /* Store 1 if the result is not empty */ -#define SRT_Discard 2 /* Do not save the results anywhere */ -#define SRT_DistFifo 3 /* Like SRT_Fifo, but unique results only */ -#define SRT_DistQueue 4 /* Like SRT_Queue, but unique results only */ +#define SRT_Union 1 /* Store result as keys in an index */ +#define SRT_Except 2 /* Remove result from a UNION index */ +#define SRT_Exists 3 /* Store 1 if the result is not empty */ +#define SRT_Discard 4 /* Do not save the results anywhere */ +#define SRT_DistFifo 5 /* Like SRT_Fifo, but unique results only */ +#define SRT_DistQueue 6 /* Like SRT_Queue, but unique results only */ /* The DISTINCT clause is ignored for all of the above. Not that ** IgnorableDistinct() implies IgnorableOrderby() */ #define IgnorableDistinct(X) ((X->eDest)<=SRT_DistQueue) -#define SRT_Queue 5 /* Store result in an queue */ -#define SRT_Fifo 6 /* Store result as data with an automatic rowid */ +#define SRT_Queue 7 /* Store result in an queue */ +#define SRT_Fifo 8 /* Store result as data with an automatic rowid */ /* The ORDER BY clause is ignored for all of the above */ #define IgnorableOrderby(X) ((X->eDest)<=SRT_Fifo) -#define SRT_Output 7 /* Output each row of result */ -#define SRT_Mem 8 /* Store result in a memory cell */ -#define SRT_Set 9 /* Store results as keys in an index */ -#define SRT_EphemTab 10 /* Create transient tab and store like SRT_Table */ -#define SRT_Coroutine 11 /* Generate a single row of result */ -#define SRT_Table 12 /* Store result as data with an automatic rowid */ -#define SRT_Upfrom 13 /* Store result as data with rowid */ +#define SRT_Output 9 /* Output each row of result */ +#define SRT_Mem 10 /* Store result in a memory cell */ +#define SRT_Set 11 /* Store results as keys in an index */ +#define SRT_EphemTab 12 /* Create transient tab and store like SRT_Table */ +#define SRT_Coroutine 13 /* Generate a single row of result */ +#define SRT_Table 14 /* Store result as data with an automatic rowid */ +#define SRT_Upfrom 15 /* Store result as data with rowid */ /* ** An instance of this object describes where to put of the results of @@ -20640,12 +20500,17 @@ struct Parse { u8 nested; /* Number of nested calls to the parser/code generator */ u8 nTempReg; /* Number of temporary registers in aTempReg[] */ u8 isMultiWrite; /* True if statement may modify/insert multiple rows */ + u8 mayAbort; /* True if statement may throw an ABORT exception */ + u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ + u8 bHasExists; /* Has a correlated "EXISTS (SELECT ....)" expression */ u8 mSubrtnSig; /* mini Bloom filter on available SubrtnSig.selId */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ + u8 bReturning; /* Coding a RETURNING trigger */ u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ + u8 disableTriggers; /* True to disable triggers */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ #endif @@ -20654,15 +20519,10 @@ struct Parse { u8 isCreate; /* CREATE TABLE, INDEX, or VIEW (but not TRIGGER) ** and ALTER TABLE ADD COLUMN. */ #endif - bft disableTriggers:1; /* True to disable triggers */ - bft mayAbort :1; /* True if statement may throw an ABORT exception */ - bft hasCompound :1; /* Need to invoke convertCompoundSelectToSubquery() */ - bft bReturning :1; /* Coding a RETURNING trigger */ - bft bHasExists :1; /* Has a correlated "EXISTS (SELECT ....)" expression */ - bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */ - bft bHasWith :1; /* True if statement contains WITH */ - bft okConstFactor:1; /* OK to factor out constants */ - bft checkSchema :1; /* Causes schema cookie check after an error */ + bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */ + bft bHasWith :1; /* True if statement contains WITH */ + bft okConstFactor :1; /* OK to factor out constants */ + bft checkSchema :1; /* Causes schema cookie check after an error */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ @@ -20891,19 +20751,19 @@ struct Trigger { ** orconf -> stores the ON CONFLICT algorithm ** pSelect -> The content to be inserted - either a SELECT statement or ** a VALUES clause. -** pSrc -> Table to insert into. +** zTarget -> Dequoted name of the table to insert into. ** pIdList -> If this is an INSERT INTO ... () VALUES ... ** statement, then this stores the column-names to be ** inserted into. ** pUpsert -> The ON CONFLICT clauses for an Upsert ** ** (op == TK_DELETE) -** pSrc -> Table to delete from +** zTarget -> Dequoted name of the table to delete from. ** pWhere -> The WHERE clause of the DELETE statement if one is specified. ** Otherwise NULL. ** ** (op == TK_UPDATE) -** pSrc -> Table to update, followed by any FROM clause tables. +** zTarget -> Dequoted name of the table to update. ** pWhere -> The WHERE clause of the UPDATE statement if one is specified. ** Otherwise NULL. ** pExprList -> A list of the columns to update and the expressions to update @@ -20923,7 +20783,8 @@ struct TriggerStep { u8 orconf; /* OE_Rollback etc. */ Trigger *pTrig; /* The trigger that this step is a part of */ Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ - SrcList *pSrc; /* Table to insert/update/delete */ + char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ + SrcList *pFrom; /* FROM clause for UPDATE statement (if any) */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ ExprList *pExprList; /* SET clause for UPDATE, or RETURNING clause */ IdList *pIdList; /* Column names for INSERT */ @@ -21006,11 +20867,10 @@ typedef struct { /* ** Allowed values for mInitFlags */ -#define INITFLAG_AlterMask 0x0007 /* Types of ALTER */ +#define INITFLAG_AlterMask 0x0003 /* Types of ALTER */ #define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ #define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ #define INITFLAG_AlterAdd 0x0003 /* Reparse after an ADD COLUMN */ -#define INITFLAG_AlterDropCons 0x0004 /* Reparse after an ADD COLUMN */ /* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled ** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning @@ -21140,7 +21000,6 @@ struct Walker { NameContext *pNC; /* Naming context */ int n; /* A counter */ int iCur; /* A cursor number */ - int sz; /* String literal length */ SrcList *pSrcList; /* FROM clause */ struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ @@ -21545,20 +21404,7 @@ SQLITE_PRIVATE int sqlite3LookasideUsed(sqlite3*,int*); SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void); SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void); - -/* The SQLITE_THREAD_MISUSE_WARNINGS compile-time option used to be called -** SQLITE_ENABLE_MULTITHREADED_CHECKS. Keep that older macro for backwards -** compatibility, at least for a while... */ -#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS -# define SQLITE_THREAD_MISUSE_WARNINGS 1 -#endif - -/* SQLITE_THREAD_MISUSE_ABORT implies SQLITE_THREAD_MISUSE_WARNINGS */ -#ifdef SQLITE_THREAD_MISUSE_ABORT -# define SQLITE_THREAD_MISUSE_WARNINGS 1 -#endif - -#if defined(SQLITE_THREAD_MISUSE_WARNINGS) && !defined(SQLITE_MUTEX_OMIT) +#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT) SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex*); #else # define sqlite3MutexWarnOnContention(x) @@ -21592,12 +21438,12 @@ struct PrintfArguments { ** value into an approximate decimal representation. */ struct FpDecode { + char sign; /* '+' or '-' */ + char isSpecial; /* 1: Infinity 2: NaN */ int n; /* Significant digits in the decode */ int iDP; /* Location of the decimal point */ char *z; /* Start of significant digits */ - char zBuf[20]; /* Storage for significant digits */ - char sign; /* '+' or '-' */ - char isSpecial; /* 1: Infinity 2: NaN */ + char zBuf[24]; /* Storage for significant digits */ }; SQLITE_PRIVATE void sqlite3FpDecode(FpDecode*,double,int,int); @@ -21686,7 +21532,6 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse*,int,int); #endif SQLITE_PRIVATE Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int); SQLITE_PRIVATE Expr *sqlite3Expr(sqlite3*,int,const char*); -SQLITE_PRIVATE Expr *sqlite3ExprInt32(sqlite3*,int); SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*); SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*); @@ -21938,7 +21783,6 @@ SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*); SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*, Parse*); SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*); SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); -SQLITE_PRIVATE int sqlite3ExprIsLikeOperator(const Expr*); SQLITE_PRIVATE int sqlite3IsRowid(const char*); SQLITE_PRIVATE const char *sqlite3RowidAlias(Table *pTab); SQLITE_PRIVATE void sqlite3GenerateRowDelete( @@ -22007,16 +21851,17 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, i SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, const char*,const char*); -SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(Parse*,SrcList*, IdList*, +SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*, Select*,u8,Upsert*, const char*,const char*); -SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(Parse*,SrcList*,SrcList*,ExprList*, +SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,SrcList*,ExprList*, Expr*, u8, const char*,const char*); -SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(Parse*,SrcList*, Expr*, +SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*, const char*,const char*); SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3*, Trigger*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); SQLITE_PRIVATE u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); +SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*); # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) # define sqlite3IsToplevel(p) ((p)->pToplevel==0) #else @@ -22030,6 +21875,7 @@ SQLITE_PRIVATE u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Tab # define sqlite3ParseToplevel(p) p # define sqlite3IsToplevel(p) 1 # define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 +# define sqlite3TriggerStepSrc(A,B) 0 #endif SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*); @@ -22062,7 +21908,7 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); SQLITE_PRIVATE int sqlite3RealSameAsInt(double,sqlite3_int64); SQLITE_PRIVATE i64 sqlite3RealToI64(double); SQLITE_PRIVATE int sqlite3Int64ToText(i64,char*); -SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*); +SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*, int, u8); SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*); SQLITE_PRIVATE int sqlite3GetUInt32(const char*, u32*); SQLITE_PRIVATE int sqlite3Atoi(const char*); @@ -22206,13 +22052,10 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*); SQLITE_PRIVATE void sqlite3AlterFunctions(void); SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); SQLITE_PRIVATE void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*); -SQLITE_PRIVATE void sqlite3AlterDropConstraint(Parse*,SrcList*,Token*,Token*); -SQLITE_PRIVATE void sqlite3AlterAddConstraint(Parse*,SrcList*,Token*,Token*,const char*,int); -SQLITE_PRIVATE void sqlite3AlterSetNotNull(Parse*, SrcList*, Token*, Token*); SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *, int *); SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...); SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*, int); -SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int, int); +SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int); SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr*); SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*); SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse*, SrcItem*); @@ -24627,7 +24470,6 @@ SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem*, Mem*); SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*)); -SQLITE_PRIVATE int sqlite3VdbeMemSetText(Mem*, const char*, i64, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64); #ifdef SQLITE_OMIT_FLOATING_POINT # define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 @@ -24646,14 +24488,13 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem*,int); SQLITE_PRIVATE int sqlite3VdbeMemIsRowSet(const Mem*); #endif SQLITE_PRIVATE int sqlite3VdbeMemSetRowSet(Mem*); -SQLITE_PRIVATE int sqlite3VdbeMemZeroTerminateIfAble(Mem*); +SQLITE_PRIVATE void sqlite3VdbeMemZeroTerminateIfAble(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, u8, u8); SQLITE_PRIVATE int sqlite3IntFloatCompare(i64,double); SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem*); SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem*); SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem*); -SQLITE_PRIVATE SQLITE_NOINLINE double sqlite3MemRealValueRC(Mem*, int*); SQLITE_PRIVATE int sqlite3VdbeBooleanValue(Mem*, int ifNull); SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem*); @@ -25613,7 +25454,7 @@ static int parseDateOrTime( return 0; }else if( sqlite3StrICmp(zDate,"now")==0 && sqlite3NotPureFunc(context) ){ return setDateTimeToCurrent(context, p); - }else if( sqlite3AtoF(zDate, &r)>0 ){ + }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8)>0 ){ setRawDateNumber(p, r); return 0; }else if( (sqlite3StrICmp(zDate,"subsec")==0 @@ -26059,7 +25900,7 @@ static int parseModifier( ** date is already on the appropriate weekday, this is a no-op. */ if( sqlite3_strnicmp(z, "weekday ", 8)==0 - && sqlite3AtoF(&z[8], &r)>0 + && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)>0 && r>=0.0 && r<7.0 && (n=(int)r)==r ){ sqlite3_int64 Z; computeYMD_HMS(p); @@ -26130,11 +25971,9 @@ static int parseModifier( case '8': case '9': { double rRounder; - int i, rx; + int i; int Y,M,D,h,m,x; const char *z2 = z; - char *zCopy; - sqlite3 *db = sqlite3_context_db_handle(pCtx); char z0 = z[0]; for(n=1; z[n]; n++){ if( z[n]==':' ) break; @@ -26144,11 +25983,7 @@ static int parseModifier( if( n==6 && getDigits(&z[1], "50f", &Y)==1 ) break; } } - zCopy = sqlite3DbStrNDup(db, z, n); - if( zCopy==0 ) break; - rx = sqlite3AtoF(zCopy, &r)<=0; - sqlite3DbFree(db, zCopy); - if( rx ){ + if( sqlite3AtoF(z, &r, n, SQLITE_UTF8)<=0 ){ assert( rc==1 ); break; } @@ -26968,7 +26803,7 @@ static void datedebugFunc( char *zJson; zJson = sqlite3_mprintf( "{iJD:%lld,Y:%d,M:%d,D:%d,h:%d,m:%d,tz:%d," - "s:%.3f,validJD:%d,validYMD:%d,validHMS:%d," + "s:%.3f,validJD:%d,validYMS:%d,validHMS:%d," "nFloor:%d,rawS:%d,isError:%d,useSubsec:%d," "isUtc:%d,isLocal:%d}", x.iJD, x.Y, x.M, x.D, x.h, x.m, x.tz, @@ -29747,28 +29582,23 @@ static SQLITE_WSD int mutexIsInit = 0; #ifndef SQLITE_MUTEX_OMIT -#ifdef SQLITE_THREAD_MISUSE_WARNINGS +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS /* -** This block (enclosed by SQLITE_THREAD_MISUSE_WARNINGS) contains +** This block (enclosed by SQLITE_ENABLE_MULTITHREADED_CHECKS) contains ** the implementation of a wrapper around the system default mutex ** implementation (sqlite3DefaultMutex()). ** ** Most calls are passed directly through to the underlying default ** mutex implementation. Except, if a mutex is configured by calling ** sqlite3MutexWarnOnContention() on it, then if contention is ever -** encountered within xMutexEnter() then a warning is emitted via -** sqlite3_log(). Furthermore, if SQLITE_THREAD_MISUSE_ABORT is -** defined then abort() is called after the sqlite3_log() warning. +** encountered within xMutexEnter() a warning is emitted via sqlite3_log(). ** -** This type of mutex is used on the database handle mutex when testing -** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. A failure -** indicates that the app ought to be using SQLITE_OPEN_FULLMUTEX or -** similar because it is trying to use the same database handle from -** two different connections at the same time. +** This type of mutex is used as the database handle mutex when testing +** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. */ /* -** Type for all mutexes used when SQLITE_THREAD_MISUSE_WARNINGS +** Type for all mutexes used when SQLITE_ENABLE_MULTITHREADED_CHECKS ** is defined. Variable CheckMutex.mutex is a pointer to the real mutex ** allocated by the system mutex implementation. Variable iType is usually set ** to the type of mutex requested - SQLITE_MUTEX_RECURSIVE, SQLITE_MUTEX_FAST @@ -29804,12 +29634,11 @@ static int checkMutexNotheld(sqlite3_mutex *p){ */ static int checkMutexInit(void){ pGlobalMutexMethods = sqlite3DefaultMutex(); - return pGlobalMutexMethods->xMutexInit(); + return SQLITE_OK; } static int checkMutexEnd(void){ - int rc = pGlobalMutexMethods->xMutexEnd(); pGlobalMutexMethods = 0; - return rc; + return SQLITE_OK; } /* @@ -29886,9 +29715,6 @@ static void checkMutexEnter(sqlite3_mutex *p){ sqlite3_log(SQLITE_MISUSE, "illegal multi-threaded access to database connection" ); -#if SQLITE_THREAD_MISUSE_ABORT - abort(); -#endif } pGlobalMutexMethods->xMutexEnter(pCheck->mutex); } @@ -29940,7 +29766,7 @@ SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex *p){ pCheck->iType = SQLITE_MUTEX_WARNONCONTENTION; } } -#endif /* ifdef SQLITE_THREAD_MISUSE_WARNINGS */ +#endif /* ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS */ /* ** Initialize the mutex system. @@ -29957,7 +29783,7 @@ SQLITE_PRIVATE int sqlite3MutexInit(void){ sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex; if( sqlite3GlobalConfig.bCoreMutex ){ -#ifdef SQLITE_THREAD_MISUSE_WARNINGS +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS pFrom = multiThreadedCheckMutex(); #else pFrom = sqlite3DefaultMutex(); @@ -30805,6 +30631,14 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ # define SQLITE_OS_WINCE 0 #endif +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + /* ** For WinCE, some API function parameters do not appear to be declared as ** volatile. @@ -30819,7 +30653,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ ** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() ** functions are not available (e.g. those not using MSVC, Cygwin, etc). */ -#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && \ +#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) # define SQLITE_OS_WIN_THREADS 1 #else @@ -30936,7 +30770,11 @@ static int winMutexInit(void){ if( InterlockedCompareExchange(&winMutex_lock, 1, 0)==0 ){ int i; for(i=0; itrace = 1; #endif #endif +#if SQLITE_OS_WINRT + InitializeCriticalSectionEx(&p->mutex, 0, 0); +#else InitializeCriticalSection(&p->mutex); +#endif } break; } @@ -32651,7 +32493,7 @@ SQLITE_API void sqlite3_str_vappendf( }else{ iRound = precision+1; } - sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 20 : 16); + sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 26 : 16); if( s.isSpecial ){ if( s.isSpecial==2 ){ bufpt = flag_zeropad ? "null" : "NaN"; @@ -33330,14 +33172,6 @@ SQLITE_API int sqlite3_str_length(sqlite3_str *p){ return p ? p->nChar : 0; } -/* Truncate the text of the string to be no more than N bytes. */ -SQLITE_API void sqlite3_str_truncate(sqlite3_str *p, int N){ - if( p!=0 && N>=0 && (u32)NnChar ){ - p->nChar = N; - p->zText[p->nChar] = 0; - } -} - /* Return the current value for p */ SQLITE_API char *sqlite3_str_value(sqlite3_str *p){ if( p==0 || p->nChar==0 ) return 0; @@ -33358,17 +33192,6 @@ SQLITE_API void sqlite3_str_reset(StrAccum *p){ p->zText = 0; } -/* -** Destroy a dynamically allocate sqlite3_str object and all -** of its content, all in one call. -*/ -SQLITE_API void sqlite3_str_free(sqlite3_str *p){ - if( p ){ - sqlite3_str_reset(p); - sqlite3_free(p); - } -} - /* ** Initialize a string accumulator. ** @@ -34982,13 +34805,7 @@ SQLITE_PRIVATE void sqlite3TreeViewTrigger( SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } -SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ - TreeView *pView = 0; - sqlite3TreeViewPush(&pView, 0); - sqlite3TreeViewLine(pView, "SRCLIST"); - sqlite3TreeViewSrcList(pView,p); - sqlite3TreeViewPop(&pView); -} +SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); } SQLITE_PRIVATE void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); } SQLITE_PRIVATE void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); } SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); } @@ -36508,262 +36325,48 @@ SQLITE_PRIVATE u8 sqlite3StrIHash(const char *z){ return h; } -/* -** Two inputs are multiplied to get a 128-bit result. Return -** the high-order 64 bits of that result. -*/ -static u64 sqlite3Multiply128(u64 a, u64 b){ -#if (defined(__GNUC__) || defined(__clang__)) \ - && (defined(__x86_64__) || defined(__aarch64__) || defined(__riscv)) - return ((__uint128_t)a * b) >> 64; -#elif defined(_MSC_VER) && defined(_M_X64) - return __umulh(a, b); -#else - u64 a1 = (u32)a; - u64 a2 = a >> 32; - u64 b1 = (u32)b; - u64 b2 = b >> 32; - u64 p0 = a1 * b1; - u64 p1 = a1 * b2; - u64 p2 = a2 * b1; - u64 p3 = a2 * b2; - u64 carry = ((p0 >> 32) + (u32)p1 + (u32)p2) >> 32; - return p3 + (p1 >> 32) + (p2 >> 32) + carry; -#endif -} - -/* -** Return a u64 with the N-th bit set. -*/ -#define U64_BIT(N) (((u64)1)<<(N)) - -/* -** Range of powers of 10 that we need to deal with when converting -** IEEE754 doubles to and from decimal. -*/ -#define POWERSOF10_FIRST (-348) -#define POWERSOF10_LAST (+347) - -/* -** For any p between -348 and +347, return the integer part of -** -** pow(10,p) * pow(2,63-pow10to2(p)) -** -** Or, in other words, for any p in range, return the most significant -** 64 bits of pow(10,p). The pow(10,p) value is shifted left or right, -** as appropriate so the most significant 64 bits fit exactly into a -** 64-bit unsigned integer. -** -** Algorithm: -** -** (1) For p between 0 and 26, return the value directly from the aBase[] -** lookup table. -** -** (2) For p outside the range 0 to 26, use aScale[] for the initial value -** then refine that result (if necessary) by a single multiplication -** against aBase[]. -*/ -static u64 powerOfTen(int p){ - static const u64 aBase[] = { - 0x8000000000000000LLU, /* 0: 1.0e+0 << 63 */ - 0xa000000000000000LLU, /* 1: 1.0e+1 << 60 */ - 0xc800000000000000LLU, /* 2: 1.0e+2 << 57 */ - 0xfa00000000000000LLU, /* 3: 1.0e+3 << 54 */ - 0x9c40000000000000LLU, /* 4: 1.0e+4 << 50 */ - 0xc350000000000000LLU, /* 5: 1.0e+5 << 47 */ - 0xf424000000000000LLU, /* 6: 1.0e+6 << 44 */ - 0x9896800000000000LLU, /* 7: 1.0e+7 << 40 */ - 0xbebc200000000000LLU, /* 8: 1.0e+8 << 37 */ - 0xee6b280000000000LLU, /* 9: 1.0e+9 << 34 */ - 0x9502f90000000000LLU, /* 10: 1.0e+10 << 30 */ - 0xba43b74000000000LLU, /* 11: 1.0e+11 << 27 */ - 0xe8d4a51000000000LLU, /* 12: 1.0e+12 << 24 */ - 0x9184e72a00000000LLU, /* 13: 1.0e+13 << 20 */ - 0xb5e620f480000000LLU, /* 14: 1.0e+14 << 17 */ - 0xe35fa931a0000000LLU, /* 15: 1.0e+15 << 14 */ - 0x8e1bc9bf04000000LLU, /* 16: 1.0e+16 << 10 */ - 0xb1a2bc2ec5000000LLU, /* 17: 1.0e+17 << 7 */ - 0xde0b6b3a76400000LLU, /* 18: 1.0e+18 << 4 */ - 0x8ac7230489e80000LLU, /* 19: 1.0e+19 >> 0 */ - 0xad78ebc5ac620000LLU, /* 20: 1.0e+20 >> 3 */ - 0xd8d726b7177a8000LLU, /* 21: 1.0e+21 >> 6 */ - 0x878678326eac9000LLU, /* 22: 1.0e+22 >> 10 */ - 0xa968163f0a57b400LLU, /* 23: 1.0e+23 >> 13 */ - 0xd3c21bcecceda100LLU, /* 24: 1.0e+24 >> 16 */ - 0x84595161401484a0LLU, /* 25: 1.0e+25 >> 20 */ - 0xa56fa5b99019a5c8LLU, /* 26: 1.0e+26 >> 23 */ - }; - static const u64 aScale[] = { - 0x8049a4ac0c5811aeLLU, /* 0: 1.0e-351 << 1229 */ - 0xcf42894a5dce35eaLLU, /* 1: 1.0e-324 << 1140 */ - 0xa76c582338ed2622LLU, /* 2: 1.0e-297 << 1050 */ - 0x873e4f75e2224e68LLU, /* 3: 1.0e-270 << 960 */ - 0xda7f5bf590966849LLU, /* 4: 1.0e-243 << 871 */ - 0xb080392cc4349dedLLU, /* 5: 1.0e-216 << 781 */ - 0x8e938662882af53eLLU, /* 6: 1.0e-189 << 691 */ - 0xe65829b3046b0afaLLU, /* 7: 1.0e-162 << 602 */ - 0xba121a4650e4ddecLLU, /* 8: 1.0e-135 << 512 */ - 0x964e858c91ba2655LLU, /* 9: 1.0e-108 << 422 */ - 0xf2d56790ab41c2a3LLU, /* 10: 1.0e-81 << 333 */ - 0xc428d05aa4751e4dLLU, /* 11: 1.0e-54 << 243 */ - 0x9e74d1b791e07e48LLU, /* 12: 1.0e-27 << 153 */ - 0x8000000000000000LLU, /* 13: 1.0e+0 << 63 */ - 0xcecb8f27f4200f3aLLU, /* 14: 1.0e+27 >> 26 */ - 0xa70c3c40a64e6c52LLU, /* 15: 1.0e+54 >> 116 */ - 0x86f0ac99b4e8dafdLLU, /* 16: 1.0e+81 >> 206 */ - 0xda01ee641a708deaLLU, /* 17: 1.0e+108 >> 295 */ - 0xb01ae745b101e9e4LLU, /* 18: 1.0e+135 >> 385 */ - 0x8e41ade9fbebc27dLLU, /* 19: 1.0e+162 >> 475 */ - 0xe5d3ef282a242e82LLU, /* 20: 1.0e+189 >> 564 */ - 0xb9a74a0637ce2ee1LLU, /* 21: 1.0e+216 >> 654 */ - 0x95f83d0a1fb69cd9LLU, /* 22: 1.0e+243 >> 744 */ - 0xf24a01a73cf2dcd0LLU, /* 23: 1.0e+270 >> 833 */ - 0xc3b8358109e84f07LLU, /* 24: 1.0e+297 >> 923 */ - 0x9e19db92b4e31ba9LLU, /* 25: 1.0e+324 >> 1013 */ - }; - int g, n; - u64 x, y; - - assert( p>=POWERSOF10_FIRST && p<=POWERSOF10_LAST ); - if( p<0 ){ - g = p/27; - n = p%27; - if( n ){ - g--; - n += 27; - } - }else if( p<27 ){ - return aBase[p]; - }else{ - g = p/27; - n = p%27; - } - y = aScale[g+13]; - if( n==0 ){ - return y; - } - x = sqlite3Multiply128(aBase[n],y); - if( (U64_BIT(63) & x)==0 ){ - x = (x<<1)|1; - } - return x; -} - -/* -** pow10to2(x) computes floor(log2(pow(10,x))). -** pow2to10(y) computes floor(log10(pow(2,y))). -** -** Conceptually, pow10to2(p) converts a base-10 exponent p into -** a corresponding base-2 exponent, and pow2to10(e) converts a base-2 -** exponent into a base-10 exponent. -** -** The conversions are based on the observation that: -** -** ln(10.0)/ln(2.0) == 108853/32768 (approximately) -** ln(2.0)/ln(10.0) == 78913/262144 (approximately) -** -** These ratios are approximate, but they are accurate to 5 digits, -** which is close enough for the usage here. Right-shift is used -** for division so that rounding of negative numbers happens in the -** right direction. -*/ -static int pwr10to2(int p){ return (p*108853) >> 15; } -static int pwr2to10(int p){ return (p*78913) >> 18; } - -/* -** Count leading zeros for a 64-bit unsigned integer. -*/ -static int countLeadingZeros(u64 m){ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_clzll(m); -#else - int n = 0; - if( m <= 0x00000000ffffffffULL) { n += 32; m <<= 32; } - if( m <= 0x0000ffffffffffffULL) { n += 16; m <<= 16; } - if( m <= 0x00ffffffffffffffULL) { n += 8; m <<= 8; } - if( m <= 0x0fffffffffffffffULL) { n += 4; m <<= 4; } - if( m <= 0x3fffffffffffffffULL) { n += 2; m <<= 2; } - if( m <= 0x7fffffffffffffffULL) { n += 1; } - return n; -#endif -} - -/* -** Given m and e, which represent a quantity r == m*pow(2,e), -** return values *pD and *pP such that r == (*pD)*pow(10,*pP), -** approximately. *pD should contain at least n significant digits. +/* Double-Double multiplication. (x[0],x[1]) *= (y,yy) ** -** The input m is required to have its highest bit set. In other words, -** m should be left-shifted, and e decremented, to maximize the value of m. -*/ -static void sqlite3Fp2Convert10(u64 m, int e, int n, u64 *pD, int *pP){ - int p; - u64 h; - assert( n>=1 && n<=18 ); - p = n - 1 - pwr2to10(e+63); - h = sqlite3Multiply128(m, powerOfTen(p)); - assert( -(e + pwr10to2(p) + 2) >= 0 ); - assert( -(e + pwr10to2(p) + 1) <= 63 ); - if( n==18 ){ - h >>= -(e + pwr10to2(p) + 2); - *pD = (h + ((h<<1)&2))>>1; - }else{ - *pD = h >> -(e + pwr10to2(p) + 1); - } - *pP = -p; -} - -/* -** Return an IEEE754 floating point value that approximates d*pow(10,p). +** Reference: +** T. J. Dekker, "A Floating-Point Technique for Extending the +** Available Precision". 1971-07-26. */ -static double sqlite3Fp10Convert2(u64 d, int p){ - u64 out; - int e1; - int lz; - int lp; - int x; - u64 h; - double r; - assert( (d & U64_BIT(63))==0 ); - assert( d!=0 ); - if( pPOWERSOF10_LAST ){ - return INFINITY; - } - lz = countLeadingZeros(d); - lp = pwr10to2(p); - e1 = lz - (lp + 11); - if( e1>1074 ){ - if( e1>=1130 ) return 0.0; - e1 = 1074; - } - h = sqlite3Multiply128(d<= 0 ); - assert( x <= 63 ); - out = h >> x; - if( out >= U64_BIT(55)-2 ){ - out >>= 1; - e1--; - } - if( e1<=(-972) ){ - return INFINITY; - } - out = (out + 2) >> 2; - if( (out & U64_BIT(52))!=0 ){ - out = (out & ~U64_BIT(52)) | ((u64)(1075-e1)<<52); - } - memcpy(&r, &out, 8); - return r; +static void dekkerMul2(volatile double *x, double y, double yy){ + /* + ** The "volatile" keywords on parameter x[] and on local variables + ** below are needed force intermediate results to be truncated to + ** binary64 rather than be carried around in an extended-precision + ** format. The truncation is necessary for the Dekker algorithm to + ** work. Intel x86 floating point might omit the truncation without + ** the use of volatile. + */ + volatile double tx, ty, p, q, c, cc; + double hx, hy; + u64 m; + memcpy(&m, (void*)&x[0], 8); + m &= 0xfffffffffc000000LL; + memcpy(&hx, &m, 8); + tx = x[0] - hx; + memcpy(&m, &y, 8); + m &= 0xfffffffffc000000LL; + memcpy(&hy, &m, 8); + ty = y - hy; + p = hx*hy; + q = hx*ty + tx*hy; + c = p+q; + cc = p - c + q + tx*ty; + cc = x[0]*yy + x[1]*y + cc; + x[0] = c + cc; + x[1] = c - x[0]; + x[1] += cc; } /* ** The string z[] is an text representation of a real number. ** Convert this string to a double and write it into *pResult. ** -** z[] must be UTF-8 and zero-terminated. +** The string z[] is length bytes in length (bytes, not characters) and +** uses the encoding enc. The string is not necessarily zero-terminated. ** ** Return TRUE if the result is a valid real number (or integer) and FALSE ** if the string is empty or contains extraneous text. More specifically @@ -36790,131 +36393,198 @@ static double sqlite3Fp10Convert2(u64 d, int p){ #if defined(_MSC_VER) #pragma warning(disable : 4756) #endif -SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult){ +SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ #ifndef SQLITE_OMIT_FLOATING_POINT + int incr; + const char *zEnd; /* sign * significand * (10 ^ (esign * exponent)) */ - int neg = 0; /* True for a negative value */ - u64 s = 0; /* mantissa */ - int d = 0; /* Value is s * pow(10,d) */ + int sign = 1; /* sign of significand */ + u64 s = 0; /* significand */ + int d = 0; /* adjust exponent for shifting decimal point */ + int esign = 1; /* sign of exponent */ + int e = 0; /* exponent */ + int eValid = 1; /* True exponent is either not used or is well-formed */ int nDigit = 0; /* Number of digits processed */ - int eType = 1; /* 1: pure integer, 2+: fractional */ + int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */ + u64 s2; /* round-tripped significand */ + double rr[2]; + assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); *pResult = 0.0; /* Default return value, in case of an error */ + if( length==0 ) return 0; + + if( enc==SQLITE_UTF8 ){ + incr = 1; + zEnd = z + length; + }else{ + int i; + incr = 2; + length &= ~1; + assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); + testcase( enc==SQLITE_UTF16LE ); + testcase( enc==SQLITE_UTF16BE ); + for(i=3-enc; i=zEnd ) return 0; /* get sign of significand */ if( *z=='-' ){ - neg = 1; - z++; + sign = -1; + z+=incr; }else if( *z=='+' ){ - z++; + z+=incr; } /* copy max significant digits to significand */ - while( sqlite3Isdigit(*z) ){ + while( z=((LARGEST_INT64-9)/10) ){ + z+=incr; nDigit++; + if( s>=((LARGEST_UINT64-9)/10) ){ /* skip non-significant significand digits ** (increase exponent by d to shift decimal left) */ - while( sqlite3Isdigit(*z) ){ z++; d++; } + while( z=zEnd ) goto do_atof_calc; /* if decimal point is present */ if( *z=='.' ){ - z++; + z+=incr; eType++; /* copy digits from after decimal to significand ** (decrease exponent by d to shift decimal right) */ - while( sqlite3Isdigit(*z) ){ - if( s<((LARGEST_INT64-9)/10) ){ + while( z=zEnd ) goto do_atof_calc; /* if exponent is present */ if( *z=='e' || *z=='E' ){ - int esign = 1; /* sign of exponent */ - z++; + z+=incr; + eValid = 0; eType++; + /* This branch is needed to avoid a (harmless) buffer overread. The + ** special comment alerts the mutation tester that the correct answer + ** is obtained even if the branch is omitted */ + if( z>=zEnd ) goto do_atof_calc; /*PREVENTS-HARMLESS-OVERREAD*/ + /* get sign of exponent */ if( *z=='-' ){ esign = -1; - z++; + z+=incr; }else if( *z=='+' ){ - z++; + z+=incr; } /* copy digits to exponent */ - if( sqlite3Isdigit(*z) ){ - int exp = *z - '0'; - z++; - while( sqlite3Isdigit(*z) ){ - exp = exp<10000 ? (exp*10 + (*z - '0')) : 10000; - z++; - } - d += esign*exp; - }else{ - eType = -1; + while( z0 && s<((LARGEST_UINT64-0x7ff)/10) ){ + s *= 10; + e--; + } + while( e<0 && (s%10)==0 ){ + s /= 10; + e++; + } + + rr[0] = (double)s; + assert( sizeof(s2)==sizeof(rr[0]) ); +#ifdef SQLITE_DEBUG + rr[1] = 18446744073709549568.0; + memcpy(&s2, &rr[1], sizeof(s2)); + assert( s2==0x43efffffffffffffLL ); +#endif + /* Largest double that can be safely converted to u64 + ** vvvvvvvvvvvvvvvvvvvvvv */ + if( rr[0]<=18446744073709549568.0 ){ + s2 = (u64)rr[0]; + rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s); + }else{ + rr[1] = 0.0; + } + assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */ + + if( e>0 ){ + while( e>=100 ){ + e -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( e>=10 ){ + e -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( e>=1 ){ + e -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } }else{ - *pResult = sqlite3Fp10Convert2(s,d); - if( neg ) *pResult = -*pResult; - assert( !sqlite3IsNaN(*pResult) ); + while( e<=-100 ){ + e += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( e<=-10 ){ + e += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( e<=-1 ){ + e += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } } + *pResult = rr[0]+rr[1]; + if( sqlite3IsNaN(*pResult) ) *pResult = 1e300*1e300; + if( sign<0 ) *pResult = -*pResult; + assert( !sqlite3IsNaN(*pResult) ); +atof_return: /* return true if number and no extra non-whitespace characters after */ - if( z[0]==0 && nDigit>0 ){ + if( z==zEnd && nDigit>0 && eValid && eType>0 ){ return eType; - }else if( eType>=2 && nDigit>0 ){ + }else if( eType>=2 && (eType==3 || eValid) && nDigit>0 ){ return -1; }else{ return 0; } #else - return !sqlite3Atoi64(z, pResult, strlen(z), SQLITE_UTF8); + return !sqlite3Atoi64(z, pResult, length, enc); #endif /* SQLITE_OMIT_FLOATING_POINT */ } #if defined(_MSC_VER) #pragma warning(default : 4756) #endif -/* -** Digit pairs used to convert a U64 or I64 into text, two digits -** at a time. -*/ -static const union { - char a[201]; - short int forceAlignment; -} sqlite3DigitPairs = { - "00010203040506070809" - "10111213141516171819" - "20212223242526272829" - "30313233343536373839" - "40414243444546474849" - "50515253545556575859" - "60616263646566676869" - "70717273747576777879" - "80818283848586878889" - "90919293949596979899" -}; - - /* ** Render an signed 64-bit integer as text. Store the result in zOut[] and ** return the length of the string that was stored, in bytes. The value @@ -36926,35 +36596,23 @@ static const union { SQLITE_PRIVATE int sqlite3Int64ToText(i64 v, char *zOut){ int i; u64 x; - union { - char a[23]; - u16 forceAlignment; - } u; - if( v>0 ){ - x = v; - }else if( v==0 ){ - zOut[0] = '0'; - zOut[1] = 0; - return 1; - }else{ + char zTemp[22]; + if( v<0 ){ x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v; + }else{ + x = v; } - i = sizeof(u.a)-1; - u.a[i] = 0; - while( x>=10 ){ - int kk = (x%100)*2; - assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); - assert( TWO_BYTE_ALIGNMENT(&u.a[i-2]) ); - *(u16*)(&u.a[i-2]) = *(u16*)&sqlite3DigitPairs.a[kk]; - i -= 2; - x /= 100; - } - if( x ){ - u.a[--i] = x + '0'; - } - if( v<0 ) u.a[--i] = '-'; - memcpy(zOut, &u.a[i], sizeof(u.a)-i); - return sizeof(u.a)-1-i; + i = sizeof(zTemp)-2; + zTemp[sizeof(zTemp)-1] = 0; + while( 1 /*exit-by-break*/ ){ + zTemp[i] = (x%10) + '0'; + x = x/10; + if( x==0 ) break; + i--; + }; + if( v<0 ) zTemp[--i] = '-'; + memcpy(zOut, &zTemp[i], sizeof(zTemp)-i); + return sizeof(zTemp)-1-i; } /* @@ -37211,7 +36869,7 @@ SQLITE_PRIVATE int sqlite3Atoi(const char *z){ ** representation. ** ** If iRound<=0 then round to -iRound significant digits to the -** the right of the decimal point, or to a maximum of mxRound total +** the left of the decimal point, or to a maximum of mxRound total ** significant digits. ** ** If iRound>0 round to min(iRound,mxRound) significant digits total. @@ -37224,14 +36882,13 @@ SQLITE_PRIVATE int sqlite3Atoi(const char *z){ ** The p->z[] array is *not* zero-terminated. */ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ - int i; /* Index into zBuf[] where to put next character */ - int n; /* Number of digits */ - u64 v; /* mantissa */ - int e, exp = 0; /* Base-2 and base-10 exponent */ - char *zBuf; /* Local alias for p->zBuf */ - char *z; /* Local alias for p->z */ + int i; + u64 v; + int e, exp = 0; + double rr[2]; p->isSpecial = 0; + p->z = p->zBuf; assert( mxRound>0 ); /* Convert negative numbers to positive. Deal with Infinity, 0.0, and @@ -37249,94 +36906,78 @@ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRou p->sign = '+'; } memcpy(&v,&r,8); - e = (v>>52)&0x7ff; - if( e==0x7ff ){ + e = v>>52; + if( (e&0x7ff)==0x7ff ){ p->isSpecial = 1 + (v!=0x7ff0000000000000LL); p->n = 0; p->iDP = 0; - p->z = p->zBuf; return; } - v &= 0x000fffffffffffffULL; - if( e==0 ){ - int nn = countLeadingZeros(v); - v <<= nn; - e = -1074 - nn; + + /* Multiply r by powers of ten until it lands somewhere in between + ** 1.0e+19 and 1.0e+17. + ** + ** Use Dekker-style double-double computation to increase the + ** precision. + ** + ** The error terms on constants like 1.0e+100 computed using the + ** decimal extension, for example as follows: + ** + ** SELECT decimal_exp(decimal_sub('1.0e+100',decimal(1.0e+100))); + */ + rr[0] = r; + rr[1] = 0.0; + if( rr[0]>9.223372036854774784e+18 ){ + while( rr[0]>9.223372036854774784e+118 ){ + exp += 100; + dekkerMul2(rr, 1.0e-100, -1.99918998026028836196e-117); + } + while( rr[0]>9.223372036854774784e+28 ){ + exp += 10; + dekkerMul2(rr, 1.0e-10, -3.6432197315497741579e-27); + } + while( rr[0]>9.223372036854774784e+18 ){ + exp += 1; + dekkerMul2(rr, 1.0e-01, -5.5511151231257827021e-18); + } }else{ - v = (v<<11) | U64_BIT(63); - e -= 1086; + while( rr[0]<9.223372036854774784e-83 ){ + exp -= 100; + dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83); + } + while( rr[0]<9.223372036854774784e+07 ){ + exp -= 10; + dekkerMul2(rr, 1.0e+10, 0.0); + } + while( rr[0]<9.22337203685477478e+17 ){ + exp -= 1; + dekkerMul2(rr, 1.0e+01, 0.0); + } } - sqlite3Fp2Convert10(v, e, (iRound<=0||iRound>=18)?18:iRound+1, &v, &exp); + v = rr[1]<0.0 ? (u64)rr[0]-(u64)(-rr[1]) : (u64)rr[0]+(u64)rr[1]; - /* Extract significant digits, start at the right-most slot in p->zBuf - ** and working back to the right. "i" keeps track of the next slot in - ** which to store a digit. */ + /* Extract significant digits. */ i = sizeof(p->zBuf)-1; - zBuf = p->zBuf; assert( v>0 ); - while( v>=10 ){ - int kk = (v%100)*2; - assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); - assert( TWO_BYTE_ALIGNMENT(&zBuf[i-1]) ); - *(u16*)(&zBuf[i-1]) = *(u16*)&sqlite3DigitPairs.a[kk]; - i -= 2; - v /= 100; - } - if( v ){ - assert( v<10 ); - zBuf[i--] = v + '0'; - } + while( v ){ p->zBuf[i--] = (v%10) + '0'; v /= 10; } assert( i>=0 && izBuf)-1 ); - n = sizeof(p->zBuf) - 1 - i; /* Total number of digits extracted */ - assert( n>0 ); - assert( nzBuf) ); - testcase( n==sizeof(p->zBuf)-1 ); - p->iDP = n + exp; + p->n = sizeof(p->zBuf) - 1 - i; + assert( p->n>0 ); + assert( p->nzBuf) ); + p->iDP = p->n + exp; if( iRound<=0 ){ iRound = p->iDP - iRound; - if( iRound==0 && zBuf[i+1]>='5' ){ + if( iRound==0 && p->zBuf[i+1]>='5' ){ iRound = 1; - zBuf[i--] = '0'; - n++; + p->zBuf[i--] = '0'; + p->n++; p->iDP++; } } - z = &zBuf[i+1]; /* z points to the first digit */ - if( iRound>0 && (iRoundmxRound) ){ + if( iRound>0 && (iRoundn || p->n>mxRound) ){ + char *z = &p->zBuf[i+1]; if( iRound>mxRound ) iRound = mxRound; - if( iRound==17 ){ - /* If the precision is exactly 17, which only happens with the "!" - ** flag (ex: "%!.17g") then try to reduce the precision if that - ** yields text that will round-trip to the original floating-point. - ** value. Thus, for exaple, 49.47 will render as 49.47, rather than - ** as 49.469999999999999. */ - if( z[15]=='9' && z[14]=='9' ){ - int jj, kk; - u64 v2; - for(jj=14; jj>0 && z[jj-1]=='9'; jj--){} - if( jj==0 ){ - v2 = 1; - }else{ - v2 = z[0] - '0'; - for(kk=1; kkiDP>=n || (z[15]=='0' && z[14]=='0' && z[13]=='0') ){ - int jj, kk; - u64 v2; - assert( z[0]!='0' ); - for(jj=14; z[jj-1]=='0'; jj--){} - v2 = z[0] - '0'; - for(kk=1; kkn = iRound; if( z[iRound]>='5' ){ int j = iRound-1; while( 1 /*exit-by-break*/ ){ @@ -37344,9 +36985,8 @@ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRou if( z[j]<='9' ) break; z[j] = '0'; if( j==0 ){ - z--; - z[0] = '1'; - n++; + p->z[i--] = '1'; + p->n++; p->iDP++; break; }else{ @@ -37355,13 +36995,13 @@ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRou } } } - assert( n>0 ); - while( z[n-1]=='0' ){ - n--; - assert( n>0 ); + p->z = &p->zBuf[i+1]; + assert( i+p->n < sizeof(p->zBuf) ); + assert( p->n>0 ); + while( p->z[p->n-1]=='0' ){ + p->n--; + assert( p->n>0 ); } - p->n = n; - p->z = z; } /* @@ -38600,7 +38240,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ ** Debugging logic */ -/* SQLITE_KV_TRACE() is used for tracing calls to kvrecord routines. */ +/* SQLITE_KV_TRACE() is used for tracing calls to kvstorage routines. */ #if 0 #define SQLITE_KV_TRACE(X) printf X #else @@ -38614,6 +38254,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ #define SQLITE_KV_LOG(X) #endif + /* ** Forward declaration of objects used by this VFS implementation */ @@ -38621,11 +38262,6 @@ typedef struct KVVfsFile KVVfsFile; /* A single open file. There are only two files represented by this ** VFS - the database and the rollback journal. -** -** Maintenance reminder: if this struct changes in any way, the JSON -** rendering of its structure must be updated in -** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary -** compatibility concerns, so it does not need an iVersion member. */ struct KVVfsFile { sqlite3_file base; /* IO methods */ @@ -38675,7 +38311,7 @@ static int kvvfsCurrentTime(sqlite3_vfs*, double*); static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); static sqlite3_vfs sqlite3OsKvvfsObject = { - 2, /* iVersion */ + 1, /* iVersion */ sizeof(KVVfsFile), /* szOsFile */ 1024, /* mxPathname */ 0, /* pNext */ @@ -38751,37 +38387,23 @@ static sqlite3_io_methods kvvfs_jrnl_io_methods = { /* Forward declarations for the low-level storage engine */ -#ifndef SQLITE_WASM -/* In WASM builds these are implemented in JS. */ -static int kvrecordWrite(const char*, const char *zKey, const char *zData); -static int kvrecordDelete(const char*, const char *zKey); -static int kvrecordRead(const char*, const char *zKey, char *zBuf, int nBuf); -#endif -#ifndef KVRECORD_KEY_SZ -#define KVRECORD_KEY_SZ 32 -#endif +static int kvstorageWrite(const char*, const char *zKey, const char *zData); +static int kvstorageDelete(const char*, const char *zKey); +static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); +#define KVSTORAGE_KEY_SZ 32 /* Expand the key name with an appropriate prefix and put the result ** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least -** KVRECORD_KEY_SZ bytes. +** KVSTORAGE_KEY_SZ bytes. */ -static void kvrecordMakeKey( +static void kvstorageMakeKey( const char *zClass, const char *zKeyIn, char *zKeyOut ){ - assert( zKeyIn ); - assert( zKeyOut ); - assert( zClass ); - sqlite3_snprintf(KVRECORD_KEY_SZ, zKeyOut, "kvvfs-%s-%s", - zClass, zKeyIn); + sqlite3_snprintf(KVSTORAGE_KEY_SZ, zKeyOut, "kvvfs-%s-%s", zClass, zKeyIn); } -#ifndef SQLITE_WASM -/* In WASM builds do not define APIs which use fopen(), fwrite(), -** and the like because those APIs are a portability issue for -** WASM. -*/ /* Write content into a key. zClass is the particular namespace of the ** underlying key/value store to use - either "local" or "session". ** @@ -38789,14 +38411,14 @@ static void kvrecordMakeKey( ** ** Return the number of errors. */ -static int kvrecordWrite( +static int kvstorageWrite( const char *zClass, const char *zKey, const char *zData ){ FILE *fd; - char zXKey[KVRECORD_KEY_SZ]; - kvrecordMakeKey(zClass, zKey, zXKey); + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); fd = fopen(zXKey, "wb"); if( fd ){ SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey, @@ -38814,9 +38436,9 @@ static int kvrecordWrite( ** namespace given by zClass. If the key does not previously exist, ** this routine is a no-op. */ -static int kvrecordDelete(const char *zClass, const char *zKey){ - char zXKey[KVRECORD_KEY_SZ]; - kvrecordMakeKey(zClass, zKey, zXKey); +static int kvstorageDelete(const char *zClass, const char *zKey){ + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); unlink(zXKey); SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey)); return 0; @@ -38837,7 +38459,7 @@ static int kvrecordDelete(const char *zClass, const char *zKey){ ** zero-terminates zBuf at zBuf[0] and returns the size of the data ** without reading it. */ -static int kvrecordRead( +static int kvstorageRead( const char *zClass, const char *zKey, char *zBuf, @@ -38845,8 +38467,8 @@ static int kvrecordRead( ){ FILE *fd; struct stat buf; - char zXKey[KVRECORD_KEY_SZ]; - kvrecordMakeKey(zClass, zKey, zXKey); + char zXKey[KVSTORAGE_KEY_SZ]; + kvstorageMakeKey(zClass, zKey, zXKey); if( access(zXKey, R_OK)!=0 || stat(zXKey, &buf)!=0 || !S_ISREG(buf.st_mode) @@ -38878,8 +38500,6 @@ static int kvrecordRead( return (int)n; } } -#endif /* #ifndef SQLITE_WASM */ - /* ** An internal level of indirection which enables us to replace the @@ -38887,27 +38507,17 @@ static int kvrecordRead( ** Maintenance reminder: if this struct changes in any way, the JSON ** rendering of its structure must be updated in ** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary -** compatibility concerns, so it does not need an iVersion member. +** compatibility concerns, so it does not need an iVersion +** member. */ typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; struct sqlite3_kvvfs_methods { - int (*xRcrdRead)(const char*, const char *zKey, char *zBuf, int nBuf); - int (*xRcrdWrite)(const char*, const char *zKey, const char *zData); - int (*xRcrdDelete)(const char*, const char *zKey); + int (*xRead)(const char *zClass, const char *zKey, char *zBuf, int nBuf); + int (*xWrite)(const char *zClass, const char *zKey, const char *zData); + int (*xDelete)(const char *zClass, const char *zKey); const int nKeySize; - const int nBufferSize; -#ifndef SQLITE_WASM -# define MAYBE_CONST const -#else -# define MAYBE_CONST -#endif - MAYBE_CONST sqlite3_vfs * pVfs; - MAYBE_CONST sqlite3_io_methods *pIoDb; - MAYBE_CONST sqlite3_io_methods *pIoJrnl; -#undef MAYBE_CONST }; - /* ** This object holds the kvvfs I/O methods which may be swapped out ** for JavaScript-side implementations in WASM builds. In such builds @@ -38922,20 +38532,10 @@ struct sqlite3_kvvfs_methods { const #endif SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = { -#ifndef SQLITE_WASM - .xRcrdRead = kvrecordRead, - .xRcrdWrite = kvrecordWrite, - .xRcrdDelete = kvrecordDelete, -#else - .xRcrdRead = 0, - .xRcrdWrite = 0, - .xRcrdDelete = 0, -#endif - .nKeySize = KVRECORD_KEY_SZ, - .nBufferSize = SQLITE_KVOS_SZ, - .pVfs = &sqlite3OsKvvfsObject, - .pIoDb = &kvvfs_db_io_methods, - .pIoJrnl = &kvvfs_jrnl_io_methods +kvstorageRead, +kvstorageWrite, +kvstorageDelete, +KVSTORAGE_KEY_SZ }; /****** Utility subroutines ************************************************/ @@ -38962,10 +38562,7 @@ SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = { ** of hexadecimal and base-26 numbers, it is always clear where ** one stops and the next begins. */ -#ifndef SQLITE_WASM -static -#endif -int kvvfsEncode(const char *aData, int nData, char *aOut){ +static int kvvfsEncode(const char *aData, int nData, char *aOut){ int i, j; const unsigned char *a = (const unsigned char*)aData; for(i=j=0; izClass, "sz", zData, - sizeof(zData)-1); + sqlite3KvvfsMethods.xRead(pFile->zClass, "sz", zData, sizeof(zData)-1); return strtoll(zData, 0, 0); } static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ char zData[50]; sqlite3_snprintf(sizeof(zData), zData, "%lld", sz); - return sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "sz", zData); + return sqlite3KvvfsMethods.xWrite(pFile->zClass, "sz", zData); } /****** sqlite3_io_methods methods ******************************************/ @@ -39122,9 +38714,6 @@ static int kvvfsClose(sqlite3_file *pProtoFile){ pFile->isJournal ? "journal" : "db")); sqlite3_free(pFile->aJrnl); sqlite3_free(pFile->aData); -#ifdef SQLITE_WASM - memset(pFile, 0, sizeof(*pFile)); -#endif return SQLITE_OK; } @@ -39141,22 +38730,16 @@ static int kvvfsReadJrnl( assert( pFile->isJournal ); SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); if( pFile->aJrnl==0 ){ - int rc; - int szTxt = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", - 0, 0); + int szTxt = kvstorageRead(pFile->zClass, "jrnl", 0, 0); char *aTxt; if( szTxt<=4 ){ return SQLITE_IOERR; } aTxt = sqlite3_malloc64( szTxt+1 ); if( aTxt==0 ) return SQLITE_NOMEM; - rc = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", - aTxt, szTxt+1); - if( rc>=0 ){ - kvvfsDecodeJournal(pFile, aTxt, szTxt); - } + kvstorageRead(pFile->zClass, "jrnl", aTxt, szTxt+1); + kvvfsDecodeJournal(pFile, aTxt, szTxt); sqlite3_free(aTxt); - if( rc ) return rc; if( pFile->aJrnl==0 ) return SQLITE_IOERR; } if( iOfst+iAmt>pFile->nJrnl ){ @@ -39196,8 +38779,8 @@ static int kvvfsReadDb( pgno = 1; } sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); - got = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, zKey, - aData, SQLITE_KVOS_SZ-1); + got = sqlite3KvvfsMethods.xRead(pFile->zClass, zKey, + aData, SQLITE_KVOS_SZ-1); if( got<0 ){ n = 0; }else{ @@ -39265,7 +38848,6 @@ static int kvvfsWriteDb( unsigned int pgno; char zKey[30]; char *aData = pFile->aData; - int rc; SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); assert( iAmt>=512 && iAmt<=65536 ); assert( (iAmt & (iAmt-1))==0 ); @@ -39274,13 +38856,13 @@ static int kvvfsWriteDb( pgno = 1 + iOfst/iAmt; sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); kvvfsEncode(zBuf, iAmt, aData); - rc = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, zKey, aData); - if( 0==rc ){ - if( iOfst+iAmt > pFile->szDb ){ - pFile->szDb = iOfst + iAmt; - } + if( sqlite3KvvfsMethods.xWrite(pFile->zClass, zKey, aData) ){ + return SQLITE_IOERR; } - return rc; + if( iOfst+iAmt > pFile->szDb ){ + pFile->szDb = iOfst + iAmt; + } + return SQLITE_OK; } /* @@ -39290,7 +38872,7 @@ static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ KVVfsFile *pFile = (KVVfsFile *)pProtoFile; SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size)); assert( size==0 ); - sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, "jrnl"); + sqlite3KvvfsMethods.xDelete(pFile->zClass, "jrnl"); sqlite3_free(pFile->aJrnl); pFile->aJrnl = 0; pFile->nJrnl = 0; @@ -39309,7 +38891,7 @@ static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ pgnoMax = 2 + pFile->szDb/pFile->szPage; while( pgno<=pgnoMax ){ sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); - sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, zKey); + sqlite3KvvfsMethods.xDelete(pFile->zClass, zKey); pgno++; } pFile->szDb = size; @@ -39341,7 +38923,7 @@ static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){ }while( n>0 ); zOut[i++] = ' '; kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]); - i = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "jrnl", zOut); + i = sqlite3KvvfsMethods.xWrite(pFile->zClass, "jrnl", zOut); sqlite3_free(zOut); return i ? SQLITE_IOERR : SQLITE_OK; } @@ -39455,32 +39037,33 @@ static int kvvfsOpen( KVVfsFile *pFile = (KVVfsFile*)pProtoFile; if( zName==0 ) zName = ""; SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName)); - assert(!pFile->zClass); - assert(!pFile->aData); - assert(!pFile->aJrnl); - assert(!pFile->nJrnl); - assert(!pFile->base.pMethods); - pFile->szPage = -1; - pFile->szDb = -1; - if( 0==sqlite3_strglob("*-journal", zName) ){ + if( strcmp(zName, "local")==0 + || strcmp(zName, "session")==0 + ){ + pFile->isJournal = 0; + pFile->base.pMethods = &kvvfs_db_io_methods; + }else + if( strcmp(zName, "local-journal")==0 + || strcmp(zName, "session-journal")==0 + ){ pFile->isJournal = 1; pFile->base.pMethods = &kvvfs_jrnl_io_methods; - if( 0==strcmp("session-journal",zName) ){ - pFile->zClass = "session"; - }else if( 0==strcmp("local-journal",zName) ){ - pFile->zClass = "local"; - } }else{ - pFile->isJournal = 0; - pFile->base.pMethods = &kvvfs_db_io_methods; + return SQLITE_CANTOPEN; } - if( !pFile->zClass ){ - pFile->zClass = zName; + if( zName[0]=='s' ){ + pFile->zClass = "session"; + }else{ + pFile->zClass = "local"; } pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ); if( pFile->aData==0 ){ return SQLITE_NOMEM; } + pFile->aJrnl = 0; + pFile->nJrnl = 0; + pFile->szPage = -1; + pFile->szDb = -1; return SQLITE_OK; } @@ -39490,17 +39073,13 @@ static int kvvfsOpen( ** returning. */ static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ - int rc /* The JS impl can fail with OOM in argument conversion */; if( strcmp(zPath, "local-journal")==0 ){ - rc = sqlite3KvvfsMethods.xRcrdDelete("local", "jrnl"); + sqlite3KvvfsMethods.xDelete("local", "jrnl"); }else if( strcmp(zPath, "session-journal")==0 ){ - rc = sqlite3KvvfsMethods.xRcrdDelete("session", "jrnl"); + sqlite3KvvfsMethods.xDelete("session", "jrnl"); } - else{ - rc = 0; - } - return rc; + return SQLITE_OK; } /* @@ -39514,42 +39093,21 @@ static int kvvfsAccess( int *pResOut ){ SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath)); -#if 0 && defined(SQLITE_WASM) - /* - ** This is not having the desired effect in the JS bindings. - ** It's ostensibly the same logic as the #else block, but - ** it's not behaving that way. - ** - ** In JS we map all zPaths to Storage objects, and -journal files - ** are mapped to the storage for the main db (which is is exactly - ** what the mapping of "local-journal" -> "local" is doing). - */ - const char *zKey = (0==sqlite3_strglob("*-journal", zPath)) - ? "jrnl" : "sz"; - *pResOut = - sqlite3KvvfsMethods.xRcrdRead(zPath, zKey, 0, 0)>0; -#else if( strcmp(zPath, "local-journal")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("local", "jrnl", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("local", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "session-journal")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("session", "jrnl", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("session", "jrnl", 0, 0)>0; }else if( strcmp(zPath, "local")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("local", "sz", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("local", "sz", 0, 0)>0; }else if( strcmp(zPath, "session")==0 ){ - *pResOut = - sqlite3KvvfsMethods.xRcrdRead("session", "sz", 0, 0)>0; + *pResOut = sqlite3KvvfsMethods.xRead("session", "sz", 0, 0)>0; }else { *pResOut = 0; } - /*all current JS tests avoid triggering: assert( *pResOut == 0 ); */ -#endif SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut)); return SQLITE_OK; } @@ -44833,7 +44391,7 @@ static int unixShmMap( } /* Map the requested memory region into this processes address space. */ - apNew = (char **)sqlite3_realloc64( + apNew = (char **)sqlite3_realloc( pShmNode->apRegion, nReqRegion*sizeof(char *) ); if( !apNew ){ @@ -48278,7 +47836,7 @@ SQLITE_API int sqlite3_os_end(void){ ** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_ANSI) +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(SQLITE_WIN32_NO_ANSI) # define SQLITE_WIN32_HAS_ANSI #endif @@ -48286,7 +47844,7 @@ SQLITE_API int sqlite3_os_end(void){ ** Are most of the Win32 Unicode APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT) && \ +#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT || SQLITE_OS_WINRT) && \ !defined(SQLITE_WIN32_NO_WIDE) # define SQLITE_WIN32_HAS_WIDE #endif @@ -48425,7 +47983,16 @@ SQLITE_API int sqlite3_os_end(void){ */ #if SQLITE_WIN32_FILEMAPPING_API && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) +/* +** Two of the file mapping APIs are different under WinRT. Figure out which +** set we need. +*/ +#if SQLITE_OS_WINRT +WINBASEAPI HANDLE WINAPI CreateFileMappingFromApp(HANDLE, \ + LPSECURITY_ATTRIBUTES, ULONG, ULONG64, LPCWSTR); +WINBASEAPI LPVOID WINAPI MapViewOfFileFromApp(HANDLE, ULONG, ULONG64, SIZE_T); +#else #if defined(SQLITE_WIN32_HAS_ANSI) WINBASEAPI HANDLE WINAPI CreateFileMappingA(HANDLE, LPSECURITY_ATTRIBUTES, \ DWORD, DWORD, DWORD, LPCSTR); @@ -48437,6 +48004,7 @@ WINBASEAPI HANDLE WINAPI CreateFileMappingW(HANDLE, LPSECURITY_ATTRIBUTES, \ #endif /* defined(SQLITE_WIN32_HAS_WIDE) */ WINBASEAPI LPVOID WINAPI MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); +#endif /* SQLITE_OS_WINRT */ /* ** These file mapping APIs are common to both Win32 and WinRT. @@ -48727,7 +48295,7 @@ static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; ** This function is not available on Windows CE or WinRT. */ -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || SQLITE_OS_WINRT # define osAreFileApisANSI() 1 #endif @@ -48742,7 +48310,7 @@ static struct win_syscall { sqlite3_syscall_ptr pCurrent; /* Current value of the system call */ sqlite3_syscall_ptr pDefault; /* Default value */ } aSyscall[] = { -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "AreFileApisANSI", (SYSCALL)AreFileApisANSI, 0 }, #else { "AreFileApisANSI", (SYSCALL)0, 0 }, @@ -48781,7 +48349,7 @@ static struct win_syscall { #define osCreateFileA ((HANDLE(WINAPI*)(LPCSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[4].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "CreateFileW", (SYSCALL)CreateFileW, 0 }, #else { "CreateFileW", (SYSCALL)0, 0 }, @@ -48790,7 +48358,7 @@ static struct win_syscall { #define osCreateFileW ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) -#if defined(SQLITE_WIN32_HAS_ANSI) && \ +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) && \ SQLITE_WIN32_CREATEFILEMAPPINGA { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, @@ -48801,8 +48369,8 @@ static struct win_syscall { #define osCreateFileMappingA ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent) -#if (SQLITE_OS_WINCE || defined(SQLITE_WIN32_HAS_WIDE)) && \ - (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 }, #else { "CreateFileMappingW", (SYSCALL)0, 0 }, @@ -48811,7 +48379,7 @@ static struct win_syscall { #define osCreateFileMappingW ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCWSTR))aSyscall[7].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "CreateMutexW", (SYSCALL)CreateMutexW, 0 }, #else { "CreateMutexW", (SYSCALL)0, 0 }, @@ -48897,7 +48465,7 @@ static struct win_syscall { #define osGetDiskFreeSpaceA ((BOOL(WINAPI*)(LPCSTR,LPDWORD,LPDWORD,LPDWORD, \ LPDWORD))aSyscall[18].pCurrent) -#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetDiskFreeSpaceW", (SYSCALL)GetDiskFreeSpaceW, 0 }, #else { "GetDiskFreeSpaceW", (SYSCALL)0, 0 }, @@ -48914,7 +48482,7 @@ static struct win_syscall { #define osGetFileAttributesA ((DWORD(WINAPI*)(LPCSTR))aSyscall[20].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetFileAttributesW", (SYSCALL)GetFileAttributesW, 0 }, #else { "GetFileAttributesW", (SYSCALL)0, 0 }, @@ -48931,7 +48499,11 @@ static struct win_syscall { #define osGetFileAttributesExW ((BOOL(WINAPI*)(LPCWSTR,GET_FILEEX_INFO_LEVELS, \ LPVOID))aSyscall[22].pCurrent) +#if !SQLITE_OS_WINRT { "GetFileSize", (SYSCALL)GetFileSize, 0 }, +#else + { "GetFileSize", (SYSCALL)0, 0 }, +#endif #define osGetFileSize ((DWORD(WINAPI*)(HANDLE,LPDWORD))aSyscall[23].pCurrent) @@ -48944,7 +48516,7 @@ static struct win_syscall { #define osGetFullPathNameA ((DWORD(WINAPI*)(LPCSTR,DWORD,LPSTR, \ LPSTR*))aSyscall[24].pCurrent) -#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetFullPathNameW", (SYSCALL)GetFullPathNameW, 0 }, #else { "GetFullPathNameW", (SYSCALL)0, 0 }, @@ -48979,10 +48551,16 @@ static struct win_syscall { #define osGetProcAddressA ((FARPROC(WINAPI*)(HMODULE, \ LPCSTR))aSyscall[27].pCurrent) +#if !SQLITE_OS_WINRT { "GetSystemInfo", (SYSCALL)GetSystemInfo, 0 }, +#else + { "GetSystemInfo", (SYSCALL)0, 0 }, +#endif + #define osGetSystemInfo ((VOID(WINAPI*)(LPSYSTEM_INFO))aSyscall[28].pCurrent) { "GetSystemTime", (SYSCALL)GetSystemTime, 0 }, + #define osGetSystemTime ((VOID(WINAPI*)(LPSYSTEMTIME))aSyscall[29].pCurrent) #if !SQLITE_OS_WINCE @@ -49002,7 +48580,7 @@ static struct win_syscall { #define osGetTempPathA ((DWORD(WINAPI*)(DWORD,LPSTR))aSyscall[31].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) { "GetTempPathW", (SYSCALL)GetTempPathW, 0 }, #else { "GetTempPathW", (SYSCALL)0, 0 }, @@ -49010,7 +48588,11 @@ static struct win_syscall { #define osGetTempPathW ((DWORD(WINAPI*)(DWORD,LPWSTR))aSyscall[32].pCurrent) +#if !SQLITE_OS_WINRT { "GetTickCount", (SYSCALL)GetTickCount, 0 }, +#else + { "GetTickCount", (SYSCALL)0, 0 }, +#endif #define osGetTickCount ((DWORD(WINAPI*)(VOID))aSyscall[33].pCurrent) @@ -49023,7 +48605,7 @@ static struct win_syscall { #define osGetVersionExA ((BOOL(WINAPI*)( \ LPOSVERSIONINFOA))aSyscall[34].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) && \ +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ SQLITE_WIN32_GETVERSIONEX { "GetVersionExW", (SYSCALL)GetVersionExW, 0 }, #else @@ -49038,12 +48620,20 @@ static struct win_syscall { #define osHeapAlloc ((LPVOID(WINAPI*)(HANDLE,DWORD, \ SIZE_T))aSyscall[36].pCurrent) +#if !SQLITE_OS_WINRT { "HeapCreate", (SYSCALL)HeapCreate, 0 }, +#else + { "HeapCreate", (SYSCALL)0, 0 }, +#endif #define osHeapCreate ((HANDLE(WINAPI*)(DWORD,SIZE_T, \ SIZE_T))aSyscall[37].pCurrent) +#if !SQLITE_OS_WINRT { "HeapDestroy", (SYSCALL)HeapDestroy, 0 }, +#else + { "HeapDestroy", (SYSCALL)0, 0 }, +#endif #define osHeapDestroy ((BOOL(WINAPI*)(HANDLE))aSyscall[38].pCurrent) @@ -49061,12 +48651,16 @@ static struct win_syscall { #define osHeapSize ((SIZE_T(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[41].pCurrent) +#if !SQLITE_OS_WINRT { "HeapValidate", (SYSCALL)HeapValidate, 0 }, +#else + { "HeapValidate", (SYSCALL)0, 0 }, +#endif #define osHeapValidate ((BOOL(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[42].pCurrent) -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "HeapCompact", (SYSCALL)HeapCompact, 0 }, #else { "HeapCompact", (SYSCALL)0, 0 }, @@ -49082,7 +48676,7 @@ static struct win_syscall { #define osLoadLibraryA ((HMODULE(WINAPI*)(LPCSTR))aSyscall[44].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) && \ +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ !defined(SQLITE_OMIT_LOAD_EXTENSION) { "LoadLibraryW", (SYSCALL)LoadLibraryW, 0 }, #else @@ -49091,11 +48685,15 @@ static struct win_syscall { #define osLoadLibraryW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[45].pCurrent) +#if !SQLITE_OS_WINRT { "LocalFree", (SYSCALL)LocalFree, 0 }, +#else + { "LocalFree", (SYSCALL)0, 0 }, +#endif #define osLocalFree ((HLOCAL(WINAPI*)(HLOCAL))aSyscall[46].pCurrent) -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "LockFile", (SYSCALL)LockFile, 0 }, #else { "LockFile", (SYSCALL)0, 0 }, @@ -49117,7 +48715,8 @@ static struct win_syscall { LPOVERLAPPED))aSyscall[48].pCurrent) #endif -#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 }, #else { "MapViewOfFile", (SYSCALL)0, 0 }, @@ -49145,12 +48744,20 @@ static struct win_syscall { #define osSetEndOfFile ((BOOL(WINAPI*)(HANDLE))aSyscall[53].pCurrent) +#if !SQLITE_OS_WINRT { "SetFilePointer", (SYSCALL)SetFilePointer, 0 }, +#else + { "SetFilePointer", (SYSCALL)0, 0 }, +#endif #define osSetFilePointer ((DWORD(WINAPI*)(HANDLE,LONG,PLONG, \ DWORD))aSyscall[54].pCurrent) +#if !SQLITE_OS_WINRT { "Sleep", (SYSCALL)Sleep, 0 }, +#else + { "Sleep", (SYSCALL)0, 0 }, +#endif #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) @@ -49159,7 +48766,7 @@ static struct win_syscall { #define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \ LPFILETIME))aSyscall[56].pCurrent) -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT { "UnlockFile", (SYSCALL)UnlockFile, 0 }, #else { "UnlockFile", (SYSCALL)0, 0 }, @@ -49197,6 +48804,15 @@ static struct win_syscall { #define osWriteFile ((BOOL(WINAPI*)(HANDLE,LPCVOID,DWORD,LPDWORD, \ LPOVERLAPPED))aSyscall[61].pCurrent) +#if SQLITE_OS_WINRT + { "CreateEventExW", (SYSCALL)CreateEventExW, 0 }, +#else + { "CreateEventExW", (SYSCALL)0, 0 }, +#endif + +#define osCreateEventExW ((HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES,LPCWSTR, \ + DWORD,DWORD))aSyscall[62].pCurrent) + /* ** For WaitForSingleObject(), MSDN says: ** @@ -49206,7 +48822,7 @@ static struct win_syscall { { "WaitForSingleObject", (SYSCALL)WaitForSingleObject, 0 }, #define osWaitForSingleObject ((DWORD(WINAPI*)(HANDLE, \ - DWORD))aSyscall[62].pCurrent) + DWORD))aSyscall[63].pCurrent) #if !SQLITE_OS_WINCE { "WaitForSingleObjectEx", (SYSCALL)WaitForSingleObjectEx, 0 }, @@ -49215,12 +48831,69 @@ static struct win_syscall { #endif #define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ - BOOL))aSyscall[63].pCurrent) + BOOL))aSyscall[64].pCurrent) +#if SQLITE_OS_WINRT + { "SetFilePointerEx", (SYSCALL)SetFilePointerEx, 0 }, +#else + { "SetFilePointerEx", (SYSCALL)0, 0 }, +#endif + +#define osSetFilePointerEx ((BOOL(WINAPI*)(HANDLE,LARGE_INTEGER, \ + PLARGE_INTEGER,DWORD))aSyscall[65].pCurrent) + +#if SQLITE_OS_WINRT + { "GetFileInformationByHandleEx", (SYSCALL)GetFileInformationByHandleEx, 0 }, +#else + { "GetFileInformationByHandleEx", (SYSCALL)0, 0 }, +#endif + +#define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \ + FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent) + +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) + { "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 }, +#else + { "MapViewOfFileFromApp", (SYSCALL)0, 0 }, +#endif + +#define osMapViewOfFileFromApp ((LPVOID(WINAPI*)(HANDLE,ULONG,ULONG64, \ + SIZE_T))aSyscall[67].pCurrent) + +#if SQLITE_OS_WINRT + { "CreateFile2", (SYSCALL)CreateFile2, 0 }, +#else + { "CreateFile2", (SYSCALL)0, 0 }, +#endif + +#define osCreateFile2 ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD,DWORD, \ + LPCREATEFILE2_EXTENDED_PARAMETERS))aSyscall[68].pCurrent) + +#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_LOAD_EXTENSION) + { "LoadPackagedLibrary", (SYSCALL)LoadPackagedLibrary, 0 }, +#else + { "LoadPackagedLibrary", (SYSCALL)0, 0 }, +#endif + +#define osLoadPackagedLibrary ((HMODULE(WINAPI*)(LPCWSTR, \ + DWORD))aSyscall[69].pCurrent) + +#if SQLITE_OS_WINRT + { "GetTickCount64", (SYSCALL)GetTickCount64, 0 }, +#else + { "GetTickCount64", (SYSCALL)0, 0 }, +#endif + +#define osGetTickCount64 ((ULONGLONG(WINAPI*)(VOID))aSyscall[70].pCurrent) + +#if SQLITE_OS_WINRT { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, +#else + { "GetNativeSystemInfo", (SYSCALL)0, 0 }, +#endif #define osGetNativeSystemInfo ((VOID(WINAPI*)( \ - LPSYSTEM_INFO))aSyscall[64].pCurrent) + LPSYSTEM_INFO))aSyscall[71].pCurrent) #if defined(SQLITE_WIN32_HAS_ANSI) { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, @@ -49228,7 +48901,7 @@ static struct win_syscall { { "OutputDebugStringA", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[65].pCurrent) +#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[72].pCurrent) #if defined(SQLITE_WIN32_HAS_WIDE) { "OutputDebugStringW", (SYSCALL)OutputDebugStringW, 0 }, @@ -49236,11 +48909,20 @@ static struct win_syscall { { "OutputDebugStringW", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[66].pCurrent) +#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[73].pCurrent) { "GetProcessHeap", (SYSCALL)GetProcessHeap, 0 }, -#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[67].pCurrent) +#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent) + +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) + { "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 }, +#else + { "CreateFileMappingFromApp", (SYSCALL)0, 0 }, +#endif + +#define osCreateFileMappingFromApp ((HANDLE(WINAPI*)(HANDLE, \ + LPSECURITY_ATTRIBUTES,ULONG,ULONG64,LPCWSTR))aSyscall[75].pCurrent) /* ** NOTE: On some sub-platforms, the InterlockedCompareExchange "function" @@ -49255,25 +48937,25 @@ static struct win_syscall { { "InterlockedCompareExchange", (SYSCALL)InterlockedCompareExchange, 0 }, #define osInterlockedCompareExchange ((LONG(WINAPI*)(LONG \ - SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[68].pCurrent) + SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[76].pCurrent) #endif /* defined(InterlockedCompareExchange) */ -#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID { "UuidCreate", (SYSCALL)UuidCreate, 0 }, #else { "UuidCreate", (SYSCALL)0, 0 }, #endif -#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[69].pCurrent) +#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[77].pCurrent) -#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID { "UuidCreateSequential", (SYSCALL)UuidCreateSequential, 0 }, #else { "UuidCreateSequential", (SYSCALL)0, 0 }, #endif #define osUuidCreateSequential \ - ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[70].pCurrent) + ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[78].pCurrent) #if !defined(SQLITE_NO_SYNC) && SQLITE_MAX_MMAP_SIZE>0 { "FlushViewOfFile", (SYSCALL)FlushViewOfFile, 0 }, @@ -49282,7 +48964,7 @@ static struct win_syscall { #endif #define osFlushViewOfFile \ - ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[71].pCurrent) + ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[79].pCurrent) /* ** If SQLITE_ENABLE_SETLK_TIMEOUT is defined, we require CreateEvent() @@ -49299,7 +48981,7 @@ static struct win_syscall { #define osCreateEvent ( \ (HANDLE(WINAPI*) (LPSECURITY_ATTRIBUTES,BOOL,BOOL,LPCSTR)) \ - aSyscall[72].pCurrent \ + aSyscall[80].pCurrent \ ) /* @@ -49316,7 +48998,7 @@ static struct win_syscall { { "CancelIo", (SYSCALL)0, 0 }, #endif -#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[73].pCurrent) +#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent) #if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32) { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 }, @@ -49324,7 +49006,7 @@ static struct win_syscall { { "GetModuleHandleW", (SYSCALL)0, 0 }, #endif -#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[74].pCurrent) +#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent) #ifndef _WIN32 { "getenv", (SYSCALL)getenv, 0 }, @@ -49332,7 +49014,7 @@ static struct win_syscall { { "getenv", (SYSCALL)0, 0 }, #endif -#define osGetenv ((const char *(*)(const char *))aSyscall[75].pCurrent) +#define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent) #ifndef _WIN32 { "getcwd", (SYSCALL)getcwd, 0 }, @@ -49340,7 +49022,7 @@ static struct win_syscall { { "getcwd", (SYSCALL)0, 0 }, #endif -#define osGetcwd ((char*(*)(char*,size_t))aSyscall[76].pCurrent) +#define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent) #ifndef _WIN32 { "readlink", (SYSCALL)readlink, 0 }, @@ -49348,7 +49030,7 @@ static struct win_syscall { { "readlink", (SYSCALL)0, 0 }, #endif -#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[77].pCurrent) +#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent) #ifndef _WIN32 { "lstat", (SYSCALL)lstat, 0 }, @@ -49356,7 +49038,7 @@ static struct win_syscall { { "lstat", (SYSCALL)0, 0 }, #endif -#define osLstat ((int(*)(const char*,struct stat*))aSyscall[78].pCurrent) +#define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent) #ifndef _WIN32 { "__errno", (SYSCALL)__errno, 0 }, @@ -49364,7 +49046,7 @@ static struct win_syscall { { "__errno", (SYSCALL)0, 0 }, #endif -#define osErrno (*((int*(*)(void))aSyscall[79].pCurrent)()) +#define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)()) #ifndef _WIN32 { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 }, @@ -49373,7 +49055,7 @@ static struct win_syscall { #endif #define osCygwin_conv_path ((size_t(*)(unsigned int, \ - const void *, void *, size_t))aSyscall[80].pCurrent) + const void *, void *, size_t))aSyscall[88].pCurrent) }; /* End of the overrideable system calls */ @@ -49477,10 +49159,10 @@ SQLITE_API int sqlite3_win32_compact_heap(LPUINT pnLargest){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT if( (nLargest=osHeapCompact(hHeap, SQLITE_WIN32_HEAP_FLAGS))==0 ){ DWORD lastErrno = osGetLastError(); if( lastErrno==NO_ERROR ){ @@ -49593,11 +49275,28 @@ SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){ } #endif /* _WIN32 */ +/* +** The following routine suspends the current thread for at least ms +** milliseconds. This is equivalent to the Win32 Sleep() interface. +*/ +#if SQLITE_OS_WINRT +static HANDLE sleepObj = NULL; +#endif + SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds){ +#if SQLITE_OS_WINRT + if ( sleepObj==NULL ){ + sleepObj = osCreateEventExW(NULL, NULL, CREATE_EVENT_MANUAL_RESET, + SYNCHRONIZE); + } + assert( sleepObj!=NULL ); + osWaitForSingleObjectEx(sleepObj, milliseconds, FALSE); +#else osSleep(milliseconds); +#endif } -#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && \ +#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ SQLITE_THREADSAFE>0 SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ DWORD rc; @@ -49621,7 +49320,7 @@ SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ #if !SQLITE_WIN32_GETVERSIONEX # define osIsNT() (1) -#elif SQLITE_OS_WINCE || !defined(SQLITE_WIN32_HAS_ANSI) +#elif SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) # define osIsNT() (1) #elif !defined(SQLITE_WIN32_HAS_WIDE) # define osIsNT() (0) @@ -49634,7 +49333,13 @@ SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ ** based on the NT kernel. */ SQLITE_API int sqlite3_win32_is_nt(void){ -#if SQLITE_WIN32_GETVERSIONEX +#if SQLITE_OS_WINRT + /* + ** NOTE: The WinRT sub-platform is always assumed to be based on the NT + ** kernel. + */ + return 1; +#elif SQLITE_WIN32_GETVERSIONEX if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ #if defined(SQLITE_WIN32_HAS_ANSI) OSVERSIONINFOA sInfo; @@ -49676,7 +49381,7 @@ static void *winMemMalloc(int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif assert( nBytes>=0 ); @@ -49698,7 +49403,7 @@ static void winMemFree(void *pPrior){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif if( !pPrior ) return; /* Passing NULL to HeapFree is undefined. */ @@ -49719,7 +49424,7 @@ static void *winMemRealloc(void *pPrior, int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif assert( nBytes>=0 ); @@ -49747,7 +49452,7 @@ static int winMemSize(void *p){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, p) ); #endif if( !p ) return 0; @@ -49777,7 +49482,7 @@ static int winMemInit(void *pAppData){ assert( pWinMemData->magic1==WINMEM_MAGIC1 ); assert( pWinMemData->magic2==WINMEM_MAGIC2 ); -#if SQLITE_WIN32_HEAP_CREATE +#if !SQLITE_OS_WINRT && SQLITE_WIN32_HEAP_CREATE if( !pWinMemData->hHeap ){ DWORD dwInitialSize = SQLITE_WIN32_HEAP_INIT_SIZE; DWORD dwMaximumSize = (DWORD)sqlite3GlobalConfig.nHeap; @@ -49810,7 +49515,7 @@ static int winMemInit(void *pAppData){ #endif assert( pWinMemData->hHeap!=0 ); assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif return SQLITE_OK; @@ -49828,7 +49533,7 @@ static void winMemShutdown(void *pAppData){ if( pWinMemData->hHeap ){ assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif if( pWinMemData->bOwned ){ @@ -50209,6 +49914,17 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ char *zOut = 0; if( osIsNT() ){ +#if SQLITE_OS_WINRT + WCHAR zTempWide[SQLITE_WIN32_MAX_ERRMSG_CHARS+1]; + dwLen = osFormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + lastErrno, + 0, + zTempWide, + SQLITE_WIN32_MAX_ERRMSG_CHARS, + 0); +#else LPWSTR zTempWide = NULL; dwLen = osFormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | @@ -50219,13 +49935,16 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ (LPWSTR) &zTempWide, 0, 0); +#endif if( dwLen > 0 ){ /* allocate a buffer and convert to UTF8 */ sqlite3BeginBenignMalloc(); zOut = winUnicodeToUtf8(zTempWide); sqlite3EndBenignMalloc(); +#if !SQLITE_OS_WINRT /* free the system buffer allocated by FormatMessage */ osLocalFree(zTempWide); +#endif } } #ifdef SQLITE_WIN32_HAS_ANSI @@ -50886,6 +50605,7 @@ static int winHandleUnlock(HANDLE h, int iOff, int nByte){ static int winHandleSeek(HANDLE h, sqlite3_int64 iOffset){ int rc = SQLITE_OK; /* Return value */ +#if !SQLITE_OS_WINRT LONG upperBits; /* Most sig. 32 bits of new offset */ LONG lowerBits; /* Least sig. 32 bits of new offset */ DWORD dwRet; /* Value returned by SetFilePointer() */ @@ -50907,7 +50627,20 @@ static int winHandleSeek(HANDLE h, sqlite3_int64 iOffset){ rc = SQLITE_IOERR_SEEK; } } - OSTRACE(("SEEK file=%p, offset=%lld rc=%s\n", h, iOffset,sqlite3ErrName(rc))); +#else + /* This implementation works for WinRT. */ + LARGE_INTEGER x; /* The new offset */ + BOOL bRet; /* Value returned by SetFilePointerEx() */ + + x.QuadPart = iOffset; + bRet = osSetFilePointerEx(h, x, 0, FILE_BEGIN); + + if(!bRet){ + rc = SQLITE_IOERR_SEEK; + } +#endif + + OSTRACE(("SEEK file=%p, offset=%lld rc=%s\n", h, iOffset, sqlite3ErrName(rc))); return rc; } @@ -51208,6 +50941,17 @@ static int winHandleTruncate(HANDLE h, sqlite3_int64 nByte){ */ static int winHandleSize(HANDLE h, sqlite3_int64 *pnByte){ int rc = SQLITE_OK; + +#if SQLITE_OS_WINRT + FILE_STANDARD_INFO info; + BOOL b; + b = osGetFileInformationByHandleEx(h, FileStandardInfo, &info, sizeof(info)); + if( b ){ + *pnByte = info.EndOfFile.QuadPart; + }else{ + rc = SQLITE_IOERR_FSTAT; + } +#else DWORD upperBits = 0; DWORD lowerBits = 0; @@ -51217,6 +50961,8 @@ static int winHandleSize(HANDLE h, sqlite3_int64 *pnByte){ if( lowerBits==INVALID_FILE_SIZE && osGetLastError()!=NO_ERROR ){ rc = SQLITE_IOERR_FSTAT; } +#endif + return rc; } @@ -51415,6 +51161,20 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ assert( pSize!=0 ); SimulateIOError(return SQLITE_IOERR_FSTAT); OSTRACE(("SIZE file=%p, pSize=%p\n", pFile->h, pSize)); + +#if SQLITE_OS_WINRT + { + FILE_STANDARD_INFO info; + if( osGetFileInformationByHandleEx(pFile->h, FileStandardInfo, + &info, sizeof(info)) ){ + *pSize = info.EndOfFile.QuadPart; + }else{ + pFile->lastErrno = osGetLastError(); + rc = winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, + "winFileSize", pFile->zPath); + } + } +#else { DWORD upperBits; DWORD lowerBits; @@ -51429,6 +51189,7 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ "winFileSize", pFile->zPath); } } +#endif OSTRACE(("SIZE file=%p, pSize=%p, *pSize=%lld, rc=%s\n", pFile->h, pSize, *pSize, sqlite3ErrName(rc))); return rc; @@ -52390,6 +52151,20 @@ static int winHandleOpen( ** TODO: retry-on-ioerr. */ if( osIsNT() ){ +#if SQLITE_OS_WINRT + CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; + memset(&extendedParameters, 0, sizeof(extendedParameters)); + extendedParameters.dwSize = sizeof(extendedParameters); + extendedParameters.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + extendedParameters.dwFileFlags = flag_overlapped; + extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; + h = osCreateFile2((LPCWSTR)zConverted, + (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)),/* dwDesiredAccess */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ + OPEN_ALWAYS, /* dwCreationDisposition */ + &extendedParameters + ); +#else h = osCreateFileW((LPCWSTR)zConverted, /* lpFileName */ (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)), /* dwDesiredAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ @@ -52398,6 +52173,7 @@ static int winHandleOpen( FILE_ATTRIBUTE_NORMAL|flag_overlapped, NULL ); +#endif }else{ /* Due to pre-processor directives earlier in this file, ** SQLITE_WIN32_HAS_ANSI is always defined if osIsNT() is false. */ @@ -52865,7 +52641,9 @@ static int winShmMap( HANDLE hMap = NULL; /* file-mapping handle */ void *pMap = 0; /* Mapped memory region */ -#if defined(SQLITE_WIN32_HAS_WIDE) +#if SQLITE_OS_WINRT + hMap = osCreateFileMappingFromApp(hShared, NULL, protect, nByte, NULL); +#elif defined(SQLITE_WIN32_HAS_WIDE) hMap = osCreateFileMappingW(hShared, NULL, protect, 0, nByte, NULL); #elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA hMap = osCreateFileMappingA(hShared, NULL, protect, 0, nByte, NULL); @@ -52877,9 +52655,15 @@ static int winShmMap( if( hMap ){ int iOffset = pShmNode->nRegion*szRegion; int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; +#if SQLITE_OS_WINRT + pMap = osMapViewOfFileFromApp(hMap, flags, + iOffset - iOffsetShift, szRegion + iOffsetShift + ); +#else pMap = osMapViewOfFile(hMap, flags, 0, iOffset - iOffsetShift, szRegion + iOffsetShift ); +#endif OSTRACE(("SHM-MAP-MAP pid=%lu, region=%d, offset=%d, size=%d, rc=%s\n", osGetCurrentProcessId(), pShmNode->nRegion, iOffset, szRegion, pMap ? "ok" : "failed")); @@ -53012,7 +52796,9 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ flags |= FILE_MAP_WRITE; } #endif -#if defined(SQLITE_WIN32_HAS_WIDE) +#if SQLITE_OS_WINRT + pFd->hMap = osCreateFileMappingFromApp(pFd->h, NULL, protect, nMap, NULL); +#elif defined(SQLITE_WIN32_HAS_WIDE) pFd->hMap = osCreateFileMappingW(pFd->h, NULL, protect, (DWORD)((nMap>>32) & 0xffffffff), (DWORD)(nMap & 0xffffffff), NULL); @@ -53032,7 +52818,11 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ } assert( (nMap % winSysInfo.dwPageSize)==0 ); assert( sizeof(SIZE_T)==sizeof(sqlite3_int64) || nMap<=0xffffffff ); +#if SQLITE_OS_WINRT + pNew = osMapViewOfFileFromApp(pFd->hMap, flags, 0, (SIZE_T)nMap); +#else pNew = osMapViewOfFile(pFd->hMap, flags, 0, 0, (SIZE_T)nMap); +#endif if( pNew==NULL ){ osCloseHandle(pFd->hMap); pFd->hMap = NULL; @@ -53367,6 +53157,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ } #endif +#if !SQLITE_OS_WINRT && defined(_WIN32) else if( osIsNT() ){ char *zMulti; LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); @@ -53420,6 +53211,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ } } #endif /* SQLITE_WIN32_HAS_ANSI */ +#endif /* !SQLITE_OS_WINRT */ /* ** Check to make sure the temporary directory ends with an appropriate @@ -53594,6 +53386,13 @@ static int winOpen( memset(pFile, 0, sizeof(winFile)); pFile->h = INVALID_HANDLE_VALUE; +#if SQLITE_OS_WINRT + if( !zUtf8Name && !sqlite3_temp_directory ){ + sqlite3_log(SQLITE_ERROR, + "sqlite3_temp_directory variable should be set for WinRT"); + } +#endif + /* If the second argument to this function is NULL, generate a ** temporary file name to use */ @@ -53676,6 +53475,31 @@ static int winOpen( #endif if( osIsNT() ){ +#if SQLITE_OS_WINRT + CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; + extendedParameters.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS); + extendedParameters.dwFileAttributes = + dwFlagsAndAttributes & FILE_ATTRIBUTE_MASK; + extendedParameters.dwFileFlags = dwFlagsAndAttributes & FILE_FLAG_MASK; + extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; + extendedParameters.lpSecurityAttributes = NULL; + extendedParameters.hTemplateFile = NULL; + do{ + h = osCreateFile2((LPCWSTR)zConverted, + dwDesiredAccess, + dwShareMode, + dwCreationDisposition, + &extendedParameters); + if( h!=INVALID_HANDLE_VALUE ) break; + if( isReadWrite ){ + int rc2; + sqlite3BeginBenignMalloc(); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); + sqlite3EndBenignMalloc(); + if( rc2==SQLITE_OK && isRO ) break; + } + }while( winRetryIoerr(&cnt, &lastErrno) ); +#else do{ h = osCreateFileW((LPCWSTR)zConverted, dwDesiredAccess, @@ -53692,6 +53516,7 @@ static int winOpen( if( rc2==SQLITE_OK && isRO ) break; } }while( winRetryIoerr(&cnt, &lastErrno) ); +#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -53828,7 +53653,25 @@ static int winDelete( } if( osIsNT() ){ do { +#if SQLITE_OS_WINRT + WIN32_FILE_ATTRIBUTE_DATA sAttrData; + memset(&sAttrData, 0, sizeof(sAttrData)); + if ( osGetFileAttributesExW(zConverted, GetFileExInfoStandard, + &sAttrData) ){ + attr = sAttrData.dwFileAttributes; + }else{ + lastErrno = osGetLastError(); + if( lastErrno==ERROR_FILE_NOT_FOUND + || lastErrno==ERROR_PATH_NOT_FOUND ){ + rc = SQLITE_IOERR_DELETE_NOENT; /* Already gone? */ + }else{ + rc = SQLITE_ERROR; + } + break; + } +#else attr = osGetFileAttributesW(zConverted); +#endif if ( attr==INVALID_FILE_ATTRIBUTES ){ lastErrno = osGetLastError(); if( lastErrno==ERROR_FILE_NOT_FOUND @@ -53951,7 +53794,6 @@ static int winAccess( attr = sAttrData.dwFileAttributes; } }else{ - if( noRetry ) lastErrno = osGetLastError(); winLogIoerr(cnt, __LINE__); if( lastErrno!=ERROR_FILE_NOT_FOUND && lastErrno!=ERROR_PATH_NOT_FOUND ){ sqlite3_free(zConverted); @@ -54120,7 +53962,7 @@ static int winFullPathnameNoMutex( int nFull, /* Size of output buffer in bytes */ char *zFull /* Output buffer */ ){ -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT int nByte; void *zConverted; char *zOut; @@ -54209,7 +54051,7 @@ static int winFullPathnameNoMutex( } #endif /* __CYGWIN__ */ -#if SQLITE_OS_WINCE && defined(_WIN32) +#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32) SimulateIOError( return SQLITE_ERROR ); /* WinCE has no concept of a relative pathname, or so I am told. */ /* WinRT has no way to convert a relative path to an absolute one. */ @@ -54228,7 +54070,7 @@ static int winFullPathnameNoMutex( return SQLITE_OK; #endif -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT #if defined(_WIN32) /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this @@ -54360,7 +54202,11 @@ static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ return 0; } if( osIsNT() ){ +#if SQLITE_OS_WINRT + h = osLoadPackagedLibrary((LPCWSTR)zConverted, 0); +#else h = osLoadLibraryW((LPCWSTR)zConverted); +#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -54442,16 +54288,23 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ DWORD pid = osGetCurrentProcessId(); xorMemory(&e, (unsigned char*)&pid, sizeof(DWORD)); } +#if SQLITE_OS_WINRT + { + ULONGLONG cnt = osGetTickCount64(); + xorMemory(&e, (unsigned char*)&cnt, sizeof(ULONGLONG)); + } +#else { DWORD cnt = osGetTickCount(); xorMemory(&e, (unsigned char*)&cnt, sizeof(DWORD)); } +#endif /* SQLITE_OS_WINRT */ { LARGE_INTEGER i; osQueryPerformanceCounter(&i); xorMemory(&e, (unsigned char*)&i, sizeof(LARGE_INTEGER)); } -#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID { UUID id; memset(&id, 0, sizeof(UUID)); @@ -54461,7 +54314,7 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ osUuidCreateSequential(&id); xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); } -#endif /* !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID */ +#endif /* !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID */ return e.nXor>nBuf ? nBuf : e.nXor; #endif /* defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) */ } @@ -54692,16 +54545,15 @@ SQLITE_API int sqlite3_os_init(void){ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==81 ); - assert( strcmp(aSyscall[0].zName,"AreFileApisANSI")==0 ); - assert( strcmp(aSyscall[20].zName,"GetFileAttributesA")==0 ); - assert( strcmp(aSyscall[40].zName,"HeapReAlloc")==0 ); - assert( strcmp(aSyscall[60].zName,"WideCharToMultiByte")==0 ); - assert( strcmp(aSyscall[80].zName,"cygwin_conv_path")==0 ); + assert( ArraySize(aSyscall)==89 ); /* get memory map allocation granularity */ memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); +#if SQLITE_OS_WINRT + osGetNativeSystemInfo(&winSysInfo); +#else osGetSystemInfo(&winSysInfo); +#endif assert( winSysInfo.dwAllocationGranularity>0 ); assert( winSysInfo.dwPageSize>0 ); @@ -54725,9 +54577,17 @@ SQLITE_API int sqlite3_os_init(void){ } SQLITE_API int sqlite3_os_end(void){ +#if SQLITE_OS_WINRT + if( sleepObj!=NULL ){ + osCloseHandle(sleepObj); + sleepObj = NULL; + } +#endif + #ifndef SQLITE_OMIT_WAL winBigLock = 0; #endif + return SQLITE_OK; } @@ -59876,8 +59736,6 @@ SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); if( iRead ) return 0; /* Case (4) */ } -#else - UNUSED_PARAMETER(pgno); #endif assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 ); if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd) @@ -60298,17 +60156,17 @@ static int jrnlBufferSize(Pager *pPager){ */ #ifdef SQLITE_CHECK_PAGES /* -** Return a 64-bit hash of the page data for pPage. +** Return a 32-bit hash of the page data for pPage. */ -static u64 pager_datahash(int nByte, unsigned char *pData){ - u64 hash = 0; +static u32 pager_datahash(int nByte, unsigned char *pData){ + u32 hash = 0; int i; for(i=0; ipPager->pageSize, (unsigned char *)pPage->pData); } static void pager_set_pagehash(PgHdr *pPage){ @@ -63257,8 +63115,6 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a); pPager->pWal = 0; } -#else - UNUSED_PARAMETER(db); #endif pager_reset(pPager); if( MEMDB ){ @@ -76268,30 +76124,6 @@ static SQLITE_NOINLINE int btreeBeginTrans( } #endif -#ifdef SQLITE_EXPERIMENTAL_PRAGMA_20251114 - /* If both a read and write transaction will be opened by this call, - ** then issue a file-control as if the following pragma command had - ** been evaluated: - ** - ** PRAGMA experimental_pragma_20251114 = 1|2 - ** - ** where the RHS is "1" if wrflag is 1 (RESERVED lock), or "2" if wrflag - ** is 2 (EXCLUSIVE lock). Ignore any result or error returned by the VFS. - ** - ** WARNING: This code will likely remain part of SQLite only temporarily - - ** it exists to allow users to experiment with certain types of blocking - ** locks in custom VFS implementations. It MAY BE REMOVED AT ANY TIME. */ - if( pBt->pPage1==0 && wrflag ){ - sqlite3_file *fd = sqlite3PagerFile(pPager); - char *aFcntl[3] = {0,0,0}; - aFcntl[1] = "experimental_pragma_20251114"; - assert( wrflag==1 || wrflag==2 ); - aFcntl[2] = (wrflag==1 ? "1" : "2"); - sqlite3OsFileControlHint(fd, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); - sqlite3_free(aFcntl[0]); - } -#endif - /* Call lockBtree() until either pBt->pPage1 is populated or ** lockBtree() returns something other than SQLITE_OK. lockBtree() ** may return SQLITE_OK but leave pBt->pPage1 set to 0 if after @@ -78296,7 +78128,7 @@ SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - if( NEVER(pCur->eState==CURSOR_VALID) ){ + if( pCur->eState==CURSOR_VALID ){ *pRes = 0; return SQLITE_OK; } @@ -82377,7 +82209,7 @@ SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 }while( rc==SQLITE_OK && nOut>0 ); if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ - Pgno pgnoNew = 0; /* Prevent harmless static-analyzer warning */ + Pgno pgnoNew; MemPage *pNew = 0; rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); put4byte(pPgnoOut, pgnoNew); @@ -85043,27 +84875,21 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ StrAccum acc; assert( p->flags & (MEM_Int|MEM_Real|MEM_IntReal) ); assert( sz>22 ); - if( p->flags & (MEM_Int|MEM_IntReal) ){ -#if GCC_VERSION>=7000000 && GCC_VERSION<15000000 && defined(__i386__) - /* Work-around for GCC bug or bugs: - ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 - ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114659 - ** The problem appears to be fixed in GCC 15 */ + if( p->flags & MEM_Int ){ +#if GCC_VERSION>=7000000 + /* Work-around for GCC bug + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 */ i64 x; - assert( (MEM_Str&~p->flags)*4==sizeof(x) ); - memcpy(&x, (char*)&p->u, (MEM_Str&~p->flags)*4); + assert( (p->flags&MEM_Int)*2==sizeof(x) ); + memcpy(&x, (char*)&p->u, (p->flags&MEM_Int)*2); p->n = sqlite3Int64ToText(x, zBuf); #else p->n = sqlite3Int64ToText(p->u.i, zBuf); #endif - if( p->flags & MEM_IntReal ){ - memcpy(zBuf+p->n,".0", 3); - p->n += 2; - } }else{ sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); - sqlite3_str_appendf(&acc, "%!.*g", - (p->db ? p->db->nFpDigit : 17), p->u.r); + sqlite3_str_appendf(&acc, "%!.15g", + (p->flags & MEM_IntReal)!=0 ? (double)p->u.i : p->u.r); assert( acc.zText==zBuf && acc.mxAlloc<=0 ); zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ p->n = acc.nChar; @@ -85112,9 +84938,6 @@ SQLITE_PRIVATE int sqlite3VdbeMemValidStrRep(Mem *p){ assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 ); } if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1; - if( p->db==0 ){ - return 1; /* db->nFpDigit required to validate p->z[] */ - } memcpy(&tmp, p, sizeof(tmp)); vdbeMemRenderNum(sizeof(zBuf), zBuf, &tmp); z = p->z; @@ -85265,16 +85088,13 @@ SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ ** ** This is an optimization. Correct operation continues even if ** this routine is a no-op. -** -** Return true if the strig is zero-terminated after this routine is -** called and false if it is not. */ -SQLITE_PRIVATE int sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ +SQLITE_PRIVATE void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ if( (pMem->flags & (MEM_Str|MEM_Term|MEM_Ephem|MEM_Static))!=MEM_Str ){ /* pMem must be a string, and it cannot be an ephemeral or static string */ - return 0; + return; } - if( pMem->enc!=SQLITE_UTF8 ) return 0; + if( pMem->enc!=SQLITE_UTF8 ) return; assert( pMem->z!=0 ); if( pMem->flags & MEM_Dyn ){ if( pMem->xDel==sqlite3_free @@ -85282,19 +85102,18 @@ SQLITE_PRIVATE int sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; - return 1; + return; } if( pMem->xDel==sqlite3RCStrUnref ){ /* Blindly assume that all RCStr objects are zero-terminated */ pMem->flags |= MEM_Term; - return 1; + return; } }else if( pMem->szMalloc >= pMem->n+1 ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; - return 1; + return; } - return 0; } /* @@ -85592,70 +85411,18 @@ SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem *pMem){ } } -/* -** Invoke sqlite3AtoF() on the text value of pMem and return the -** double result. If sqlite3AtoF() returns an error code, write -** that code into *pRC if (*pRC)!=NULL. -** -** The caller must ensure that pMem->db!=0 and that pMem is in -** mode MEM_Str or MEM_Blob. -*/ -SQLITE_PRIVATE SQLITE_NOINLINE double sqlite3MemRealValueRC(Mem *pMem, int *pRC){ - double val = (double)0; - int rc = 0; - assert( pMem->db!=0 ); - assert( pMem->flags & (MEM_Str|MEM_Blob) ); - if( pMem->z==0 ){ - /* no-op */ - }else if( pMem->enc==SQLITE_UTF8 - && ((pMem->flags & MEM_Term)!=0 || sqlite3VdbeMemZeroTerminateIfAble(pMem)) - ){ - rc = sqlite3AtoF(pMem->z, &val); - }else if( pMem->n==0 ){ - /* no-op */ - }else if( pMem->enc==SQLITE_UTF8 ){ - char *zCopy = sqlite3DbStrNDup(pMem->db, pMem->z, pMem->n); - if( zCopy ){ - rc = sqlite3AtoF(zCopy, &val); - sqlite3DbFree(pMem->db, zCopy); - } - }else{ - int n, i, j; - char *zCopy; - const char *z; - - n = pMem->n & ~1; - zCopy = sqlite3DbMallocRaw(pMem->db, n/2 + 2); - if( zCopy ){ - z = pMem->z; - if( pMem->enc==SQLITE_UTF16LE ){ - for(i=j=0; idb, zCopy); - } - } - if( pRC ) *pRC = rc; - return val; -} - /* ** Return the best representation of pMem that we can get into a ** double. If pMem is already a double or an integer, return its ** value. If it is a string or blob, try to convert it to a double. ** If it is a NULL, return 0.0. */ +static SQLITE_NOINLINE double memRealValue(Mem *pMem){ + /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ + double val = (double)0; + sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); + return val; +} SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -85666,7 +85433,7 @@ SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ testcase( pMem->flags & MEM_IntReal ); return (double)pMem->u.i; }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ - return sqlite3MemRealValueRC(pMem, 0); + return memRealValue(pMem); }else{ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ return (double)0; @@ -85790,7 +85557,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){ sqlite3_int64 ix; assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - pMem->u.r = sqlite3MemRealValueRC(pMem, &rc); + rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); if( ((rc==0 || rc==1) && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1) || sqlite3RealSameAsInt(pMem->u.r, (ix = sqlite3RealToI64(pMem->u.r))) ){ @@ -86255,84 +86022,6 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( return SQLITE_OK; } -/* Like sqlite3VdbeMemSetStr() except: -** -** enc is always SQLITE_UTF8 -** pMem->db is always non-NULL -*/ -SQLITE_PRIVATE int sqlite3VdbeMemSetText( - Mem *pMem, /* Memory cell to set to string value */ - const char *z, /* String pointer */ - i64 n, /* Bytes in string, or negative */ - void (*xDel)(void*) /* Destructor function */ -){ - i64 nByte = n; /* New value for pMem->n */ - u16 flags; - - assert( pMem!=0 ); - assert( pMem->db!=0 ); - assert( sqlite3_mutex_held(pMem->db->mutex) ); - assert( !sqlite3VdbeMemIsRowSet(pMem) ); - - /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ - if( !z ){ - sqlite3VdbeMemSetNull(pMem); - return SQLITE_OK; - } - - if( nByte<0 ){ - nByte = strlen(z); - flags = MEM_Str|MEM_Term; - }else{ - flags = MEM_Str; - } - if( nByte>(i64)pMem->db->aLimit[SQLITE_LIMIT_LENGTH] ){ - if( xDel && xDel!=SQLITE_TRANSIENT ){ - if( xDel==SQLITE_DYNAMIC ){ - sqlite3DbFree(pMem->db, (void*)z); - }else{ - xDel((void*)z); - } - } - sqlite3VdbeMemSetNull(pMem); - return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); - } - - /* The following block sets the new values of Mem.z and Mem.xDel. It - ** also sets a flag in local variable "flags" to indicate the memory - ** management (one of MEM_Dyn or MEM_Static). - */ - if( xDel==SQLITE_TRANSIENT ){ - i64 nAlloc = nByte + 1; - testcase( nAlloc==31 ); - testcase( nAlloc==32 ); - if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ - return SQLITE_NOMEM_BKPT; - } - assert( pMem->z!=0 ); - memcpy(pMem->z, z, nByte); - pMem->z[nByte] = 0; - }else{ - sqlite3VdbeMemRelease(pMem); - pMem->z = (char *)z; - if( xDel==SQLITE_DYNAMIC ){ - pMem->zMalloc = pMem->z; - pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); - pMem->xDel = 0; - }else if( xDel==SQLITE_STATIC ){ - pMem->xDel = xDel; - flags |= MEM_Static; - }else{ - pMem->xDel = xDel; - flags |= MEM_Dyn; - } - } - pMem->flags = flags; - pMem->n = (int)(nByte & 0x7fffffff); - pMem->enc = SQLITE_UTF8; - return SQLITE_OK; -} - /* ** Move data out of a btree key or data field and into a Mem structure. ** The data is payload from the entry that pCur is currently pointing @@ -86761,7 +86450,7 @@ static int valueFromExpr( if( affinity==SQLITE_AFF_BLOB ){ if( op==TK_FLOAT ){ assert( pVal && pVal->z && pVal->flags==(MEM_Str|MEM_Term) ); - sqlite3AtoF(pVal->z, &pVal->u.r); + sqlite3AtoF(pVal->z, &pVal->u.r, pVal->n, SQLITE_UTF8); pVal->flags = MEM_Real; }else if( op==TK_INTEGER ){ /* This case is required by -9223372036854775808 and other strings @@ -87029,11 +86718,6 @@ SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr( ** ** If *ppVal is initially NULL then the caller is responsible for ** ensuring that the value written into *ppVal is eventually freed. -** -** If the buffer does not contain a well-formed record, this routine may -** read several bytes past the end of the buffer. Callers must therefore -** ensure that any buffer which may contain a corrupt record is padded -** with at least 8 bytes of addressable memory. */ SQLITE_PRIVATE int sqlite3Stat4Column( sqlite3 *db, /* Database handle */ @@ -90050,7 +89734,7 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName( } assert( p->aColName!=0 ); pColName = &(p->aColName[idx+var*p->nResAlloc]); - rc = sqlite3VdbeMemSetText(pColName, zName, -1, xDel); + rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; } @@ -93128,23 +92812,7 @@ static void setResultStrOrError( void (*xDel)(void*) /* Destructor function */ ){ Mem *pOut = pCtx->pOut; - int rc; - if( enc==SQLITE_UTF8 ){ - rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); - }else if( enc==SQLITE_UTF8_ZT ){ - /* It is usually considered improper to assert() on an input. However, - ** the following assert() is checking for inputs that are documented - ** to result in undefined behavior. */ - assert( z==0 - || n<0 - || n>pOut->db->aLimit[SQLITE_LIMIT_LENGTH] - || z[n]==0 - ); - rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); - pOut->flags |= MEM_Term; - }else{ - rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); - } + int rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); if( rc ){ if( rc==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(pCtx); @@ -93337,7 +93005,7 @@ SQLITE_API void sqlite3_result_text64( #endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); - if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ + if( enc!=SQLITE_UTF8 ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; n &= ~(u64)1; } @@ -93488,8 +93156,6 @@ static int doWalCallbacks(sqlite3 *db){ } } } -#else - UNUSED_PARAMETER(db); #endif return rc; } @@ -94446,25 +94112,13 @@ static int bindText( assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ if( zData!=0 ){ pVar = &p->aVar[i-1]; - if( encoding==SQLITE_UTF8 ){ - rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); - }else if( encoding==SQLITE_UTF8_ZT ){ - /* It is usually consider improper to assert() on an input. - ** However, the following assert() is checking for inputs - ** that are documented to result in undefined behavior. */ - assert( zData==0 - || nData<0 - || nData>pVar->db->aLimit[SQLITE_LIMIT_LENGTH] - || ((u8*)zData)[nData]==0 - ); - rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); - pVar->flags |= MEM_Term; - }else{ - rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); - if( encoding==0 ) pVar->enc = ENC(p->db); - } - if( rc==SQLITE_OK && encoding!=0 ){ - rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); + rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); + if( rc==SQLITE_OK ){ + if( encoding==0 ){ + pVar->enc = ENC(p->db); + }else{ + rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); + } } if( rc ){ sqlite3Error(p->db, rc); @@ -94576,7 +94230,7 @@ SQLITE_API int sqlite3_bind_text64( unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); - if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ + if( enc!=SQLITE_UTF8 ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; nData &= ~(u64)1; } @@ -95613,26 +95267,35 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( #ifndef SQLITE_HWTIME_H #define SQLITE_HWTIME_H -#if defined(_MSC_VER) && defined(_WIN32) - -/* #include "windows.h" */ - #include - - __inline sqlite3_uint64 sqlite3Hwtime(void){ - LARGE_INTEGER tm; - QueryPerformanceCounter(&tm); - return (sqlite3_uint64)tm.QuadPart; - } - -#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && \ +/* +** The following routine only works on Pentium-class (or newer) processors. +** It uses the RDTSC opcode to read the cycle count value out of the +** processor and returns that value. This can be used for high-res +** profiling. +*/ +#if !defined(__STRICT_ANSI__) && \ + (defined(__GNUC__) || defined(_MSC_VER)) && \ (defined(i386) || defined(__i386__) || defined(_M_IX86)) + #if defined(__GNUC__) + __inline__ sqlite_uint64 sqlite3Hwtime(void){ unsigned int lo, hi; __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); return (sqlite_uint64)hi << 32 | lo; } + #elif defined(_MSC_VER) + + __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ + __asm { + rdtsc + ret ; return value at EDX:EAX + } + } + + #endif + #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ @@ -95641,14 +95304,6 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( return (sqlite_uint64)hi << 32 | lo; } -#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && defined(__aarch64__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - sqlite3_uint64 cnt; - __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (cnt)); - return cnt; - } - #elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) __inline__ sqlite_uint64 sqlite3Hwtime(void){ @@ -96007,9 +95662,10 @@ static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){ */ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ double rValue; + u8 enc = pRec->enc; int rc; assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real|MEM_IntReal))==MEM_Str ); - rValue = sqlite3MemRealValueRC(pRec, &rc); + rc = sqlite3AtoF(pRec->z, &rValue, pRec->n, enc); if( rc<=0 ) return; if( rc==1 && alsoAnInt(pRec, rValue, &pRec->u.i) ){ pRec->flags |= MEM_Int; @@ -96091,10 +95747,7 @@ SQLITE_API int sqlite3_value_numeric_type(sqlite3_value *pVal){ int eType = sqlite3_value_type(pVal); if( eType==SQLITE_TEXT ){ Mem *pMem = (Mem*)pVal; - assert( pMem->db!=0 ); - sqlite3_mutex_enter(pMem->db->mutex); applyNumericAffinity(pMem, 0); - sqlite3_mutex_leave(pMem->db->mutex); eType = sqlite3_value_type(pVal); } return eType; @@ -96127,7 +95780,7 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ pMem->u.i = 0; return MEM_Int; } - pMem->u.r = sqlite3MemRealValueRC(pMem, &rc); + rc = sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); if( rc<=0 ){ if( rc==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){ pMem->u.i = ix; @@ -102276,15 +101929,20 @@ case OP_SorterInsert: { /* in2 */ break; } -/* Opcode: IdxDelete P1 P2 P3 * * +/* Opcode: IdxDelete P1 P2 P3 * P5 ** Synopsis: key=r[P2@P3] ** ** The content of P3 registers starting at register P2 form ** an unpacked index key. This opcode removes that entry from the ** index opened by cursor P1. ** -** Raise an SQLITE_CORRUPT_INDEX error if no matching index entry is found -** and not in writable_schema mode. +** If P5 is not zero, then raise an SQLITE_CORRUPT_INDEX error +** if no matching index entry is found. This happens when running +** an UPDATE or DELETE statement and the index entry to be updated +** or deleted is not found. For some uses of IdxDelete +** (example: the EXCEPT operator) it does not matter that no matching +** entry is found. For those cases, P5 is zero. Also, do not raise +** this (self-correcting and non-critical) error if in writable_schema mode. */ case OP_IdxDelete: { VdbeCursor *pC; @@ -102310,7 +101968,7 @@ case OP_IdxDelete: { if( res==0 ){ rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); if( rc ) goto abort_due_to_error; - }else if( !sqlite3WritableSchema(db) ){ + }else if( pOp->p5 && !sqlite3WritableSchema(db) ){ rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); goto abort_due_to_error; } @@ -110404,7 +110062,7 @@ static int exprProbability(Expr *p){ double r = -1.0; if( p->op!=TK_FLOAT ) return -1; assert( !ExprHasProperty(p, EP_IntValue) ); - sqlite3AtoF(p->u.zToken, &r); + sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); assert( r>=0.0 ); if( r>1.0 ) return -1; return (int)(r*134217728.0); @@ -111124,8 +110782,10 @@ static int resolveCompoundOrderBy( /* Convert the ORDER BY term into an integer column number iCol, ** taking care to preserve the COLLATE clause if it exists. */ if( !IN_RENAME_OBJECT ){ - Expr *pNew = sqlite3ExprInt32(db, iCol); + Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); if( pNew==0 ) return 1; + pNew->flags |= EP_IntValue; + pNew->u.iValue = iCol; if( pItem->pExpr==pE ){ pItem->pExpr = pNew; }else{ @@ -111479,6 +111139,10 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } #endif + /* The ORDER BY and GROUP BY clauses may not refer to terms in + ** outer queries + */ + sNC.pNext = 0; sNC.ncFlags |= NC_AllowAgg|NC_AllowWin; /* If this is a converted compound query, move the ORDER BY clause from @@ -112725,22 +112389,34 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( int dequote /* True to dequote */ ){ Expr *pNew; - int nExtra = pToken ? pToken->n+1 : 0; + int nExtra = 0; + int iValue = 0; assert( db!=0 ); + if( pToken ){ + if( op!=TK_INTEGER || pToken->z==0 + || sqlite3GetInt32(pToken->z, &iValue)==0 ){ + nExtra = pToken->n+1; /* tag-20240227-a */ + assert( iValue>=0 ); + } + } pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra); if( pNew ){ memset(pNew, 0, sizeof(Expr)); pNew->op = (u8)op; pNew->iAgg = -1; - if( nExtra ){ - assert( pToken!=0 ); - pNew->u.zToken = (char*)&pNew[1]; - assert( pToken->z!=0 || pToken->n==0 ); - if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); - pNew->u.zToken[pToken->n] = 0; - if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ - sqlite3DequoteExpr(pNew); + if( pToken ){ + if( nExtra==0 ){ + pNew->flags |= EP_IntValue|EP_Leaf|(iValue?EP_IsTrue:EP_IsFalse); + pNew->u.iValue = iValue; + }else{ + pNew->u.zToken = (char*)&pNew[1]; + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); + pNew->u.zToken[pToken->n] = 0; + if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ + sqlite3DequoteExpr(pNew); + } } } #if SQLITE_MAX_EXPR_DEPTH>0 @@ -112765,24 +112441,6 @@ SQLITE_PRIVATE Expr *sqlite3Expr( return sqlite3ExprAlloc(db, op, &x, 0); } -/* -** Allocate an expression for a 32-bit signed integer literal. -*/ -SQLITE_PRIVATE Expr *sqlite3ExprInt32(sqlite3 *db, int iVal){ - Expr *pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)); - if( pNew ){ - memset(pNew, 0, sizeof(Expr)); - pNew->op = TK_INTEGER; - pNew->iAgg = -1; - pNew->flags = EP_IntValue|EP_Leaf|(iVal?EP_IsTrue:EP_IsFalse); - pNew->u.iValue = iVal; -#if SQLITE_MAX_EXPR_DEPTH>0 - pNew->nHeight = 1; -#endif - } - return pNew; -} - /* ** Attach subtrees pLeft and pRight to the Expr node pRoot. ** @@ -112945,7 +112603,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ ){ sqlite3ExprDeferredDelete(pParse, pLeft); sqlite3ExprDeferredDelete(pParse, pRight); - return sqlite3ExprInt32(db, 0); + return sqlite3Expr(db, TK_INTEGER, "0"); }else{ return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); } @@ -113070,9 +112728,7 @@ SQLITE_PRIVATE void sqlite3ExprFunctionUsable( ){ assert( !IN_RENAME_OBJECT ); assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 ); - if( ExprHasProperty(pExpr, EP_FromDDL) - || pParse->prepFlags & SQLITE_PREPARE_FROM_DDL - ){ + if( ExprHasProperty(pExpr, EP_FromDDL) ){ if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0 || (pParse->db->flags & SQLITE_TrustedSchema)==0 ){ @@ -113768,7 +113424,9 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int fla pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); pNew->iLimit = 0; pNew->iOffset = 0; - pNew->selFlags = p->selFlags; + pNew->selFlags = p->selFlags & ~(u32)SF_UsesEphemeral; + pNew->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = p->nSelectRow; pNew->pWith = sqlite3WithDup(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -114420,7 +114078,7 @@ static int exprIsConst(Parse *pParse, Expr *p, int initFlag){ /* ** Walk an expression tree. Return non-zero if the expression is constant -** or return zero if the expression involves variables or function calls. +** and 0 if it involves variables or function calls. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is @@ -115210,7 +114868,6 @@ SQLITE_PRIVATE int sqlite3FindInIndex( */ u32 savedNQueryLoop = pParse->nQueryLoop; int rMayHaveNull = 0; - int bloomOk = (inFlags & IN_INDEX_MEMBERSHIP)!=0; eType = IN_INDEX_EPH; if( inFlags & IN_INDEX_LOOP ){ pParse->nQueryLoop = 0; @@ -115218,13 +114875,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex( *prRhsHasNull = rMayHaveNull = ++pParse->nMem; } assert( pX->op==TK_IN ); - if( !bloomOk - && ExprUseXSelect(pX) - && (pX->x.pSelect->selFlags & SF_ClonedRhsIn)!=0 - ){ - bloomOk = 1; - } - sqlite3CodeRhsOfIN(pParse, pX, iTab, bloomOk); + sqlite3CodeRhsOfIN(pParse, pX, iTab); if( rMayHaveNull ){ sqlite3SetHasNullFlag(v, iTab, rMayHaveNull); } @@ -115382,8 +115033,7 @@ static int findCompatibleInRhsSubrtn( SQLITE_PRIVATE void sqlite3CodeRhsOfIN( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The IN operator */ - int iTab, /* Use this cursor number */ - int allowBloom /* True to allow the use of a Bloom filter */ + int iTab /* Use this cursor number */ ){ int addrOnce = 0; /* Address of the OP_Once instruction at top */ int addr; /* Address of OP_OpenEphemeral instruction */ @@ -115505,10 +115155,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( sqlite3SelectDestInit(&dest, SRT_Set, iTab); dest.zAffSdst = exprINAffinity(pParse, pExpr); pSelect->iLimit = 0; - if( addrOnce - && allowBloom - && OptimizationEnabled(pParse->db, SQLITE_BloomFilter) - ){ + if( addrOnce && OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ){ int regBloom = ++pParse->nMem; addrBloom = sqlite3VdbeAddOp2(v, OP_Blob, 10000, regBloom); VdbeComment((v, "Bloom filter")); @@ -115729,7 +115376,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ || (pLeft->u.iValue!=1 && pLeft->u.iValue!=0) ){ sqlite3 *db = pParse->db; - pLimit = sqlite3ExprInt32(db, 0); + pLimit = sqlite3Expr(db, TK_INTEGER, "0"); if( pLimit ){ pLimit->affExpr = SQLITE_AFF_NUMERIC; pLimit = sqlite3PExpr(pParse, TK_NE, @@ -115740,7 +115387,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ } }else{ /* If there is no pre-existing limit add a limit of 1 */ - pLimit = sqlite3ExprInt32(pParse->db, 1); + pLimit = sqlite3Expr(pParse->db, TK_INTEGER, "1"); pSel->pLimit = sqlite3PExpr(pParse, TK_LIMIT, pLimit, 0); } pSel->iLimit = 0; @@ -116001,9 +115648,8 @@ static void sqlite3ExprCodeIN( if( ExprHasProperty(pExpr, EP_Subrtn) ){ const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); assert( pOp->opcode==OP_Once || pParse->nErr ); - if( pOp->p3>0 ){ /* tag-202407032019 */ - assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) - || pParse->nErr ); + if( pOp->opcode==OP_Once && pOp->p3>0 ){ /* tag-202407032019 */ + assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ); sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse, rLhs, nVector); VdbeCoverage(v); } @@ -116093,7 +115739,7 @@ static void sqlite3ExprCodeIN( static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){ if( ALWAYS(z!=0) ){ double value; - sqlite3AtoF(z, &value); + sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */ if( negateFlag ) value = -value; sqlite3VdbeAddOp4Dup8(v, OP_Real, 0, iMem, 0, (u8*)&value, P4_REAL); @@ -119194,10 +118840,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ if( pIEpr==0 ) break; if( NEVER(!ExprUseYTab(pExpr)) ) break; for(i=0; inSrc; i++){ - if( pSrcList->a[i].iCursor==pIEpr->iDataCur ){ - testcase( i>0 ); - break; - } + if( pSrcList->a[0].iCursor==pIEpr->iDataCur ) break; } if( i>=pSrcList->nSrc ) break; if( NEVER(pExpr->pAggInfo!=0) ) break; /* Resolved by outer context */ @@ -119986,7 +119629,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ /* Look up the table being altered. */ assert( pParse->pNewTable==0 ); assert( sqlite3BtreeHoldsAllMutexes(db) ); - if( NEVER(db->mallocFailed) ) goto exit_begin_add_column; + if( db->mallocFailed ) goto exit_begin_add_column; pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_begin_add_column; @@ -120058,7 +119701,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ ** Or, if pTab is not a view or virtual table, zero is returned. */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) -static int isRealTable(Parse *pParse, Table *pTab, int iOp){ +static int isRealTable(Parse *pParse, Table *pTab, int bDrop){ const char *zType = 0; #ifndef SQLITE_OMIT_VIEW if( IsView(pTab) ){ @@ -120071,12 +119714,9 @@ static int isRealTable(Parse *pParse, Table *pTab, int iOp){ } #endif if( zType ){ - const char *azMsg[] = { - "rename columns of", "drop column from", "edit constraints of" - }; - assert( iOp>=0 && iOpzName + (bDrop ? "drop column from" : "rename columns of"), + zType, pTab->zName ); return 1; } @@ -120547,25 +120187,6 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ return pBest; } -/* -** Set the error message of the context passed as the first argument to -** the result of formatting zFmt using printf() style formatting. -*/ -static void errorMPrintf(sqlite3_context *pCtx, const char *zFmt, ...){ - sqlite3 *db = sqlite3_context_db_handle(pCtx); - char *zErr = 0; - va_list ap; - va_start(ap, zFmt); - zErr = sqlite3VMPrintf(db, zFmt, ap); - va_end(ap); - if( zErr ){ - sqlite3_result_error(pCtx, zErr, -1); - sqlite3DbFree(db, zErr); - }else{ - sqlite3_result_error_nomem(pCtx); - } -} - /* ** An error occurred while parsing or otherwise processing a database ** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an @@ -120863,8 +120484,8 @@ static int renameResolveTrigger(Parse *pParse){ sqlite3SelectPrep(pParse, pStep->pSelect, &sNC); if( pParse->nErr ) rc = pParse->rc; } - if( rc==SQLITE_OK && pStep->pSrc ){ - SrcList *pSrc = sqlite3SrcListDup(db, pStep->pSrc, 0); + if( rc==SQLITE_OK && pStep->zTarget ){ + SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep); if( pSrc ){ Select *pSel = sqlite3SelectNew( pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 @@ -120892,10 +120513,10 @@ static int renameResolveTrigger(Parse *pParse){ pSel->pSrc = 0; sqlite3SelectDelete(db, pSel); } - if( ALWAYS(pStep->pSrc) ){ + if( pStep->pFrom ){ int i; - for(i=0; ipSrc->nSrc && rc==SQLITE_OK; i++){ - SrcItem *p = &pStep->pSrc->a[i]; + for(i=0; ipFrom->nSrc && rc==SQLITE_OK; i++){ + SrcItem *p = &pStep->pFrom->a[i]; if( p->fg.isSubquery ){ assert( p->u4.pSubq!=0 ); sqlite3SelectPrep(pParse, p->u4.pSubq->pSelect, 0); @@ -120964,13 +120585,13 @@ static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){ sqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere); sqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere); } - if( pStep->pSrc ){ + if( pStep->pFrom ){ int i; - SrcList *pSrc = pStep->pSrc; - for(i=0; inSrc; i++){ - if( pSrc->a[i].fg.isSubquery ){ - assert( pSrc->a[i].u4.pSubq!=0 ); - sqlite3WalkSelect(pWalker, pSrc->a[i].u4.pSubq->pSelect); + SrcList *pFrom = pStep->pFrom; + for(i=0; inSrc; i++){ + if( pFrom->a[i].fg.isSubquery ){ + assert( pFrom->a[i].u4.pSubq!=0 ); + sqlite3WalkSelect(pWalker, pFrom->a[i].u4.pSubq->pSelect); } } } @@ -121141,8 +120762,8 @@ static void renameColumnFunc( if( rc!=SQLITE_OK ) goto renameColumnFunc_done; for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->pSrc ){ - Table *pTarget = sqlite3LocateTableItem(&sParse, 0, &pStep->pSrc->a[0]); + if( pStep->zTarget ){ + Table *pTarget = sqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb); if( pTarget==pTab ){ if( pStep->pUpsert ){ ExprList *pUpsertSet = pStep->pUpsert->pUpsertSet; @@ -121154,6 +120775,7 @@ static void renameColumnFunc( } } + /* Find tokens to edit in UPDATE OF clause */ if( sParse.pTriggerTab==pTab ){ renameColumnIdlistNames(&sParse, &sCtx,sParse.pNewTrigger->pColumns,zOld); @@ -121355,10 +120977,13 @@ static void renameTableFunc( if( rc==SQLITE_OK ){ renameWalkTrigger(&sWalker, pTrigger); for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->pSrc ){ + if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ + renameTokenFind(&sParse, &sCtx, pStep->zTarget); + } + if( pStep->pFrom ){ int i; - for(i=0; ipSrc->nSrc; i++){ - SrcItem *pItem = &pStep->pSrc->a[i]; + for(i=0; ipFrom->nSrc; i++){ + SrcItem *pItem = &pStep->pFrom->a[i]; if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ renameTokenFind(&sParse, &sCtx, pItem->zName); } @@ -121605,57 +121230,6 @@ static void renameTableTest( #endif } - -/* -** Return the number of bytes until the end of the next non-whitespace and -** non-comment token. For the purpose of this function, a "(" token includes -** all of the bytes through and including the matching ")", or until the -** first illegal token, whichever comes first. -** -** Write the token type into *piToken. -** -** The value returned is the number of bytes in the token itself plus -** the number of bytes of leading whitespace and comments skipped plus -** all bytes through the next matching ")" if the token is TK_LP. -** -** Example: (Note: '.' used in place of '*' in the example z[] text) -** -** ,--------- *piToken := TK_RP -** v -** z[] = " /.comment./ --comment\n (two three four) five" -** | | -** |<-------------------------------------->| -** | -** `--- return value -*/ -static int getConstraintToken(const u8 *z, int *piToken){ - int iOff = 0; - int t = 0; - do { - iOff += sqlite3GetToken(&z[iOff], &t); - }while( t==TK_SPACE || t==TK_COMMENT ); - - *piToken = t; - - if( t==TK_LP ){ - int nNest = 1; - while( nNest>0 ){ - iOff += sqlite3GetToken(&z[iOff], &t); - if( t==TK_LP ){ - nNest++; - }else if( t==TK_RP ){ - t = TK_LP; - nNest--; - }else if( t==TK_ILLEGAL ){ - break; - } - } - } - - *piToken = t; - return iOff; -} - /* ** The implementation of internal UDF sqlite_drop_column(). ** @@ -121700,24 +121274,15 @@ static void dropColumnFunc( goto drop_column_done; } + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); if( iColnCol-1 ){ RenameToken *pEnd; - pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); zEnd = (const char*)pEnd->t.z; }else{ - int eTok; assert( IsOrdinaryTable(pTab) ); - assert( iCol!=0 ); - /* Point pCol->t.z at the "," immediately preceding the definition of - ** the column being dropped. To do this, start at the name of the - ** previous column, and tokenize until the next ",". */ - pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol-1].zCnName); - do { - pCol->t.z += getConstraintToken((const u8*)pCol->t.z, &eTok); - }while( eTok!=TK_COMMA ); - pCol->t.z--; zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; + while( ALWAYS(pCol->t.z[0]!=0) && pCol->t.z[0]!=',' ) pCol->t.z--; } zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd); @@ -121886,651 +121451,6 @@ SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const T sqlite3SrcListDelete(db, pSrc); } -/* -** Return the number of bytes of leading whitespace/comments in string z[]. -*/ -static int getWhitespace(const u8 *z){ - int nRet = 0; - while( 1 ){ - int t = 0; - int n = sqlite3GetToken(&z[nRet], &t); - if( t!=TK_SPACE && t!=TK_COMMENT ) break; - nRet += n; - } - return nRet; -} - - -/* -** Argument z points into the body of a constraint - specifically the -** second token of the constraint definition. For a named constraint, -** z points to the first token past the CONSTRAINT keyword. For an -** unnamed NOT NULL constraint, z points to the first byte past the NOT -** keyword. -** -** Return the number of bytes until the end of the constraint. -*/ -static int getConstraint(const u8 *z){ - int iOff = 0; - int t = 0; - - /* Now, the current constraint proceeds until the next occurence of one - ** of the following tokens: - ** - ** CONSTRAINT, PRIMARY, NOT, UNIQUE, CHECK, DEFAULT, - ** COLLATE, REFERENCES, FOREIGN, GENERATED, AS, RP, or COMMA - ** - ** Also exit the loop if ILLEGAL turns up. - */ - while( 1 ){ - int n = getConstraintToken(&z[iOff], &t); - if( t==TK_CONSTRAINT || t==TK_PRIMARY || t==TK_NOT || t==TK_UNIQUE - || t==TK_CHECK || t==TK_DEFAULT || t==TK_COLLATE || t==TK_REFERENCES - || t==TK_FOREIGN || t==TK_RP || t==TK_COMMA || t==TK_ILLEGAL - || t==TK_AS || t==TK_GENERATED - ){ - break; - } - iOff += n; - } - - return iOff; -} - -/* -** Compare two constraint names. -** -** Summary: *pRes := zQuote != zCmp -** -** Details: -** Compare the (possibly quoted) constraint name zQuote[0..nQuote-1] -** against zCmp[]. Write zero into *pRes if they are the same and -** non-zero if they differ. Normally return SQLITE_OK, except if there -** is an OOM, set the OOM error condition on ctx and return SQLITE_NOMEM. -*/ -static int quotedCompare( - sqlite3_context *ctx, /* Function context on which to report errors */ - int t, /* Token type */ - const u8 *zQuote, /* Possibly quoted text. Not zero-terminated. */ - int nQuote, /* Length of zQuote in bytes */ - const u8 *zCmp, /* Zero-terminated, unquoted name to compare against */ - int *pRes /* OUT: Set to 0 if equal, non-zero if unequal */ -){ - char *zCopy = 0; /* De-quoted, zero-terminated copy of zQuote[] */ - - if( t==TK_ILLEGAL ){ - *pRes = 1; - return SQLITE_OK; - } - zCopy = sqlite3MallocZero(nQuote+1); - if( zCopy==0 ){ - sqlite3_result_error_nomem(ctx); - return SQLITE_NOMEM_BKPT; - } - memcpy(zCopy, zQuote, nQuote); - sqlite3Dequote(zCopy); - *pRes = sqlite3_stricmp((const char*)zCopy, (const char*)zCmp); - sqlite3_free(zCopy); - return SQLITE_OK; -} - -/* -** zSql[] is a CREATE TABLE statement, supposedly. Find the offset -** into zSql[] of the first character past the first "(" and write -** that offset into *piOff and return SQLITE_OK. Or, if not found, -** set the SQLITE_CORRUPT error code and return SQLITE_ERROR. -*/ -static int skipCreateTable(sqlite3_context *ctx, const u8 *zSql, int *piOff){ - int iOff = 0; - - if( zSql==0 ) return SQLITE_ERROR; - - /* Jump past the "CREATE TABLE" bit. */ - while( 1 ){ - int t = 0; - iOff += sqlite3GetToken(&zSql[iOff], &t); - if( t==TK_LP ) break; - if( t==TK_ILLEGAL ){ - sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); - return SQLITE_ERROR; - } - } - - *piOff = iOff; - return SQLITE_OK; -} - -/* -** Internal SQL function sqlite3_drop_constraint(): Given an input -** CREATE TABLE statement, return a revised CREATE TABLE statement -** with a constraint removed. Two forms, depending on the datatype -** of argv[2]: -** -** sqlite_drop_constraint(SQL, INT) -- Omit NOT NULL from the INT-th column -** sqlite_drop_constraint(SQL, TEXT) -- OMIT constraint with name TEXT -** -** In the first case, the left-most column is 0. -*/ -static void dropConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const u8 *zSql = sqlite3_value_text(argv[0]); - const u8 *zCons = 0; - int iNotNull = -1; - int ii; - int iOff = 0; - int iStart = 0; - int iEnd = 0; - char *zNew = 0; - int t = 0; - sqlite3 *db; - UNUSED_PARAMETER(NotUsed); - - if( zSql==0 ) return; - - /* Jump past the "CREATE TABLE" bit. */ - if( skipCreateTable(ctx, zSql, &iOff) ) return; - - if( sqlite3_value_type(argv[1])==SQLITE_INTEGER ){ - iNotNull = sqlite3_value_int(argv[1]); - }else{ - zCons = sqlite3_value_text(argv[1]); - } - - /* Search for the named constraint within column definitions. */ - for(ii=0; iEnd==0; ii++){ - - /* Now parse the column or table constraint definition. Search - ** for the token CONSTRAINT if this is a DROP CONSTRAINT command, or - ** NOT in the right column if this is a DROP NOT NULL. */ - while( 1 ){ - iStart = iOff; - iOff += getConstraintToken(&zSql[iOff], &t); - if( t==TK_CONSTRAINT && (zCons || iNotNull==ii) ){ - /* Check if this is the constraint we are searching for. */ - int nTok = 0; - int cmp = 1; - - /* Skip past any whitespace. */ - iOff += getWhitespace(&zSql[iOff]); - - /* Compare the next token - which may be quoted - with the name of - ** the constraint being dropped. */ - nTok = getConstraintToken(&zSql[iOff], &t); - if( zCons ){ - if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; - } - iOff += nTok; - - /* The next token is usually the first token of the constraint - ** definition. This is enough to tell the type of the constraint - - ** TK_NOT means it is a NOT NULL, TK_CHECK a CHECK constraint etc. - ** - ** There is also the chance that the next token is TK_CONSTRAINT - ** (or TK_DEFAULT or TK_COLLATE), for example if a table has been - ** created as follows: - ** - ** CREATE TABLE t1(cols, CONSTRAINT one CONSTRAINT two NOT NULL); - ** - ** In this case, allow the "CONSTRAINT one" bit to be dropped by - ** this command if that is what is requested, or to advance to - ** the next iteration of the loop with &zSql[iOff] still pointing - ** to the CONSTRAINT keyword. */ - nTok = getConstraintToken(&zSql[iOff], &t); - if( t==TK_CONSTRAINT || t==TK_DEFAULT || t==TK_COLLATE - || t==TK_COMMA || t==TK_RP || t==TK_GENERATED || t==TK_AS - ){ - t = TK_CHECK; - }else{ - iOff += nTok; - iOff += getConstraint(&zSql[iOff]); - } - - if( cmp==0 || (iNotNull>=0 && t==TK_NOT) ){ - if( t!=TK_NOT && t!=TK_CHECK ){ - errorMPrintf(ctx, "constraint may not be dropped: %s", zCons); - return; - } - iEnd = iOff; - break; - } - - }else if( t==TK_NOT && iNotNull==ii ){ - iEnd = iOff + getConstraint(&zSql[iOff]); - break; - }else if( t==TK_RP || t==TK_ILLEGAL ){ - iEnd = -1; - break; - }else if( t==TK_COMMA ){ - break; - } - } - } - - /* If the constraint has not been found it is an error. */ - if( iEnd<=0 ){ - if( zCons ){ - errorMPrintf(ctx, "no such constraint: %s", zCons); - }else{ - /* SQLite follows postgres in that a DROP NOT NULL on a column that is - ** not NOT NULL is not an error. So just return the original SQL here. */ - sqlite3_result_text(ctx, (const char*)zSql, -1, SQLITE_TRANSIENT); - } - }else{ - - /* Figure out if an extra space should be inserted after the constraint - ** is removed. And if an additional comma preceding the constraint - ** should be removed. */ - const char *zSpace = " "; - iEnd += getWhitespace(&zSql[iEnd]); - sqlite3GetToken(&zSql[iEnd], &t); - if( t==TK_RP || t==TK_COMMA ){ - zSpace = ""; - if( zSql[iStart-1]==',' ) iStart--; - } - - db = sqlite3_context_db_handle(ctx); - zNew = sqlite3MPrintf(db, "%.*s%s%s", iStart, zSql, zSpace, &zSql[iEnd]); - sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); - } -} - -/* -** Internal SQL function: -** -** sqlite_add_constraint(SQL, CONSTRAINT-TEXT, ICOL) -** -** SQL is a CREATE TABLE statement. Return a modified version of -** SQL that adds CONSTRAINT-TEXT at the end of the ICOL-th column -** definition. (The left-most column defintion is 0.) -*/ -static void addConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const u8 *zSql = sqlite3_value_text(argv[0]); - const char *zCons = (const char*)sqlite3_value_text(argv[1]); - int iCol = sqlite3_value_int(argv[2]); - int iOff = 0; - int ii; - char *zNew = 0; - int t = 0; - sqlite3 *db; - UNUSED_PARAMETER(NotUsed); - - if( skipCreateTable(ctx, zSql, &iOff) ) return; - - for(ii=0; ii<=iCol || (iCol<0 && t!=TK_RP); ii++){ - iOff += getConstraintToken(&zSql[iOff], &t); - while( 1 ){ - int nTok = getConstraintToken(&zSql[iOff], &t); - if( t==TK_COMMA || t==TK_RP ) break; - if( t==TK_ILLEGAL ){ - sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); - return; - } - iOff += nTok; - } - } - - iOff += getWhitespace(&zSql[iOff]); - - db = sqlite3_context_db_handle(ctx); - if( iCol<0 ){ - zNew = sqlite3MPrintf(db, "%.*s, %s%s", iOff, zSql, zCons, &zSql[iOff]); - }else{ - zNew = sqlite3MPrintf(db, "%.*s %s%s", iOff, zSql, zCons, &zSql[iOff]); - } - sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); -} - -/* -** Find a column named pCol in table pTab. If successful, set output -** parameter *piCol to the index of the column in the table and return -** SQLITE_OK. Otherwise, set *piCol to -1 and return an SQLite error -** code. -*/ -static int alterFindCol(Parse *pParse, Table *pTab, Token *pCol, int *piCol){ - sqlite3 *db = pParse->db; - char *zName = sqlite3NameFromToken(db, pCol); - int rc = SQLITE_NOMEM; - int iCol = -1; - - if( zName ){ - iCol = sqlite3ColumnIndex(pTab, zName); - if( iCol<0 ){ - sqlite3ErrorMsg(pParse, "no such column: %s", zName); - rc = SQLITE_ERROR; - }else{ - rc = SQLITE_OK; - } - } - -#ifndef SQLITE_OMIT_AUTHORIZATION - if( rc==SQLITE_OK ){ - const char *zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; - const char *zCol = pTab->aCol[iCol].zCnName; - if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ - pTab = 0; - } - } -#endif - - sqlite3DbFree(db, zName); - *piCol = iCol; - return rc; -} - - -/* -** Find the table named by the first entry in source list pSrc. If successful, -** return a pointer to the Table structure and set output variable (*pzDb) -** to point to the name of the database containin the table (i.e. "main", -** "temp" or the name of an attached database). -** -** If the table cannot be located, return NULL. The value of the two output -** parameters is undefined in this case. -*/ -static Table *alterFindTable( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* Name of the table to look for */ - int *piDb, /* OUT: write the iDb here */ - const char **pzDb, /* OUT: write name of schema here */ - int bAuth /* Do ALTER TABLE authorization checks if true */ -){ - sqlite3 *db = pParse->db; - Table *pTab = 0; - assert( sqlite3BtreeHoldsAllMutexes(db) ); - pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); - if( pTab ){ - int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - *pzDb = db->aDb[iDb].zDbSName; - *piDb = iDb; - - if( SQLITE_OK!=isRealTable(pParse, pTab, 2) - || SQLITE_OK!=isAlterableTable(pParse, pTab) - ){ - pTab = 0; - } - } -#ifndef SQLITE_OMIT_AUTHORIZATION - if( pTab && bAuth ){ - if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, *pzDb, pTab->zName, 0) ){ - pTab = 0; - } - } -#endif - sqlite3SrcListDelete(db, pSrc); - return pTab; -} - -/* -** Generate bytecode for one of: -** -** (1) ALTER TABLE pSrc DROP CONSTRAINT pCons -** (2) ALTER TABLE pSrc ALTER pCol DROP NOT NULL -** -** One of pCons and pCol must be NULL and the other non-null. -*/ -SQLITE_PRIVATE void sqlite3AlterDropConstraint( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* The table being altered */ - Token *pCons, /* Name of the constraint to drop */ - Token *pCol /* Name of the column from which to remove the NOT NULL */ -){ - sqlite3 *db = pParse->db; - Table *pTab = 0; - int iDb = 0; - const char *zDb = 0; - char *zArg = 0; - - assert( (pCol==0)!=(pCons==0) ); - assert( pSrc->nSrc==1 ); - pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, pCons!=0); - if( !pTab ) return; - - if( pCons ){ - zArg = sqlite3MPrintf(db, "%.*Q", pCons->n, pCons->z); - }else{ - int iCol; - if( alterFindCol(pParse, pTab, pCol, &iCol) ) return; - zArg = sqlite3MPrintf(db, "%d", iCol); - } - - /* Edit the SQL for the named table. */ - sqlite3NestedParse(pParse, - "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " - "sql = sqlite_drop_constraint(sql, %s) " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase" - , zDb, zArg, pTab->zName - ); - sqlite3DbFree(db, zArg); - - /* Finally, reload the database schema. */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); -} - -/* -** The implementation of SQL function sqlite_fail(MSG). This takes a single -** argument, and returns it as an error message with the error code set to -** SQLITE_CONSTRAINT. -*/ -static void failConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const char *zText = (const char*)sqlite3_value_text(argv[0]); - int err = sqlite3_value_int(argv[1]); - (void)NotUsed; - sqlite3_result_error(ctx, zText, -1); - sqlite3_result_error_code(ctx, err); -} - -/* -** Buffer pCons, which is nCons bytes in size, contains the text of a -** NOT NULL or CHECK constraint that will be inserted into a CREATE TABLE -** statement. If successful, this function returns the size of the buffer in -** bytes not including any trailing whitespace or "--" style comments. Or, -** if an OOM occurs, it returns 0 and sets db->mallocFailed to true. -** -** C-style comments at the end are preserved. "--" style comments are -** removed because the comment terminator might be \000, and we are about -** to insert the pCons[] text into the middle of a larger string, and that -** will have the effect of removing the comment terminator and messing up -** the syntax. -*/ -static int alterRtrimConstraint( - sqlite3 *db, /* used to record OOM error */ - const char *pCons, /* Buffer containing constraint */ - int nCons /* Size of pCons in bytes */ -){ - u8 *zTmp = (u8*)sqlite3MPrintf(db, "%.*s", nCons, pCons); - int iOff = 0; - int iEnd = 0; - - if( zTmp==0 ) return 0; - - while( 1 ){ - int t = 0; - int nToken = sqlite3GetToken(&zTmp[iOff], &t); - if( t==TK_ILLEGAL ) break; - if( t!=TK_SPACE && (t!=TK_COMMENT || zTmp[iOff]!='-') ){ - iEnd = iOff+nToken; - } - iOff += nToken; - } - - sqlite3DbFree(db, zTmp); - return iEnd; -} - -/* -** Prepare a statement of the form: -** -** ALTER TABLE pSrc ALTER pCol SET NOT NULL -*/ -SQLITE_PRIVATE void sqlite3AlterSetNotNull( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* Name of the table being altered */ - Token *pCol, /* Name of the column to add a NOT NULL constraint to */ - Token *pFirst /* The NOT token of the NOT NULL constraint text */ -){ - Table *pTab = 0; - int iCol = 0; - int iDb = 0; - const char *zDb = 0; - const char *pCons = 0; - int nCons = 0; - - /* Look up the table being altered. */ - assert( pSrc->nSrc==1 ); - pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 0); - if( !pTab ) return; - - /* Find the column being altered. */ - if( alterFindCol(pParse, pTab, pCol, &iCol) ){ - return; - } - - /* Find the length in bytes of the constraint definition */ - pCons = pFirst->z; - nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); - - /* Search for a constraint violation. Throw an exception if one is found. */ - sqlite3NestedParse(pParse, - "SELECT sqlite_fail('constraint failed', %d) " - "FROM %Q.%Q AS x WHERE x.%.*s IS NULL", - SQLITE_CONSTRAINT, zDb, pTab->zName, (int)pCol->n, pCol->z - ); - - /* Edit the SQL for the named table. */ - sqlite3NestedParse(pParse, - "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " - "sql = sqlite_add_constraint(sqlite_drop_constraint(sql, %d), %.*Q, %d) " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase" - , zDb, iCol, nCons, pCons, iCol, pTab->zName - ); - - /* Finally, reload the database schema. */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); -} - -/* -** Implementation of internal SQL function: -** -** sqlite_find_constraint(SQL, CONSTRAINT-NAME) -** -** This function returns true if the SQL passed as the first argument is a -** CREATE TABLE that contains a constraint with the name CONSTRAINT-NAME, -** or false otherwise. -*/ -static void findConstraintFunc( - sqlite3_context *ctx, - int NotUsed, - sqlite3_value **argv -){ - const u8 *zSql = 0; - const u8 *zCons = 0; - int iOff = 0; - int t = 0; - - (void)NotUsed; - zSql = sqlite3_value_text(argv[0]); - zCons = sqlite3_value_text(argv[1]); - - if( zSql==0 || zCons==0 ) return; - while( t!=TK_LP && t!=TK_ILLEGAL ){ - iOff += sqlite3GetToken(&zSql[iOff], &t); - } - - while( 1 ){ - iOff += getConstraintToken(&zSql[iOff], &t); - if( t==TK_CONSTRAINT ){ - int nTok = 0; - int cmp = 0; - iOff += getWhitespace(&zSql[iOff]); - nTok = getConstraintToken(&zSql[iOff], &t); - if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; - if( cmp==0 ){ - sqlite3_result_int(ctx, 1); - return; - } - }else if( t==TK_ILLEGAL ){ - break; - } - } - - sqlite3_result_int(ctx, 0); -} - -/* -** Generate bytecode to implement: -** -** ALTER TABLE pSrc ADD [CONSTRAINT pName] CHECK(pExpr) -** -** Any "ON CONFLICT" text that occurs after the "CHECK(...)", up -** until pParse->sLastToken, is included as part of the new constraint. -*/ -SQLITE_PRIVATE void sqlite3AlterAddConstraint( - Parse *pParse, /* Parse context */ - SrcList *pSrc, /* Table to add constraint to */ - Token *pFirst, /* First token of new constraint */ - Token *pName, /* Name of new constraint. NULL if name omitted. */ - const char *pExpr, /* Text of CHECK expression */ - int nExpr /* Size of pExpr in bytes */ -){ - Table *pTab = 0; /* Table identified by pSrc */ - int iDb = 0; /* Which schema does pTab live in */ - const char *zDb = 0; /* Name of the schema in which pTab lives */ - const char *pCons = 0; /* Text of the constraint */ - int nCons; /* Bytes of text to use from pCons[] */ - - /* Look up the table being altered. */ - assert( pSrc->nSrc==1 ); - pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 1); - if( !pTab ) return; - - /* If this new constraint has a name, check that it is not a duplicate of - ** an existing constraint. It is an error if it is. */ - if( pName ){ - char *zName = sqlite3NameFromToken(pParse->db, pName); - - sqlite3NestedParse(pParse, - "SELECT sqlite_fail('constraint %q already exists', %d) " - "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase " - "AND sqlite_find_constraint(sql, %Q)", - zName, SQLITE_ERROR, zDb, pTab->zName, zName - ); - sqlite3DbFree(pParse->db, zName); - } - - /* Search for a constraint violation. Throw an exception if one is found. */ - sqlite3NestedParse(pParse, - "SELECT sqlite_fail('constraint failed', %d) " - "FROM %Q.%Q WHERE (%.*s) IS NOT TRUE", - SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, pExpr - ); - - /* Edit the SQL for the named table. */ - pCons = pFirst->z; - nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); - - sqlite3NestedParse(pParse, - "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " - "sql = sqlite_add_constraint(sql, %.*Q, -1) " - "WHERE type='table' AND tbl_name=%Q COLLATE nocase" - , zDb, nCons, pCons, pTab->zName - ); - - /* Finally, reload the database schema. */ - renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); -} - /* ** Register built-in functions used to help implement ALTER TABLE */ @@ -122541,10 +121461,6 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), - INTERNAL_FUNCTION(sqlite_drop_constraint,2, dropConstraintFunc), - INTERNAL_FUNCTION(sqlite_fail, 2, failConstraintFunc), - INTERNAL_FUNCTION(sqlite_add_constraint, 3, addConstraintFunc), - INTERNAL_FUNCTION(sqlite_find_constraint,2, findConstraintFunc), }; sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); } @@ -125165,7 +124081,7 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep( if( sqlite3WalkSelect(&pFix->w, pStep->pSelect) || sqlite3WalkExpr(&pFix->w, pStep->pWhere) || sqlite3WalkExprList(&pFix->w, pStep->pExprList) - || sqlite3FixSrcList(pFix, pStep->pSrc) + || sqlite3FixSrcList(pFix, pStep->pFrom) ){ return 1; } @@ -125272,7 +124188,7 @@ SQLITE_API int sqlite3_set_authorizer( sqlite3_mutex_enter(db->mutex); db->xAuth = (sqlite3_xauth)xAuth; db->pAuthArg = pArg; - sqlite3ExpirePreparedStatements(db, 1); + if( db->xAuth ) sqlite3ExpirePreparedStatements(db, 1); sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } @@ -125943,7 +124859,6 @@ SQLITE_PRIVATE Table *sqlite3LocateTableItem( const char *zDb; if( p->fg.fixedSchema ){ int iDb = sqlite3SchemaToIndex(pParse->db, p->u4.pSchema); - assert( iDb>=0 && iDbdb->nDb ); zDb = pParse->db->aDb[iDb].zDbSName; }else{ assert( !p->fg.isSubquery ); @@ -127517,8 +126432,8 @@ SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){ ** The estimate is conservative. It might be larger that what is ** really needed. */ -static i64 identLength(const char *z){ - i64 n; +static int identLength(const char *z){ + int n; for(n=0; *z; n++, z++){ if( *z=='"' ){ n++; } } @@ -128028,14 +126943,13 @@ SQLITE_PRIVATE void sqlite3MarkAllShadowTablesOf(sqlite3 *db, Table *pTab){ ** restored to its original value prior to this routine returning. */ SQLITE_PRIVATE int sqlite3ShadowTableName(sqlite3 *db, const char *zName){ - const char *zTail; /* Pointer to the last "_" in zName */ + char *zTail; /* Pointer to the last "_" in zName */ Table *pTab; /* Table that zName is a shadow of */ - char *zCopy; zTail = strrchr(zName, '_'); if( zTail==0 ) return 0; - zCopy = sqlite3DbStrNDup(db, zName, (int)(zTail-zName)); - pTab = zCopy ? sqlite3FindTable(db, zCopy, 0) : 0; - sqlite3DbFree(db, zCopy); + *zTail = 0; + pTab = sqlite3FindTable(db, zName, 0); + *zTail = '_'; if( pTab==0 ) return 0; if( !IsVirtual(pTab) ) return 0; return sqlite3IsShadowTableOf(db, pTab, zName); @@ -128188,7 +127102,6 @@ SQLITE_PRIVATE void sqlite3EndTable( convertToWithoutRowidTable(pParse, p); } iDb = sqlite3SchemaToIndex(db, p->pSchema); - assert( iDb>=0 && iDb<=db->nDb ); #ifndef SQLITE_OMIT_CHECK /* Resolve names in all CHECK constraint expressions. @@ -128484,7 +127397,6 @@ SQLITE_PRIVATE void sqlite3CreateView( sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); - assert( iDb>=0 && iDbnDb ); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); if( sqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail; @@ -130081,7 +128993,6 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists goto exit_drop_index; } iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); - assert( iDb>=0 && iDbnDb ); #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_INDEX; @@ -131916,7 +130827,7 @@ static int vtabIsReadOnly(Parse *pParse, Table *pTab){ ** * Only allow DELETE, INSERT, or UPDATE of non-SQLITE_VTAB_INNOCUOUS ** virtual tables if PRAGMA trusted_schema=ON. */ - if( (pParse->pToplevel!=0 || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) + if( pParse->pToplevel!=0 && pTab->u.vtab.p->eVtabRisk > ((pParse->db->flags & SQLITE_TrustedSchema)!=0) ){ @@ -132754,6 +131665,7 @@ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete( &iPartIdxLabel, pPrior, r1); sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); + sqlite3VdbeChangeP5(v, 1); /* Cause IdxDelete to error if no entry found */ sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); pPrior = pIdx; } @@ -133328,7 +132240,7 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ sqlite3_result_error_nomem(context); return; } - sqlite3AtoF(zBuf, &r); + sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); sqlite3_free(zBuf); } sqlite3_result_double(context, r); @@ -133966,7 +132878,7 @@ SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue, int sqlite3_str_appendf(pStr, "%!0.15g", r1); zVal = sqlite3_str_value(pStr); if( zVal ){ - sqlite3AtoF(zVal, &r2); + sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8); if( r1!=r2 ){ sqlite3_str_reset(pStr); sqlite3_str_appendf(pStr, "%!0.20e", r1); @@ -134063,7 +132975,7 @@ static void unistrFunc( } i = j = 0; while( ifuncFlags |= flags; pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; } @@ -137016,7 +135926,6 @@ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *pTab){ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ if( p ){ TriggerStep *pStep = p->step_list; - sqlite3SrcListDelete(dbMem, pStep->pSrc); sqlite3ExprDelete(dbMem, pStep->pWhere); sqlite3ExprListDelete(dbMem, pStep->pExprList); sqlite3SelectDelete(dbMem, pStep->pSelect); @@ -137236,7 +136145,6 @@ SQLITE_PRIVATE void sqlite3FkCheck( if( !IsOrdinaryTable(pTab) ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - assert( iDb>=00 && iDbnDb ); zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the @@ -137684,14 +136592,14 @@ static Trigger *fkActionTrigger( pTrigger = (Trigger *)sqlite3DbMallocZero(db, sizeof(Trigger) + /* struct Trigger */ - sizeof(TriggerStep) /* Single step in trigger program */ + sizeof(TriggerStep) + /* Single step in trigger program */ + nFrom + 1 /* Space for pStep->zTarget */ ); if( pTrigger ){ pStep = pTrigger->step_list = (TriggerStep *)&pTrigger[1]; - pStep->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); - if( pStep->pSrc ){ - pStep->pSrc->a[0].zName = sqlite3DbStrNDup(db, zFrom, nFrom); - } + pStep->zTarget = (char *)&pStep[1]; + memcpy((char *)pStep->zTarget, zFrom, nFrom); + pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); @@ -141748,11 +140656,7 @@ struct sqlite3_api_routines { /* Version 3.51.0 and later */ int (*set_errmsg)(sqlite3*,int,const char*); int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); - /* Version 3.52.0 and later */ - void (*str_truncate)(sqlite3_str*,int); - void (*str_free)(sqlite3_str*); - int (*carray_bind)(sqlite3_stmt*,int,void*,int,int,void(*)(void*)); - int (*carray_bind_v2)(sqlite3_stmt*,int,void*,int,int,void(*)(void*),void*); + }; /* @@ -142091,11 +140995,6 @@ typedef int (*sqlite3_loadext_entry)( /* Version 3.51.0 and later */ #define sqlite3_set_errmsg sqlite3_api->set_errmsg #define sqlite3_db_status64 sqlite3_api->db_status64 -/* Version 3.52.0 and later */ -#define sqlite3_str_truncate sqlite3_api->str_truncate -#define sqlite3_str_free sqlite3_api->str_free -#define sqlite3_carray_bind sqlite3_api->carray_bind -#define sqlite3_carray_bind_v2 sqlite3_api->carray_bind_v2 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) @@ -142622,17 +141521,7 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_setlk_timeout, /* Version 3.51.0 and later */ sqlite3_set_errmsg, - sqlite3_db_status64, - /* Version 3.52.0 and later */ - sqlite3_str_truncate, - sqlite3_str_free, -#ifdef SQLITE_ENABLE_CARRAY - sqlite3_carray_bind, - sqlite3_carray_bind_v2 -#else - 0, - 0 -#endif + sqlite3_db_status64 }; /* True if x is the directory separator character @@ -142734,42 +141623,33 @@ static int sqlite3LoadExtension( ** entry point name "sqlite3_extension_init" was not found, then ** construct an entry point name "sqlite3_X_init" where the X is ** replaced by the lowercase value of every ASCII alphabetic - ** character in the filename after the last "/" up to the first ".", - ** and skipping the first three characters if they are "lib". + ** character in the filename after the last "/" upto the first ".", + ** and eliding the first three characters if they are "lib". ** Examples: ** ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example_init ** C:/lib/mathfuncs.dll ==> sqlite3_mathfuncs_init - ** - ** If that still finds no entry point, repeat a second time but this - ** time include both alphabetic and numeric characters up to the first - ** ".". Example: - ** - ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example5_init */ if( xInit==0 && zProc==0 ){ int iFile, iEntry, c; int ncFile = sqlite3Strlen30(zFile); - int cnt = 0; zAltEntry = sqlite3_malloc64(ncFile+30); if( zAltEntry==0 ){ sqlite3OsDlClose(pVfs, handle); return SQLITE_NOMEM_BKPT; } - do{ - memcpy(zAltEntry, "sqlite3_", 8); - for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} - iFile++; - if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; - for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ - if( sqlite3Isalpha(c) || (cnt && sqlite3Isdigit(c)) ){ - zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; - } + memcpy(zAltEntry, "sqlite3_", 8); + for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} + iFile++; + if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; + for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ + if( sqlite3Isalpha(c) ){ + zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; } - memcpy(zAltEntry+iEntry, "_init", 6); - zEntry = zAltEntry; - xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); - }while( xInit==0 && (++cnt)<2 ); + } + memcpy(zAltEntry+iEntry, "_init", 6); + zEntry = zAltEntry; + xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); } if( xInit==0 ){ if( pzErrMsg ){ @@ -146841,8 +145721,7 @@ static void corruptSchema( static const char *azAlterType[] = { "rename", "drop column", - "add column", - "drop constraint" + "add column" }; *pData->pzErrMsg = sqlite3MPrintf(db, "error in %s %s after %s: %s", azObj[0], azObj[1], @@ -147925,7 +146804,7 @@ SQLITE_API int sqlite3_prepare16_v3( */ typedef struct DistinctCtx DistinctCtx; struct DistinctCtx { - u8 isTnct; /* 0: Not distinct. 1: DISTINCT 2: DISTINCT and ORDER BY */ + u8 isTnct; /* 0: Not distinct. 1: DISTICT 2: DISTINCT and ORDER BY */ u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ int tabTnct; /* Ephemeral table used for DISTINCT processing */ int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ @@ -148055,6 +146934,8 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( pNew->iLimit = 0; pNew->iOffset = 0; pNew->selId = ++pParse->nSelect; + pNew->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = 0; if( pSrc==0 ) pSrc = sqlite3DbMallocZero(pParse->db, SZ_SRCLIST_1); pNew->pSrc = pSrc; @@ -148563,10 +147444,6 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ pRight->fg.isOn = 1; p->selFlags |= SF_OnToWhere; } - - if( IsVirtual(pRightTab) && joinType==EP_OuterON && pRight->u1.pFuncArg ){ - p->selFlags |= SF_OnToWhere; - } } return 0; } @@ -149206,6 +148083,29 @@ static void selectInnerLoop( } switch( eDest ){ + /* In this mode, write each query result to the key of the temporary + ** table iParm. + */ +#ifndef SQLITE_OMIT_COMPOUND_SELECT + case SRT_Union: { + int r1; + r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); + sqlite3ReleaseTempReg(pParse, r1); + break; + } + + /* Construct a record from the query result, but instead of + ** saving that record, use it as a key to delete elements from + ** the temporary table iParm. + */ + case SRT_Except: { + sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol); + break; + } +#endif /* SQLITE_OMIT_COMPOUND_SELECT */ + /* Store the result as data using a unique key. */ case SRT_Fifo: @@ -150318,8 +149218,8 @@ SQLITE_PRIVATE void sqlite3SubqueryColumnTypes( } } if( zType ){ - const i64 k = strlen(zType); - n = strlen(pCol->zCnName); + const i64 k = sqlite3Strlen30(zType); + n = sqlite3Strlen30(pCol->zCnName); pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+k+2); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); if( pCol->zCnName ){ @@ -150492,9 +149392,9 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ ** function is responsible for ensuring that this structure is eventually ** freed. */ -static KeyInfo *multiSelectByMergeKeyInfo(Parse *pParse, Select *p, int nExtra){ +static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ ExprList *pOrderBy = p->pOrderBy; - int nOrderBy = (pOrderBy!=0) ? pOrderBy->nExpr : 0; + int nOrderBy = ALWAYS(pOrderBy!=0) ? pOrderBy->nExpr : 0; sqlite3 *db = pParse->db; KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); if( pRet ){ @@ -150627,7 +149527,7 @@ static void generateWithRecursiveQuery( regCurrent = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol); if( pOrderBy ){ - KeyInfo *pKeyInfo = multiSelectByMergeKeyInfo(pParse, p, 1); + KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0, (char*)pKeyInfo, P4_KEYINFO); destQueue.pOrderBy = pOrderBy; @@ -150636,28 +149536,8 @@ static void generateWithRecursiveQuery( } VdbeComment((v, "Queue table")); if( iDistinct ){ - /* Generate an ephemeral table used to enforce distinctness on the - ** output of the recursive part of the CTE. - */ - KeyInfo *pKeyInfo; /* Collating sequence for the result set */ - CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ - - assert( p->pNext==0 ); - assert( p->pEList!=0 ); - nCol = p->pEList->nExpr; - pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCol, 1); - if( pKeyInfo ){ - for(i=0, apColl=pKeyInfo->aColl; idb->pDfltColl; - } - } - sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iDistinct, nCol, 0, - (void*)pKeyInfo, P4_KEYINFO); - }else{ - assert( pParse->nErr>0 ); - } + p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0); + p->selFlags |= SF_UsesEphemeral; } /* Detach the ORDER BY clause from the compound SELECT */ @@ -150732,7 +149612,7 @@ static void generateWithRecursiveQuery( #endif /* SQLITE_OMIT_CTE */ /* Forward references */ -static int multiSelectByMerge( +static int multiSelectOrderBy( Parse *pParse, /* Parsing context */ Select *p, /* The right-most of SELECTs to be coded */ SelectDest *pDest /* What to do with query results */ @@ -150881,26 +149761,12 @@ static int multiSelect( generateWithRecursiveQuery(pParse, p, &dest); }else #endif + + /* Compound SELECTs that have an ORDER BY clause are handled separately. + */ if( p->pOrderBy ){ - /* If the compound has an ORDER BY clause, then always use the merge - ** algorithm. */ - return multiSelectByMerge(pParse, p, pDest); - }else if( p->op!=TK_ALL ){ - /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than - ** UNION ALL) then also always use the merge algorithm. However, the - ** multiSelectByMerge() routine requires that the compound have an - ** ORDER BY clause, and it doesn't right now. So invent one first. */ - Expr *pOne = sqlite3ExprInt32(db, 1); - p->pOrderBy = sqlite3ExprListAppend(pParse, 0, pOne); - if( pParse->nErr ) goto multi_select_end; - assert( p->pOrderBy!=0 ); - p->pOrderBy->a[0].u.x.iOrderByCol = 1; - return multiSelectByMerge(pParse, p, pDest); - }else{ - /* For a UNION ALL compound without ORDER BY, simply run the left - ** query, then run the right query */ - int addr = 0; - int nLimit = 0; /* Initialize to suppress harmless compiler warning */ + return multiSelectOrderBy(pParse, p, pDest); + }else{ #ifndef SQLITE_OMIT_EXPLAIN if( pPrior->pPrior==0 ){ @@ -150908,49 +149774,300 @@ static int multiSelect( ExplainQueryPlan((pParse, 1, "LEFT-MOST SUBQUERY")); } #endif - assert( !pPrior->pLimit ); - pPrior->iLimit = p->iLimit; - pPrior->iOffset = p->iOffset; - pPrior->pLimit = sqlite3ExprDup(db, p->pLimit, 0); - TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); - rc = sqlite3Select(pParse, pPrior, &dest); - sqlite3ExprDelete(db, pPrior->pLimit); - pPrior->pLimit = 0; - if( rc ){ - goto multi_select_end; - } - p->pPrior = 0; - p->iLimit = pPrior->iLimit; - p->iOffset = pPrior->iOffset; - if( p->iLimit ){ - addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); - VdbeComment((v, "Jump ahead if LIMIT reached")); - if( p->iOffset ){ - sqlite3VdbeAddOp3(v, OP_OffsetLimit, - p->iLimit, p->iOffset+1, p->iOffset); - } - } - ExplainQueryPlan((pParse, 1, "UNION ALL")); - TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); - rc = sqlite3Select(pParse, p, &dest); - testcase( rc!=SQLITE_OK ); - pDelete = p->pPrior; - p->pPrior = pPrior; - p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); - if( p->pLimit - && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse) - && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) - ){ - p->nSelectRow = sqlite3LogEst((u64)nLimit); - } - if( addr ){ - sqlite3VdbeJumpHere(v, addr); + + /* Generate code for the left and right SELECT statements. + */ + switch( p->op ){ + case TK_ALL: { + int addr = 0; + int nLimit = 0; /* Initialize to suppress harmless compiler warning */ + assert( !pPrior->pLimit ); + pPrior->iLimit = p->iLimit; + pPrior->iOffset = p->iOffset; + pPrior->pLimit = p->pLimit; + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); + rc = sqlite3Select(pParse, pPrior, &dest); + pPrior->pLimit = 0; + if( rc ){ + goto multi_select_end; + } + p->pPrior = 0; + p->iLimit = pPrior->iLimit; + p->iOffset = pPrior->iOffset; + if( p->iLimit ){ + addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); + VdbeComment((v, "Jump ahead if LIMIT reached")); + if( p->iOffset ){ + sqlite3VdbeAddOp3(v, OP_OffsetLimit, + p->iLimit, p->iOffset+1, p->iOffset); + } + } + ExplainQueryPlan((pParse, 1, "UNION ALL")); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); + rc = sqlite3Select(pParse, p, &dest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + if( p->pLimit + && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse) + && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) + ){ + p->nSelectRow = sqlite3LogEst((u64)nLimit); + } + if( addr ){ + sqlite3VdbeJumpHere(v, addr); + } + break; + } + case TK_EXCEPT: + case TK_UNION: { + int unionTab; /* Cursor number of the temp table holding result */ + u8 op = 0; /* One of the SRT_ operations to apply to self */ + int priorOp; /* The SRT_ operation to apply to prior selects */ + Expr *pLimit; /* Saved values of p->nLimit */ + int addr; + int emptyBypass = 0; /* IfEmpty opcode to bypass RHS */ + SelectDest uniondest; + + + testcase( p->op==TK_EXCEPT ); + testcase( p->op==TK_UNION ); + priorOp = SRT_Union; + if( dest.eDest==priorOp ){ + /* We can reuse a temporary table generated by a SELECT to our + ** right. + */ + assert( p->pLimit==0 ); /* Not allowed on leftward elements */ + unionTab = dest.iSDParm; + }else{ + /* We will need to create our own temporary table to hold the + ** intermediate results. + */ + unionTab = pParse->nTab++; + assert( p->pOrderBy==0 ); + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0); + assert( p->addrOpenEphm[0] == -1 ); + p->addrOpenEphm[0] = addr; + findRightmost(p)->selFlags |= SF_UsesEphemeral; + assert( p->pEList ); + } + + + /* Code the SELECT statements to our left + */ + assert( !pPrior->pOrderBy ); + sqlite3SelectDestInit(&uniondest, priorOp, unionTab); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION left...\n")); + rc = sqlite3Select(pParse, pPrior, &uniondest); + if( rc ){ + goto multi_select_end; + } + + /* Code the current SELECT statement + */ + if( p->op==TK_EXCEPT ){ + op = SRT_Except; + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, unionTab); + VdbeCoverage(v); + }else{ + assert( p->op==TK_UNION ); + op = SRT_Union; + } + p->pPrior = 0; + pLimit = p->pLimit; + p->pLimit = 0; + uniondest.eDest = op; + ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", + sqlite3SelectOpName(p->op))); + TREETRACE(0x200, pParse, p, ("multiSelect EXCEPT/UNION right...\n")); + rc = sqlite3Select(pParse, p, &uniondest); + testcase( rc!=SQLITE_OK ); + assert( p->pOrderBy==0 ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->pOrderBy = 0; + if( p->op==TK_UNION ){ + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + } + if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass); + sqlite3ExprDelete(db, p->pLimit); + p->pLimit = pLimit; + p->iLimit = 0; + p->iOffset = 0; + + /* Convert the data in the temporary table into whatever form + ** it is that we currently need. + */ + assert( unionTab==dest.iSDParm || dest.eDest!=priorOp ); + assert( p->pEList || db->mallocFailed ); + if( dest.eDest!=priorOp && db->mallocFailed==0 ){ + int iCont, iBreak, iStart; + iBreak = sqlite3VdbeMakeLabel(pParse); + iCont = sqlite3VdbeMakeLabel(pParse); + computeLimitRegisters(pParse, p, iBreak); + sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v); + iStart = sqlite3VdbeCurrentAddr(v); + selectInnerLoop(pParse, p, unionTab, + 0, 0, &dest, iCont, iBreak); + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); + } + break; + } + default: assert( p->op==TK_INTERSECT ); { + int tab1, tab2; + int iCont, iBreak, iStart; + Expr *pLimit; + int addr, iLimit, iOffset; + SelectDest intersectdest; + int r1; + int emptyBypass; + + /* INTERSECT is different from the others since it requires + ** two temporary tables. Hence it has its own case. Begin + ** by allocating the tables we will need. + */ + tab1 = pParse->nTab++; + tab2 = pParse->nTab++; + assert( p->pOrderBy==0 ); + + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0); + assert( p->addrOpenEphm[0] == -1 ); + p->addrOpenEphm[0] = addr; + findRightmost(p)->selFlags |= SF_UsesEphemeral; + assert( p->pEList ); + + /* Code the SELECTs to our left into temporary table "tab1". + */ + sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT left...\n")); + rc = sqlite3Select(pParse, pPrior, &intersectdest); + if( rc ){ + goto multi_select_end; + } + + /* Initialize LIMIT counters before checking to see if the LHS + ** is empty, in case the jump is taken */ + iBreak = sqlite3VdbeMakeLabel(pParse); + computeLimitRegisters(pParse, p, iBreak); + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, tab1); VdbeCoverage(v); + + /* Code the current SELECT into temporary table "tab2" + */ + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); + assert( p->addrOpenEphm[1] == -1 ); + p->addrOpenEphm[1] = addr; + + /* Disable prior SELECTs and the LIMIT counters during the computation + ** of the RHS select */ + pLimit = p->pLimit; + iLimit = p->iLimit; + iOffset = p->iOffset; + p->pPrior = 0; + p->pLimit = 0; + p->iLimit = 0; + p->iOffset = 0; + + intersectdest.iSDParm = tab2; + ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", + sqlite3SelectOpName(p->op))); + TREETRACE(0x400, pParse, p, ("multiSelect INTERSECT right...\n")); + rc = sqlite3Select(pParse, p, &intersectdest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + if( p->nSelectRow>pPrior->nSelectRow ){ + p->nSelectRow = pPrior->nSelectRow; + } + sqlite3ExprDelete(db, p->pLimit); + + /* Reinstate the LIMIT counters prior to running the final intersect */ + p->pLimit = pLimit; + p->iLimit = iLimit; + p->iOffset = iOffset; + + /* Generate code to take the intersection of the two temporary + ** tables. + */ + if( rc ) break; + assert( p->pEList ); + sqlite3VdbeAddOp1(v, OP_Rewind, tab1); + r1 = sqlite3GetTempReg(pParse); + iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); + iCont = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, r1); + selectInnerLoop(pParse, p, tab1, + 0, 0, &dest, iCont, iBreak); + sqlite3VdbeResolveLabel(v, iCont); + sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); + sqlite3VdbeResolveLabel(v, iBreak); + sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); + sqlite3VdbeJumpHere(v, emptyBypass); + sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); + break; + } } -#ifndef SQLITE_OMIT_EXPLAIN + + #ifndef SQLITE_OMIT_EXPLAIN if( p->pNext==0 ){ ExplainQueryPlanPop(pParse); } -#endif + #endif + } + if( pParse->nErr ) goto multi_select_end; + + /* Compute collating sequences used by + ** temporary tables needed to implement the compound select. + ** Attach the KeyInfo structure to all temporary tables. + ** + ** This section is run by the right-most SELECT statement only. + ** SELECT statements to the left always skip this part. The right-most + ** SELECT might also skip this part if it has no ORDER BY clause and + ** no temp tables are required. + */ + if( p->selFlags & SF_UsesEphemeral ){ + int i; /* Loop counter */ + KeyInfo *pKeyInfo; /* Collating sequence for the result set */ + Select *pLoop; /* For looping through SELECT statements */ + CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ + int nCol; /* Number of columns in result set */ + + assert( p->pNext==0 ); + assert( p->pEList!=0 ); + nCol = p->pEList->nExpr; + pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); + if( !pKeyInfo ){ + rc = SQLITE_NOMEM_BKPT; + goto multi_select_end; + } + for(i=0, apColl=pKeyInfo->aColl; ipDfltColl; + } + } + + for(pLoop=p; pLoop; pLoop=pLoop->pPrior){ + for(i=0; i<2; i++){ + int addr = pLoop->addrOpenEphm[i]; + if( addr<0 ){ + /* If [0] is unused then [1] is also unused. So we can + ** always safely abort as soon as the first unused slot is found */ + assert( pLoop->addrOpenEphm[1]<0 ); + break; + } + sqlite3VdbeChangeP2(v, addr, nCol); + sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo), + P4_KEYINFO); + pLoop->addrOpenEphm[i] = -1; + } + } + sqlite3KeyInfoUnref(pKeyInfo); } multi_select_end: @@ -150982,8 +150099,8 @@ SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ ** Code an output subroutine for a coroutine implementation of a ** SELECT statement. ** -** The data to be output is contained in an array of pIn->nSdst registers -** starting at register pIn->iSdst. pDest is where the output should +** The data to be output is contained in pIn->iSdst. There are +** pIn->nSdst columns to be output. pDest is where the output should ** be sent. ** ** regReturn is the number of the register holding the subroutine @@ -151012,8 +150129,6 @@ static int generateOutputSubroutine( int iContinue; int addr; - assert( pIn->eDest==SRT_Coroutine ); - addr = sqlite3VdbeCurrentAddr(v); iContinue = sqlite3VdbeMakeLabel(pParse); @@ -151035,60 +150150,23 @@ static int generateOutputSubroutine( */ codeOffset(v, p->iOffset, iContinue); + assert( pDest->eDest!=SRT_Exists ); + assert( pDest->eDest!=SRT_Table ); switch( pDest->eDest ){ /* Store the result as data using a unique key. */ - case SRT_Fifo: - case SRT_DistFifo: - case SRT_Table: case SRT_EphemTab: { int r1 = sqlite3GetTempReg(pParse); int r2 = sqlite3GetTempReg(pParse); - int iParm = pDest->iSDParm; - testcase( pDest->eDest==SRT_Table ); - testcase( pDest->eDest==SRT_EphemTab ); - testcase( pDest->eDest==SRT_Fifo ); - testcase( pDest->eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1); -#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) - /* A destination of SRT_Table and a non-zero iSDParm2 parameter means - ** that this is an "UPDATE ... FROM" on a virtual table or view. In this - ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. - ** This does not affect operation in any way - it just allows MakeRecord - ** to process OPFLAG_NOCHANGE values without an assert() failing. */ - if( pDest->eDest==SRT_Table && pDest->iSDParm2 ){ - sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); - } -#endif -#ifndef SQLITE_OMIT_CTE - if( pDest->eDest==SRT_DistFifo ){ - /* If the destination is DistFifo, then cursor (iParm+1) is open - ** on an ephemeral index that is used to enforce uniqueness on the - ** total result. At this point, we are processing the setup portion - ** of the recursive CTE using the merge algorithm, so the results are - ** guaranteed to be unique anyhow. But we still need to populate the - ** (iParm+1) cursor for use by the subsequent recursive phase. - */ - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1, - pIn->iSdst, pIn->nSdst); - } -#endif - sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2); - sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2); + sqlite3VdbeAddOp2(v, OP_NewRowid, pDest->iSDParm, r2); + sqlite3VdbeAddOp3(v, OP_Insert, pDest->iSDParm, r1, r2); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3ReleaseTempReg(pParse, r2); sqlite3ReleaseTempReg(pParse, r1); break; } - /* If any row exist in the result set, record that fact and abort. - */ - case SRT_Exists: { - sqlite3VdbeAddOp2(v, OP_Integer, 1, pDest->iSDParm); - /* The LIMIT clause will terminate the loop for us */ - break; - } - #ifndef SQLITE_OMIT_SUBQUERY /* If we are creating a set for an "expr IN (SELECT ...)". */ @@ -151135,51 +150213,9 @@ static int generateOutputSubroutine( break; } -#ifndef SQLITE_OMIT_CTE - /* Write the results into a priority queue that is order according to - ** pDest->pOrderBy (in pSO). pDest->iSDParm (in iParm) is the cursor for an - ** index with pSO->nExpr+2 columns. Build a key using pSO for the first - ** pSO->nExpr columns, then make sure all keys are unique by adding a - ** final OP_Sequence column. The last column is the record as a blob. - */ - case SRT_DistQueue: - case SRT_Queue: { - int nKey; - int r1, r2, r3, ii; - ExprList *pSO; - int iParm = pDest->iSDParm; - pSO = pDest->pOrderBy; - assert( pSO ); - nKey = pSO->nExpr; - r1 = sqlite3GetTempReg(pParse); - r2 = sqlite3GetTempRange(pParse, nKey+2); - r3 = r2+nKey+1; - - sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r3); - if( pDest->eDest==SRT_DistQueue ){ - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3); - } - for(ii=0; iiiSdst + pSO->a[ii].u.x.iOrderByCol - 1, - r2+ii); - } - sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); - sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2); - sqlite3ReleaseTempReg(pParse, r1); - sqlite3ReleaseTempRange(pParse, r2, nKey+2); - break; - } -#endif /* SQLITE_OMIT_CTE */ - - /* Ignore the output */ - case SRT_Discard: { - break; - } - /* If none of the above, then the result destination must be - ** SRT_Output. + ** SRT_Output. This routine is never called with any other + ** destination other than the ones handled above or SRT_Output. ** ** For SRT_Output, results are stored in a sequence of registers. ** Then the OP_ResultRow opcode is used to cause sqlite3_step() to @@ -151207,9 +150243,8 @@ static int generateOutputSubroutine( } /* -** Generate code for a compound SELECT statement using a merge -** algorithm. The compound must have an ORDER BY clause for this -** to work. +** Alternative compound select code generator for cases when there +** is an ORDER BY clause. ** ** We assume a query of the following form: ** @@ -151226,7 +150261,7 @@ static int generateOutputSubroutine( ** ** outB: Move the output of the selectB coroutine into the output ** of the compound query. (Only generated for UNION and -** UNION ALL. EXCEPT and INTERSECT never output a row that +** UNION ALL. EXCEPT and INSERTSECT never output a row that ** appears only in B.) ** ** AltB: Called when there is data from both coroutines and Au.x.iOrderByCol==i ) break; } if( j==nOrderBy ){ - Expr *pNew = sqlite3ExprInt32(db, i); + Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); if( pNew==0 ) return SQLITE_NOMEM_BKPT; + pNew->flags |= EP_IntValue; + pNew->u.iValue = i; p->pOrderBy = pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i; } @@ -151372,29 +150411,26 @@ static int multiSelectByMerge( } /* Compute the comparison permutation and keyinfo that is used with - ** the permutation to determine if the next row of results comes - ** from selectA or selectB. Also add literal collations to the - ** ORDER BY clause terms so that when selectA and selectB are - ** evaluated, they use the correct collation. + ** the permutation used to determine if the next + ** row of results comes from selectA or selectB. Also add explicit + ** collations to the ORDER BY clause terms so that when the subqueries + ** to the right and the left are evaluated, they use the correct + ** collation. */ aPermute = sqlite3DbMallocRawNN(db, sizeof(u32)*(nOrderBy + 1)); if( aPermute ){ struct ExprList_item *pItem; - int bKeep = 0; aPermute[0] = nOrderBy; for(i=1, pItem=pOrderBy->a; i<=nOrderBy; i++, pItem++){ assert( pItem!=0 ); assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; - if( aPermute[i]!=(u32)i-1 ) bKeep = 1; - } - if( bKeep==0 ){ - sqlite3DbFreeNN(db, aPermute); - aPermute = 0; } + pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1); + }else{ + pKeyMerge = 0; } - pKeyMerge = multiSelectByMergeKeyInfo(pParse, p, 1); /* Allocate a range of temporary registers and the KeyInfo needed ** for the logic that removes duplicate result rows when the @@ -151473,7 +150509,7 @@ static int multiSelectByMerge( */ addrSelectA = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA); - VdbeComment((v, "SUBR: next-A")); + VdbeComment((v, "left SELECT")); pPrior->iLimit = regLimitA; ExplainQueryPlan((pParse, 1, "LEFT")); sqlite3Select(pParse, pPrior, &destA); @@ -151485,7 +150521,7 @@ static int multiSelectByMerge( */ addrSelectB = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB); - VdbeComment((v, "SUBR: next-B")); + VdbeComment((v, "right SELECT")); savedLimit = p->iLimit; savedOffset = p->iOffset; p->iLimit = regLimitB; @@ -151499,7 +150535,7 @@ static int multiSelectByMerge( /* Generate a subroutine that outputs the current row of the A ** select as the next output row of the compound select. */ - VdbeNoopComment((v, "SUBR: out-A")); + VdbeNoopComment((v, "Output routine for A")); addrOutA = generateOutputSubroutine(pParse, p, &destA, pDest, regOutA, regPrev, pKeyDup, labelEnd); @@ -151508,7 +150544,7 @@ static int multiSelectByMerge( ** select as the next output row of the compound select. */ if( op==TK_ALL || op==TK_UNION ){ - VdbeNoopComment((v, "SUBR: out-B")); + VdbeNoopComment((v, "Output routine for B")); addrOutB = generateOutputSubroutine(pParse, p, &destB, pDest, regOutB, regPrev, pKeyDup, labelEnd); @@ -151521,12 +150557,10 @@ static int multiSelectByMerge( if( op==TK_EXCEPT || op==TK_INTERSECT ){ addrEofA_noB = addrEofA = labelEnd; }else{ - VdbeNoopComment((v, "SUBR: eof-A")); + VdbeNoopComment((v, "eof-A subroutine")); addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); - VdbeComment((v, "out-B")); addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); VdbeCoverage(v); - VdbeComment((v, "next-B")); sqlite3VdbeGoto(v, addrEofA); p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); } @@ -151538,20 +150572,17 @@ static int multiSelectByMerge( addrEofB = addrEofA; if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; }else{ - VdbeNoopComment((v, "SUBR: eof-B")); + VdbeNoopComment((v, "eof-B subroutine")); addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); - VdbeComment((v, "out-A")); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v); - VdbeComment((v, "next-A")); sqlite3VdbeGoto(v, addrEofB); } /* Generate code to handle the case of AB */ + VdbeNoopComment((v, "A-gt-B subroutine")); addrAgtB = sqlite3VdbeCurrentAddr(v); if( op==TK_ALL || op==TK_UNION ){ sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); - VdbeComment((v, "out-B")); - sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); - VdbeComment((v, "next-B")); - sqlite3VdbeGoto(v, labelCmpr); - }else{ - addrAgtB++; /* Just do next-B. Might as well use the next-B call - ** in the next code block */ } + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + sqlite3VdbeGoto(v, labelCmpr); /* This code runs once to initialize everything. */ sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v); - VdbeComment((v, "next-A")); - /* v--- Also the A>B case for EXCEPT and INTERSECT */ sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); - VdbeComment((v, "next-B")); /* Implement the main merge loop */ - if( aPermute!=0 ){ - sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); - } sqlite3VdbeResolveLabel(v, labelCmpr); + sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy, (char*)pKeyMerge, P4_KEYINFO); - if( aPermute!=0 ){ - sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); - } - sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); - VdbeCoverageIf(v, op==TK_ALL); - VdbeCoverageIf(v, op==TK_UNION); - VdbeCoverageIf(v, op==TK_EXCEPT); - VdbeCoverageIf(v, op==TK_INTERSECT); + sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); + sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v); /* Jump to the this point in order to terminate the query. */ @@ -152520,7 +151539,7 @@ static int flattenSubquery( } pSubitem->fg.jointype |= jointype; - /* Begin substituting subquery result set expressions for + /* Now begin substituting subquery result set expressions for ** references to the iParent in the outer query. ** ** Example: @@ -152532,7 +151551,7 @@ static int flattenSubquery( ** We look at every expression in the outer query and every place we see ** "a" we substitute "x*3" and every place we see "b" we substitute "y+10". */ - if( pSub->pOrderBy ){ + if( pSub->pOrderBy && (pParent->selFlags & SF_NoopOrderBy)==0 ){ /* At this point, any non-zero iOrderByCol values indicate that the ** ORDER BY column expression is identical to the iOrderByCol'th ** expression returned by SELECT statement pSub. Since these values @@ -152540,9 +151559,9 @@ static int flattenSubquery( ** zero them before transferring the ORDER BY clause. ** ** Not doing this may cause an error if a subsequent call to this - ** function attempts to flatten a compound sub-query into pParent. - ** See ticket [d11a6e908f]. - */ + ** function attempts to flatten a compound sub-query into pParent + ** (the only way this can happen is if the compound sub-query is + ** currently part of pSub->pSrc). See ticket [d11a6e908f]. */ ExprList *pOrderBy = pSub->pOrderBy; for(i=0; inExpr; i++){ pOrderBy->a[i].u.x.iOrderByCol = 0; @@ -153160,16 +152179,6 @@ static int pushDownWhereTerms( x.pEList = pSubq->pEList; x.pCList = findLeftmostExprlist(pSubq); pNew = substExpr(&x, pNew); - assert( pNew!=0 || pParse->nErr!=0 ); - if( pParse->nErr==0 && pNew->op==TK_IN && ExprUseXSelect(pNew) ){ - assert( pNew->x.pSelect!=0 ); - pNew->x.pSelect->selFlags |= SF_ClonedRhsIn; - assert( pWhere!=0 ); - assert( pWhere->op==TK_IN ); - assert( ExprUseXSelect(pWhere) ); - assert( pWhere->x.pSelect!=0 ); - pWhere->x.pSelect->selFlags |= SF_ClonedRhsIn; - } #ifndef SQLITE_OMIT_WINDOWFUNC if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){ /* Restriction 6c has prevented push-down in this case */ @@ -153404,14 +152413,14 @@ SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ ** SELECT * FROM (SELECT ... FROM t1 EXCEPT SELECT ... FROM t2) ** ORDER BY ... COLLATE ... ** -** This transformation is necessary because the multiSelectByMerge() routine +** This transformation is necessary because the multiSelectOrderBy() routine ** above that generates the code for a compound SELECT with an ORDER BY clause ** uses a merge algorithm that requires the same collating sequence on the ** result columns as on the ORDER BY clause. See ticket ** http://sqlite.org/src/info/6709574d2a ** ** This transformation is only needed for EXCEPT, INTERSECT, and UNION. -** The UNION ALL operator works fine with multiSelectByMerge() even when +** The UNION ALL operator works fine with multiSelectOrderBy() even when ** there are COLLATE terms in the ORDER BY. */ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ @@ -153957,7 +152966,7 @@ static int selectExpander(Walker *pWalker, Select *p){ } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( ALWAYS(IsVirtual(pTab)) - && (pFrom->fg.fromDDL || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) + && pFrom->fg.fromDDL && ALWAYS(pTab->u.vtab.p!=0) && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) ){ @@ -154910,7 +153919,7 @@ static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ && pExpr->pAggInfo==0 ){ sqlite3 *db = pWalker->pParse->db; - Expr *pNew = sqlite3ExprInt32(db, 1); + Expr *pNew = sqlite3Expr(db, TK_INTEGER, "1"); if( pNew ){ Expr *pWhere = pS->pWhere; SWAP(Expr, *pNew, *pExpr); @@ -155261,6 +154270,7 @@ static SQLITE_NOINLINE void existsToJoin( ExprSetProperty(pWhere, EP_IntValue); assert( p->pWhere!=0 ); pSub->pSrc->a[0].fg.fromExists = 1; + pSub->pSrc->a[0].fg.jointype |= JT_CROSS; p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); if( pSubWhere ){ p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere); @@ -155288,7 +154298,6 @@ typedef struct CheckOnCtx CheckOnCtx; struct CheckOnCtx { SrcList *pSrc; /* SrcList for this context */ int iJoin; /* Cursor numbers must be =< than this */ - int bFuncArg; /* True for table-function arg */ CheckOnCtx *pParent; /* Parent context */ }; @@ -155340,9 +154349,7 @@ static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){ if( pCtx->iJoin && iTab>pCtx->iJoin ){ sqlite3ErrorMsg(pWalker->pParse, - "%s references tables to its right", - (pCtx->bFuncArg ? "table-function argument" : "ON clause") - ); + "ON clause references tables to its right"); return WRC_Abort; } break; @@ -155380,7 +154387,6 @@ static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){ SQLITE_PRIVATE void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect){ Walker w; CheckOnCtx sCtx; - int ii; assert( pSelect->selFlags & SF_OnToWhere ); assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 ); memset(&w, 0, sizeof(w)); @@ -155390,46 +154396,8 @@ SQLITE_PRIVATE void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect){ w.u.pCheckOnCtx = &sCtx; memset(&sCtx, 0, sizeof(sCtx)); sCtx.pSrc = pSelect->pSrc; - sqlite3WalkExpr(&w, pSelect->pWhere); + sqlite3WalkExprNN(&w, pSelect->pWhere); pSelect->selFlags &= ~SF_OnToWhere; - - /* Check for any table-function args that are attached to virtual tables - ** on the RHS of an outer join. They are subject to the same constraints - ** as ON clauses. */ - sCtx.bFuncArg = 1; - for(ii=0; iipSrc->nSrc; ii++){ - SrcItem *pItem = &pSelect->pSrc->a[ii]; - if( pItem->fg.isTabFunc - && (pItem->fg.jointype & JT_OUTER) - ){ - sCtx.iJoin = pItem->iCursor; - sqlite3WalkExprList(&w, pItem->u1.pFuncArg); - } - } -} - -/* -** If p2 exists and p1 and p2 have the same number of terms, then change -** every term of p1 to have the same sort order as p2 and return true. -** -** If p2 is NULL or p1 and p2 are different lengths, then make no changes -** and return false. -** -** p1 must be non-NULL. -*/ -static int sqlite3CopySortOrder(ExprList *p1, ExprList *p2){ - assert( p1 ); - if( p2 && p1->nExpr==p2->nExpr ){ - int ii; - for(ii=0; iinExpr; ii++){ - u8 sortFlags; - sortFlags = p2->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; - p1->a[ii].fg.sortFlags = sortFlags; - } - return 1; - }else{ - return 0; - } } /* @@ -155527,7 +154495,8 @@ SQLITE_PRIVATE int sqlite3Select( assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); if( IgnorableDistinct(pDest) ){ - assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Discard || + assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || + pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); /* All of these destinations are also able to ignore the ORDER BY clause */ if( p->pOrderBy ){ @@ -155543,6 +154512,7 @@ SQLITE_PRIVATE int sqlite3Select( p->pOrderBy = 0; } p->selFlags &= ~(u32)SF_Distinct; + p->selFlags |= SF_NoopOrderBy; } sqlite3SelectPrep(pParse, p, 0); if( pParse->nErr ){ @@ -156070,8 +155040,7 @@ SQLITE_PRIVATE int sqlite3Select( ** BY and DISTINCT, and an index or separate temp-table for the other. */ if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct - && sqlite3CopySortOrder(pEList, sSort.pOrderBy) - && sqlite3ExprListCompare(pEList, sSort.pOrderBy, -1)==0 + && sqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0 && OptimizationEnabled(db, SQLITE_GroupByOrder) #ifndef SQLITE_OMIT_WINDOWFUNC && p->pWin==0 @@ -156285,10 +155254,21 @@ SQLITE_PRIVATE int sqlite3Select( ** but not actually sorted. Either way, record the fact that the ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp ** variable. */ - if( sqlite3CopySortOrder(pGroupBy, sSort.pOrderBy) - && sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 - ){ - orderByGrp = 1; + if( sSort.pOrderBy && pGroupBy->nExpr==sSort.pOrderBy->nExpr ){ + int ii; + /* The GROUP BY processing doesn't care whether rows are delivered in + ** ASC or DESC order - only that each group is returned contiguously. + ** So set the ASC/DESC flags in the GROUP BY to match those in the + ** ORDER BY to maximize the chances of rows being delivered in an + ** order that makes the ORDER BY redundant. */ + for(ii=0; iinExpr; ii++){ + u8 sortFlags; + sortFlags = sSort.pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; + pGroupBy->a[ii].fg.sortFlags = sortFlags; + } + if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ + orderByGrp = 1; + } } }else{ assert( 0==sqlite3LogEst(1) ); @@ -157098,7 +156078,7 @@ SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerS sqlite3SelectDelete(db, pTmp->pSelect); sqlite3IdListDelete(db, pTmp->pIdList); sqlite3UpsertDelete(db, pTmp->pUpsert); - sqlite3SrcListDelete(db, pTmp->pSrc); + sqlite3SrcListDelete(db, pTmp->pFrom); sqlite3DbFree(db, pTmp->zSpan); sqlite3DbFree(db, pTmp); @@ -157287,16 +156267,11 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( } } - /* NB: The SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES compile-time option is - ** experimental and unsupported. Do not use it unless understand the - ** implications and you cannot get by without this capability. */ -#if !defined(SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES) /* Experimental */ /* Do not create a trigger on a system table */ if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ sqlite3ErrorMsg(pParse, "cannot create trigger on system table"); goto trigger_cleanup; } -#endif /* INSTEAD of triggers are only for views and views only support INSTEAD ** of triggers. @@ -157408,7 +156383,6 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup; zName = pTrig->zName; iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); - assert( iDb>=00 && iDbnDb ); pTrig->step_list = pStepList; while( pStepList ){ pStepList->pTrig = pTrig; @@ -157443,12 +156417,12 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( if( sqlite3ReadOnlyShadowTables(db) ){ TriggerStep *pStep; for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){ - if( pStep->pSrc!=0 - && sqlite3ShadowTableName(db, pStep->pSrc->a[0].zName) + if( pStep->zTarget!=0 + && sqlite3ShadowTableName(db, pStep->zTarget) ){ sqlite3ErrorMsg(pParse, "trigger \"%s\" may not write to shadow table \"%s\"", - pTrig->zName, pStep->pSrc->a[0].zName); + pTrig->zName, pStep->zTarget); goto triggerfinish_cleanup; } } @@ -157539,39 +156513,26 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep( static TriggerStep *triggerStepAllocate( Parse *pParse, /* Parser context */ u8 op, /* Trigger opcode */ - SrcList *pTabList, /* Target table */ + Token *pName, /* The target name */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ ){ - Trigger *pNew = pParse->pNewTrigger; sqlite3 *db = pParse->db; - TriggerStep *pTriggerStep = 0; + TriggerStep *pTriggerStep; - if( pParse->nErr==0 ){ - if( pNew - && pNew->pSchema!=db->aDb[1].pSchema - && pTabList->a[0].u4.zDatabase - ){ - sqlite3ErrorMsg(pParse, - "qualified table names are not allowed on INSERT, UPDATE, and DELETE " - "statements within triggers"); - }else{ - pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep)); - if( pTriggerStep ){ - pTriggerStep->pSrc = sqlite3SrcListDup(db, pTabList, EXPRDUP_REDUCE); - pTriggerStep->op = op; - pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); - if( pTriggerStep->pSrc && IN_RENAME_OBJECT ){ - sqlite3RenameTokenRemap(pParse, - pTriggerStep->pSrc->a[0].zName, - pTabList->a[0].zName - ); - } - } + if( pParse->nErr ) return 0; + pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); + if( pTriggerStep ){ + char *z = (char*)&pTriggerStep[1]; + memcpy(z, pName->z, pName->n); + sqlite3Dequote(z); + pTriggerStep->zTarget = z; + pTriggerStep->op = op; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, pTriggerStep->zTarget, pName); } } - - sqlite3SrcListDelete(db, pTabList); return pTriggerStep; } @@ -157584,7 +156545,7 @@ static TriggerStep *triggerStepAllocate( */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( Parse *pParse, /* Parser */ - SrcList *pTabList, /* Table to INSERT into */ + Token *pTableName, /* Name of the table into which we insert */ IdList *pColumn, /* List of columns in pTableName to insert into */ Select *pSelect, /* A SELECT statement that supplies values */ u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ @@ -157597,7 +156558,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( assert(pSelect != 0 || db->mallocFailed); - pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTabList, zStart, zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTableName,zStart,zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pSelect = pSelect; @@ -157629,7 +156590,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( Parse *pParse, /* Parser */ - SrcList *pTabList, /* Name of the table to be updated */ + Token *pTableName, /* Name of the table to be updated */ SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ @@ -157640,36 +156601,21 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTabList, zStart, zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTableName,zStart,zEnd); if( pTriggerStep ){ - SrcList *pFromDup = 0; if( IN_RENAME_OBJECT ){ pTriggerStep->pExprList = pEList; pTriggerStep->pWhere = pWhere; - pFromDup = pFrom; + pTriggerStep->pFrom = pFrom; pEList = 0; pWhere = 0; pFrom = 0; }else{ pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); - pFromDup = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); + pTriggerStep->pFrom = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); } pTriggerStep->orconf = orconf; - - if( pFromDup && !IN_RENAME_OBJECT){ - Select *pSub; - Token as = {0, 0}; - pSub = sqlite3SelectNew(pParse, 0, pFromDup, 0,0,0,0, SF_NestedFrom, 0); - pFromDup = sqlite3SrcListAppendFromTerm(pParse, 0, 0, 0, &as, pSub ,0); - } - if( pFromDup && pTriggerStep->pSrc ){ - pTriggerStep->pSrc = sqlite3SrcListAppendList( - pParse, pTriggerStep->pSrc, pFromDup - ); - }else{ - sqlite3SrcListDelete(db, pFromDup); - } } sqlite3ExprListDelete(db, pEList); sqlite3ExprDelete(db, pWhere); @@ -157684,7 +156630,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep( Parse *pParse, /* Parser */ - SrcList *pTabList, /* The table from which rows are deleted */ + Token *pTableName, /* The table from which rows are deleted */ Expr *pWhere, /* The WHERE clause */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ @@ -157692,7 +156638,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTabList, zStart, zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTableName,zStart,zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pWhere = pWhere; @@ -157892,7 +156838,6 @@ static SQLITE_NOINLINE Trigger *triggersReallyExist( p = pList; if( (pParse->db->flags & SQLITE_EnableTrigger)==0 && pTab->pTrigger!=0 - && sqlite3SchemaToIndex(pParse->db, pTab->pTrigger->pSchema)!=1 ){ /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that ** only TEMP triggers are allowed. Truncate the pList so that it @@ -157955,6 +156900,52 @@ SQLITE_PRIVATE Trigger *sqlite3TriggersExist( return triggersReallyExist(pParse,pTab,op,pChanges,pMask); } +/* +** Convert the pStep->zTarget string into a SrcList and return a pointer +** to that SrcList. +** +** This routine adds a specific database name, if needed, to the target when +** forming the SrcList. This prevents a trigger in one database from +** referring to a target in another database. An exception is when the +** trigger is in TEMP in which case it can refer to any other database it +** wants. +*/ +SQLITE_PRIVATE SrcList *sqlite3TriggerStepSrc( + Parse *pParse, /* The parsing context */ + TriggerStep *pStep /* The trigger containing the target token */ +){ + sqlite3 *db = pParse->db; + SrcList *pSrc; /* SrcList to be returned */ + char *zName = sqlite3DbStrDup(db, pStep->zTarget); + pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + assert( pSrc==0 || pSrc->nSrc==1 ); + assert( zName || pSrc==0 ); + if( pSrc ){ + Schema *pSchema = pStep->pTrig->pSchema; + pSrc->a[0].zName = zName; + if( pSchema!=db->aDb[1].pSchema ){ + assert( pSrc->a[0].fg.fixedSchema || pSrc->a[0].u4.zDatabase==0 ); + pSrc->a[0].u4.pSchema = pSchema; + pSrc->a[0].fg.fixedSchema = 1; + } + if( pStep->pFrom ){ + SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0); + if( pDup && pDup->nSrc>1 && !IN_RENAME_OBJECT ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pDup,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pDup = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup); + } + }else{ + sqlite3DbFree(db, zName); + } + return pSrc; +} + /* ** Return true if the pExpr term from the RETURNING clause argument ** list is of the form "*". Raise an error if the terms if of the @@ -158220,7 +157211,7 @@ static int codeTriggerProgram( switch( pStep->op ){ case TK_UPDATE: { sqlite3Update(pParse, - sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3TriggerStepSrc(pParse, pStep), sqlite3ExprListDup(db, pStep->pExprList, 0), sqlite3ExprDup(db, pStep->pWhere, 0), pParse->eOrconf, 0, 0, 0 @@ -158230,7 +157221,7 @@ static int codeTriggerProgram( } case TK_INSERT: { sqlite3Insert(pParse, - sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3TriggerStepSrc(pParse, pStep), sqlite3SelectDup(db, pStep->pSelect, 0), sqlite3IdListDup(db, pStep->pIdList), pParse->eOrconf, @@ -158241,7 +157232,7 @@ static int codeTriggerProgram( } case TK_DELETE: { sqlite3DeleteFrom(pParse, - sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3TriggerStepSrc(pParse, pStep), sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0 ); sqlite3VdbeAddOp0(v, OP_ResetCount); @@ -160563,11 +159554,9 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( pDb = &db->aDb[nDb]; assert( strcmp(pDb->zDbSName,zDbVacuum)==0 ); pTemp = pDb->pBt; - nRes = sqlite3BtreeGetRequestedReserve(pMain); if( pOut ){ sqlite3_file *id = sqlite3PagerFile(sqlite3BtreePager(pTemp)); i64 sz = 0; - const char *zFilename; if( id->pMethods!=0 && (sqlite3OsFileSize(id, &sz)!=SQLITE_OK || sz>0) ){ rc = SQLITE_ERROR; sqlite3SetString(pzErrMsg, db, "output file already exists"); @@ -160579,16 +159568,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( ** they are for the database being vacuumed, except that PAGER_CACHESPILL ** is always set. */ pgflags = db->aDb[iDb].safety_level | (db->flags & PAGER_FLAGS_MASK); - - /* If the VACUUM INTO target file is a URI filename and if the - ** "reserve=N" query parameter is present, reset the reserve to the - ** amount specified, if the amount is within range */ - zFilename = sqlite3BtreeGetFilename(pTemp); - if( ALWAYS(zFilename) ){ - int nNew = (int)sqlite3_uri_int64(zFilename, "reserve", nRes); - if( nNew>=0 && nNew<=255 ) nRes = nNew; - } } + nRes = sqlite3BtreeGetRequestedReserve(pMain); sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); @@ -164393,7 +163374,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ int iTab = pParse->nTab++; int iCache = ++pParse->nMem; - sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab, 0); + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab); sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); }else{ codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); @@ -165712,6 +164693,15 @@ SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); } } + if( pLevel->iIdxCur ){ + /* pSubWhere may contain expressions that read from an index on the + ** table on the RHS of the right join. All such expressions first test + ** if the index is pointing at a NULL row, and if so, read from the + ** table cursor instead. So ensure that the index cursor really is + ** pointing at a NULL row here, so that no values are read from it during + ** the scan of the RHS of the RIGHT join below. */ + sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); + } pFrom = &uSrc.sSrc; pFrom->nSrc = 1; pFrom->nAlloc = 1; @@ -166052,14 +165042,13 @@ static int isLikeOrGlob( ){ int isNum; double rDummy; - assert( zNew[iTo]==0 ); - isNum = sqlite3AtoF(zNew, &rDummy); + isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); if( isNum<=0 ){ if( iTo==1 && zNew[0]=='-' ){ isNum = +1; }else{ zNew[iTo-1]++; - isNum = sqlite3AtoF(zNew, &rDummy); + isNum = sqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8); zNew[iTo-1]--; } } @@ -166102,34 +165091,6 @@ static int isLikeOrGlob( } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ -/* -** If pExpr is one of "like", "glob", "match", or "regexp", then -** return the corresponding SQLITE_INDEX_CONSTRAINT_xxxx value. -** If not, return 0. -** -** pExpr is guaranteed to be a TK_FUNCTION. -*/ -SQLITE_PRIVATE int sqlite3ExprIsLikeOperator(const Expr *pExpr){ - static const struct { - const char *zOp; - unsigned char eOp; - } aOp[] = { - { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, - { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, - { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, - { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } - }; - int i; - assert( pExpr->op==TK_FUNCTION ); - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - for(i=0; iu.zToken, aOp[i].zOp)==0 ){ - return aOp[i].eOp; - } - } - return 0; -} - #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -166166,6 +165127,15 @@ static int isAuxiliaryVtabOperator( Expr **ppRight /* Expression to left of MATCH/op2 */ ){ if( pExpr->op==TK_FUNCTION ){ + static const struct Op2 { + const char *zOp; + unsigned char eOp2; + } aOp[] = { + { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, + { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, + { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, + { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } + }; ExprList *pList; Expr *pCol; /* Column reference */ int i; @@ -166185,11 +165155,16 @@ static int isAuxiliaryVtabOperator( */ pCol = pList->a[1].pExpr; assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); - if( ExprIsVtab(pCol) && (i = sqlite3ExprIsLikeOperator(pExpr))!=0 ){ - *peOp2 = i; - *ppRight = pList->a[0].pExpr; - *ppLeft = pCol; - return 1; + if( ExprIsVtab(pCol) ){ + for(i=0; iu.zToken, aOp[i].zOp)==0 ){ + *peOp2 = aOp[i].eOp2; + *ppRight = pList->a[0].pExpr; + *ppLeft = pCol; + return 1; + } + } } /* We can also match against the first column of overloaded @@ -166323,22 +165298,16 @@ static void whereCombineDisjuncts( Expr *pNew; /* New virtual expression */ int op; /* Operator for the combined expression */ int idxNew; /* Index in pWC of the next virtual term */ - Expr *pA, *pB; /* Expressions associated with pOne and pTwo */ if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return; if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp && (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return; - pA = pOne->pExpr; - pB = pTwo->pExpr; - assert( pA->pLeft!=0 && pA->pRight!=0 ); - assert( pB->pLeft!=0 && pB->pRight!=0 ); - if( sqlite3ExprCompare(0,pA->pLeft, pB->pLeft, -1) ) return; - if( sqlite3ExprCompare(0,pA->pRight, pB->pRight,-1) ) return; - if( ExprHasProperty(pA,EP_Commuted)!=ExprHasProperty(pB,EP_Commuted) ){ - return; - } + assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 ); + assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 ); + if( sqlite3ExprCompare(0,pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; + if( sqlite3ExprCompare(0,pOne->pExpr->pRight, pTwo->pExpr->pRight,-1) )return; /* If we reach this point, it means the two subterms can be combined */ if( (eOp & (eOp-1))!=0 ){ if( eOp & (WO_LT|WO_LE) ){ @@ -166349,7 +165318,7 @@ static void whereCombineDisjuncts( } } db = pWC->pWInfo->pParse->db; - pNew = sqlite3ExprDup(db, pA, 0); + pNew = sqlite3ExprDup(db, pOne->pExpr, 0); if( pNew==0 ) return; for(op=TK_EQ; eOp!=(WO_EQ<<(op-TK_EQ)); op++){ assert( opop = op; @@ -167389,11 +166358,13 @@ static void whereAddLimitExpr( int iVal = 0; if( sqlite3ExprIsInteger(pExpr, &iVal, pParse) && iVal>=0 ){ - Expr *pVal = sqlite3ExprInt32(db, iVal); + Expr *pVal = sqlite3Expr(db, TK_INTEGER, 0); if( pVal==0 ) return; + ExprSetProperty(pVal, EP_IntValue); + pVal->u.iValue = iVal; pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); }else{ - Expr *pVal = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0); + Expr *pVal = sqlite3Expr(db, TK_REGISTER, 0); if( pVal==0 ) return; pVal->iTable = iReg; pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); @@ -169220,14 +168191,11 @@ static sqlite3_index_info *allocateIndexInfo( break; } if( i==n ){ - int bSortByGroup = (pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0; nOrderBy = n; if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) && !pSrc->fg.rowidUsed ){ - eDistinct = 2 + bSortByGroup; + eDistinct = 2 + ((pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0); }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ - eDistinct = 1 - bSortByGroup; - }else if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ - eDistinct = 3; + eDistinct = 1; } } } @@ -170638,67 +169606,6 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ return rc; } -/* -** Callback for estLikePatternLength(). -** -** If this node is a string literal that is longer pWalker->sz, then set -** pWalker->sz to the byte length of that string literal. -** -** pWalker->eCode indicates how to count characters: -** -** eCode==0 Count as a GLOB pattern -** eCode==1 Count as a LIKE pattern -*/ -static int exprNodePatternLengthEst(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_STRING ){ - int sz = 0; /* Pattern size in bytes */ - u8 *z = (u8*)pExpr->u.zToken; /* The pattern */ - u8 c; /* Next character of the pattern */ - u8 c1, c2, c3; /* Wildcards */ - if( pWalker->eCode ){ - c1 = '%'; - c2 = '_'; - c3 = 0; - }else{ - c1 = '*'; - c2 = '?'; - c3 = '['; - } - while( (c = *(z++))!=0 ){ - if( c==c3 ){ - if( *z ) z++; - while( *z && *z!=']' ) z++; - }else if( c!=c1 && c!=c2 ){ - sz++; - } - } - if( sz>pWalker->u.sz ) pWalker->u.sz = sz; - } - return WRC_Continue; -} - -/* -** Return the length of the longest string literal in the given -** expression. -** -** eCode indicates how to count characters: -** -** eCode==0 Count as a GLOB pattern -** eCode==1 Count as a LIKE pattern -*/ -static int estLikePatternLength(Expr *p, u16 eCode){ - Walker w; - w.u.sz = 0; - w.eCode = eCode; - w.xExprCallback = exprNodePatternLengthEst; - w.xSelectCallback = sqlite3SelectWalkFail; -#ifdef SQLITE_DEBUG - w.xSelectCallback2 = sqlite3SelectWalkAssert2; -#endif - sqlite3WalkExpr(&w, p); - return w.u.sz; -} - /* ** Adjust the WhereLoop.nOut value downward to account for terms of the ** WHERE clause that reference the loop but which are not used by an @@ -170727,13 +169634,6 @@ static int estLikePatternLength(Expr *p, u16 eCode){ ** "x" column is boolean or else -1 or 0 or 1 is a common default value ** on the "x" column and so in that case only cap the output row estimate ** at 1/2 instead of 1/4. -** -** Heuristic 3: If there is a LIKE or GLOB (or REGEXP or MATCH) operator -** with a large constant pattern, then reduce the size of the search -** space according to the length of the pattern, under the theory that -** longer patterns are less likely to match. This heuristic was added -** to give better output-row count estimates when preparing queries for -** the Join-Order Benchmarks. See forum thread 2026-01-30T09:57:54z */ static void whereLoopOutputAdjust( WhereClause *pWC, /* The WHERE clause */ @@ -170783,14 +169683,13 @@ static void whereLoopOutputAdjust( }else{ /* In the absence of explicit truth probabilities, use heuristics to ** guess a reasonable truth probability. */ - Expr *pOpExpr = pTerm->pExpr; pLoop->nOut--; if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && (pTerm->wtFlags & TERM_HIGHTRUTH)==0 /* tag-20200224-1 */ ){ - Expr *pRight = pOpExpr->pRight; + Expr *pRight = pTerm->pExpr->pRight; int k = 0; - testcase( pOpExpr->op==TK_IS ); + testcase( pTerm->pExpr->op==TK_IS ); if( sqlite3ExprIsInteger(pRight, &k, 0) && k>=(-1) && k<=1 ){ k = 10; }else{ @@ -170800,23 +169699,6 @@ static void whereLoopOutputAdjust( pTerm->wtFlags |= TERM_HEURTRUTH; iReduce = k; } - }else - if( ExprHasProperty(pOpExpr, EP_InfixFunc) - && pOpExpr->op==TK_FUNCTION - ){ - int eOp; - assert( ExprUseXList(pOpExpr) ); - assert( pOpExpr->x.pList->nExpr>=2 ); - eOp = sqlite3ExprIsLikeOperator(pOpExpr); - if( ALWAYS(eOp>0) ){ - int szPattern; - Expr *pRHS = pOpExpr->x.pList->a[0].pExpr; - eOp = eOp==SQLITE_INDEX_CONSTRAINT_LIKE; - szPattern = estLikePatternLength(pRHS, eOp); - if( szPattern>0 ){ - pLoop->nOut -= szPattern*2; - } - } } } } @@ -170888,7 +169770,7 @@ static int whereRangeVectorLen( idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); if( aff!=idxaff ) break; - pColl = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr); + pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); if( pColl==0 ) break; if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; } @@ -171277,7 +170159,6 @@ static int whereLoopAddBtreeIndex( pNew->rRun += nInMul + nIn; pNew->nOut += nInMul + nIn; whereLoopOutputAdjust(pBuilder->pWC, pNew, rSize); - if( pSrc->fg.fromExists ) pNew->nOut = 0; rc = whereLoopInsert(pBuilder, pNew); if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ @@ -171874,8 +170755,6 @@ static int whereLoopAddBtree( if( pSrc->fg.isSubquery ){ if( pSrc->fg.viaCoroutine ) pNew->wsFlags |= WHERE_COROUTINE; pNew->u.btree.pOrderBy = pSrc->u4.pSubq->pSelect->pOrderBy; - }else if( pSrc->fg.fromExists ){ - pNew->nOut = 0; } rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; @@ -171978,7 +170857,6 @@ static int whereLoopAddBtree( ** positioned to the correct row during the right-join no-match ** loop. */ }else{ - if( pSrc->fg.fromExists ) pNew->nOut = 0; rc = whereLoopInsert(pBuilder, pNew); } pNew->nOut = rSize; @@ -172641,7 +171519,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ sqlite3 *db = pWInfo->pParse->db; int rc = SQLITE_OK; int bFirstPastRJ = 0; - int hasRightCrossJoin = 0; + int hasRightJoin = 0; WhereLoop *pNew; @@ -172668,34 +171546,15 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ ** prevents the right operand of a RIGHT JOIN from being swapped with ** other elements even further to the right. ** - ** The hasRightCrossJoin flag prevent FROM-clause terms from moving - ** from the right side of a LEFT JOIN or CROSS JOIN over to the - ** left side of that same join. This is a required restriction in - ** the case of LEFT JOIN - an incorrect answer may results if it is - ** not enforced. This restriction is not required for CROSS JOIN. - ** It is provided merely as a means of controlling join order, under - ** the theory that no real-world queries that care about performance - ** actually use the CROSS JOIN syntax. + ** The JT_LTORJ case and the hasRightJoin flag work together to + ** prevent FROM-clause terms from moving from the right side of + ** a LEFT JOIN over to the left side of that join if the LEFT JOIN + ** is itself on the left side of a RIGHT JOIN. */ - if( pItem->fg.jointype & (JT_LTORJ|JT_CROSS) ){ - testcase( pItem->fg.jointype & JT_LTORJ ); - testcase( pItem->fg.jointype & JT_CROSS ); - hasRightCrossJoin = 1; - } + if( pItem->fg.jointype & JT_LTORJ ) hasRightJoin = 1; mPrereq |= mPrior; bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0; - }else if( pItem->fg.fromExists ){ - /* joins that result from the EXISTS-to-JOIN optimization should not - ** be moved to the left of any of their dependencies */ - WhereClause *pWC = &pWInfo->sWC; - WhereTerm *pTerm; - int i; - for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ - if( (pNew->maskSelf & pTerm->prereqAll)!=0 ){ - mPrereq |= (pTerm->prereqAll & (pNew->maskSelf-1)); - } - } - }else if( !hasRightCrossJoin ){ + }else if( !hasRightJoin ){ mPrereq = 0; } #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -172918,7 +171777,9 @@ static i8 wherePathSatisfiesOrderBy( pLoop = pLast; } if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){ - if( pLoop->u.vtab.isOrdered && pWInfo->pOrderBy==pOrderBy ){ + if( pLoop->u.vtab.isOrdered + && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) + ){ obSat = obDone; }else{ /* No further ORDER BY terms may be matched. So this call should @@ -173294,21 +172155,12 @@ static LogEst whereSortingCost( ** 12 otherwise ** ** For the purposes of this heuristic, a star-query is defined as a query -** with a central "fact" table that is joined against multiple -** "dimension" tables, subject to the following constraints: -** -** (aa) Only a five-way or larger join is considered for this -** optimization. If there are fewer than four terms in the FROM -** clause, this heuristic does not apply. -** -** (bb) The join between the fact table and the dimension tables must -** be an INNER join. CROSS and OUTER JOINs do not qualify. -** -** (cc) A table must have 3 or more dimension tables in order to be -** considered a fact table. (Was 4 prior to 2026-02-10.) -** -** (dd) A table that is a self-join cannot be a dimension table. -** Dimension tables are joined against fact tables. +** with a large central table that is joined using an INNER JOIN, +** not CROSS or OUTER JOINs, against four or more smaller tables. +** The central table is called the "fact" table. The smaller tables +** that get joined are "dimension tables". Also, any table that is +** self-joined cannot be a dimension table; we assume that dimension +** tables may only be joined against fact tables. ** ** SIDE EFFECT: (and really the whole point of this subroutine) ** @@ -173361,7 +172213,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ } #endif /* SQLITE_DEBUG */ - if( nLoop>=4 /* Constraint (aa) */ + if( nLoop>=5 && !pWInfo->bStarDone && OptimizationEnabled(pWInfo->pParse->db, SQLITE_StarQuery) ){ @@ -173373,7 +172225,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ pWInfo->bStarDone = 1; /* Only do this computation once */ - /* Look for fact tables with three or more dimensions where the + /* Look for fact tables with four or more dimensions where the ** dimension tables are not separately from the fact tables by an outer ** or cross join. Adjust cost weights if found. */ @@ -173390,17 +172242,18 @@ static int computeMxChoice(WhereInfo *pWInfo){ if( (pFactTab->fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ /* If the candidate fact-table is the right table of an outer join ** restrict the search for dimension-tables to be tables to the right - ** of the fact-table. Constraint (bb) */ - if( iFromIdx+3 > nLoop ){ - break; /* ^-- Impossible to reach nDep>=2 - Constraint (cc) */ - } + ** of the fact-table. */ + if( iFromIdx+4 > nLoop ) break; /* Impossible to reach nDep>=4 */ while( pStart && pStart->iTab<=iFromIdx ){ pStart = pStart->pNextLoop; } } for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ if( (aFromTabs[pWLoop->iTab].fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ - break; /* Constraint (bb) */ + /* Fact-tables and dimension-tables cannot be separated by an + ** outer join (at least for the definition of fact- and dimension- + ** used by this heuristic). */ + break; } if( (pWLoop->prereq & m)!=0 /* pWInfo depends on iFromIdx */ && (pWLoop->maskSelf & mSeen)==0 /* pWInfo not already a dependency */ @@ -173414,9 +172267,7 @@ static int computeMxChoice(WhereInfo *pWInfo){ } } } - if( nDep<=2 ){ - continue; /* Constraint (cc) */ - } + if( nDep<=3 ) continue; /* If we reach this point, it means that pFactTab is a fact table ** with four or more dimensions connected by inner joins. Proceed @@ -173429,23 +172280,6 @@ static int computeMxChoice(WhereInfo *pWInfo){ pWLoop->rStarDelta = 0; } } -#endif -#ifdef WHERETRACE_ENABLED /* 0x80000 */ - if( sqlite3WhereTrace & 0x80000 ){ - Bitmask mShow = mSeen; - sqlite3DebugPrintf("Fact table %s(%d), dimensions:", - pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName, - iFromIdx); - for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ - if( mShow & pWLoop->maskSelf ){ - SrcItem *pDim = aFromTabs + pWLoop->iTab; - mShow &= ~pWLoop->maskSelf; - sqlite3DebugPrintf(" %s(%d)", - pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab); - } - } - sqlite3DebugPrintf("\n"); - } #endif pWInfo->bStarUsed = 1; @@ -173469,8 +172303,10 @@ static int computeMxChoice(WhereInfo *pWInfo){ if( sqlite3WhereTrace & 0x80000 ){ SrcItem *pDim = aFromTabs + pWLoop->iTab; sqlite3DebugPrintf( - "Increase SCAN cost of %s to %d\n", - pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, mxRun + "Increase SCAN cost of dimension %s(%d) of fact %s(%d) to %d\n", + pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab, + pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName, + iFromIdx, mxRun ); } pWLoop->rStarDelta = mxRun - pWLoop->rRun; @@ -174284,7 +173120,6 @@ static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ pTerm->wtFlags |= TERM_CODED; - pTerm->prereqAll = 0; } } if( i!=pWInfo->nLevel-1 ){ @@ -175272,15 +174107,14 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ } #endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */ } - if( pTabList->a[pLevel->iFrom].fg.fromExists - && (i==pWInfo->nLevel-1 - || pTabList->a[pWInfo->a[i+1].iFrom].fg.fromExists==0) - ){ - /* This is an EXISTS-to-JOIN optimization which is either the - ** inner-most loop, or the inner-most of a group of nested - ** EXISTS-to-JOIN optimization loops. If this loop sees a successful - ** row, it should break out of itself as well as other EXISTS-to-JOIN - ** loops in which is is directly nested. */ + if( pTabList->a[pLevel->iFrom].fg.fromExists && i==pWInfo->nLevel-1 ){ + /* If the EXISTS-to-JOIN optimization was applied, then the EXISTS + ** loop(s) will be the inner-most loops of the join. There might be + ** multiple EXISTS loops, but they will all be nested, and the join + ** order will not have been changed by the query planner. If the + ** inner-most EXISTS loop sees a single successful row, it should + ** break out of *all* EXISTS loops. But only the inner-most of the + ** nested EXISTS loops should do this breakout. */ int nOuter = 0; /* Nr of outer EXISTS that this one is nested within */ while( nOutera[pLevel[-nOuter-1].iFrom].fg.fromExists ) break; @@ -175288,11 +174122,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ } testcase( nOuter>0 ); sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel[-nOuter].addrBrk); - if( nOuter ){ - VdbeComment((v, "EXISTS break %d..%d", i-nOuter, i)); - }else{ - VdbeComment((v, "EXISTS break %d", i)); - } + VdbeComment((v, "EXISTS break")); } sqlite3VdbeResolveLabel(v, pLevel->addrCont); if( pLevel->op!=OP_Noop ){ @@ -176309,7 +175139,7 @@ SQLITE_PRIVATE void sqlite3WindowUpdate( pWin->eEnd = aUp[i].eEnd; pWin->eExclude = 0; if( pWin->eStart==TK_FOLLOWING ){ - pWin->pStart = sqlite3ExprInt32(db, 1); + pWin->pStart = sqlite3Expr(db, TK_INTEGER, "1"); } break; } @@ -176654,7 +175484,9 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ ** keep everything legal in this case. */ if( pSublist==0 ){ - pSublist = sqlite3ExprListAppend(pParse, 0, sqlite3ExprInt32(db, 0)); + pSublist = sqlite3ExprListAppend(pParse, 0, + sqlite3Expr(db, TK_INTEGER, "0") + ); } pSub = sqlite3SelectNew( @@ -178878,23 +177710,8 @@ static void updateDeleteLimitError( ** sqlite3_realloc() that includes a call to sqlite3FaultSim() to facilitate ** testing. */ - static void *parserStackRealloc( - void *pOld, /* Prior allocation */ - sqlite3_uint64 newSize, /* Requested new alloation size */ - Parse *pParse /* Parsing context */ - ){ - void *p = sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); - if( p==0 ) sqlite3OomFault(pParse->db); - return p; - } - static void parserStackFree(void *pOld, Parse *pParse){ - (void)pParse; - sqlite3_free(pOld); - } - - /* Return an integer that is the maximum allowed stack size */ - static int parserStackSizeLimit(Parse *pParse){ - return pParse->db->aLimit[SQLITE_LIMIT_PARSER_DEPTH]; + static void *parserStackRealloc(void *pOld, sqlite3_uint64 newSize){ + return sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); } @@ -178933,46 +177750,15 @@ static void updateDeleteLimitError( } - /* Create a TK_ISNULL or TK_NOTNULL expression, perhaps optimized to - ** to TK_TRUEFALSE, if possible */ - static Expr *sqlite3PExprIsNull( - Parse *pParse, /* Parsing context */ - int op, /* TK_ISNULL or TK_NOTNULL */ - Expr *pLeft /* Operand */ - ){ - Expr *p = pLeft; - assert( op==TK_ISNULL || op==TK_NOTNULL ); - assert( pLeft!=0 ); - while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ - p = p->pLeft; - assert( p!=0 ); - } - switch( p->op ){ - case TK_INTEGER: - case TK_STRING: - case TK_FLOAT: - case TK_BLOB: - sqlite3ExprDeferredDelete(pParse, pLeft); - return sqlite3ExprInt32(pParse->db, op==TK_NOTNULL); - default: - break; - } - return sqlite3PExpr(pParse, op, pLeft, 0); - } - - /* Create a TK_IS or TK_ISNOT operator, perhaps optimized to - ** TK_ISNULL or TK_NOTNULL or TK_TRUEFALSE. */ - static Expr *sqlite3PExprIs( - Parse *pParse, /* Parsing context */ - int op, /* TK_IS or TK_ISNOT */ - Expr *pLeft, /* Left operand */ - Expr *pRight /* Right operand */ - ){ - if( pRight && pRight->op==TK_NULL ){ - sqlite3ExprDeferredDelete(pParse, pRight); - return sqlite3PExprIsNull(pParse, op==TK_IS ? TK_ISNULL : TK_NOTNULL, pLeft); + /* A routine to convert a binary TK_IS or TK_ISNOT expression into a + ** unary TK_ISNULL or TK_NOTNULL expression. */ + static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){ + sqlite3 *db = pParse->db; + if( pA && pY && pY->op==TK_NULL && !IN_RENAME_OBJECT ){ + pA->op = (u8)op; + sqlite3ExprDelete(db, pA->pRight); + pA->pRight = 0; } - return sqlite3PExpr(pParse, op, pLeft, pRight); } /* Add a single new term to an ExprList that is used to store a @@ -179255,72 +178041,63 @@ static void updateDeleteLimitError( #endif /************* Begin control #defines *****************************************/ #define YYCODETYPE unsigned short int -#define YYNOCODE 322 +#define YYNOCODE 323 #define YYACTIONTYPE unsigned short int #define YYWILDCARD 102 #define sqlite3ParserTOKENTYPE Token typedef union { int yyinit; sqlite3ParserTOKENTYPE yy0; - ExprList* yy14; - With* yy59; - Cte* yy67; - Upsert* yy122; - IdList* yy132; - int yy144; - const char* yy168; - SrcList* yy203; - Window* yy211; - OnOrUsing yy269; - struct TrigEvent yy286; - struct {int value; int mask;} yy383; - u32 yy391; - TriggerStep* yy427; - Expr* yy454; - u8 yy462; - struct FrameBound yy509; - Select* yy555; + u32 yy9; + struct TrigEvent yy28; + With* yy125; + IdList* yy204; + struct FrameBound yy205; + TriggerStep* yy319; + const char* yy342; + Cte* yy361; + ExprList* yy402; + Upsert* yy403; + OnOrUsing yy421; + u8 yy444; + struct {int value; int mask;} yy481; + Window* yy483; + int yy502; + SrcList* yy563; + Expr* yy590; + Select* yy637; } YYMINORTYPE; #ifndef YYSTACKDEPTH -#define YYSTACKDEPTH 50 +#define YYSTACKDEPTH 100 #endif #define sqlite3ParserARG_SDECL #define sqlite3ParserARG_PDECL #define sqlite3ParserARG_PARAM #define sqlite3ParserARG_FETCH #define sqlite3ParserARG_STORE -#undef YYREALLOC #define YYREALLOC parserStackRealloc -#undef YYFREE -#define YYFREE parserStackFree -#undef YYDYNSTACK +#define YYFREE sqlite3_free #define YYDYNSTACK 1 -#undef YYSIZELIMIT -#define YYSIZELIMIT parserStackSizeLimit -#define sqlite3ParserCTX(P) ((P)->pParse) #define sqlite3ParserCTX_SDECL Parse *pParse; #define sqlite3ParserCTX_PDECL ,Parse *pParse #define sqlite3ParserCTX_PARAM ,pParse #define sqlite3ParserCTX_FETCH Parse *pParse=yypParser->pParse; #define sqlite3ParserCTX_STORE yypParser->pParse=pParse; -#undef YYERRORSYMBOL -#undef YYERRSYMDT -#undef YYFALLBACK #define YYFALLBACK 1 -#define YYNSTATE 600 -#define YYNRULE 412 -#define YYNRULE_WITH_ACTION 348 +#define YYNSTATE 583 +#define YYNRULE 409 +#define YYNRULE_WITH_ACTION 344 #define YYNTOKEN 187 -#define YY_MAX_SHIFT 599 -#define YY_MIN_SHIFTREDUCE 867 -#define YY_MAX_SHIFTREDUCE 1278 -#define YY_ERROR_ACTION 1279 -#define YY_ACCEPT_ACTION 1280 -#define YY_NO_ACTION 1281 -#define YY_MIN_REDUCE 1282 -#define YY_MAX_REDUCE 1693 +#define YY_MAX_SHIFT 582 +#define YY_MIN_SHIFTREDUCE 845 +#define YY_MAX_SHIFTREDUCE 1253 +#define YY_ERROR_ACTION 1254 +#define YY_ACCEPT_ACTION 1255 +#define YY_NO_ACTION 1256 +#define YY_MIN_REDUCE 1257 +#define YY_MAX_REDUCE 1665 #define YY_MIN_DSTRCTR 206 -#define YY_MAX_DSTRCTR 319 +#define YY_MAX_DSTRCTR 320 /************* End control #defines *******************************************/ #define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) @@ -179403,680 +178180,643 @@ typedef union { ** yy_default[] Default action for each state. ** *********** Begin parsing tables **********************************************/ -#define YY_ACTTAB_COUNT (2379) +#define YY_ACTTAB_COUNT (2207) static const YYACTIONTYPE yy_action[] = { - /* 0 */ 134, 131, 238, 290, 290, 1353, 593, 1332, 478, 1606, - /* 10 */ 593, 1315, 593, 7, 593, 1353, 590, 593, 579, 424, - /* 20 */ 1566, 134, 131, 238, 1318, 541, 478, 477, 575, 84, - /* 30 */ 84, 1005, 303, 84, 84, 51, 51, 63, 63, 1006, - /* 40 */ 84, 84, 498, 141, 142, 93, 442, 1254, 1254, 1085, - /* 50 */ 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, 424, - /* 60 */ 296, 296, 498, 296, 296, 567, 553, 296, 296, 1306, - /* 70 */ 574, 1358, 1358, 590, 542, 579, 590, 574, 579, 548, - /* 80 */ 590, 1304, 579, 141, 142, 93, 576, 1254, 1254, 1085, - /* 90 */ 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, 399, - /* 100 */ 478, 395, 6, 138, 138, 138, 138, 137, 137, 136, - /* 110 */ 136, 136, 135, 132, 463, 44, 342, 593, 305, 1127, - /* 120 */ 1280, 1, 1, 599, 2, 1284, 598, 1200, 1284, 1200, - /* 130 */ 330, 424, 158, 330, 1613, 158, 390, 116, 308, 1366, - /* 140 */ 51, 51, 1366, 138, 138, 138, 138, 137, 137, 136, - /* 150 */ 136, 136, 135, 132, 463, 141, 142, 93, 515, 1254, - /* 160 */ 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, - /* 170 */ 140, 1230, 329, 584, 296, 296, 212, 296, 296, 568, - /* 180 */ 568, 488, 143, 1072, 1072, 1086, 1089, 590, 1195, 579, - /* 190 */ 590, 340, 579, 140, 140, 140, 140, 133, 392, 564, - /* 200 */ 536, 1195, 250, 425, 1195, 250, 137, 137, 136, 136, - /* 210 */ 136, 135, 132, 463, 291, 138, 138, 138, 138, 137, - /* 220 */ 137, 136, 136, 136, 135, 132, 463, 966, 1230, 1231, - /* 230 */ 1230, 412, 965, 467, 412, 424, 467, 489, 357, 1611, - /* 240 */ 391, 138, 138, 138, 138, 137, 137, 136, 136, 136, - /* 250 */ 135, 132, 463, 463, 134, 131, 238, 555, 1076, 141, - /* 260 */ 142, 93, 593, 1254, 1254, 1085, 1088, 1075, 1075, 139, - /* 270 */ 139, 140, 140, 140, 140, 1317, 134, 131, 238, 424, - /* 280 */ 549, 1597, 1531, 333, 97, 83, 83, 140, 140, 140, - /* 290 */ 140, 138, 138, 138, 138, 137, 137, 136, 136, 136, - /* 300 */ 135, 132, 463, 141, 142, 93, 1657, 1254, 1254, 1085, - /* 310 */ 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, 138, - /* 320 */ 138, 138, 138, 137, 137, 136, 136, 136, 135, 132, - /* 330 */ 463, 591, 1230, 958, 958, 138, 138, 138, 138, 137, - /* 340 */ 137, 136, 136, 136, 135, 132, 463, 44, 398, 547, - /* 350 */ 1306, 136, 136, 136, 135, 132, 463, 386, 593, 442, - /* 360 */ 595, 145, 595, 138, 138, 138, 138, 137, 137, 136, - /* 370 */ 136, 136, 135, 132, 463, 500, 1230, 112, 550, 460, - /* 380 */ 459, 51, 51, 424, 296, 296, 479, 334, 1259, 1230, - /* 390 */ 1231, 1230, 1599, 1261, 388, 312, 444, 590, 246, 579, - /* 400 */ 546, 1260, 271, 235, 329, 584, 551, 141, 142, 93, - /* 410 */ 429, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, - /* 420 */ 140, 140, 140, 22, 22, 1230, 1262, 424, 1262, 216, - /* 430 */ 296, 296, 98, 1230, 1231, 1230, 264, 884, 45, 528, - /* 440 */ 525, 524, 1041, 590, 1269, 579, 421, 420, 393, 523, - /* 450 */ 44, 141, 142, 93, 498, 1254, 1254, 1085, 1088, 1075, - /* 460 */ 1075, 139, 139, 140, 140, 140, 140, 138, 138, 138, - /* 470 */ 138, 137, 137, 136, 136, 136, 135, 132, 463, 593, - /* 480 */ 1611, 561, 1230, 1231, 1230, 23, 264, 515, 200, 528, - /* 490 */ 525, 524, 127, 585, 509, 4, 355, 487, 506, 523, - /* 500 */ 593, 498, 84, 84, 134, 131, 238, 329, 584, 588, - /* 510 */ 1627, 138, 138, 138, 138, 137, 137, 136, 136, 136, - /* 520 */ 135, 132, 463, 19, 19, 435, 1230, 1460, 297, 297, - /* 530 */ 311, 424, 1565, 464, 1631, 599, 2, 1284, 437, 574, - /* 540 */ 1107, 590, 330, 579, 158, 582, 489, 357, 573, 593, - /* 550 */ 592, 1366, 409, 1274, 1230, 141, 142, 93, 1364, 1254, - /* 560 */ 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, - /* 570 */ 140, 389, 84, 84, 1062, 567, 1230, 313, 1523, 593, - /* 580 */ 125, 125, 970, 1230, 1231, 1230, 296, 296, 126, 46, - /* 590 */ 464, 594, 464, 296, 296, 1050, 1230, 218, 439, 590, - /* 600 */ 1604, 579, 84, 84, 7, 403, 590, 515, 579, 325, - /* 610 */ 417, 1230, 1231, 1230, 250, 138, 138, 138, 138, 137, - /* 620 */ 137, 136, 136, 136, 135, 132, 463, 1050, 1050, 1052, - /* 630 */ 1053, 35, 1275, 1230, 1231, 1230, 424, 1370, 993, 574, - /* 640 */ 371, 414, 274, 412, 1597, 467, 1302, 552, 451, 590, - /* 650 */ 543, 579, 1530, 1230, 1231, 1230, 1214, 201, 409, 1174, - /* 660 */ 141, 142, 93, 223, 1254, 1254, 1085, 1088, 1075, 1075, - /* 670 */ 139, 139, 140, 140, 140, 140, 296, 296, 1250, 593, - /* 680 */ 424, 296, 296, 236, 529, 296, 296, 515, 100, 590, - /* 690 */ 1600, 579, 48, 1605, 590, 1230, 579, 7, 590, 577, - /* 700 */ 579, 904, 84, 84, 141, 142, 93, 496, 1254, 1254, - /* 710 */ 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, - /* 720 */ 138, 138, 138, 138, 137, 137, 136, 136, 136, 135, - /* 730 */ 132, 463, 1365, 1230, 296, 296, 1250, 115, 1275, 326, - /* 740 */ 233, 539, 1062, 40, 282, 127, 585, 590, 4, 579, - /* 750 */ 329, 584, 1230, 1231, 1230, 1598, 593, 388, 904, 1051, - /* 760 */ 1356, 1356, 588, 1050, 138, 138, 138, 138, 137, 137, - /* 770 */ 136, 136, 136, 135, 132, 463, 185, 593, 1230, 19, - /* 780 */ 19, 1230, 971, 1597, 424, 1651, 464, 129, 908, 1195, - /* 790 */ 1230, 1231, 1230, 1325, 443, 1050, 1050, 1052, 582, 1603, - /* 800 */ 149, 149, 1195, 7, 5, 1195, 1687, 410, 141, 142, - /* 810 */ 93, 1536, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, - /* 820 */ 140, 140, 140, 140, 1214, 397, 593, 1062, 424, 1536, - /* 830 */ 1538, 50, 901, 125, 125, 1230, 1231, 1230, 1230, 1231, - /* 840 */ 1230, 126, 1230, 464, 594, 464, 515, 1230, 1050, 84, - /* 850 */ 84, 3, 141, 142, 93, 924, 1254, 1254, 1085, 1088, - /* 860 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 138, 138, - /* 870 */ 138, 138, 137, 137, 136, 136, 136, 135, 132, 463, - /* 880 */ 1050, 1050, 1052, 1053, 35, 442, 457, 532, 433, 1230, - /* 890 */ 1062, 1361, 540, 540, 1598, 925, 388, 7, 1129, 1230, - /* 900 */ 1231, 1230, 1129, 1536, 1230, 1231, 1230, 1051, 570, 1214, - /* 910 */ 593, 1050, 138, 138, 138, 138, 137, 137, 136, 136, - /* 920 */ 136, 135, 132, 463, 6, 185, 1195, 1230, 231, 593, - /* 930 */ 382, 992, 424, 151, 151, 510, 1213, 557, 482, 1195, - /* 940 */ 381, 160, 1195, 1050, 1050, 1052, 1230, 1231, 1230, 422, - /* 950 */ 593, 447, 84, 84, 593, 217, 141, 142, 93, 593, - /* 960 */ 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, - /* 970 */ 140, 140, 1214, 19, 19, 593, 424, 19, 19, 442, - /* 980 */ 1063, 442, 19, 19, 1230, 1231, 1230, 515, 445, 458, - /* 990 */ 1597, 386, 315, 1175, 1685, 556, 1685, 450, 84, 84, - /* 1000 */ 141, 142, 93, 505, 1254, 1254, 1085, 1088, 1075, 1075, - /* 1010 */ 139, 139, 140, 140, 140, 140, 138, 138, 138, 138, - /* 1020 */ 137, 137, 136, 136, 136, 135, 132, 463, 442, 1147, - /* 1030 */ 454, 1597, 362, 1041, 593, 462, 1460, 1233, 47, 1393, - /* 1040 */ 324, 565, 565, 115, 1148, 449, 7, 460, 459, 307, - /* 1050 */ 375, 354, 593, 113, 593, 329, 584, 19, 19, 1149, - /* 1060 */ 138, 138, 138, 138, 137, 137, 136, 136, 136, 135, - /* 1070 */ 132, 463, 209, 1173, 563, 19, 19, 19, 19, 49, - /* 1080 */ 424, 944, 1175, 1686, 1046, 1686, 218, 355, 484, 343, - /* 1090 */ 210, 945, 569, 562, 1262, 1233, 1262, 490, 314, 423, - /* 1100 */ 424, 1598, 1206, 388, 141, 142, 93, 440, 1254, 1254, - /* 1110 */ 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, - /* 1120 */ 352, 316, 531, 316, 141, 142, 93, 549, 1254, 1254, - /* 1130 */ 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, - /* 1140 */ 446, 10, 1598, 274, 388, 915, 281, 299, 383, 534, - /* 1150 */ 378, 533, 269, 593, 1206, 587, 587, 587, 374, 293, - /* 1160 */ 1579, 991, 1173, 302, 138, 138, 138, 138, 137, 137, - /* 1170 */ 136, 136, 136, 135, 132, 463, 53, 53, 520, 1250, - /* 1180 */ 593, 1147, 1576, 431, 138, 138, 138, 138, 137, 137, - /* 1190 */ 136, 136, 136, 135, 132, 463, 1148, 301, 593, 1577, - /* 1200 */ 593, 1307, 431, 54, 54, 593, 268, 593, 461, 461, - /* 1210 */ 461, 1149, 347, 492, 424, 135, 132, 463, 1146, 1195, - /* 1220 */ 474, 68, 68, 69, 69, 550, 332, 287, 21, 21, - /* 1230 */ 55, 55, 1195, 581, 424, 1195, 309, 1250, 141, 142, - /* 1240 */ 93, 119, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, - /* 1250 */ 140, 140, 140, 140, 593, 237, 480, 1476, 141, 142, - /* 1260 */ 93, 593, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, - /* 1270 */ 140, 140, 140, 140, 344, 430, 346, 70, 70, 494, - /* 1280 */ 991, 1132, 1132, 512, 56, 56, 1269, 593, 268, 593, - /* 1290 */ 369, 374, 593, 481, 215, 384, 1624, 481, 138, 138, - /* 1300 */ 138, 138, 137, 137, 136, 136, 136, 135, 132, 463, - /* 1310 */ 71, 71, 72, 72, 225, 73, 73, 593, 138, 138, - /* 1320 */ 138, 138, 137, 137, 136, 136, 136, 135, 132, 463, - /* 1330 */ 586, 431, 593, 872, 873, 874, 593, 911, 593, 1602, - /* 1340 */ 74, 74, 593, 7, 1460, 242, 593, 306, 424, 1578, - /* 1350 */ 472, 306, 364, 219, 367, 75, 75, 430, 345, 57, - /* 1360 */ 57, 58, 58, 432, 187, 59, 59, 593, 424, 61, - /* 1370 */ 61, 1475, 141, 142, 93, 123, 1254, 1254, 1085, 1088, - /* 1380 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 424, 570, - /* 1390 */ 62, 62, 141, 142, 93, 911, 1254, 1254, 1085, 1088, - /* 1400 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 161, 384, - /* 1410 */ 1624, 1474, 141, 130, 93, 441, 1254, 1254, 1085, 1088, - /* 1420 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 267, 266, - /* 1430 */ 265, 1460, 138, 138, 138, 138, 137, 137, 136, 136, - /* 1440 */ 136, 135, 132, 463, 593, 1336, 593, 1269, 1460, 384, - /* 1450 */ 1624, 231, 138, 138, 138, 138, 137, 137, 136, 136, - /* 1460 */ 136, 135, 132, 463, 593, 163, 593, 76, 76, 77, - /* 1470 */ 77, 593, 138, 138, 138, 138, 137, 137, 136, 136, - /* 1480 */ 136, 135, 132, 463, 475, 593, 483, 78, 78, 20, - /* 1490 */ 20, 1249, 424, 491, 79, 79, 495, 422, 295, 235, - /* 1500 */ 1574, 38, 511, 896, 422, 335, 240, 422, 147, 147, - /* 1510 */ 112, 593, 424, 593, 101, 222, 991, 142, 93, 455, - /* 1520 */ 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, - /* 1530 */ 140, 140, 593, 39, 148, 148, 80, 80, 93, 551, - /* 1540 */ 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, - /* 1550 */ 140, 140, 328, 923, 922, 64, 64, 502, 1656, 1005, - /* 1560 */ 933, 896, 124, 422, 121, 254, 593, 1006, 593, 226, - /* 1570 */ 593, 127, 585, 164, 4, 16, 138, 138, 138, 138, - /* 1580 */ 137, 137, 136, 136, 136, 135, 132, 463, 588, 81, - /* 1590 */ 81, 65, 65, 82, 82, 593, 138, 138, 138, 138, - /* 1600 */ 137, 137, 136, 136, 136, 135, 132, 463, 593, 226, - /* 1610 */ 237, 966, 464, 593, 298, 593, 965, 593, 66, 66, - /* 1620 */ 593, 1170, 593, 411, 582, 353, 469, 115, 593, 471, - /* 1630 */ 169, 173, 173, 593, 44, 991, 174, 174, 89, 89, - /* 1640 */ 67, 67, 593, 85, 85, 150, 150, 1114, 1043, 593, - /* 1650 */ 273, 86, 86, 1062, 593, 503, 171, 171, 593, 125, - /* 1660 */ 125, 497, 593, 273, 336, 152, 152, 126, 1335, 464, - /* 1670 */ 594, 464, 146, 146, 1050, 593, 545, 172, 172, 593, - /* 1680 */ 1054, 165, 165, 256, 339, 156, 156, 127, 585, 1586, - /* 1690 */ 4, 329, 584, 499, 358, 273, 115, 348, 155, 155, - /* 1700 */ 930, 931, 153, 153, 588, 1114, 1050, 1050, 1052, 1053, - /* 1710 */ 35, 1554, 521, 593, 270, 1008, 1009, 9, 593, 372, - /* 1720 */ 593, 115, 593, 168, 593, 115, 593, 1110, 464, 270, - /* 1730 */ 996, 964, 273, 129, 1645, 1214, 154, 154, 1054, 1404, - /* 1740 */ 582, 88, 88, 90, 90, 87, 87, 52, 52, 60, - /* 1750 */ 60, 1405, 504, 537, 559, 1179, 961, 507, 129, 558, - /* 1760 */ 127, 585, 1126, 4, 1126, 1125, 894, 1125, 162, 1062, - /* 1770 */ 963, 359, 129, 1401, 363, 125, 125, 588, 366, 368, - /* 1780 */ 370, 1349, 1334, 126, 1333, 464, 594, 464, 377, 387, - /* 1790 */ 1050, 1391, 1414, 1618, 1459, 1387, 1399, 208, 580, 1464, - /* 1800 */ 1314, 464, 243, 516, 1305, 1293, 1384, 1292, 1294, 1638, - /* 1810 */ 288, 170, 228, 582, 12, 408, 321, 322, 241, 323, - /* 1820 */ 245, 1446, 1050, 1050, 1052, 1053, 35, 559, 304, 350, - /* 1830 */ 351, 501, 560, 127, 585, 1441, 4, 1451, 1434, 310, - /* 1840 */ 1450, 526, 1062, 1332, 415, 380, 232, 1527, 125, 125, - /* 1850 */ 588, 1214, 1396, 356, 1526, 583, 126, 1397, 464, 594, - /* 1860 */ 464, 1641, 535, 1050, 1581, 1395, 1269, 1583, 1582, 213, - /* 1870 */ 402, 277, 214, 227, 464, 1573, 239, 1571, 1266, 1394, - /* 1880 */ 434, 198, 100, 224, 96, 183, 582, 191, 485, 193, - /* 1890 */ 486, 194, 195, 196, 519, 1050, 1050, 1052, 1053, 35, - /* 1900 */ 559, 113, 252, 413, 1447, 558, 493, 13, 1455, 416, - /* 1910 */ 1453, 1452, 14, 202, 1521, 1062, 1532, 508, 258, 106, - /* 1920 */ 514, 125, 125, 99, 1214, 1543, 289, 260, 206, 126, - /* 1930 */ 365, 464, 594, 464, 361, 517, 1050, 261, 448, 1295, - /* 1940 */ 262, 418, 1352, 1351, 108, 1350, 1655, 1654, 1343, 915, - /* 1950 */ 419, 1322, 233, 452, 319, 379, 1321, 453, 1623, 320, - /* 1960 */ 1320, 275, 1653, 544, 276, 1609, 1608, 1342, 1050, 1050, - /* 1970 */ 1052, 1053, 35, 1630, 1218, 466, 385, 456, 300, 1419, - /* 1980 */ 144, 1418, 570, 407, 407, 406, 284, 404, 11, 1508, - /* 1990 */ 881, 396, 120, 127, 585, 394, 4, 1214, 327, 114, - /* 2000 */ 1375, 1374, 220, 247, 400, 338, 401, 554, 42, 1224, - /* 2010 */ 588, 596, 283, 337, 285, 286, 188, 597, 1290, 1285, - /* 2020 */ 175, 1558, 176, 1559, 1557, 1556, 159, 317, 229, 177, - /* 2030 */ 868, 230, 91, 465, 464, 221, 331, 468, 1165, 470, - /* 2040 */ 473, 94, 244, 95, 249, 189, 582, 1124, 1122, 341, - /* 2050 */ 427, 190, 178, 1249, 179, 43, 192, 947, 349, 428, - /* 2060 */ 1138, 197, 251, 180, 181, 436, 102, 182, 438, 103, - /* 2070 */ 104, 199, 248, 1140, 253, 1062, 105, 255, 1137, 166, - /* 2080 */ 24, 125, 125, 257, 1264, 273, 360, 513, 259, 126, - /* 2090 */ 15, 464, 594, 464, 204, 883, 1050, 518, 263, 373, - /* 2100 */ 381, 92, 585, 1130, 4, 203, 205, 426, 107, 522, - /* 2110 */ 25, 26, 329, 584, 913, 572, 527, 376, 588, 926, - /* 2120 */ 530, 109, 184, 318, 167, 110, 27, 538, 1050, 1050, - /* 2130 */ 1052, 1053, 35, 1211, 1091, 17, 476, 111, 1181, 234, - /* 2140 */ 292, 1180, 464, 294, 207, 994, 129, 1201, 272, 1000, - /* 2150 */ 28, 1197, 29, 30, 582, 1199, 1205, 1214, 31, 1204, - /* 2160 */ 32, 1186, 41, 566, 33, 1105, 211, 8, 115, 1092, - /* 2170 */ 1090, 1094, 34, 278, 578, 1095, 117, 122, 118, 1145, - /* 2180 */ 36, 18, 128, 1062, 1055, 895, 957, 37, 589, 125, - /* 2190 */ 125, 279, 186, 280, 1646, 157, 405, 126, 1220, 464, - /* 2200 */ 594, 464, 1218, 466, 1050, 1219, 300, 1281, 1281, 1281, - /* 2210 */ 1281, 407, 407, 406, 284, 404, 1281, 1281, 881, 1281, - /* 2220 */ 300, 1281, 1281, 571, 1281, 407, 407, 406, 284, 404, - /* 2230 */ 1281, 247, 881, 338, 1281, 1281, 1050, 1050, 1052, 1053, - /* 2240 */ 35, 337, 1281, 1281, 1281, 247, 1281, 338, 1281, 1281, - /* 2250 */ 1281, 1281, 1281, 1281, 1281, 337, 1281, 1281, 1281, 1281, - /* 2260 */ 1281, 1281, 1281, 1281, 1281, 1214, 1281, 1281, 1281, 1281, - /* 2270 */ 1281, 1281, 249, 1281, 1281, 1281, 1281, 1281, 1281, 1281, - /* 2280 */ 178, 1281, 1281, 43, 1281, 1281, 249, 1281, 1281, 1281, - /* 2290 */ 1281, 1281, 1281, 1281, 178, 1281, 1281, 43, 1281, 1281, - /* 2300 */ 248, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, - /* 2310 */ 1281, 1281, 1281, 1281, 248, 1281, 1281, 1281, 1281, 1281, - /* 2320 */ 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, - /* 2330 */ 1281, 1281, 1281, 1281, 1281, 426, 1281, 1281, 1281, 1281, - /* 2340 */ 329, 584, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 426, - /* 2350 */ 1281, 1281, 1281, 1281, 329, 584, 1281, 1281, 1281, 1281, - /* 2360 */ 1281, 1281, 1281, 1281, 476, 1281, 1281, 1281, 1281, 1281, - /* 2370 */ 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 476, + /* 0 */ 130, 127, 234, 282, 282, 1328, 576, 1307, 460, 289, + /* 10 */ 289, 576, 1622, 381, 576, 1328, 573, 576, 562, 413, + /* 20 */ 1300, 1542, 573, 481, 562, 524, 460, 459, 558, 82, + /* 30 */ 82, 983, 294, 375, 51, 51, 498, 61, 61, 984, + /* 40 */ 82, 82, 1577, 137, 138, 91, 7, 1228, 1228, 1063, + /* 50 */ 1066, 1053, 1053, 135, 135, 136, 136, 136, 136, 413, + /* 60 */ 288, 288, 182, 288, 288, 481, 536, 288, 288, 130, + /* 70 */ 127, 234, 432, 573, 525, 562, 573, 557, 562, 1290, + /* 80 */ 573, 421, 562, 137, 138, 91, 559, 1228, 1228, 1063, + /* 90 */ 1066, 1053, 1053, 135, 135, 136, 136, 136, 136, 296, + /* 100 */ 460, 398, 1249, 134, 134, 134, 134, 133, 133, 132, + /* 110 */ 132, 132, 131, 128, 451, 451, 1050, 1050, 1064, 1067, + /* 120 */ 1255, 1, 1, 582, 2, 1259, 581, 1174, 1259, 1174, + /* 130 */ 321, 413, 155, 321, 1584, 155, 379, 112, 481, 1341, + /* 140 */ 456, 299, 1341, 134, 134, 134, 134, 133, 133, 132, + /* 150 */ 132, 132, 131, 128, 451, 137, 138, 91, 498, 1228, + /* 160 */ 1228, 1063, 1066, 1053, 1053, 135, 135, 136, 136, 136, + /* 170 */ 136, 1204, 862, 1281, 288, 288, 283, 288, 288, 523, + /* 180 */ 523, 1250, 139, 578, 7, 578, 1345, 573, 1169, 562, + /* 190 */ 573, 1054, 562, 136, 136, 136, 136, 129, 573, 547, + /* 200 */ 562, 1169, 245, 1541, 1169, 245, 133, 133, 132, 132, + /* 210 */ 132, 131, 128, 451, 302, 134, 134, 134, 134, 133, + /* 220 */ 133, 132, 132, 132, 131, 128, 451, 1575, 1204, 1205, + /* 230 */ 1204, 7, 470, 550, 455, 413, 550, 455, 130, 127, + /* 240 */ 234, 134, 134, 134, 134, 133, 133, 132, 132, 132, + /* 250 */ 131, 128, 451, 136, 136, 136, 136, 538, 483, 137, + /* 260 */ 138, 91, 1019, 1228, 1228, 1063, 1066, 1053, 1053, 135, + /* 270 */ 135, 136, 136, 136, 136, 1085, 576, 1204, 132, 132, + /* 280 */ 132, 131, 128, 451, 93, 214, 134, 134, 134, 134, + /* 290 */ 133, 133, 132, 132, 132, 131, 128, 451, 401, 19, + /* 300 */ 19, 134, 134, 134, 134, 133, 133, 132, 132, 132, + /* 310 */ 131, 128, 451, 1498, 426, 267, 344, 467, 332, 134, + /* 320 */ 134, 134, 134, 133, 133, 132, 132, 132, 131, 128, + /* 330 */ 451, 1281, 576, 6, 1204, 1205, 1204, 257, 576, 413, + /* 340 */ 511, 508, 507, 1279, 94, 1019, 464, 1204, 551, 551, + /* 350 */ 506, 1224, 1571, 44, 38, 51, 51, 411, 576, 413, + /* 360 */ 45, 51, 51, 137, 138, 91, 530, 1228, 1228, 1063, + /* 370 */ 1066, 1053, 1053, 135, 135, 136, 136, 136, 136, 398, + /* 380 */ 1148, 82, 82, 137, 138, 91, 39, 1228, 1228, 1063, + /* 390 */ 1066, 1053, 1053, 135, 135, 136, 136, 136, 136, 344, + /* 400 */ 44, 288, 288, 375, 1204, 1205, 1204, 209, 1204, 1224, + /* 410 */ 320, 567, 471, 576, 573, 576, 562, 576, 316, 264, + /* 420 */ 231, 46, 160, 134, 134, 134, 134, 133, 133, 132, + /* 430 */ 132, 132, 131, 128, 451, 303, 82, 82, 82, 82, + /* 440 */ 82, 82, 442, 134, 134, 134, 134, 133, 133, 132, + /* 450 */ 132, 132, 131, 128, 451, 1582, 544, 320, 567, 1250, + /* 460 */ 874, 1582, 380, 382, 413, 1204, 1205, 1204, 360, 182, + /* 470 */ 288, 288, 1576, 557, 1339, 557, 7, 557, 1277, 472, + /* 480 */ 346, 526, 531, 573, 556, 562, 439, 1511, 137, 138, + /* 490 */ 91, 219, 1228, 1228, 1063, 1066, 1053, 1053, 135, 135, + /* 500 */ 136, 136, 136, 136, 465, 1511, 1513, 532, 413, 288, + /* 510 */ 288, 423, 512, 288, 288, 411, 288, 288, 874, 130, + /* 520 */ 127, 234, 573, 1107, 562, 1204, 573, 1107, 562, 573, + /* 530 */ 560, 562, 137, 138, 91, 1293, 1228, 1228, 1063, 1066, + /* 540 */ 1053, 1053, 135, 135, 136, 136, 136, 136, 134, 134, + /* 550 */ 134, 134, 133, 133, 132, 132, 132, 131, 128, 451, + /* 560 */ 493, 503, 1292, 1204, 257, 288, 288, 511, 508, 507, + /* 570 */ 1204, 1628, 1169, 123, 568, 275, 4, 506, 573, 1511, + /* 580 */ 562, 331, 1204, 1205, 1204, 1169, 548, 548, 1169, 261, + /* 590 */ 571, 7, 134, 134, 134, 134, 133, 133, 132, 132, + /* 600 */ 132, 131, 128, 451, 108, 533, 130, 127, 234, 1204, + /* 610 */ 448, 447, 413, 1451, 452, 983, 886, 96, 1598, 1233, + /* 620 */ 1204, 1205, 1204, 984, 1235, 1450, 565, 1204, 1205, 1204, + /* 630 */ 229, 522, 1234, 534, 1333, 1333, 137, 138, 91, 1449, + /* 640 */ 1228, 1228, 1063, 1066, 1053, 1053, 135, 135, 136, 136, + /* 650 */ 136, 136, 373, 1595, 971, 1040, 413, 1236, 418, 1236, + /* 660 */ 879, 121, 121, 948, 373, 1595, 1204, 1205, 1204, 122, + /* 670 */ 1204, 452, 577, 452, 363, 417, 1028, 882, 373, 1595, + /* 680 */ 137, 138, 91, 462, 1228, 1228, 1063, 1066, 1053, 1053, + /* 690 */ 135, 135, 136, 136, 136, 136, 134, 134, 134, 134, + /* 700 */ 133, 133, 132, 132, 132, 131, 128, 451, 1028, 1028, + /* 710 */ 1030, 1031, 35, 570, 570, 570, 197, 423, 1040, 198, + /* 720 */ 1204, 123, 568, 1204, 4, 320, 567, 1204, 1205, 1204, + /* 730 */ 40, 388, 576, 384, 882, 1029, 423, 1188, 571, 1028, + /* 740 */ 134, 134, 134, 134, 133, 133, 132, 132, 132, 131, + /* 750 */ 128, 451, 529, 1568, 1204, 19, 19, 1204, 575, 492, + /* 760 */ 413, 157, 452, 489, 1187, 1331, 1331, 5, 1204, 949, + /* 770 */ 431, 1028, 1028, 1030, 565, 22, 22, 1204, 1205, 1204, + /* 780 */ 1204, 1205, 1204, 477, 137, 138, 91, 212, 1228, 1228, + /* 790 */ 1063, 1066, 1053, 1053, 135, 135, 136, 136, 136, 136, + /* 800 */ 1188, 48, 111, 1040, 413, 1204, 213, 970, 1041, 121, + /* 810 */ 121, 1204, 1205, 1204, 1204, 1205, 1204, 122, 221, 452, + /* 820 */ 577, 452, 44, 487, 1028, 1204, 1205, 1204, 137, 138, + /* 830 */ 91, 378, 1228, 1228, 1063, 1066, 1053, 1053, 135, 135, + /* 840 */ 136, 136, 136, 136, 134, 134, 134, 134, 133, 133, + /* 850 */ 132, 132, 132, 131, 128, 451, 1028, 1028, 1030, 1031, + /* 860 */ 35, 461, 1204, 1205, 1204, 1569, 1040, 377, 214, 1149, + /* 870 */ 1657, 535, 1657, 437, 902, 320, 567, 1568, 364, 320, + /* 880 */ 567, 412, 329, 1029, 519, 1188, 3, 1028, 134, 134, + /* 890 */ 134, 134, 133, 133, 132, 132, 132, 131, 128, 451, + /* 900 */ 1659, 399, 1169, 307, 893, 307, 515, 576, 413, 214, + /* 910 */ 498, 944, 1024, 540, 903, 1169, 943, 392, 1169, 1028, + /* 920 */ 1028, 1030, 406, 298, 1204, 50, 1149, 1658, 413, 1658, + /* 930 */ 145, 145, 137, 138, 91, 293, 1228, 1228, 1063, 1066, + /* 940 */ 1053, 1053, 135, 135, 136, 136, 136, 136, 1188, 1147, + /* 950 */ 514, 1568, 137, 138, 91, 1505, 1228, 1228, 1063, 1066, + /* 960 */ 1053, 1053, 135, 135, 136, 136, 136, 136, 434, 323, + /* 970 */ 435, 539, 111, 1506, 274, 291, 372, 517, 367, 516, + /* 980 */ 262, 1204, 1205, 1204, 1574, 481, 363, 576, 7, 1569, + /* 990 */ 1568, 377, 134, 134, 134, 134, 133, 133, 132, 132, + /* 1000 */ 132, 131, 128, 451, 1568, 576, 1147, 576, 232, 576, + /* 1010 */ 19, 19, 134, 134, 134, 134, 133, 133, 132, 132, + /* 1020 */ 132, 131, 128, 451, 1169, 433, 576, 1207, 19, 19, + /* 1030 */ 19, 19, 19, 19, 1627, 576, 911, 1169, 47, 120, + /* 1040 */ 1169, 117, 413, 306, 498, 438, 1125, 206, 336, 19, + /* 1050 */ 19, 1435, 49, 449, 449, 449, 1368, 315, 81, 81, + /* 1060 */ 576, 304, 413, 1570, 207, 377, 137, 138, 91, 115, + /* 1070 */ 1228, 1228, 1063, 1066, 1053, 1053, 135, 135, 136, 136, + /* 1080 */ 136, 136, 576, 82, 82, 1207, 137, 138, 91, 1340, + /* 1090 */ 1228, 1228, 1063, 1066, 1053, 1053, 135, 135, 136, 136, + /* 1100 */ 136, 136, 1569, 386, 377, 82, 82, 463, 1126, 1552, + /* 1110 */ 333, 463, 335, 131, 128, 451, 1569, 161, 377, 16, + /* 1120 */ 317, 387, 428, 1127, 448, 447, 134, 134, 134, 134, + /* 1130 */ 133, 133, 132, 132, 132, 131, 128, 451, 1128, 576, + /* 1140 */ 1105, 10, 445, 267, 576, 1554, 134, 134, 134, 134, + /* 1150 */ 133, 133, 132, 132, 132, 131, 128, 451, 532, 576, + /* 1160 */ 922, 576, 19, 19, 576, 1573, 576, 147, 147, 7, + /* 1170 */ 923, 1236, 498, 1236, 576, 487, 413, 552, 285, 1224, + /* 1180 */ 969, 215, 82, 82, 66, 66, 1435, 67, 67, 21, + /* 1190 */ 21, 1110, 1110, 495, 334, 297, 413, 53, 53, 297, + /* 1200 */ 137, 138, 91, 119, 1228, 1228, 1063, 1066, 1053, 1053, + /* 1210 */ 135, 135, 136, 136, 136, 136, 413, 1336, 1311, 446, + /* 1220 */ 137, 138, 91, 227, 1228, 1228, 1063, 1066, 1053, 1053, + /* 1230 */ 135, 135, 136, 136, 136, 136, 574, 1224, 936, 936, + /* 1240 */ 137, 126, 91, 141, 1228, 1228, 1063, 1066, 1053, 1053, + /* 1250 */ 135, 135, 136, 136, 136, 136, 533, 429, 472, 346, + /* 1260 */ 134, 134, 134, 134, 133, 133, 132, 132, 132, 131, + /* 1270 */ 128, 451, 576, 457, 233, 343, 1435, 403, 498, 1550, + /* 1280 */ 134, 134, 134, 134, 133, 133, 132, 132, 132, 131, + /* 1290 */ 128, 451, 576, 324, 576, 82, 82, 487, 576, 969, + /* 1300 */ 134, 134, 134, 134, 133, 133, 132, 132, 132, 131, + /* 1310 */ 128, 451, 288, 288, 546, 68, 68, 54, 54, 553, + /* 1320 */ 413, 69, 69, 351, 6, 573, 944, 562, 410, 409, + /* 1330 */ 1435, 943, 450, 545, 260, 259, 258, 576, 158, 576, + /* 1340 */ 413, 222, 1180, 479, 969, 138, 91, 430, 1228, 1228, + /* 1350 */ 1063, 1066, 1053, 1053, 135, 135, 136, 136, 136, 136, + /* 1360 */ 70, 70, 71, 71, 576, 1126, 91, 576, 1228, 1228, + /* 1370 */ 1063, 1066, 1053, 1053, 135, 135, 136, 136, 136, 136, + /* 1380 */ 1127, 166, 850, 851, 852, 1282, 419, 72, 72, 108, + /* 1390 */ 73, 73, 1310, 358, 1180, 1128, 576, 305, 576, 123, + /* 1400 */ 568, 494, 4, 488, 134, 134, 134, 134, 133, 133, + /* 1410 */ 132, 132, 132, 131, 128, 451, 571, 564, 534, 55, + /* 1420 */ 55, 56, 56, 576, 134, 134, 134, 134, 133, 133, + /* 1430 */ 132, 132, 132, 131, 128, 451, 576, 1104, 233, 1104, + /* 1440 */ 452, 1602, 582, 2, 1259, 576, 57, 57, 576, 321, + /* 1450 */ 576, 155, 565, 1435, 485, 353, 576, 356, 1341, 59, + /* 1460 */ 59, 576, 44, 969, 569, 419, 576, 238, 60, 60, + /* 1470 */ 261, 74, 74, 75, 75, 287, 231, 576, 1366, 76, + /* 1480 */ 76, 1040, 420, 184, 20, 20, 576, 121, 121, 77, + /* 1490 */ 77, 97, 218, 288, 288, 122, 125, 452, 577, 452, + /* 1500 */ 143, 143, 1028, 576, 520, 576, 573, 576, 562, 144, + /* 1510 */ 144, 474, 227, 1244, 478, 123, 568, 576, 4, 320, + /* 1520 */ 567, 245, 411, 576, 443, 411, 78, 78, 62, 62, + /* 1530 */ 79, 79, 571, 319, 1028, 1028, 1030, 1031, 35, 418, + /* 1540 */ 63, 63, 576, 290, 411, 9, 80, 80, 1144, 576, + /* 1550 */ 400, 576, 486, 455, 576, 1223, 452, 576, 325, 342, + /* 1560 */ 576, 111, 576, 1188, 242, 64, 64, 473, 565, 576, + /* 1570 */ 23, 576, 170, 170, 171, 171, 576, 87, 87, 328, + /* 1580 */ 65, 65, 542, 83, 83, 146, 146, 541, 123, 568, + /* 1590 */ 341, 4, 84, 84, 168, 168, 576, 1040, 576, 148, + /* 1600 */ 148, 576, 1380, 121, 121, 571, 1021, 576, 266, 576, + /* 1610 */ 424, 122, 576, 452, 577, 452, 576, 553, 1028, 142, + /* 1620 */ 142, 169, 169, 576, 162, 162, 528, 889, 371, 452, + /* 1630 */ 152, 152, 151, 151, 1379, 149, 149, 109, 370, 150, + /* 1640 */ 150, 565, 576, 480, 576, 266, 86, 86, 576, 1092, + /* 1650 */ 1028, 1028, 1030, 1031, 35, 542, 482, 576, 266, 466, + /* 1660 */ 543, 123, 568, 1616, 4, 88, 88, 85, 85, 475, + /* 1670 */ 1040, 52, 52, 222, 901, 900, 121, 121, 571, 1188, + /* 1680 */ 58, 58, 244, 1032, 122, 889, 452, 577, 452, 908, + /* 1690 */ 909, 1028, 300, 347, 504, 111, 263, 361, 165, 111, + /* 1700 */ 111, 1088, 452, 263, 974, 1153, 266, 1092, 986, 987, + /* 1710 */ 942, 939, 125, 125, 565, 1103, 872, 1103, 159, 941, + /* 1720 */ 1309, 125, 1557, 1028, 1028, 1030, 1031, 35, 542, 337, + /* 1730 */ 1530, 205, 1529, 541, 499, 1589, 490, 348, 1376, 352, + /* 1740 */ 355, 1032, 357, 1040, 359, 1324, 1308, 366, 563, 121, + /* 1750 */ 121, 376, 1188, 1389, 1434, 1362, 280, 122, 1374, 452, + /* 1760 */ 577, 452, 167, 1439, 1028, 1289, 1280, 1268, 1267, 1269, + /* 1770 */ 1609, 1359, 312, 313, 314, 397, 12, 237, 224, 1421, + /* 1780 */ 295, 1416, 1409, 1426, 339, 484, 340, 509, 1371, 1612, + /* 1790 */ 1372, 1425, 1244, 404, 301, 228, 1028, 1028, 1030, 1031, + /* 1800 */ 35, 1601, 1192, 454, 345, 1307, 292, 369, 1502, 1501, + /* 1810 */ 270, 396, 396, 395, 277, 393, 1370, 1369, 859, 1549, + /* 1820 */ 186, 123, 568, 235, 4, 1188, 391, 210, 211, 223, + /* 1830 */ 1547, 239, 1241, 327, 422, 96, 220, 195, 571, 180, + /* 1840 */ 188, 326, 468, 469, 190, 191, 502, 192, 193, 566, + /* 1850 */ 247, 109, 1430, 491, 199, 251, 102, 281, 402, 476, + /* 1860 */ 405, 1496, 452, 497, 253, 1422, 13, 1428, 14, 1427, + /* 1870 */ 203, 1507, 241, 500, 565, 354, 407, 92, 95, 1270, + /* 1880 */ 175, 254, 518, 43, 1327, 255, 1326, 1325, 436, 1518, + /* 1890 */ 350, 1318, 104, 229, 893, 1626, 440, 441, 1625, 408, + /* 1900 */ 240, 1296, 268, 1040, 310, 269, 1297, 527, 444, 121, + /* 1910 */ 121, 368, 1295, 1594, 1624, 311, 1394, 122, 1317, 452, + /* 1920 */ 577, 452, 374, 1580, 1028, 1393, 140, 553, 11, 90, + /* 1930 */ 568, 385, 4, 116, 318, 414, 1579, 110, 1483, 537, + /* 1940 */ 320, 567, 1350, 555, 42, 579, 571, 1349, 1198, 383, + /* 1950 */ 276, 390, 216, 389, 278, 279, 1028, 1028, 1030, 1031, + /* 1960 */ 35, 172, 580, 1265, 458, 1260, 415, 416, 185, 156, + /* 1970 */ 452, 1534, 1535, 173, 1533, 1532, 89, 308, 225, 226, + /* 1980 */ 846, 174, 565, 453, 217, 1188, 322, 236, 1102, 154, + /* 1990 */ 1100, 330, 187, 176, 1223, 243, 189, 925, 338, 246, + /* 2000 */ 1116, 194, 177, 425, 178, 427, 98, 196, 99, 100, + /* 2010 */ 101, 1040, 179, 1119, 1115, 248, 249, 121, 121, 163, + /* 2020 */ 24, 250, 349, 1238, 496, 122, 1108, 452, 577, 452, + /* 2030 */ 1192, 454, 1028, 266, 292, 200, 252, 201, 861, 396, + /* 2040 */ 396, 395, 277, 393, 15, 501, 859, 370, 292, 256, + /* 2050 */ 202, 554, 505, 396, 396, 395, 277, 393, 103, 239, + /* 2060 */ 859, 327, 25, 26, 1028, 1028, 1030, 1031, 35, 326, + /* 2070 */ 362, 510, 891, 239, 365, 327, 513, 904, 105, 309, + /* 2080 */ 164, 181, 27, 326, 106, 521, 107, 1185, 1069, 1155, + /* 2090 */ 17, 1154, 230, 1188, 284, 286, 265, 204, 125, 1171, + /* 2100 */ 241, 28, 978, 972, 29, 41, 1175, 1179, 175, 1173, + /* 2110 */ 30, 43, 31, 8, 241, 1178, 32, 1160, 208, 549, + /* 2120 */ 33, 111, 175, 1083, 1070, 43, 1068, 1072, 240, 113, + /* 2130 */ 114, 34, 561, 118, 1124, 271, 1073, 36, 18, 572, + /* 2140 */ 1033, 873, 240, 124, 37, 935, 272, 273, 1617, 183, + /* 2150 */ 153, 394, 1194, 1193, 1256, 1256, 1256, 1256, 1256, 1256, + /* 2160 */ 1256, 1256, 1256, 414, 1256, 1256, 1256, 1256, 320, 567, + /* 2170 */ 1256, 1256, 1256, 1256, 1256, 1256, 1256, 414, 1256, 1256, + /* 2180 */ 1256, 1256, 320, 567, 1256, 1256, 1256, 1256, 1256, 1256, + /* 2190 */ 1256, 1256, 458, 1256, 1256, 1256, 1256, 1256, 1256, 1256, + /* 2200 */ 1256, 1256, 1256, 1256, 1256, 1256, 458, }; static const YYCODETYPE yy_lookahead[] = { - /* 0 */ 277, 278, 279, 241, 242, 225, 195, 227, 195, 312, - /* 10 */ 195, 218, 195, 316, 195, 235, 254, 195, 256, 19, - /* 20 */ 297, 277, 278, 279, 218, 206, 213, 214, 206, 218, - /* 30 */ 219, 31, 206, 218, 219, 218, 219, 218, 219, 39, - /* 40 */ 218, 219, 195, 43, 44, 45, 195, 47, 48, 49, + /* 0 */ 277, 278, 279, 241, 242, 225, 195, 227, 195, 241, + /* 10 */ 242, 195, 217, 221, 195, 235, 254, 195, 256, 19, + /* 20 */ 225, 298, 254, 195, 256, 206, 213, 214, 206, 218, + /* 30 */ 219, 31, 206, 195, 218, 219, 195, 218, 219, 39, + /* 40 */ 218, 219, 313, 43, 44, 45, 317, 47, 48, 49, /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 19, - /* 60 */ 241, 242, 195, 241, 242, 195, 255, 241, 242, 195, - /* 70 */ 255, 237, 238, 254, 255, 256, 254, 255, 256, 264, - /* 80 */ 254, 207, 256, 43, 44, 45, 264, 47, 48, 49, - /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 251, - /* 100 */ 287, 253, 215, 103, 104, 105, 106, 107, 108, 109, - /* 110 */ 110, 111, 112, 113, 114, 82, 265, 195, 271, 11, + /* 60 */ 241, 242, 195, 241, 242, 195, 255, 241, 242, 277, + /* 70 */ 278, 279, 234, 254, 255, 256, 254, 255, 256, 218, + /* 80 */ 254, 240, 256, 43, 44, 45, 264, 47, 48, 49, + /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 271, + /* 100 */ 287, 22, 23, 103, 104, 105, 106, 107, 108, 109, + /* 110 */ 110, 111, 112, 113, 114, 114, 47, 48, 49, 50, /* 120 */ 187, 188, 189, 190, 191, 192, 190, 87, 192, 89, - /* 130 */ 197, 19, 199, 197, 317, 199, 319, 25, 271, 206, - /* 140 */ 218, 219, 206, 103, 104, 105, 106, 107, 108, 109, + /* 130 */ 197, 19, 199, 197, 318, 199, 320, 25, 195, 206, + /* 140 */ 299, 271, 206, 103, 104, 105, 106, 107, 108, 109, /* 150 */ 110, 111, 112, 113, 114, 43, 44, 45, 195, 47, /* 160 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - /* 170 */ 58, 60, 139, 140, 241, 242, 289, 241, 242, 309, - /* 180 */ 310, 294, 70, 47, 48, 49, 50, 254, 77, 256, - /* 190 */ 254, 195, 256, 55, 56, 57, 58, 59, 221, 88, - /* 200 */ 109, 90, 269, 240, 93, 269, 107, 108, 109, 110, - /* 210 */ 111, 112, 113, 114, 215, 103, 104, 105, 106, 107, - /* 220 */ 108, 109, 110, 111, 112, 113, 114, 136, 117, 118, - /* 230 */ 119, 298, 141, 300, 298, 19, 300, 129, 130, 317, - /* 240 */ 318, 103, 104, 105, 106, 107, 108, 109, 110, 111, - /* 250 */ 112, 113, 114, 114, 277, 278, 279, 146, 122, 43, - /* 260 */ 44, 45, 195, 47, 48, 49, 50, 51, 52, 53, - /* 270 */ 54, 55, 56, 57, 58, 218, 277, 278, 279, 19, - /* 280 */ 19, 195, 286, 23, 68, 218, 219, 55, 56, 57, - /* 290 */ 58, 103, 104, 105, 106, 107, 108, 109, 110, 111, - /* 300 */ 112, 113, 114, 43, 44, 45, 232, 47, 48, 49, - /* 310 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 103, + /* 170 */ 58, 60, 21, 195, 241, 242, 215, 241, 242, 312, + /* 180 */ 313, 102, 70, 205, 317, 207, 242, 254, 77, 256, + /* 190 */ 254, 122, 256, 55, 56, 57, 58, 59, 254, 88, + /* 200 */ 256, 90, 269, 240, 93, 269, 107, 108, 109, 110, + /* 210 */ 111, 112, 113, 114, 271, 103, 104, 105, 106, 107, + /* 220 */ 108, 109, 110, 111, 112, 113, 114, 313, 117, 118, + /* 230 */ 119, 317, 81, 195, 301, 19, 195, 301, 277, 278, + /* 240 */ 279, 103, 104, 105, 106, 107, 108, 109, 110, 111, + /* 250 */ 112, 113, 114, 55, 56, 57, 58, 146, 195, 43, + /* 260 */ 44, 45, 74, 47, 48, 49, 50, 51, 52, 53, + /* 270 */ 54, 55, 56, 57, 58, 124, 195, 60, 109, 110, + /* 280 */ 111, 112, 113, 114, 68, 195, 103, 104, 105, 106, + /* 290 */ 107, 108, 109, 110, 111, 112, 113, 114, 208, 218, + /* 300 */ 219, 103, 104, 105, 106, 107, 108, 109, 110, 111, + /* 310 */ 112, 113, 114, 162, 233, 24, 128, 129, 130, 103, /* 320 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, - /* 330 */ 114, 135, 60, 137, 138, 103, 104, 105, 106, 107, - /* 340 */ 108, 109, 110, 111, 112, 113, 114, 82, 281, 206, - /* 350 */ 195, 109, 110, 111, 112, 113, 114, 195, 195, 195, - /* 360 */ 205, 22, 207, 103, 104, 105, 106, 107, 108, 109, - /* 370 */ 110, 111, 112, 113, 114, 195, 60, 116, 117, 107, - /* 380 */ 108, 218, 219, 19, 241, 242, 121, 23, 116, 117, - /* 390 */ 118, 119, 306, 121, 308, 206, 234, 254, 15, 256, - /* 400 */ 195, 129, 259, 260, 139, 140, 145, 43, 44, 45, - /* 410 */ 200, 47, 48, 49, 50, 51, 52, 53, 54, 55, - /* 420 */ 56, 57, 58, 218, 219, 60, 154, 19, 156, 265, - /* 430 */ 241, 242, 24, 117, 118, 119, 120, 21, 73, 123, - /* 440 */ 124, 125, 74, 254, 61, 256, 107, 108, 221, 133, - /* 450 */ 82, 43, 44, 45, 195, 47, 48, 49, 50, 51, - /* 460 */ 52, 53, 54, 55, 56, 57, 58, 103, 104, 105, - /* 470 */ 106, 107, 108, 109, 110, 111, 112, 113, 114, 195, - /* 480 */ 317, 318, 117, 118, 119, 22, 120, 195, 22, 123, - /* 490 */ 124, 125, 19, 20, 284, 22, 128, 81, 288, 133, - /* 500 */ 195, 195, 218, 219, 277, 278, 279, 139, 140, 36, - /* 510 */ 195, 103, 104, 105, 106, 107, 108, 109, 110, 111, - /* 520 */ 112, 113, 114, 218, 219, 62, 60, 195, 241, 242, - /* 530 */ 271, 19, 240, 60, 189, 190, 191, 192, 233, 255, - /* 540 */ 124, 254, 197, 256, 199, 72, 129, 130, 264, 195, - /* 550 */ 195, 206, 22, 23, 60, 43, 44, 45, 206, 47, - /* 560 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - /* 570 */ 58, 195, 218, 219, 101, 195, 60, 271, 162, 195, - /* 580 */ 107, 108, 109, 117, 118, 119, 241, 242, 115, 73, - /* 590 */ 117, 118, 119, 241, 242, 122, 60, 195, 266, 254, - /* 600 */ 312, 256, 218, 219, 316, 203, 254, 195, 256, 255, - /* 610 */ 208, 117, 118, 119, 269, 103, 104, 105, 106, 107, - /* 620 */ 108, 109, 110, 111, 112, 113, 114, 154, 155, 156, - /* 630 */ 157, 158, 102, 117, 118, 119, 19, 242, 144, 255, - /* 640 */ 23, 206, 24, 298, 195, 300, 206, 195, 264, 254, - /* 650 */ 206, 256, 240, 117, 118, 119, 183, 22, 22, 23, - /* 660 */ 43, 44, 45, 151, 47, 48, 49, 50, 51, 52, - /* 670 */ 53, 54, 55, 56, 57, 58, 241, 242, 60, 195, - /* 680 */ 19, 241, 242, 195, 23, 241, 242, 195, 152, 254, - /* 690 */ 310, 256, 243, 312, 254, 60, 256, 316, 254, 206, - /* 700 */ 256, 60, 218, 219, 43, 44, 45, 272, 47, 48, - /* 710 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, - /* 720 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, - /* 730 */ 113, 114, 240, 60, 241, 242, 118, 25, 102, 255, - /* 740 */ 166, 167, 101, 22, 26, 19, 20, 254, 22, 256, - /* 750 */ 139, 140, 117, 118, 119, 306, 195, 308, 117, 118, - /* 760 */ 237, 238, 36, 122, 103, 104, 105, 106, 107, 108, - /* 770 */ 109, 110, 111, 112, 113, 114, 195, 195, 60, 218, - /* 780 */ 219, 60, 109, 195, 19, 217, 60, 25, 23, 77, - /* 790 */ 117, 118, 119, 225, 233, 154, 155, 156, 72, 312, - /* 800 */ 218, 219, 90, 316, 22, 93, 303, 304, 43, 44, - /* 810 */ 45, 195, 47, 48, 49, 50, 51, 52, 53, 54, - /* 820 */ 55, 56, 57, 58, 183, 195, 195, 101, 19, 213, - /* 830 */ 214, 243, 23, 107, 108, 117, 118, 119, 117, 118, - /* 840 */ 119, 115, 60, 117, 118, 119, 195, 60, 122, 218, - /* 850 */ 219, 22, 43, 44, 45, 35, 47, 48, 49, 50, - /* 860 */ 51, 52, 53, 54, 55, 56, 57, 58, 103, 104, - /* 870 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - /* 880 */ 154, 155, 156, 157, 158, 195, 255, 67, 195, 60, - /* 890 */ 101, 240, 311, 312, 306, 75, 308, 316, 29, 117, - /* 900 */ 118, 119, 33, 287, 117, 118, 119, 118, 146, 183, - /* 910 */ 195, 122, 103, 104, 105, 106, 107, 108, 109, 110, - /* 920 */ 111, 112, 113, 114, 215, 195, 77, 60, 25, 195, - /* 930 */ 122, 144, 19, 218, 219, 66, 23, 88, 246, 90, - /* 940 */ 132, 25, 93, 154, 155, 156, 117, 118, 119, 257, - /* 950 */ 195, 131, 218, 219, 195, 265, 43, 44, 45, 195, - /* 960 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - /* 970 */ 57, 58, 183, 218, 219, 195, 19, 218, 219, 195, - /* 980 */ 23, 195, 218, 219, 117, 118, 119, 195, 233, 255, - /* 990 */ 195, 195, 233, 22, 23, 146, 25, 233, 218, 219, - /* 1000 */ 43, 44, 45, 294, 47, 48, 49, 50, 51, 52, - /* 1010 */ 53, 54, 55, 56, 57, 58, 103, 104, 105, 106, - /* 1020 */ 107, 108, 109, 110, 111, 112, 113, 114, 195, 12, - /* 1030 */ 234, 195, 240, 74, 195, 255, 195, 60, 243, 262, - /* 1040 */ 263, 311, 312, 25, 27, 19, 316, 107, 108, 265, - /* 1050 */ 24, 265, 195, 150, 195, 139, 140, 218, 219, 42, - /* 1060 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, - /* 1070 */ 113, 114, 233, 102, 67, 218, 219, 218, 219, 243, - /* 1080 */ 19, 64, 22, 23, 23, 25, 195, 128, 129, 130, - /* 1090 */ 233, 74, 233, 86, 154, 118, 156, 130, 265, 208, - /* 1100 */ 19, 306, 95, 308, 43, 44, 45, 266, 47, 48, - /* 1110 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, - /* 1120 */ 153, 230, 96, 232, 43, 44, 45, 19, 47, 48, - /* 1130 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, - /* 1140 */ 114, 22, 306, 24, 308, 127, 120, 121, 122, 123, - /* 1150 */ 124, 125, 126, 195, 147, 212, 213, 214, 132, 23, - /* 1160 */ 195, 25, 102, 100, 103, 104, 105, 106, 107, 108, - /* 1170 */ 109, 110, 111, 112, 113, 114, 218, 219, 19, 60, - /* 1180 */ 195, 12, 210, 211, 103, 104, 105, 106, 107, 108, - /* 1190 */ 109, 110, 111, 112, 113, 114, 27, 134, 195, 195, - /* 1200 */ 195, 210, 211, 218, 219, 195, 47, 195, 212, 213, - /* 1210 */ 214, 42, 16, 130, 19, 112, 113, 114, 23, 77, - /* 1220 */ 195, 218, 219, 218, 219, 117, 163, 164, 218, 219, - /* 1230 */ 218, 219, 90, 64, 19, 93, 153, 118, 43, 44, - /* 1240 */ 45, 160, 47, 48, 49, 50, 51, 52, 53, 54, - /* 1250 */ 55, 56, 57, 58, 195, 119, 272, 276, 43, 44, - /* 1260 */ 45, 195, 47, 48, 49, 50, 51, 52, 53, 54, - /* 1270 */ 55, 56, 57, 58, 78, 116, 80, 218, 219, 116, - /* 1280 */ 144, 128, 129, 130, 218, 219, 61, 195, 47, 195, - /* 1290 */ 16, 132, 195, 263, 195, 314, 315, 267, 103, 104, - /* 1300 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - /* 1310 */ 218, 219, 218, 219, 151, 218, 219, 195, 103, 104, - /* 1320 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - /* 1330 */ 210, 211, 195, 7, 8, 9, 195, 60, 195, 312, - /* 1340 */ 218, 219, 195, 316, 195, 120, 195, 263, 19, 195, - /* 1350 */ 125, 267, 78, 24, 80, 218, 219, 116, 162, 218, - /* 1360 */ 219, 218, 219, 301, 302, 218, 219, 195, 19, 218, - /* 1370 */ 219, 276, 43, 44, 45, 160, 47, 48, 49, 50, - /* 1380 */ 51, 52, 53, 54, 55, 56, 57, 58, 19, 146, - /* 1390 */ 218, 219, 43, 44, 45, 118, 47, 48, 49, 50, - /* 1400 */ 51, 52, 53, 54, 55, 56, 57, 58, 165, 314, - /* 1410 */ 315, 276, 43, 44, 45, 266, 47, 48, 49, 50, - /* 1420 */ 51, 52, 53, 54, 55, 56, 57, 58, 128, 129, - /* 1430 */ 130, 195, 103, 104, 105, 106, 107, 108, 109, 110, - /* 1440 */ 111, 112, 113, 114, 195, 228, 195, 61, 195, 314, - /* 1450 */ 315, 25, 103, 104, 105, 106, 107, 108, 109, 110, - /* 1460 */ 111, 112, 113, 114, 195, 22, 195, 218, 219, 218, - /* 1470 */ 219, 195, 103, 104, 105, 106, 107, 108, 109, 110, - /* 1480 */ 111, 112, 113, 114, 195, 195, 246, 218, 219, 218, - /* 1490 */ 219, 25, 19, 246, 218, 219, 246, 257, 259, 260, - /* 1500 */ 195, 22, 266, 60, 257, 195, 120, 257, 218, 219, - /* 1510 */ 116, 195, 19, 195, 150, 151, 25, 44, 45, 266, - /* 1520 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - /* 1530 */ 57, 58, 195, 54, 218, 219, 218, 219, 45, 145, - /* 1540 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - /* 1550 */ 57, 58, 246, 121, 122, 218, 219, 19, 23, 31, - /* 1560 */ 25, 118, 159, 257, 161, 24, 195, 39, 195, 143, - /* 1570 */ 195, 19, 20, 22, 22, 24, 103, 104, 105, 106, - /* 1580 */ 107, 108, 109, 110, 111, 112, 113, 114, 36, 218, - /* 1590 */ 219, 218, 219, 218, 219, 195, 103, 104, 105, 106, - /* 1600 */ 107, 108, 109, 110, 111, 112, 113, 114, 195, 143, - /* 1610 */ 119, 136, 60, 195, 22, 195, 141, 195, 218, 219, - /* 1620 */ 195, 23, 195, 25, 72, 23, 131, 25, 195, 134, - /* 1630 */ 23, 218, 219, 195, 82, 144, 218, 219, 218, 219, - /* 1640 */ 218, 219, 195, 218, 219, 218, 219, 60, 23, 195, - /* 1650 */ 25, 218, 219, 101, 195, 117, 218, 219, 195, 107, - /* 1660 */ 108, 23, 195, 25, 195, 218, 219, 115, 228, 117, - /* 1670 */ 118, 119, 218, 219, 122, 195, 19, 218, 219, 195, - /* 1680 */ 60, 218, 219, 142, 195, 218, 219, 19, 20, 195, - /* 1690 */ 22, 139, 140, 23, 23, 25, 25, 195, 218, 219, - /* 1700 */ 7, 8, 218, 219, 36, 118, 154, 155, 156, 157, - /* 1710 */ 158, 195, 23, 195, 25, 84, 85, 49, 195, 23, - /* 1720 */ 195, 25, 195, 23, 195, 25, 195, 23, 60, 25, - /* 1730 */ 23, 23, 25, 25, 142, 183, 218, 219, 118, 195, - /* 1740 */ 72, 218, 219, 218, 219, 218, 219, 218, 219, 218, - /* 1750 */ 219, 195, 195, 146, 86, 98, 23, 195, 25, 91, - /* 1760 */ 19, 20, 154, 22, 156, 154, 23, 156, 25, 101, - /* 1770 */ 23, 195, 25, 195, 195, 107, 108, 36, 195, 195, - /* 1780 */ 195, 195, 228, 115, 195, 117, 118, 119, 195, 195, - /* 1790 */ 122, 261, 195, 321, 195, 195, 195, 258, 238, 195, - /* 1800 */ 195, 60, 299, 291, 195, 195, 258, 195, 195, 195, - /* 1810 */ 290, 244, 216, 72, 245, 193, 258, 258, 299, 258, - /* 1820 */ 299, 274, 154, 155, 156, 157, 158, 86, 247, 295, - /* 1830 */ 248, 295, 91, 19, 20, 270, 22, 274, 270, 248, - /* 1840 */ 274, 222, 101, 227, 274, 221, 231, 221, 107, 108, - /* 1850 */ 36, 183, 262, 247, 221, 283, 115, 262, 117, 118, - /* 1860 */ 119, 198, 116, 122, 220, 262, 61, 220, 220, 251, - /* 1870 */ 247, 142, 251, 245, 60, 202, 299, 202, 38, 262, - /* 1880 */ 202, 22, 152, 151, 296, 43, 72, 236, 18, 239, - /* 1890 */ 202, 239, 239, 239, 18, 154, 155, 156, 157, 158, - /* 1900 */ 86, 150, 201, 248, 275, 91, 248, 273, 236, 248, - /* 1910 */ 275, 275, 273, 236, 248, 101, 286, 202, 201, 159, - /* 1920 */ 63, 107, 108, 296, 183, 293, 202, 201, 22, 115, - /* 1930 */ 202, 117, 118, 119, 292, 223, 122, 201, 65, 202, - /* 1940 */ 201, 223, 220, 220, 22, 220, 226, 226, 229, 127, - /* 1950 */ 223, 220, 166, 24, 285, 220, 222, 114, 315, 285, - /* 1960 */ 220, 202, 220, 307, 92, 320, 320, 229, 154, 155, - /* 1970 */ 156, 157, 158, 0, 1, 2, 223, 83, 5, 268, - /* 1980 */ 149, 268, 146, 10, 11, 12, 13, 14, 22, 280, - /* 1990 */ 17, 202, 159, 19, 20, 251, 22, 183, 282, 148, - /* 2000 */ 252, 252, 250, 30, 249, 32, 248, 147, 25, 13, - /* 2010 */ 36, 204, 196, 40, 196, 6, 302, 194, 194, 194, - /* 2020 */ 209, 215, 209, 215, 215, 215, 224, 224, 216, 209, - /* 2030 */ 4, 216, 215, 3, 60, 22, 122, 19, 122, 19, - /* 2040 */ 125, 22, 15, 22, 71, 16, 72, 23, 23, 140, - /* 2050 */ 305, 152, 79, 25, 131, 82, 143, 20, 16, 305, - /* 2060 */ 1, 143, 145, 131, 131, 62, 54, 131, 37, 54, - /* 2070 */ 54, 152, 99, 117, 34, 101, 54, 24, 1, 5, - /* 2080 */ 22, 107, 108, 116, 76, 25, 162, 41, 142, 115, - /* 2090 */ 24, 117, 118, 119, 116, 20, 122, 19, 126, 23, - /* 2100 */ 132, 19, 20, 69, 22, 69, 22, 134, 22, 68, - /* 2110 */ 22, 22, 139, 140, 60, 141, 68, 24, 36, 28, - /* 2120 */ 97, 22, 37, 68, 23, 150, 34, 22, 154, 155, - /* 2130 */ 156, 157, 158, 23, 23, 22, 163, 25, 23, 142, - /* 2140 */ 23, 98, 60, 23, 22, 144, 25, 76, 34, 117, - /* 2150 */ 34, 89, 34, 34, 72, 87, 76, 183, 34, 94, - /* 2160 */ 34, 23, 22, 24, 34, 23, 25, 44, 25, 23, - /* 2170 */ 23, 23, 22, 22, 25, 11, 143, 25, 143, 23, - /* 2180 */ 22, 22, 22, 101, 23, 23, 136, 22, 25, 107, - /* 2190 */ 108, 142, 25, 142, 142, 23, 15, 115, 1, 117, - /* 2200 */ 118, 119, 1, 2, 122, 1, 5, 322, 322, 322, - /* 2210 */ 322, 10, 11, 12, 13, 14, 322, 322, 17, 322, - /* 2220 */ 5, 322, 322, 141, 322, 10, 11, 12, 13, 14, - /* 2230 */ 322, 30, 17, 32, 322, 322, 154, 155, 156, 157, - /* 2240 */ 158, 40, 322, 322, 322, 30, 322, 32, 322, 322, - /* 2250 */ 322, 322, 322, 322, 322, 40, 322, 322, 322, 322, - /* 2260 */ 322, 322, 322, 322, 322, 183, 322, 322, 322, 322, - /* 2270 */ 322, 322, 71, 322, 322, 322, 322, 322, 322, 322, - /* 2280 */ 79, 322, 322, 82, 322, 322, 71, 322, 322, 322, - /* 2290 */ 322, 322, 322, 322, 79, 322, 322, 82, 322, 322, - /* 2300 */ 99, 322, 322, 322, 322, 322, 322, 322, 322, 322, - /* 2310 */ 322, 322, 322, 322, 99, 322, 322, 322, 322, 322, - /* 2320 */ 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, - /* 2330 */ 322, 322, 322, 322, 322, 134, 322, 322, 322, 322, - /* 2340 */ 139, 140, 322, 322, 322, 322, 322, 322, 322, 134, - /* 2350 */ 322, 322, 322, 322, 139, 140, 322, 322, 322, 322, - /* 2360 */ 322, 322, 322, 322, 163, 322, 322, 322, 322, 322, - /* 2370 */ 322, 322, 322, 322, 322, 322, 322, 322, 163, 322, - /* 2380 */ 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, - /* 2390 */ 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, - /* 2400 */ 322, 322, 322, 322, 322, 322, 322, 322, 187, 187, - /* 2410 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2420 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2430 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2440 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2450 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2460 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2470 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2480 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2490 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2500 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2510 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2520 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2530 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2540 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2550 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 2560 */ 187, 187, 187, 187, 187, 187, + /* 330 */ 114, 195, 195, 215, 117, 118, 119, 120, 195, 19, + /* 340 */ 123, 124, 125, 207, 24, 74, 246, 60, 310, 311, + /* 350 */ 133, 60, 311, 82, 22, 218, 219, 257, 195, 19, + /* 360 */ 73, 218, 219, 43, 44, 45, 206, 47, 48, 49, + /* 370 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 22, + /* 380 */ 23, 218, 219, 43, 44, 45, 54, 47, 48, 49, + /* 390 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 128, + /* 400 */ 82, 241, 242, 195, 117, 118, 119, 289, 60, 118, + /* 410 */ 139, 140, 294, 195, 254, 195, 256, 195, 255, 259, + /* 420 */ 260, 73, 22, 103, 104, 105, 106, 107, 108, 109, + /* 430 */ 110, 111, 112, 113, 114, 206, 218, 219, 218, 219, + /* 440 */ 218, 219, 234, 103, 104, 105, 106, 107, 108, 109, + /* 450 */ 110, 111, 112, 113, 114, 318, 319, 139, 140, 102, + /* 460 */ 60, 318, 319, 221, 19, 117, 118, 119, 23, 195, + /* 470 */ 241, 242, 313, 255, 206, 255, 317, 255, 206, 129, + /* 480 */ 130, 206, 264, 254, 264, 256, 264, 195, 43, 44, + /* 490 */ 45, 151, 47, 48, 49, 50, 51, 52, 53, 54, + /* 500 */ 55, 56, 57, 58, 246, 213, 214, 19, 19, 241, + /* 510 */ 242, 195, 23, 241, 242, 257, 241, 242, 118, 277, + /* 520 */ 278, 279, 254, 29, 256, 60, 254, 33, 256, 254, + /* 530 */ 206, 256, 43, 44, 45, 218, 47, 48, 49, 50, + /* 540 */ 51, 52, 53, 54, 55, 56, 57, 58, 103, 104, + /* 550 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + /* 560 */ 66, 19, 218, 60, 120, 241, 242, 123, 124, 125, + /* 570 */ 60, 232, 77, 19, 20, 26, 22, 133, 254, 287, + /* 580 */ 256, 265, 117, 118, 119, 90, 312, 313, 93, 47, + /* 590 */ 36, 317, 103, 104, 105, 106, 107, 108, 109, 110, + /* 600 */ 111, 112, 113, 114, 116, 117, 277, 278, 279, 60, + /* 610 */ 107, 108, 19, 276, 60, 31, 23, 152, 195, 116, + /* 620 */ 117, 118, 119, 39, 121, 276, 72, 117, 118, 119, + /* 630 */ 166, 167, 129, 145, 237, 238, 43, 44, 45, 276, + /* 640 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 650 */ 57, 58, 315, 316, 144, 101, 19, 154, 116, 156, + /* 660 */ 23, 107, 108, 109, 315, 316, 117, 118, 119, 115, + /* 670 */ 60, 117, 118, 119, 132, 200, 122, 60, 315, 316, + /* 680 */ 43, 44, 45, 272, 47, 48, 49, 50, 51, 52, + /* 690 */ 53, 54, 55, 56, 57, 58, 103, 104, 105, 106, + /* 700 */ 107, 108, 109, 110, 111, 112, 113, 114, 154, 155, + /* 710 */ 156, 157, 158, 212, 213, 214, 22, 195, 101, 22, + /* 720 */ 60, 19, 20, 60, 22, 139, 140, 117, 118, 119, + /* 730 */ 22, 251, 195, 253, 117, 118, 195, 183, 36, 122, + /* 740 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 750 */ 113, 114, 195, 195, 60, 218, 219, 60, 195, 284, + /* 760 */ 19, 25, 60, 288, 23, 237, 238, 22, 60, 109, + /* 770 */ 233, 154, 155, 156, 72, 218, 219, 117, 118, 119, + /* 780 */ 117, 118, 119, 116, 43, 44, 45, 265, 47, 48, + /* 790 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 800 */ 183, 243, 25, 101, 19, 60, 265, 144, 23, 107, + /* 810 */ 108, 117, 118, 119, 117, 118, 119, 115, 151, 117, + /* 820 */ 118, 119, 82, 195, 122, 117, 118, 119, 43, 44, + /* 830 */ 45, 195, 47, 48, 49, 50, 51, 52, 53, 54, + /* 840 */ 55, 56, 57, 58, 103, 104, 105, 106, 107, 108, + /* 850 */ 109, 110, 111, 112, 113, 114, 154, 155, 156, 157, + /* 860 */ 158, 121, 117, 118, 119, 307, 101, 309, 195, 22, + /* 870 */ 23, 195, 25, 19, 35, 139, 140, 195, 24, 139, + /* 880 */ 140, 208, 195, 118, 109, 183, 22, 122, 103, 104, + /* 890 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + /* 900 */ 304, 305, 77, 230, 127, 232, 67, 195, 19, 195, + /* 910 */ 195, 136, 23, 88, 75, 90, 141, 203, 93, 154, + /* 920 */ 155, 156, 208, 295, 60, 243, 22, 23, 19, 25, + /* 930 */ 218, 219, 43, 44, 45, 100, 47, 48, 49, 50, + /* 940 */ 51, 52, 53, 54, 55, 56, 57, 58, 183, 102, + /* 950 */ 96, 195, 43, 44, 45, 240, 47, 48, 49, 50, + /* 960 */ 51, 52, 53, 54, 55, 56, 57, 58, 114, 134, + /* 970 */ 131, 146, 25, 286, 120, 121, 122, 123, 124, 125, + /* 980 */ 126, 117, 118, 119, 313, 195, 132, 195, 317, 307, + /* 990 */ 195, 309, 103, 104, 105, 106, 107, 108, 109, 110, + /* 1000 */ 111, 112, 113, 114, 195, 195, 102, 195, 195, 195, + /* 1010 */ 218, 219, 103, 104, 105, 106, 107, 108, 109, 110, + /* 1020 */ 111, 112, 113, 114, 77, 233, 195, 60, 218, 219, + /* 1030 */ 218, 219, 218, 219, 23, 195, 25, 90, 243, 159, + /* 1040 */ 93, 161, 19, 233, 195, 233, 23, 233, 16, 218, + /* 1050 */ 219, 195, 243, 212, 213, 214, 262, 263, 218, 219, + /* 1060 */ 195, 271, 19, 307, 233, 309, 43, 44, 45, 160, + /* 1070 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1080 */ 57, 58, 195, 218, 219, 118, 43, 44, 45, 240, + /* 1090 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1100 */ 57, 58, 307, 195, 309, 218, 219, 263, 12, 195, + /* 1110 */ 78, 267, 80, 112, 113, 114, 307, 22, 309, 24, + /* 1120 */ 255, 281, 266, 27, 107, 108, 103, 104, 105, 106, + /* 1130 */ 107, 108, 109, 110, 111, 112, 113, 114, 42, 195, + /* 1140 */ 11, 22, 255, 24, 195, 195, 103, 104, 105, 106, + /* 1150 */ 107, 108, 109, 110, 111, 112, 113, 114, 19, 195, + /* 1160 */ 64, 195, 218, 219, 195, 313, 195, 218, 219, 317, + /* 1170 */ 74, 154, 195, 156, 195, 195, 19, 233, 23, 60, + /* 1180 */ 25, 24, 218, 219, 218, 219, 195, 218, 219, 218, + /* 1190 */ 219, 128, 129, 130, 162, 263, 19, 218, 219, 267, + /* 1200 */ 43, 44, 45, 160, 47, 48, 49, 50, 51, 52, + /* 1210 */ 53, 54, 55, 56, 57, 58, 19, 240, 228, 255, + /* 1220 */ 43, 44, 45, 25, 47, 48, 49, 50, 51, 52, + /* 1230 */ 53, 54, 55, 56, 57, 58, 135, 118, 137, 138, + /* 1240 */ 43, 44, 45, 22, 47, 48, 49, 50, 51, 52, + /* 1250 */ 53, 54, 55, 56, 57, 58, 117, 266, 129, 130, + /* 1260 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 1270 */ 113, 114, 195, 195, 119, 295, 195, 206, 195, 195, + /* 1280 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 1290 */ 113, 114, 195, 195, 195, 218, 219, 195, 195, 144, + /* 1300 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 1310 */ 113, 114, 241, 242, 67, 218, 219, 218, 219, 146, + /* 1320 */ 19, 218, 219, 240, 215, 254, 136, 256, 107, 108, + /* 1330 */ 195, 141, 255, 86, 128, 129, 130, 195, 165, 195, + /* 1340 */ 19, 143, 95, 272, 25, 44, 45, 266, 47, 48, + /* 1350 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 1360 */ 218, 219, 218, 219, 195, 12, 45, 195, 47, 48, + /* 1370 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 1380 */ 27, 23, 7, 8, 9, 210, 211, 218, 219, 116, + /* 1390 */ 218, 219, 228, 16, 147, 42, 195, 295, 195, 19, + /* 1400 */ 20, 266, 22, 294, 103, 104, 105, 106, 107, 108, + /* 1410 */ 109, 110, 111, 112, 113, 114, 36, 64, 145, 218, + /* 1420 */ 219, 218, 219, 195, 103, 104, 105, 106, 107, 108, + /* 1430 */ 109, 110, 111, 112, 113, 114, 195, 154, 119, 156, + /* 1440 */ 60, 189, 190, 191, 192, 195, 218, 219, 195, 197, + /* 1450 */ 195, 199, 72, 195, 19, 78, 195, 80, 206, 218, + /* 1460 */ 219, 195, 82, 144, 210, 211, 195, 15, 218, 219, + /* 1470 */ 47, 218, 219, 218, 219, 259, 260, 195, 261, 218, + /* 1480 */ 219, 101, 302, 303, 218, 219, 195, 107, 108, 218, + /* 1490 */ 219, 150, 151, 241, 242, 115, 25, 117, 118, 119, + /* 1500 */ 218, 219, 122, 195, 146, 195, 254, 195, 256, 218, + /* 1510 */ 219, 246, 25, 61, 246, 19, 20, 195, 22, 139, + /* 1520 */ 140, 269, 257, 195, 266, 257, 218, 219, 218, 219, + /* 1530 */ 218, 219, 36, 246, 154, 155, 156, 157, 158, 116, + /* 1540 */ 218, 219, 195, 22, 257, 49, 218, 219, 23, 195, + /* 1550 */ 25, 195, 117, 301, 195, 25, 60, 195, 195, 23, + /* 1560 */ 195, 25, 195, 183, 24, 218, 219, 130, 72, 195, + /* 1570 */ 22, 195, 218, 219, 218, 219, 195, 218, 219, 195, + /* 1580 */ 218, 219, 86, 218, 219, 218, 219, 91, 19, 20, + /* 1590 */ 153, 22, 218, 219, 218, 219, 195, 101, 195, 218, + /* 1600 */ 219, 195, 195, 107, 108, 36, 23, 195, 25, 195, + /* 1610 */ 62, 115, 195, 117, 118, 119, 195, 146, 122, 218, + /* 1620 */ 219, 218, 219, 195, 218, 219, 19, 60, 122, 60, + /* 1630 */ 218, 219, 218, 219, 195, 218, 219, 150, 132, 218, + /* 1640 */ 219, 72, 195, 23, 195, 25, 218, 219, 195, 60, + /* 1650 */ 154, 155, 156, 157, 158, 86, 23, 195, 25, 195, + /* 1660 */ 91, 19, 20, 142, 22, 218, 219, 218, 219, 130, + /* 1670 */ 101, 218, 219, 143, 121, 122, 107, 108, 36, 183, + /* 1680 */ 218, 219, 142, 60, 115, 118, 117, 118, 119, 7, + /* 1690 */ 8, 122, 153, 23, 23, 25, 25, 23, 23, 25, + /* 1700 */ 25, 23, 60, 25, 23, 98, 25, 118, 84, 85, + /* 1710 */ 23, 23, 25, 25, 72, 154, 23, 156, 25, 23, + /* 1720 */ 228, 25, 195, 154, 155, 156, 157, 158, 86, 195, + /* 1730 */ 195, 258, 195, 91, 291, 322, 195, 195, 195, 195, + /* 1740 */ 195, 118, 195, 101, 195, 195, 195, 195, 238, 107, + /* 1750 */ 108, 195, 183, 195, 195, 195, 290, 115, 195, 117, + /* 1760 */ 118, 119, 244, 195, 122, 195, 195, 195, 195, 195, + /* 1770 */ 195, 258, 258, 258, 258, 193, 245, 300, 216, 274, + /* 1780 */ 247, 270, 270, 274, 296, 296, 248, 222, 262, 198, + /* 1790 */ 262, 274, 61, 274, 248, 231, 154, 155, 156, 157, + /* 1800 */ 158, 0, 1, 2, 247, 227, 5, 221, 221, 221, + /* 1810 */ 142, 10, 11, 12, 13, 14, 262, 262, 17, 202, + /* 1820 */ 300, 19, 20, 300, 22, 183, 247, 251, 251, 245, + /* 1830 */ 202, 30, 38, 32, 202, 152, 151, 22, 36, 43, + /* 1840 */ 236, 40, 18, 202, 239, 239, 18, 239, 239, 283, + /* 1850 */ 201, 150, 236, 202, 236, 201, 159, 202, 248, 248, + /* 1860 */ 248, 248, 60, 63, 201, 275, 273, 275, 273, 275, + /* 1870 */ 22, 286, 71, 223, 72, 202, 223, 297, 297, 202, + /* 1880 */ 79, 201, 116, 82, 220, 201, 220, 220, 65, 293, + /* 1890 */ 292, 229, 22, 166, 127, 226, 24, 114, 226, 223, + /* 1900 */ 99, 222, 202, 101, 285, 92, 220, 308, 83, 107, + /* 1910 */ 108, 220, 220, 316, 220, 285, 268, 115, 229, 117, + /* 1920 */ 118, 119, 223, 321, 122, 268, 149, 146, 22, 19, + /* 1930 */ 20, 202, 22, 159, 282, 134, 321, 148, 280, 147, + /* 1940 */ 139, 140, 252, 141, 25, 204, 36, 252, 13, 251, + /* 1950 */ 196, 248, 250, 249, 196, 6, 154, 155, 156, 157, + /* 1960 */ 158, 209, 194, 194, 163, 194, 306, 306, 303, 224, + /* 1970 */ 60, 215, 215, 209, 215, 215, 215, 224, 216, 216, + /* 1980 */ 4, 209, 72, 3, 22, 183, 164, 15, 23, 16, + /* 1990 */ 23, 140, 152, 131, 25, 24, 143, 20, 16, 145, + /* 2000 */ 1, 143, 131, 62, 131, 37, 54, 152, 54, 54, + /* 2010 */ 54, 101, 131, 117, 1, 34, 142, 107, 108, 5, + /* 2020 */ 22, 116, 162, 76, 41, 115, 69, 117, 118, 119, + /* 2030 */ 1, 2, 122, 25, 5, 69, 142, 116, 20, 10, + /* 2040 */ 11, 12, 13, 14, 24, 19, 17, 132, 5, 126, + /* 2050 */ 22, 141, 68, 10, 11, 12, 13, 14, 22, 30, + /* 2060 */ 17, 32, 22, 22, 154, 155, 156, 157, 158, 40, + /* 2070 */ 23, 68, 60, 30, 24, 32, 97, 28, 22, 68, + /* 2080 */ 23, 37, 34, 40, 150, 22, 25, 23, 23, 23, + /* 2090 */ 22, 98, 142, 183, 23, 23, 34, 22, 25, 89, + /* 2100 */ 71, 34, 117, 144, 34, 22, 76, 76, 79, 87, + /* 2110 */ 34, 82, 34, 44, 71, 94, 34, 23, 25, 24, + /* 2120 */ 34, 25, 79, 23, 23, 82, 23, 23, 99, 143, + /* 2130 */ 143, 22, 25, 25, 23, 22, 11, 22, 22, 25, + /* 2140 */ 23, 23, 99, 22, 22, 136, 142, 142, 142, 25, + /* 2150 */ 23, 15, 1, 1, 323, 323, 323, 323, 323, 323, + /* 2160 */ 323, 323, 323, 134, 323, 323, 323, 323, 139, 140, + /* 2170 */ 323, 323, 323, 323, 323, 323, 323, 134, 323, 323, + /* 2180 */ 323, 323, 139, 140, 323, 323, 323, 323, 323, 323, + /* 2190 */ 323, 323, 163, 323, 323, 323, 323, 323, 323, 323, + /* 2200 */ 323, 323, 323, 323, 323, 323, 163, 323, 323, 323, + /* 2210 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2220 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2230 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2240 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2250 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2260 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2270 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2280 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2290 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2300 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2310 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2320 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2330 */ 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, + /* 2340 */ 323, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2350 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2360 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2370 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2380 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2390 */ 187, 187, 187, 187, }; -#define YY_SHIFT_COUNT (599) +#define YY_SHIFT_COUNT (582) #define YY_SHIFT_MIN (0) -#define YY_SHIFT_MAX (2215) +#define YY_SHIFT_MAX (2152) static const unsigned short int yy_shift_ofst[] = { - /* 0 */ 2201, 1973, 2215, 1552, 1552, 33, 368, 1668, 1741, 1814, - /* 10 */ 726, 726, 726, 265, 33, 33, 33, 33, 33, 0, - /* 20 */ 0, 216, 1349, 726, 726, 726, 726, 726, 726, 726, - /* 30 */ 726, 726, 726, 726, 726, 726, 726, 726, 272, 272, - /* 40 */ 111, 111, 316, 365, 516, 867, 867, 916, 916, 916, - /* 50 */ 916, 40, 112, 260, 364, 408, 512, 617, 661, 765, - /* 60 */ 809, 913, 957, 1061, 1081, 1195, 1215, 1329, 1349, 1349, - /* 70 */ 1349, 1349, 1349, 1349, 1349, 1349, 1349, 1349, 1349, 1349, - /* 80 */ 1349, 1349, 1349, 1349, 1349, 1349, 1369, 1349, 1473, 1493, - /* 90 */ 1493, 473, 1974, 2082, 726, 726, 726, 726, 726, 726, - /* 100 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, - /* 110 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, - /* 120 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, - /* 130 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, - /* 140 */ 726, 726, 726, 726, 726, 726, 138, 232, 232, 232, - /* 150 */ 232, 232, 232, 232, 188, 99, 242, 718, 416, 1159, - /* 160 */ 867, 867, 940, 940, 867, 1103, 417, 574, 574, 574, - /* 170 */ 611, 139, 139, 2379, 2379, 1026, 1026, 1026, 536, 466, - /* 180 */ 466, 466, 466, 1017, 1017, 849, 718, 971, 1060, 867, - /* 190 */ 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, - /* 200 */ 867, 867, 867, 867, 867, 867, 867, 867, 261, 712, - /* 210 */ 712, 867, 108, 1142, 1142, 977, 1108, 1108, 977, 977, - /* 220 */ 1243, 2379, 2379, 2379, 2379, 2379, 2379, 2379, 641, 789, - /* 230 */ 789, 635, 366, 721, 673, 782, 494, 787, 829, 867, - /* 240 */ 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, - /* 250 */ 959, 867, 867, 867, 867, 867, 867, 867, 867, 867, - /* 260 */ 867, 867, 867, 867, 867, 820, 820, 820, 867, 867, - /* 270 */ 867, 1136, 867, 867, 867, 1119, 1007, 867, 1169, 867, - /* 280 */ 867, 867, 867, 867, 867, 867, 867, 1225, 1153, 869, - /* 290 */ 196, 618, 618, 618, 618, 1491, 196, 196, 91, 339, - /* 300 */ 1326, 1386, 383, 1163, 1364, 1426, 1364, 1538, 903, 1163, - /* 310 */ 1163, 903, 1163, 1426, 1538, 1018, 1535, 1241, 1528, 1528, - /* 320 */ 1528, 1394, 1394, 1394, 1394, 762, 762, 1403, 1466, 1475, - /* 330 */ 1551, 1746, 1805, 1746, 1746, 1729, 1729, 1840, 1840, 1729, - /* 340 */ 1730, 1732, 1859, 1842, 1870, 1870, 1870, 1870, 1729, 1876, - /* 350 */ 1751, 1732, 1732, 1751, 1859, 1842, 1751, 1842, 1751, 1729, - /* 360 */ 1876, 1760, 1857, 1729, 1876, 1906, 1729, 1876, 1729, 1876, - /* 370 */ 1906, 1746, 1746, 1746, 1873, 1922, 1922, 1906, 1746, 1822, - /* 380 */ 1746, 1873, 1746, 1746, 1786, 1929, 1843, 1843, 1906, 1729, - /* 390 */ 1872, 1872, 1894, 1894, 1831, 1836, 1966, 1729, 1833, 1831, - /* 400 */ 1851, 1860, 1751, 1983, 1996, 1996, 2009, 2009, 2009, 2379, - /* 410 */ 2379, 2379, 2379, 2379, 2379, 2379, 2379, 2379, 2379, 2379, - /* 420 */ 2379, 2379, 2379, 2379, 136, 1063, 1196, 530, 636, 1274, - /* 430 */ 1300, 1443, 1598, 1495, 1479, 967, 1083, 1602, 463, 1625, - /* 440 */ 1638, 1670, 1541, 1671, 1689, 1696, 1277, 1432, 1693, 808, - /* 450 */ 1700, 1607, 1657, 1587, 1704, 1707, 1631, 1708, 1733, 1608, - /* 460 */ 1611, 1743, 1747, 1620, 1592, 2026, 2030, 2013, 1914, 2018, - /* 470 */ 1916, 2020, 2019, 2021, 1915, 2027, 2029, 2024, 2025, 1909, - /* 480 */ 1899, 1923, 2028, 2028, 1913, 2037, 1917, 2042, 2059, 1918, - /* 490 */ 1932, 2028, 1933, 2003, 2031, 2028, 1919, 2012, 2015, 2016, - /* 500 */ 2022, 1936, 1956, 2040, 2053, 2077, 2074, 2058, 1967, 1924, - /* 510 */ 2034, 2060, 2036, 2008, 2046, 1946, 1978, 2066, 2075, 2078, - /* 520 */ 1968, 1972, 2084, 2041, 2086, 2088, 2076, 2089, 2048, 2054, - /* 530 */ 2093, 2023, 2091, 2099, 2055, 2085, 2101, 2092, 1975, 2105, - /* 540 */ 2110, 2111, 2112, 2115, 2113, 2043, 1997, 2117, 2120, 2032, - /* 550 */ 2114, 2122, 2001, 2121, 2116, 2118, 2119, 2124, 2062, 2071, - /* 560 */ 2068, 2123, 2080, 2065, 2126, 2138, 2140, 2139, 2141, 2143, - /* 570 */ 2130, 2033, 2035, 2142, 2121, 2146, 2147, 2148, 2150, 2149, - /* 580 */ 2152, 2156, 2151, 2164, 2158, 2159, 2161, 2162, 2160, 2165, - /* 590 */ 2163, 2050, 2049, 2051, 2052, 2167, 2172, 2181, 2197, 2204, + /* 0 */ 2029, 1801, 2043, 1380, 1380, 318, 271, 1496, 1569, 1642, + /* 10 */ 702, 702, 702, 740, 318, 318, 318, 318, 318, 0, + /* 20 */ 0, 216, 1177, 702, 702, 702, 702, 702, 702, 702, + /* 30 */ 702, 702, 702, 702, 702, 702, 702, 702, 503, 503, + /* 40 */ 111, 111, 217, 287, 348, 610, 610, 736, 736, 736, + /* 50 */ 736, 40, 112, 320, 340, 445, 489, 593, 637, 741, + /* 60 */ 785, 889, 909, 1023, 1043, 1157, 1177, 1177, 1177, 1177, + /* 70 */ 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, + /* 80 */ 1177, 1177, 1177, 1177, 1197, 1177, 1301, 1321, 1321, 554, + /* 90 */ 1802, 1910, 702, 702, 702, 702, 702, 702, 702, 702, + /* 100 */ 702, 702, 702, 702, 702, 702, 702, 702, 702, 702, + /* 110 */ 702, 702, 702, 702, 702, 702, 702, 702, 702, 702, + /* 120 */ 702, 702, 702, 702, 702, 702, 702, 702, 702, 702, + /* 130 */ 702, 702, 702, 702, 702, 702, 702, 702, 702, 702, + /* 140 */ 702, 702, 138, 198, 198, 198, 198, 198, 198, 198, + /* 150 */ 183, 99, 169, 549, 610, 151, 542, 610, 610, 1017, + /* 160 */ 1017, 610, 1001, 350, 464, 464, 464, 586, 1, 1, + /* 170 */ 2207, 2207, 854, 854, 854, 465, 694, 694, 694, 694, + /* 180 */ 1096, 1096, 825, 549, 847, 904, 610, 610, 610, 610, + /* 190 */ 610, 610, 610, 610, 610, 610, 610, 610, 610, 610, + /* 200 */ 610, 610, 610, 610, 610, 488, 947, 947, 610, 1129, + /* 210 */ 495, 495, 1139, 1139, 967, 967, 1173, 2207, 2207, 2207, + /* 220 */ 2207, 2207, 2207, 2207, 617, 765, 765, 697, 444, 708, + /* 230 */ 660, 745, 510, 663, 864, 610, 610, 610, 610, 610, + /* 240 */ 610, 610, 610, 610, 610, 188, 610, 610, 610, 610, + /* 250 */ 610, 610, 610, 610, 610, 610, 610, 610, 839, 839, + /* 260 */ 839, 610, 610, 610, 1155, 610, 610, 610, 1119, 1247, + /* 270 */ 610, 1353, 610, 610, 610, 610, 610, 610, 610, 610, + /* 280 */ 1063, 494, 1101, 291, 291, 291, 291, 1319, 1101, 1101, + /* 290 */ 775, 1221, 1375, 1452, 667, 1341, 1198, 1341, 1435, 1487, + /* 300 */ 667, 667, 1487, 667, 1198, 1435, 777, 1011, 1423, 584, + /* 310 */ 584, 584, 1273, 1273, 1273, 1273, 1471, 1471, 880, 1530, + /* 320 */ 1190, 1095, 1731, 1731, 1668, 1668, 1794, 1794, 1668, 1683, + /* 330 */ 1685, 1815, 1796, 1824, 1824, 1824, 1824, 1668, 1828, 1701, + /* 340 */ 1685, 1685, 1701, 1815, 1796, 1701, 1796, 1701, 1668, 1828, + /* 350 */ 1697, 1800, 1668, 1828, 1848, 1668, 1828, 1668, 1828, 1848, + /* 360 */ 1766, 1766, 1766, 1823, 1870, 1870, 1848, 1766, 1767, 1766, + /* 370 */ 1823, 1766, 1766, 1727, 1872, 1783, 1783, 1848, 1668, 1813, + /* 380 */ 1813, 1825, 1825, 1777, 1781, 1906, 1668, 1774, 1777, 1789, + /* 390 */ 1792, 1701, 1919, 1935, 1935, 1949, 1949, 1949, 2207, 2207, + /* 400 */ 2207, 2207, 2207, 2207, 2207, 2207, 2207, 2207, 2207, 2207, + /* 410 */ 2207, 2207, 2207, 69, 1032, 79, 357, 1377, 1206, 400, + /* 420 */ 1525, 835, 332, 1540, 1437, 1539, 1536, 1548, 1583, 1620, + /* 430 */ 1633, 1670, 1671, 1674, 1567, 1553, 1682, 1506, 1675, 1358, + /* 440 */ 1607, 1589, 1678, 1681, 1624, 1687, 1688, 1283, 1561, 1693, + /* 450 */ 1696, 1623, 1521, 1976, 1980, 1962, 1822, 1972, 1973, 1965, + /* 460 */ 1967, 1851, 1840, 1862, 1969, 1969, 1971, 1853, 1977, 1854, + /* 470 */ 1982, 1999, 1858, 1871, 1969, 1873, 1941, 1968, 1969, 1855, + /* 480 */ 1952, 1954, 1955, 1956, 1881, 1896, 1981, 1874, 2013, 2014, + /* 490 */ 1998, 1905, 1860, 1957, 2008, 1966, 1947, 1983, 1894, 1921, + /* 500 */ 2020, 2018, 2026, 1915, 1923, 2028, 1984, 2036, 2040, 2047, + /* 510 */ 2041, 2003, 2012, 2050, 1979, 2049, 2056, 2011, 2044, 2057, + /* 520 */ 2048, 1934, 2063, 2064, 2065, 2061, 2066, 2068, 1993, 1950, + /* 530 */ 2071, 2072, 1985, 2062, 2075, 1959, 2073, 2067, 2070, 2076, + /* 540 */ 2078, 2010, 2030, 2022, 2069, 2031, 2021, 2082, 2094, 2083, + /* 550 */ 2095, 2093, 2096, 2086, 1986, 1987, 2100, 2073, 2101, 2103, + /* 560 */ 2104, 2109, 2107, 2108, 2111, 2113, 2125, 2115, 2116, 2117, + /* 570 */ 2118, 2121, 2122, 2114, 2009, 2004, 2005, 2006, 2124, 2127, + /* 580 */ 2136, 2151, 2152, }; -#define YY_REDUCE_COUNT (423) -#define YY_REDUCE_MIN (-303) -#define YY_REDUCE_MAX (1825) +#define YY_REDUCE_COUNT (412) +#define YY_REDUCE_MIN (-277) +#define YY_REDUCE_MAX (1772) static const short yy_reduce_ofst[] = { - /* 0 */ -67, 345, -64, -178, -181, 143, 435, -78, -183, 163, - /* 10 */ -185, 284, 384, -174, 189, 352, 440, 444, 493, -23, - /* 20 */ 227, -277, -1, 305, 561, 755, 759, 764, -189, 839, - /* 30 */ 857, 354, 484, 859, 631, 67, 734, 780, -187, 616, - /* 40 */ 581, 730, 891, 449, 588, 795, 836, -238, 287, -238, - /* 50 */ 287, -256, -256, -256, -256, -256, -256, -256, -256, -256, - /* 60 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256, - /* 70 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256, - /* 80 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256, - /* 90 */ -256, 205, 582, 715, 958, 985, 1003, 1005, 1010, 1012, - /* 100 */ 1059, 1066, 1092, 1094, 1097, 1122, 1137, 1141, 1143, 1147, - /* 110 */ 1151, 1172, 1249, 1251, 1269, 1271, 1276, 1290, 1316, 1318, - /* 120 */ 1337, 1371, 1373, 1375, 1400, 1413, 1418, 1420, 1422, 1425, - /* 130 */ 1427, 1433, 1438, 1447, 1454, 1459, 1463, 1467, 1480, 1484, - /* 140 */ 1518, 1523, 1525, 1527, 1529, 1531, -256, -256, -256, -256, - /* 150 */ -256, -256, -256, -256, -256, -256, -256, 155, 210, -220, - /* 160 */ 86, -130, 943, 996, 402, -256, -113, 981, 1095, 1135, - /* 170 */ 395, -256, -256, -256, -256, 568, 568, 568, -4, -153, - /* 180 */ -133, 259, 306, -166, 523, -303, -126, 503, 503, -37, - /* 190 */ -149, 164, 690, 292, 412, 492, 651, 784, 332, 786, - /* 200 */ 841, 1149, 833, 1236, 792, 162, 796, 1253, 777, 288, - /* 210 */ 381, 380, 709, 487, 1027, 972, 1030, 1084, 991, 1120, - /* 220 */ -152, 1062, 692, 1240, 1247, 1250, 1239, 1306, -207, -194, - /* 230 */ 57, 180, 74, 315, 355, 376, 452, 488, 630, 693, - /* 240 */ 965, 1004, 1025, 1099, 1154, 1289, 1305, 1310, 1469, 1489, - /* 250 */ 984, 1494, 1502, 1516, 1544, 1556, 1557, 1562, 1576, 1578, - /* 260 */ 1579, 1583, 1584, 1585, 1586, 1217, 1440, 1554, 1589, 1593, - /* 270 */ 1594, 1530, 1597, 1599, 1600, 1539, 1472, 1601, 1560, 1604, - /* 280 */ 355, 1605, 1609, 1610, 1612, 1613, 1614, 1503, 1512, 1520, - /* 290 */ 1567, 1548, 1558, 1559, 1561, 1530, 1567, 1567, 1569, 1596, - /* 300 */ 1622, 1519, 1521, 1547, 1565, 1581, 1568, 1534, 1582, 1563, - /* 310 */ 1566, 1591, 1570, 1606, 1536, 1619, 1615, 1616, 1624, 1626, - /* 320 */ 1633, 1590, 1595, 1603, 1617, 1618, 1621, 1572, 1623, 1628, - /* 330 */ 1663, 1644, 1577, 1647, 1648, 1673, 1675, 1588, 1627, 1678, - /* 340 */ 1630, 1629, 1634, 1651, 1650, 1652, 1653, 1654, 1688, 1701, - /* 350 */ 1655, 1635, 1636, 1658, 1639, 1672, 1661, 1677, 1666, 1715, - /* 360 */ 1717, 1632, 1642, 1724, 1726, 1712, 1728, 1736, 1737, 1739, - /* 370 */ 1718, 1722, 1723, 1725, 1719, 1720, 1721, 1727, 1731, 1734, - /* 380 */ 1735, 1738, 1740, 1742, 1643, 1656, 1669, 1674, 1753, 1759, - /* 390 */ 1645, 1646, 1711, 1713, 1748, 1744, 1709, 1789, 1716, 1749, - /* 400 */ 1752, 1755, 1758, 1807, 1816, 1818, 1823, 1824, 1825, 1745, - /* 410 */ 1754, 1714, 1811, 1806, 1808, 1809, 1810, 1813, 1802, 1803, - /* 420 */ 1812, 1815, 1817, 1820, + /* 0 */ -67, 1252, -64, -178, -181, 160, 1071, 143, -184, 137, + /* 10 */ 218, 220, 222, -174, 229, 268, 272, 275, 324, -208, + /* 20 */ 242, -277, -39, 81, 537, 792, 810, 812, -189, 814, + /* 30 */ 831, 163, 865, 944, 887, 840, 964, 1077, -187, 292, + /* 40 */ -133, 274, 673, 558, 682, 795, 809, -238, -232, -238, + /* 50 */ -232, 329, 329, 329, 329, 329, 329, 329, 329, 329, + /* 60 */ 329, 329, 329, 329, 329, 329, 329, 329, 329, 329, + /* 70 */ 329, 329, 329, 329, 329, 329, 329, 329, 329, 329, + /* 80 */ 329, 329, 329, 329, 329, 329, 329, 329, 329, 557, + /* 90 */ 712, 949, 966, 969, 971, 979, 1097, 1099, 1103, 1142, + /* 100 */ 1144, 1169, 1172, 1201, 1203, 1228, 1241, 1250, 1253, 1255, + /* 110 */ 1261, 1266, 1271, 1282, 1291, 1308, 1310, 1312, 1322, 1328, + /* 120 */ 1347, 1354, 1356, 1359, 1362, 1365, 1367, 1374, 1376, 1381, + /* 130 */ 1401, 1403, 1406, 1412, 1414, 1417, 1421, 1428, 1447, 1449, + /* 140 */ 1453, 1462, 329, 329, 329, 329, 329, 329, 329, 329, + /* 150 */ 329, 329, 329, -22, -159, 475, -220, 756, 38, 501, + /* 160 */ 841, 714, 329, 118, 337, 349, 363, -56, 329, 329, + /* 170 */ 329, 329, -205, -205, -205, 687, -172, -130, -57, 790, + /* 180 */ 397, 528, -271, 136, 596, 596, 90, 316, 522, 541, + /* 190 */ -37, 715, 849, 977, 628, 856, 980, 991, 1081, 1102, + /* 200 */ 1135, 1083, -162, 208, 1258, 794, -86, 159, 41, 1109, + /* 210 */ 671, 852, 844, 932, 1175, 1254, 480, 1180, 100, 258, + /* 220 */ 1265, 1268, 1216, 1287, -139, 317, 344, 63, 339, 423, + /* 230 */ 563, 636, 676, 813, 908, 914, 950, 1078, 1084, 1098, + /* 240 */ 1363, 1384, 1407, 1439, 1464, 411, 1527, 1534, 1535, 1537, + /* 250 */ 1541, 1542, 1543, 1544, 1545, 1547, 1549, 1550, 990, 1164, + /* 260 */ 1492, 1551, 1552, 1556, 1217, 1558, 1559, 1560, 1473, 1413, + /* 270 */ 1563, 1510, 1568, 563, 1570, 1571, 1572, 1573, 1574, 1575, + /* 280 */ 1443, 1466, 1518, 1513, 1514, 1515, 1516, 1217, 1518, 1518, + /* 290 */ 1531, 1562, 1582, 1477, 1505, 1511, 1533, 1512, 1488, 1538, + /* 300 */ 1509, 1517, 1546, 1519, 1557, 1489, 1565, 1564, 1578, 1586, + /* 310 */ 1587, 1588, 1526, 1528, 1554, 1555, 1576, 1577, 1566, 1579, + /* 320 */ 1584, 1591, 1520, 1523, 1617, 1628, 1580, 1581, 1632, 1585, + /* 330 */ 1590, 1593, 1604, 1605, 1606, 1608, 1609, 1641, 1649, 1610, + /* 340 */ 1592, 1594, 1611, 1595, 1616, 1612, 1618, 1613, 1651, 1654, + /* 350 */ 1596, 1598, 1655, 1663, 1650, 1673, 1680, 1677, 1684, 1653, + /* 360 */ 1664, 1666, 1667, 1662, 1669, 1672, 1676, 1686, 1679, 1691, + /* 370 */ 1689, 1692, 1694, 1597, 1599, 1619, 1630, 1699, 1700, 1602, + /* 380 */ 1615, 1648, 1657, 1690, 1698, 1658, 1729, 1652, 1695, 1702, + /* 390 */ 1704, 1703, 1741, 1754, 1758, 1768, 1769, 1771, 1660, 1661, + /* 400 */ 1665, 1752, 1756, 1757, 1759, 1760, 1764, 1745, 1753, 1762, + /* 410 */ 1763, 1761, 1772, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 1691, 1691, 1691, 1516, 1279, 1392, 1279, 1279, 1279, 1279, - /* 10 */ 1516, 1516, 1516, 1279, 1279, 1279, 1279, 1279, 1279, 1422, - /* 20 */ 1422, 1568, 1312, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 30 */ 1279, 1279, 1279, 1279, 1279, 1515, 1279, 1279, 1279, 1279, - /* 40 */ 1607, 1607, 1279, 1279, 1279, 1279, 1279, 1592, 1591, 1279, - /* 50 */ 1279, 1279, 1431, 1279, 1279, 1279, 1438, 1279, 1279, 1279, - /* 60 */ 1279, 1279, 1517, 1518, 1279, 1279, 1279, 1279, 1567, 1569, - /* 70 */ 1533, 1445, 1444, 1443, 1442, 1551, 1410, 1436, 1429, 1433, - /* 80 */ 1512, 1513, 1511, 1670, 1518, 1517, 1279, 1432, 1480, 1496, - /* 90 */ 1479, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 100 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 110 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 120 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 130 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 140 */ 1279, 1279, 1279, 1279, 1279, 1279, 1488, 1495, 1494, 1493, - /* 150 */ 1502, 1492, 1489, 1482, 1481, 1483, 1484, 1303, 1300, 1354, - /* 160 */ 1279, 1279, 1279, 1279, 1279, 1485, 1312, 1473, 1472, 1471, - /* 170 */ 1279, 1499, 1486, 1498, 1497, 1575, 1644, 1643, 1534, 1279, - /* 180 */ 1279, 1279, 1279, 1279, 1279, 1607, 1279, 1279, 1279, 1279, - /* 190 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 200 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1412, 1607, - /* 210 */ 1607, 1279, 1312, 1607, 1607, 1308, 1413, 1413, 1308, 1308, - /* 220 */ 1416, 1587, 1383, 1383, 1383, 1383, 1392, 1383, 1279, 1279, - /* 230 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 240 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1572, 1570, 1279, - /* 250 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 260 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 270 */ 1279, 1279, 1279, 1279, 1279, 1388, 1279, 1279, 1279, 1279, - /* 280 */ 1279, 1279, 1279, 1279, 1279, 1279, 1637, 1683, 1279, 1546, - /* 290 */ 1368, 1388, 1388, 1388, 1388, 1390, 1369, 1367, 1382, 1313, - /* 300 */ 1286, 1683, 1683, 1448, 1437, 1389, 1437, 1680, 1435, 1448, - /* 310 */ 1448, 1435, 1448, 1389, 1680, 1329, 1659, 1324, 1422, 1422, - /* 320 */ 1422, 1412, 1412, 1412, 1412, 1416, 1416, 1514, 1389, 1382, - /* 330 */ 1279, 1355, 1683, 1355, 1355, 1398, 1398, 1682, 1682, 1398, - /* 340 */ 1534, 1667, 1457, 1357, 1363, 1363, 1363, 1363, 1398, 1297, - /* 350 */ 1435, 1667, 1667, 1435, 1457, 1357, 1435, 1357, 1435, 1398, - /* 360 */ 1297, 1550, 1678, 1398, 1297, 1524, 1398, 1297, 1398, 1297, - /* 370 */ 1524, 1355, 1355, 1355, 1344, 1279, 1279, 1524, 1355, 1329, - /* 380 */ 1355, 1344, 1355, 1355, 1625, 1279, 1528, 1528, 1524, 1398, - /* 390 */ 1617, 1617, 1425, 1425, 1430, 1416, 1519, 1398, 1279, 1430, - /* 400 */ 1428, 1426, 1435, 1347, 1640, 1640, 1636, 1636, 1636, 1688, - /* 410 */ 1688, 1587, 1652, 1312, 1312, 1312, 1312, 1652, 1331, 1331, - /* 420 */ 1313, 1313, 1312, 1652, 1279, 1279, 1279, 1279, 1279, 1279, - /* 430 */ 1279, 1647, 1279, 1279, 1535, 1279, 1279, 1279, 1279, 1279, - /* 440 */ 1279, 1279, 1402, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 450 */ 1279, 1279, 1593, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 460 */ 1279, 1279, 1279, 1279, 1462, 1279, 1282, 1584, 1279, 1279, - /* 470 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 480 */ 1279, 1279, 1439, 1440, 1279, 1279, 1279, 1279, 1279, 1279, - /* 490 */ 1279, 1454, 1279, 1279, 1279, 1449, 1279, 1279, 1279, 1279, - /* 500 */ 1279, 1279, 1279, 1279, 1403, 1279, 1279, 1279, 1279, 1279, - /* 510 */ 1279, 1549, 1548, 1279, 1279, 1400, 1279, 1279, 1279, 1279, - /* 520 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1327, - /* 530 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 540 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 550 */ 1279, 1279, 1279, 1427, 1279, 1279, 1279, 1279, 1279, 1279, - /* 560 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1622, 1417, - /* 570 */ 1279, 1279, 1279, 1279, 1671, 1279, 1279, 1279, 1279, 1377, - /* 580 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, - /* 590 */ 1663, 1371, 1463, 1279, 1466, 1301, 1279, 1291, 1279, 1279, + /* 0 */ 1663, 1663, 1663, 1491, 1254, 1367, 1254, 1254, 1254, 1254, + /* 10 */ 1491, 1491, 1491, 1254, 1254, 1254, 1254, 1254, 1254, 1397, + /* 20 */ 1397, 1544, 1287, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 30 */ 1254, 1254, 1254, 1254, 1254, 1490, 1254, 1254, 1254, 1254, + /* 40 */ 1578, 1578, 1254, 1254, 1254, 1254, 1254, 1563, 1562, 1254, + /* 50 */ 1254, 1254, 1406, 1254, 1413, 1254, 1254, 1254, 1254, 1254, + /* 60 */ 1492, 1493, 1254, 1254, 1254, 1254, 1543, 1545, 1508, 1420, + /* 70 */ 1419, 1418, 1417, 1526, 1385, 1411, 1404, 1408, 1487, 1488, + /* 80 */ 1486, 1641, 1493, 1492, 1254, 1407, 1455, 1471, 1454, 1254, + /* 90 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 100 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 110 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 120 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 130 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 140 */ 1254, 1254, 1463, 1470, 1469, 1468, 1477, 1467, 1464, 1457, + /* 150 */ 1456, 1458, 1459, 1278, 1254, 1275, 1329, 1254, 1254, 1254, + /* 160 */ 1254, 1254, 1460, 1287, 1448, 1447, 1446, 1254, 1474, 1461, + /* 170 */ 1473, 1472, 1551, 1615, 1614, 1509, 1254, 1254, 1254, 1254, + /* 180 */ 1254, 1254, 1578, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 190 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 200 */ 1254, 1254, 1254, 1254, 1254, 1387, 1578, 1578, 1254, 1287, + /* 210 */ 1578, 1578, 1388, 1388, 1283, 1283, 1391, 1558, 1358, 1358, + /* 220 */ 1358, 1358, 1367, 1358, 1254, 1254, 1254, 1254, 1254, 1254, + /* 230 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1548, + /* 240 */ 1546, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 250 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 260 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1363, 1254, + /* 270 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1608, + /* 280 */ 1254, 1521, 1343, 1363, 1363, 1363, 1363, 1365, 1344, 1342, + /* 290 */ 1357, 1288, 1261, 1655, 1423, 1412, 1364, 1412, 1652, 1410, + /* 300 */ 1423, 1423, 1410, 1423, 1364, 1652, 1304, 1630, 1299, 1397, + /* 310 */ 1397, 1397, 1387, 1387, 1387, 1387, 1391, 1391, 1489, 1364, + /* 320 */ 1357, 1254, 1655, 1655, 1373, 1373, 1654, 1654, 1373, 1509, + /* 330 */ 1638, 1432, 1332, 1338, 1338, 1338, 1338, 1373, 1272, 1410, + /* 340 */ 1638, 1638, 1410, 1432, 1332, 1410, 1332, 1410, 1373, 1272, + /* 350 */ 1525, 1649, 1373, 1272, 1499, 1373, 1272, 1373, 1272, 1499, + /* 360 */ 1330, 1330, 1330, 1319, 1254, 1254, 1499, 1330, 1304, 1330, + /* 370 */ 1319, 1330, 1330, 1596, 1254, 1503, 1503, 1499, 1373, 1588, + /* 380 */ 1588, 1400, 1400, 1405, 1391, 1494, 1373, 1254, 1405, 1403, + /* 390 */ 1401, 1410, 1322, 1611, 1611, 1607, 1607, 1607, 1660, 1660, + /* 400 */ 1558, 1623, 1287, 1287, 1287, 1287, 1623, 1306, 1306, 1288, + /* 410 */ 1288, 1287, 1623, 1254, 1254, 1254, 1254, 1254, 1254, 1618, + /* 420 */ 1254, 1553, 1510, 1377, 1254, 1254, 1254, 1254, 1254, 1254, + /* 430 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 440 */ 1564, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 450 */ 1254, 1254, 1437, 1254, 1257, 1555, 1254, 1254, 1254, 1254, + /* 460 */ 1254, 1254, 1254, 1254, 1414, 1415, 1378, 1254, 1254, 1254, + /* 470 */ 1254, 1254, 1254, 1254, 1429, 1254, 1254, 1254, 1424, 1254, + /* 480 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1651, 1254, 1254, + /* 490 */ 1254, 1254, 1254, 1254, 1524, 1523, 1254, 1254, 1375, 1254, + /* 500 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 510 */ 1254, 1254, 1302, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 520 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 530 */ 1254, 1254, 1254, 1254, 1254, 1254, 1402, 1254, 1254, 1254, + /* 540 */ 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 550 */ 1254, 1593, 1392, 1254, 1254, 1254, 1254, 1642, 1254, 1254, + /* 560 */ 1254, 1254, 1352, 1254, 1254, 1254, 1254, 1254, 1254, 1254, + /* 570 */ 1254, 1254, 1254, 1634, 1346, 1438, 1254, 1441, 1276, 1254, + /* 580 */ 1266, 1254, 1254, }; /********** End of lemon-generated parsing tables *****************************/ @@ -180661,33 +179401,34 @@ static const char *const yyTokenName[] = { /* 292 */ "foreach_clause", /* 293 */ "when_clause", /* 294 */ "trigger_cmd", - /* 295 */ "tridxby", - /* 296 */ "database_kw_opt", - /* 297 */ "key_opt", - /* 298 */ "alter_add", - /* 299 */ "kwcolumn_opt", - /* 300 */ "create_vtab", - /* 301 */ "vtabarglist", - /* 302 */ "vtabarg", - /* 303 */ "vtabargtoken", - /* 304 */ "lp", - /* 305 */ "anylist", - /* 306 */ "wqitem", - /* 307 */ "wqas", - /* 308 */ "withnm", - /* 309 */ "windowdefn_list", - /* 310 */ "windowdefn", - /* 311 */ "window", - /* 312 */ "frame_opt", - /* 313 */ "part_opt", - /* 314 */ "filter_clause", - /* 315 */ "over_clause", - /* 316 */ "range_or_rows", - /* 317 */ "frame_bound", - /* 318 */ "frame_bound_s", - /* 319 */ "frame_bound_e", - /* 320 */ "frame_exclude_opt", - /* 321 */ "frame_exclude", + /* 295 */ "trnm", + /* 296 */ "tridxby", + /* 297 */ "database_kw_opt", + /* 298 */ "key_opt", + /* 299 */ "add_column_fullname", + /* 300 */ "kwcolumn_opt", + /* 301 */ "create_vtab", + /* 302 */ "vtabarglist", + /* 303 */ "vtabarg", + /* 304 */ "vtabargtoken", + /* 305 */ "lp", + /* 306 */ "anylist", + /* 307 */ "wqitem", + /* 308 */ "wqas", + /* 309 */ "withnm", + /* 310 */ "windowdefn_list", + /* 311 */ "windowdefn", + /* 312 */ "window", + /* 313 */ "frame_opt", + /* 314 */ "part_opt", + /* 315 */ "filter_clause", + /* 316 */ "over_clause", + /* 317 */ "range_or_rows", + /* 318 */ "frame_bound", + /* 319 */ "frame_bound_s", + /* 320 */ "frame_bound_e", + /* 321 */ "frame_exclude_opt", + /* 322 */ "frame_exclude", }; #endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ @@ -180817,8 +179558,8 @@ static const char *const yyRuleName[] = { /* 119 */ "fullname ::= nm DOT nm", /* 120 */ "xfullname ::= nm", /* 121 */ "xfullname ::= nm DOT nm", - /* 122 */ "xfullname ::= nm AS nm", - /* 123 */ "xfullname ::= nm DOT nm AS nm", + /* 122 */ "xfullname ::= nm DOT nm AS nm", + /* 123 */ "xfullname ::= nm AS nm", /* 124 */ "joinop ::= COMMA|JOIN", /* 125 */ "joinop ::= JOIN_KW JOIN", /* 126 */ "joinop ::= JOIN_KW nm JOIN", @@ -180967,146 +179708,143 @@ static const char *const yyRuleName[] = { /* 269 */ "when_clause ::= WHEN expr", /* 270 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", /* 271 */ "trigger_cmd_list ::= trigger_cmd SEMI", - /* 272 */ "tridxby ::= INDEXED BY nm", - /* 273 */ "tridxby ::= NOT INDEXED", - /* 274 */ "trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt", - /* 275 */ "trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt", - /* 276 */ "trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt", - /* 277 */ "trigger_cmd ::= scanpt select scanpt", - /* 278 */ "expr ::= RAISE LP IGNORE RP", - /* 279 */ "expr ::= RAISE LP raisetype COMMA expr RP", - /* 280 */ "raisetype ::= ROLLBACK", - /* 281 */ "raisetype ::= ABORT", - /* 282 */ "raisetype ::= FAIL", - /* 283 */ "cmd ::= DROP TRIGGER ifexists fullname", - /* 284 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", - /* 285 */ "cmd ::= DETACH database_kw_opt expr", - /* 286 */ "key_opt ::=", - /* 287 */ "key_opt ::= KEY expr", - /* 288 */ "cmd ::= REINDEX", - /* 289 */ "cmd ::= REINDEX nm dbnm", - /* 290 */ "cmd ::= ANALYZE", - /* 291 */ "cmd ::= ANALYZE nm dbnm", - /* 292 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", - /* 293 */ "cmd ::= alter_add carglist", - /* 294 */ "alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken", + /* 272 */ "trnm ::= nm DOT nm", + /* 273 */ "tridxby ::= INDEXED BY nm", + /* 274 */ "tridxby ::= NOT INDEXED", + /* 275 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt", + /* 276 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt", + /* 277 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt", + /* 278 */ "trigger_cmd ::= scanpt select scanpt", + /* 279 */ "expr ::= RAISE LP IGNORE RP", + /* 280 */ "expr ::= RAISE LP raisetype COMMA expr RP", + /* 281 */ "raisetype ::= ROLLBACK", + /* 282 */ "raisetype ::= ABORT", + /* 283 */ "raisetype ::= FAIL", + /* 284 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 285 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 286 */ "cmd ::= DETACH database_kw_opt expr", + /* 287 */ "key_opt ::=", + /* 288 */ "key_opt ::= KEY expr", + /* 289 */ "cmd ::= REINDEX", + /* 290 */ "cmd ::= REINDEX nm dbnm", + /* 291 */ "cmd ::= ANALYZE", + /* 292 */ "cmd ::= ANALYZE nm dbnm", + /* 293 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 294 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", /* 295 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm", - /* 296 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm", - /* 297 */ "cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm", - /* 298 */ "cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL", - /* 299 */ "cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf", - /* 300 */ "cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf", - /* 301 */ "cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf", - /* 302 */ "cmd ::= create_vtab", - /* 303 */ "cmd ::= create_vtab LP vtabarglist RP", - /* 304 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", - /* 305 */ "vtabarg ::=", - /* 306 */ "vtabargtoken ::= ANY", - /* 307 */ "vtabargtoken ::= lp anylist RP", - /* 308 */ "lp ::= LP", - /* 309 */ "with ::= WITH wqlist", - /* 310 */ "with ::= WITH RECURSIVE wqlist", - /* 311 */ "wqas ::= AS", - /* 312 */ "wqas ::= AS MATERIALIZED", - /* 313 */ "wqas ::= AS NOT MATERIALIZED", - /* 314 */ "wqitem ::= withnm eidlist_opt wqas LP select RP", - /* 315 */ "withnm ::= nm", - /* 316 */ "wqlist ::= wqitem", - /* 317 */ "wqlist ::= wqlist COMMA wqitem", - /* 318 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", - /* 319 */ "windowdefn ::= nm AS LP window RP", - /* 320 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", - /* 321 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", - /* 322 */ "window ::= ORDER BY sortlist frame_opt", - /* 323 */ "window ::= nm ORDER BY sortlist frame_opt", - /* 324 */ "window ::= nm frame_opt", - /* 325 */ "frame_opt ::=", - /* 326 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", - /* 327 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", - /* 328 */ "range_or_rows ::= RANGE|ROWS|GROUPS", - /* 329 */ "frame_bound_s ::= frame_bound", - /* 330 */ "frame_bound_s ::= UNBOUNDED PRECEDING", - /* 331 */ "frame_bound_e ::= frame_bound", - /* 332 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", - /* 333 */ "frame_bound ::= expr PRECEDING|FOLLOWING", - /* 334 */ "frame_bound ::= CURRENT ROW", - /* 335 */ "frame_exclude_opt ::=", - /* 336 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", - /* 337 */ "frame_exclude ::= NO OTHERS", - /* 338 */ "frame_exclude ::= CURRENT ROW", - /* 339 */ "frame_exclude ::= GROUP|TIES", - /* 340 */ "window_clause ::= WINDOW windowdefn_list", - /* 341 */ "filter_over ::= filter_clause over_clause", - /* 342 */ "filter_over ::= over_clause", - /* 343 */ "filter_over ::= filter_clause", - /* 344 */ "over_clause ::= OVER LP window RP", - /* 345 */ "over_clause ::= OVER nm", - /* 346 */ "filter_clause ::= FILTER LP WHERE expr RP", - /* 347 */ "term ::= QNUMBER", - /* 348 */ "input ::= cmdlist", - /* 349 */ "cmdlist ::= cmdlist ecmd", - /* 350 */ "cmdlist ::= ecmd", - /* 351 */ "ecmd ::= SEMI", - /* 352 */ "ecmd ::= cmdx SEMI", - /* 353 */ "ecmd ::= explain cmdx SEMI", - /* 354 */ "trans_opt ::=", - /* 355 */ "trans_opt ::= TRANSACTION", - /* 356 */ "trans_opt ::= TRANSACTION nm", - /* 357 */ "savepoint_opt ::= SAVEPOINT", - /* 358 */ "savepoint_opt ::=", - /* 359 */ "cmd ::= create_table create_table_args", - /* 360 */ "table_option_set ::= table_option", - /* 361 */ "columnlist ::= columnlist COMMA columnname carglist", - /* 362 */ "columnlist ::= columnname carglist", - /* 363 */ "nm ::= ID|INDEXED|JOIN_KW", - /* 364 */ "nm ::= STRING", - /* 365 */ "typetoken ::= typename", - /* 366 */ "typename ::= ID|STRING", - /* 367 */ "signed ::= plus_num", - /* 368 */ "signed ::= minus_num", - /* 369 */ "carglist ::= carglist ccons", - /* 370 */ "carglist ::=", - /* 371 */ "ccons ::= NULL onconf", - /* 372 */ "ccons ::= GENERATED ALWAYS AS generated", - /* 373 */ "ccons ::= AS generated", - /* 374 */ "conslist_opt ::= COMMA conslist", - /* 375 */ "conslist ::= conslist tconscomma tcons", - /* 376 */ "conslist ::= tcons", - /* 377 */ "tconscomma ::=", - /* 378 */ "defer_subclause_opt ::= defer_subclause", - /* 379 */ "resolvetype ::= raisetype", - /* 380 */ "selectnowith ::= oneselect", - /* 381 */ "oneselect ::= values", - /* 382 */ "sclp ::= selcollist COMMA", - /* 383 */ "as ::= ID|STRING", - /* 384 */ "indexed_opt ::= indexed_by", - /* 385 */ "returning ::=", - /* 386 */ "expr ::= term", - /* 387 */ "likeop ::= LIKE_KW|MATCH", - /* 388 */ "case_operand ::= expr", - /* 389 */ "exprlist ::= nexprlist", - /* 390 */ "nmnum ::= plus_num", - /* 391 */ "nmnum ::= nm", - /* 392 */ "nmnum ::= ON", - /* 393 */ "nmnum ::= DELETE", - /* 394 */ "nmnum ::= DEFAULT", - /* 395 */ "plus_num ::= INTEGER|FLOAT", - /* 396 */ "foreach_clause ::=", - /* 397 */ "foreach_clause ::= FOR EACH ROW", - /* 398 */ "tridxby ::=", - /* 399 */ "database_kw_opt ::= DATABASE", - /* 400 */ "database_kw_opt ::=", - /* 401 */ "kwcolumn_opt ::=", - /* 402 */ "kwcolumn_opt ::= COLUMNKW", - /* 403 */ "vtabarglist ::= vtabarg", - /* 404 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 405 */ "vtabarg ::= vtabarg vtabargtoken", - /* 406 */ "anylist ::=", - /* 407 */ "anylist ::= anylist LP anylist RP", - /* 408 */ "anylist ::= anylist ANY", - /* 409 */ "with ::=", - /* 410 */ "windowdefn_list ::= windowdefn", - /* 411 */ "window ::= frame_opt", + /* 296 */ "add_column_fullname ::= fullname", + /* 297 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm", + /* 298 */ "cmd ::= create_vtab", + /* 299 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 300 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 301 */ "vtabarg ::=", + /* 302 */ "vtabargtoken ::= ANY", + /* 303 */ "vtabargtoken ::= lp anylist RP", + /* 304 */ "lp ::= LP", + /* 305 */ "with ::= WITH wqlist", + /* 306 */ "with ::= WITH RECURSIVE wqlist", + /* 307 */ "wqas ::= AS", + /* 308 */ "wqas ::= AS MATERIALIZED", + /* 309 */ "wqas ::= AS NOT MATERIALIZED", + /* 310 */ "wqitem ::= withnm eidlist_opt wqas LP select RP", + /* 311 */ "withnm ::= nm", + /* 312 */ "wqlist ::= wqitem", + /* 313 */ "wqlist ::= wqlist COMMA wqitem", + /* 314 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", + /* 315 */ "windowdefn ::= nm AS LP window RP", + /* 316 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", + /* 317 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", + /* 318 */ "window ::= ORDER BY sortlist frame_opt", + /* 319 */ "window ::= nm ORDER BY sortlist frame_opt", + /* 320 */ "window ::= nm frame_opt", + /* 321 */ "frame_opt ::=", + /* 322 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", + /* 323 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", + /* 324 */ "range_or_rows ::= RANGE|ROWS|GROUPS", + /* 325 */ "frame_bound_s ::= frame_bound", + /* 326 */ "frame_bound_s ::= UNBOUNDED PRECEDING", + /* 327 */ "frame_bound_e ::= frame_bound", + /* 328 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", + /* 329 */ "frame_bound ::= expr PRECEDING|FOLLOWING", + /* 330 */ "frame_bound ::= CURRENT ROW", + /* 331 */ "frame_exclude_opt ::=", + /* 332 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", + /* 333 */ "frame_exclude ::= NO OTHERS", + /* 334 */ "frame_exclude ::= CURRENT ROW", + /* 335 */ "frame_exclude ::= GROUP|TIES", + /* 336 */ "window_clause ::= WINDOW windowdefn_list", + /* 337 */ "filter_over ::= filter_clause over_clause", + /* 338 */ "filter_over ::= over_clause", + /* 339 */ "filter_over ::= filter_clause", + /* 340 */ "over_clause ::= OVER LP window RP", + /* 341 */ "over_clause ::= OVER nm", + /* 342 */ "filter_clause ::= FILTER LP WHERE expr RP", + /* 343 */ "term ::= QNUMBER", + /* 344 */ "input ::= cmdlist", + /* 345 */ "cmdlist ::= cmdlist ecmd", + /* 346 */ "cmdlist ::= ecmd", + /* 347 */ "ecmd ::= SEMI", + /* 348 */ "ecmd ::= cmdx SEMI", + /* 349 */ "ecmd ::= explain cmdx SEMI", + /* 350 */ "trans_opt ::=", + /* 351 */ "trans_opt ::= TRANSACTION", + /* 352 */ "trans_opt ::= TRANSACTION nm", + /* 353 */ "savepoint_opt ::= SAVEPOINT", + /* 354 */ "savepoint_opt ::=", + /* 355 */ "cmd ::= create_table create_table_args", + /* 356 */ "table_option_set ::= table_option", + /* 357 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 358 */ "columnlist ::= columnname carglist", + /* 359 */ "nm ::= ID|INDEXED|JOIN_KW", + /* 360 */ "nm ::= STRING", + /* 361 */ "typetoken ::= typename", + /* 362 */ "typename ::= ID|STRING", + /* 363 */ "signed ::= plus_num", + /* 364 */ "signed ::= minus_num", + /* 365 */ "carglist ::= carglist ccons", + /* 366 */ "carglist ::=", + /* 367 */ "ccons ::= NULL onconf", + /* 368 */ "ccons ::= GENERATED ALWAYS AS generated", + /* 369 */ "ccons ::= AS generated", + /* 370 */ "conslist_opt ::= COMMA conslist", + /* 371 */ "conslist ::= conslist tconscomma tcons", + /* 372 */ "conslist ::= tcons", + /* 373 */ "tconscomma ::=", + /* 374 */ "defer_subclause_opt ::= defer_subclause", + /* 375 */ "resolvetype ::= raisetype", + /* 376 */ "selectnowith ::= oneselect", + /* 377 */ "oneselect ::= values", + /* 378 */ "sclp ::= selcollist COMMA", + /* 379 */ "as ::= ID|STRING", + /* 380 */ "indexed_opt ::= indexed_by", + /* 381 */ "returning ::=", + /* 382 */ "expr ::= term", + /* 383 */ "likeop ::= LIKE_KW|MATCH", + /* 384 */ "case_operand ::= expr", + /* 385 */ "exprlist ::= nexprlist", + /* 386 */ "nmnum ::= plus_num", + /* 387 */ "nmnum ::= nm", + /* 388 */ "nmnum ::= ON", + /* 389 */ "nmnum ::= DELETE", + /* 390 */ "nmnum ::= DEFAULT", + /* 391 */ "plus_num ::= INTEGER|FLOAT", + /* 392 */ "foreach_clause ::=", + /* 393 */ "foreach_clause ::= FOR EACH ROW", + /* 394 */ "trnm ::= nm", + /* 395 */ "tridxby ::=", + /* 396 */ "database_kw_opt ::= DATABASE", + /* 397 */ "database_kw_opt ::=", + /* 398 */ "kwcolumn_opt ::=", + /* 399 */ "kwcolumn_opt ::= COLUMNKW", + /* 400 */ "vtabarglist ::= vtabarg", + /* 401 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 402 */ "vtabarg ::= vtabarg vtabargtoken", + /* 403 */ "anylist ::=", + /* 404 */ "anylist ::= anylist LP anylist RP", + /* 405 */ "anylist ::= anylist ANY", + /* 406 */ "with ::=", + /* 407 */ "windowdefn_list ::= windowdefn", + /* 408 */ "window ::= frame_opt", }; #endif /* NDEBUG */ @@ -181121,24 +179859,15 @@ static int yyGrowStack(yyParser *p){ int newSize; int idx; yyStackEntry *pNew; -#ifdef YYSIZELIMIT - int nLimit = YYSIZELIMIT(sqlite3ParserCTX(p)); -#endif newSize = oldSize*2 + 100; -#ifdef YYSIZELIMIT - if( newSize>nLimit ){ - newSize = nLimit; - if( newSize<=oldSize ) return 1; - } -#endif idx = (int)(p->yytos - p->yystack); if( p->yystack==p->yystk0 ){ - pNew = YYREALLOC(0, newSize*sizeof(pNew[0]), sqlite3ParserCTX(p)); + pNew = YYREALLOC(0, newSize*sizeof(pNew[0])); if( pNew==0 ) return 1; memcpy(pNew, p->yystack, oldSize*sizeof(pNew[0])); }else{ - pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0]), sqlite3ParserCTX(p)); + pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0])); if( pNew==0 ) return 1; } p->yystack = pNew; @@ -181245,7 +179974,7 @@ static void yy_destructor( case 254: /* values */ case 256: /* mvalues */ { -sqlite3SelectDelete(pParse->db, (yypminor->yy555)); +sqlite3SelectDelete(pParse->db, (yypminor->yy637)); } break; case 218: /* term */ @@ -181257,10 +179986,10 @@ sqlite3SelectDelete(pParse->db, (yypminor->yy555)); case 283: /* case_else */ case 286: /* vinto */ case 293: /* when_clause */ - case 297: /* key_opt */ - case 314: /* filter_clause */ + case 298: /* key_opt */ + case 315: /* filter_clause */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy454)); +sqlite3ExprDelete(pParse->db, (yypminor->yy590)); } break; case 223: /* eidlist_opt */ @@ -181275,9 +180004,9 @@ sqlite3ExprDelete(pParse->db, (yypminor->yy454)); case 271: /* setlist */ case 280: /* paren_exprlist */ case 282: /* case_exprlist */ - case 313: /* part_opt */ + case 314: /* part_opt */ { -sqlite3ExprListDelete(pParse->db, (yypminor->yy14)); +sqlite3ExprListDelete(pParse->db, (yypminor->yy402)); } break; case 240: /* fullname */ @@ -181286,51 +180015,51 @@ sqlite3ExprListDelete(pParse->db, (yypminor->yy14)); case 260: /* stl_prefix */ case 265: /* xfullname */ { -sqlite3SrcListDelete(pParse->db, (yypminor->yy203)); +sqlite3SrcListDelete(pParse->db, (yypminor->yy563)); } break; case 243: /* wqlist */ { -sqlite3WithDelete(pParse->db, (yypminor->yy59)); +sqlite3WithDelete(pParse->db, (yypminor->yy125)); } break; case 253: /* window_clause */ - case 309: /* windowdefn_list */ + case 310: /* windowdefn_list */ { -sqlite3WindowListDelete(pParse->db, (yypminor->yy211)); +sqlite3WindowListDelete(pParse->db, (yypminor->yy483)); } break; case 266: /* idlist */ case 273: /* idlist_opt */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy132)); +sqlite3IdListDelete(pParse->db, (yypminor->yy204)); } break; case 276: /* filter_over */ - case 310: /* windowdefn */ - case 311: /* window */ - case 312: /* frame_opt */ - case 315: /* over_clause */ + case 311: /* windowdefn */ + case 312: /* window */ + case 313: /* frame_opt */ + case 316: /* over_clause */ { -sqlite3WindowDelete(pParse->db, (yypminor->yy211)); +sqlite3WindowDelete(pParse->db, (yypminor->yy483)); } break; case 289: /* trigger_cmd_list */ case 294: /* trigger_cmd */ { -sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy427)); +sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy319)); } break; case 291: /* trigger_event */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy286).b); +sqlite3IdListDelete(pParse->db, (yypminor->yy28).b); } break; - case 317: /* frame_bound */ - case 318: /* frame_bound_s */ - case 319: /* frame_bound_e */ + case 318: /* frame_bound */ + case 319: /* frame_bound_s */ + case 320: /* frame_bound_e */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy509).pExpr); +sqlite3ExprDelete(pParse->db, (yypminor->yy205).pExpr); } break; /********* End destructor definitions *****************************************/ @@ -181383,9 +180112,7 @@ SQLITE_PRIVATE void sqlite3ParserFinalize(void *p){ } #if YYGROWABLESTACK - if( pParser->yystack!=pParser->yystk0 ){ - YYFREE(pParser->yystack, sqlite3ParserCTX(pParser)); - } + if( pParser->yystack!=pParser->yystk0 ) YYFREE(pParser->yystack); #endif } @@ -181568,7 +180295,7 @@ static void yyStackOverflow(yyParser *yypParser){ ** stack every overflows */ /******** Begin %stack_overflow code ******************************************/ - if( pParse->nErr==0 ) sqlite3ErrorMsg(pParse, "Recursion limit"); + sqlite3OomFault(pParse->db); /******** End %stack_overflow code ********************************************/ sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument var */ sqlite3ParserCTX_STORE @@ -181756,8 +180483,8 @@ static const YYCODETYPE yyRuleInfoLhs[] = { 240, /* (119) fullname ::= nm DOT nm */ 265, /* (120) xfullname ::= nm */ 265, /* (121) xfullname ::= nm DOT nm */ - 265, /* (122) xfullname ::= nm AS nm */ - 265, /* (123) xfullname ::= nm DOT nm AS nm */ + 265, /* (122) xfullname ::= nm DOT nm AS nm */ + 265, /* (123) xfullname ::= nm AS nm */ 261, /* (124) joinop ::= COMMA|JOIN */ 261, /* (125) joinop ::= JOIN_KW JOIN */ 261, /* (126) joinop ::= JOIN_KW nm JOIN */ @@ -181906,146 +180633,143 @@ static const YYCODETYPE yyRuleInfoLhs[] = { 293, /* (269) when_clause ::= WHEN expr */ 289, /* (270) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ 289, /* (271) trigger_cmd_list ::= trigger_cmd SEMI */ - 295, /* (272) tridxby ::= INDEXED BY nm */ - 295, /* (273) tridxby ::= NOT INDEXED */ - 294, /* (274) trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt */ - 294, /* (275) trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt */ - 294, /* (276) trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt */ - 294, /* (277) trigger_cmd ::= scanpt select scanpt */ - 219, /* (278) expr ::= RAISE LP IGNORE RP */ - 219, /* (279) expr ::= RAISE LP raisetype COMMA expr RP */ - 238, /* (280) raisetype ::= ROLLBACK */ - 238, /* (281) raisetype ::= ABORT */ - 238, /* (282) raisetype ::= FAIL */ - 192, /* (283) cmd ::= DROP TRIGGER ifexists fullname */ - 192, /* (284) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ - 192, /* (285) cmd ::= DETACH database_kw_opt expr */ - 297, /* (286) key_opt ::= */ - 297, /* (287) key_opt ::= KEY expr */ - 192, /* (288) cmd ::= REINDEX */ - 192, /* (289) cmd ::= REINDEX nm dbnm */ - 192, /* (290) cmd ::= ANALYZE */ - 192, /* (291) cmd ::= ANALYZE nm dbnm */ - 192, /* (292) cmd ::= ALTER TABLE fullname RENAME TO nm */ - 192, /* (293) cmd ::= alter_add carglist */ - 298, /* (294) alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken */ + 295, /* (272) trnm ::= nm DOT nm */ + 296, /* (273) tridxby ::= INDEXED BY nm */ + 296, /* (274) tridxby ::= NOT INDEXED */ + 294, /* (275) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ + 294, /* (276) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + 294, /* (277) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ + 294, /* (278) trigger_cmd ::= scanpt select scanpt */ + 219, /* (279) expr ::= RAISE LP IGNORE RP */ + 219, /* (280) expr ::= RAISE LP raisetype COMMA expr RP */ + 238, /* (281) raisetype ::= ROLLBACK */ + 238, /* (282) raisetype ::= ABORT */ + 238, /* (283) raisetype ::= FAIL */ + 192, /* (284) cmd ::= DROP TRIGGER ifexists fullname */ + 192, /* (285) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + 192, /* (286) cmd ::= DETACH database_kw_opt expr */ + 298, /* (287) key_opt ::= */ + 298, /* (288) key_opt ::= KEY expr */ + 192, /* (289) cmd ::= REINDEX */ + 192, /* (290) cmd ::= REINDEX nm dbnm */ + 192, /* (291) cmd ::= ANALYZE */ + 192, /* (292) cmd ::= ANALYZE nm dbnm */ + 192, /* (293) cmd ::= ALTER TABLE fullname RENAME TO nm */ + 192, /* (294) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ 192, /* (295) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ - 192, /* (296) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ - 192, /* (297) cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm */ - 192, /* (298) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL */ - 192, /* (299) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf */ - 192, /* (300) cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf */ - 192, /* (301) cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf */ - 192, /* (302) cmd ::= create_vtab */ - 192, /* (303) cmd ::= create_vtab LP vtabarglist RP */ - 300, /* (304) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ - 302, /* (305) vtabarg ::= */ - 303, /* (306) vtabargtoken ::= ANY */ - 303, /* (307) vtabargtoken ::= lp anylist RP */ - 304, /* (308) lp ::= LP */ - 269, /* (309) with ::= WITH wqlist */ - 269, /* (310) with ::= WITH RECURSIVE wqlist */ - 307, /* (311) wqas ::= AS */ - 307, /* (312) wqas ::= AS MATERIALIZED */ - 307, /* (313) wqas ::= AS NOT MATERIALIZED */ - 306, /* (314) wqitem ::= withnm eidlist_opt wqas LP select RP */ - 308, /* (315) withnm ::= nm */ - 243, /* (316) wqlist ::= wqitem */ - 243, /* (317) wqlist ::= wqlist COMMA wqitem */ - 309, /* (318) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - 310, /* (319) windowdefn ::= nm AS LP window RP */ - 311, /* (320) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - 311, /* (321) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - 311, /* (322) window ::= ORDER BY sortlist frame_opt */ - 311, /* (323) window ::= nm ORDER BY sortlist frame_opt */ - 311, /* (324) window ::= nm frame_opt */ - 312, /* (325) frame_opt ::= */ - 312, /* (326) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - 312, /* (327) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - 316, /* (328) range_or_rows ::= RANGE|ROWS|GROUPS */ - 318, /* (329) frame_bound_s ::= frame_bound */ - 318, /* (330) frame_bound_s ::= UNBOUNDED PRECEDING */ - 319, /* (331) frame_bound_e ::= frame_bound */ - 319, /* (332) frame_bound_e ::= UNBOUNDED FOLLOWING */ - 317, /* (333) frame_bound ::= expr PRECEDING|FOLLOWING */ - 317, /* (334) frame_bound ::= CURRENT ROW */ - 320, /* (335) frame_exclude_opt ::= */ - 320, /* (336) frame_exclude_opt ::= EXCLUDE frame_exclude */ - 321, /* (337) frame_exclude ::= NO OTHERS */ - 321, /* (338) frame_exclude ::= CURRENT ROW */ - 321, /* (339) frame_exclude ::= GROUP|TIES */ - 253, /* (340) window_clause ::= WINDOW windowdefn_list */ - 276, /* (341) filter_over ::= filter_clause over_clause */ - 276, /* (342) filter_over ::= over_clause */ - 276, /* (343) filter_over ::= filter_clause */ - 315, /* (344) over_clause ::= OVER LP window RP */ - 315, /* (345) over_clause ::= OVER nm */ - 314, /* (346) filter_clause ::= FILTER LP WHERE expr RP */ - 218, /* (347) term ::= QNUMBER */ - 187, /* (348) input ::= cmdlist */ - 188, /* (349) cmdlist ::= cmdlist ecmd */ - 188, /* (350) cmdlist ::= ecmd */ - 189, /* (351) ecmd ::= SEMI */ - 189, /* (352) ecmd ::= cmdx SEMI */ - 189, /* (353) ecmd ::= explain cmdx SEMI */ - 194, /* (354) trans_opt ::= */ - 194, /* (355) trans_opt ::= TRANSACTION */ - 194, /* (356) trans_opt ::= TRANSACTION nm */ - 196, /* (357) savepoint_opt ::= SAVEPOINT */ - 196, /* (358) savepoint_opt ::= */ - 192, /* (359) cmd ::= create_table create_table_args */ - 205, /* (360) table_option_set ::= table_option */ - 203, /* (361) columnlist ::= columnlist COMMA columnname carglist */ - 203, /* (362) columnlist ::= columnname carglist */ - 195, /* (363) nm ::= ID|INDEXED|JOIN_KW */ - 195, /* (364) nm ::= STRING */ - 210, /* (365) typetoken ::= typename */ - 211, /* (366) typename ::= ID|STRING */ - 212, /* (367) signed ::= plus_num */ - 212, /* (368) signed ::= minus_num */ - 209, /* (369) carglist ::= carglist ccons */ - 209, /* (370) carglist ::= */ - 217, /* (371) ccons ::= NULL onconf */ - 217, /* (372) ccons ::= GENERATED ALWAYS AS generated */ - 217, /* (373) ccons ::= AS generated */ - 204, /* (374) conslist_opt ::= COMMA conslist */ - 230, /* (375) conslist ::= conslist tconscomma tcons */ - 230, /* (376) conslist ::= tcons */ - 231, /* (377) tconscomma ::= */ - 235, /* (378) defer_subclause_opt ::= defer_subclause */ - 237, /* (379) resolvetype ::= raisetype */ - 241, /* (380) selectnowith ::= oneselect */ - 242, /* (381) oneselect ::= values */ - 257, /* (382) sclp ::= selcollist COMMA */ - 258, /* (383) as ::= ID|STRING */ - 267, /* (384) indexed_opt ::= indexed_by */ - 275, /* (385) returning ::= */ - 219, /* (386) expr ::= term */ - 277, /* (387) likeop ::= LIKE_KW|MATCH */ - 281, /* (388) case_operand ::= expr */ - 264, /* (389) exprlist ::= nexprlist */ - 287, /* (390) nmnum ::= plus_num */ - 287, /* (391) nmnum ::= nm */ - 287, /* (392) nmnum ::= ON */ - 287, /* (393) nmnum ::= DELETE */ - 287, /* (394) nmnum ::= DEFAULT */ - 213, /* (395) plus_num ::= INTEGER|FLOAT */ - 292, /* (396) foreach_clause ::= */ - 292, /* (397) foreach_clause ::= FOR EACH ROW */ - 295, /* (398) tridxby ::= */ - 296, /* (399) database_kw_opt ::= DATABASE */ - 296, /* (400) database_kw_opt ::= */ - 299, /* (401) kwcolumn_opt ::= */ - 299, /* (402) kwcolumn_opt ::= COLUMNKW */ - 301, /* (403) vtabarglist ::= vtabarg */ - 301, /* (404) vtabarglist ::= vtabarglist COMMA vtabarg */ - 302, /* (405) vtabarg ::= vtabarg vtabargtoken */ - 305, /* (406) anylist ::= */ - 305, /* (407) anylist ::= anylist LP anylist RP */ - 305, /* (408) anylist ::= anylist ANY */ - 269, /* (409) with ::= */ - 309, /* (410) windowdefn_list ::= windowdefn */ - 311, /* (411) window ::= frame_opt */ + 299, /* (296) add_column_fullname ::= fullname */ + 192, /* (297) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + 192, /* (298) cmd ::= create_vtab */ + 192, /* (299) cmd ::= create_vtab LP vtabarglist RP */ + 301, /* (300) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 303, /* (301) vtabarg ::= */ + 304, /* (302) vtabargtoken ::= ANY */ + 304, /* (303) vtabargtoken ::= lp anylist RP */ + 305, /* (304) lp ::= LP */ + 269, /* (305) with ::= WITH wqlist */ + 269, /* (306) with ::= WITH RECURSIVE wqlist */ + 308, /* (307) wqas ::= AS */ + 308, /* (308) wqas ::= AS MATERIALIZED */ + 308, /* (309) wqas ::= AS NOT MATERIALIZED */ + 307, /* (310) wqitem ::= withnm eidlist_opt wqas LP select RP */ + 309, /* (311) withnm ::= nm */ + 243, /* (312) wqlist ::= wqitem */ + 243, /* (313) wqlist ::= wqlist COMMA wqitem */ + 310, /* (314) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + 311, /* (315) windowdefn ::= nm AS LP window RP */ + 312, /* (316) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + 312, /* (317) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + 312, /* (318) window ::= ORDER BY sortlist frame_opt */ + 312, /* (319) window ::= nm ORDER BY sortlist frame_opt */ + 312, /* (320) window ::= nm frame_opt */ + 313, /* (321) frame_opt ::= */ + 313, /* (322) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + 313, /* (323) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + 317, /* (324) range_or_rows ::= RANGE|ROWS|GROUPS */ + 319, /* (325) frame_bound_s ::= frame_bound */ + 319, /* (326) frame_bound_s ::= UNBOUNDED PRECEDING */ + 320, /* (327) frame_bound_e ::= frame_bound */ + 320, /* (328) frame_bound_e ::= UNBOUNDED FOLLOWING */ + 318, /* (329) frame_bound ::= expr PRECEDING|FOLLOWING */ + 318, /* (330) frame_bound ::= CURRENT ROW */ + 321, /* (331) frame_exclude_opt ::= */ + 321, /* (332) frame_exclude_opt ::= EXCLUDE frame_exclude */ + 322, /* (333) frame_exclude ::= NO OTHERS */ + 322, /* (334) frame_exclude ::= CURRENT ROW */ + 322, /* (335) frame_exclude ::= GROUP|TIES */ + 253, /* (336) window_clause ::= WINDOW windowdefn_list */ + 276, /* (337) filter_over ::= filter_clause over_clause */ + 276, /* (338) filter_over ::= over_clause */ + 276, /* (339) filter_over ::= filter_clause */ + 316, /* (340) over_clause ::= OVER LP window RP */ + 316, /* (341) over_clause ::= OVER nm */ + 315, /* (342) filter_clause ::= FILTER LP WHERE expr RP */ + 218, /* (343) term ::= QNUMBER */ + 187, /* (344) input ::= cmdlist */ + 188, /* (345) cmdlist ::= cmdlist ecmd */ + 188, /* (346) cmdlist ::= ecmd */ + 189, /* (347) ecmd ::= SEMI */ + 189, /* (348) ecmd ::= cmdx SEMI */ + 189, /* (349) ecmd ::= explain cmdx SEMI */ + 194, /* (350) trans_opt ::= */ + 194, /* (351) trans_opt ::= TRANSACTION */ + 194, /* (352) trans_opt ::= TRANSACTION nm */ + 196, /* (353) savepoint_opt ::= SAVEPOINT */ + 196, /* (354) savepoint_opt ::= */ + 192, /* (355) cmd ::= create_table create_table_args */ + 205, /* (356) table_option_set ::= table_option */ + 203, /* (357) columnlist ::= columnlist COMMA columnname carglist */ + 203, /* (358) columnlist ::= columnname carglist */ + 195, /* (359) nm ::= ID|INDEXED|JOIN_KW */ + 195, /* (360) nm ::= STRING */ + 210, /* (361) typetoken ::= typename */ + 211, /* (362) typename ::= ID|STRING */ + 212, /* (363) signed ::= plus_num */ + 212, /* (364) signed ::= minus_num */ + 209, /* (365) carglist ::= carglist ccons */ + 209, /* (366) carglist ::= */ + 217, /* (367) ccons ::= NULL onconf */ + 217, /* (368) ccons ::= GENERATED ALWAYS AS generated */ + 217, /* (369) ccons ::= AS generated */ + 204, /* (370) conslist_opt ::= COMMA conslist */ + 230, /* (371) conslist ::= conslist tconscomma tcons */ + 230, /* (372) conslist ::= tcons */ + 231, /* (373) tconscomma ::= */ + 235, /* (374) defer_subclause_opt ::= defer_subclause */ + 237, /* (375) resolvetype ::= raisetype */ + 241, /* (376) selectnowith ::= oneselect */ + 242, /* (377) oneselect ::= values */ + 257, /* (378) sclp ::= selcollist COMMA */ + 258, /* (379) as ::= ID|STRING */ + 267, /* (380) indexed_opt ::= indexed_by */ + 275, /* (381) returning ::= */ + 219, /* (382) expr ::= term */ + 277, /* (383) likeop ::= LIKE_KW|MATCH */ + 281, /* (384) case_operand ::= expr */ + 264, /* (385) exprlist ::= nexprlist */ + 287, /* (386) nmnum ::= plus_num */ + 287, /* (387) nmnum ::= nm */ + 287, /* (388) nmnum ::= ON */ + 287, /* (389) nmnum ::= DELETE */ + 287, /* (390) nmnum ::= DEFAULT */ + 213, /* (391) plus_num ::= INTEGER|FLOAT */ + 292, /* (392) foreach_clause ::= */ + 292, /* (393) foreach_clause ::= FOR EACH ROW */ + 295, /* (394) trnm ::= nm */ + 296, /* (395) tridxby ::= */ + 297, /* (396) database_kw_opt ::= DATABASE */ + 297, /* (397) database_kw_opt ::= */ + 300, /* (398) kwcolumn_opt ::= */ + 300, /* (399) kwcolumn_opt ::= COLUMNKW */ + 302, /* (400) vtabarglist ::= vtabarg */ + 302, /* (401) vtabarglist ::= vtabarglist COMMA vtabarg */ + 303, /* (402) vtabarg ::= vtabarg vtabargtoken */ + 306, /* (403) anylist ::= */ + 306, /* (404) anylist ::= anylist LP anylist RP */ + 306, /* (405) anylist ::= anylist ANY */ + 269, /* (406) with ::= */ + 310, /* (407) windowdefn_list ::= windowdefn */ + 312, /* (408) window ::= frame_opt */ }; /* For rule J, yyRuleInfoNRhs[J] contains the negative of the number @@ -182173,8 +180897,8 @@ static const signed char yyRuleInfoNRhs[] = { -3, /* (119) fullname ::= nm DOT nm */ -1, /* (120) xfullname ::= nm */ -3, /* (121) xfullname ::= nm DOT nm */ - -3, /* (122) xfullname ::= nm AS nm */ - -5, /* (123) xfullname ::= nm DOT nm AS nm */ + -5, /* (122) xfullname ::= nm DOT nm AS nm */ + -3, /* (123) xfullname ::= nm AS nm */ -1, /* (124) joinop ::= COMMA|JOIN */ -2, /* (125) joinop ::= JOIN_KW JOIN */ -3, /* (126) joinop ::= JOIN_KW nm JOIN */ @@ -182323,146 +181047,143 @@ static const signed char yyRuleInfoNRhs[] = { -2, /* (269) when_clause ::= WHEN expr */ -3, /* (270) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ -2, /* (271) trigger_cmd_list ::= trigger_cmd SEMI */ - -3, /* (272) tridxby ::= INDEXED BY nm */ - -2, /* (273) tridxby ::= NOT INDEXED */ - -9, /* (274) trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt */ - -8, /* (275) trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt */ - -6, /* (276) trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt */ - -3, /* (277) trigger_cmd ::= scanpt select scanpt */ - -4, /* (278) expr ::= RAISE LP IGNORE RP */ - -6, /* (279) expr ::= RAISE LP raisetype COMMA expr RP */ - -1, /* (280) raisetype ::= ROLLBACK */ - -1, /* (281) raisetype ::= ABORT */ - -1, /* (282) raisetype ::= FAIL */ - -4, /* (283) cmd ::= DROP TRIGGER ifexists fullname */ - -6, /* (284) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ - -3, /* (285) cmd ::= DETACH database_kw_opt expr */ - 0, /* (286) key_opt ::= */ - -2, /* (287) key_opt ::= KEY expr */ - -1, /* (288) cmd ::= REINDEX */ - -3, /* (289) cmd ::= REINDEX nm dbnm */ - -1, /* (290) cmd ::= ANALYZE */ - -3, /* (291) cmd ::= ANALYZE nm dbnm */ - -6, /* (292) cmd ::= ALTER TABLE fullname RENAME TO nm */ - -2, /* (293) cmd ::= alter_add carglist */ - -7, /* (294) alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken */ + -3, /* (272) trnm ::= nm DOT nm */ + -3, /* (273) tridxby ::= INDEXED BY nm */ + -2, /* (274) tridxby ::= NOT INDEXED */ + -9, /* (275) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ + -8, /* (276) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + -6, /* (277) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ + -3, /* (278) trigger_cmd ::= scanpt select scanpt */ + -4, /* (279) expr ::= RAISE LP IGNORE RP */ + -6, /* (280) expr ::= RAISE LP raisetype COMMA expr RP */ + -1, /* (281) raisetype ::= ROLLBACK */ + -1, /* (282) raisetype ::= ABORT */ + -1, /* (283) raisetype ::= FAIL */ + -4, /* (284) cmd ::= DROP TRIGGER ifexists fullname */ + -6, /* (285) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + -3, /* (286) cmd ::= DETACH database_kw_opt expr */ + 0, /* (287) key_opt ::= */ + -2, /* (288) key_opt ::= KEY expr */ + -1, /* (289) cmd ::= REINDEX */ + -3, /* (290) cmd ::= REINDEX nm dbnm */ + -1, /* (291) cmd ::= ANALYZE */ + -3, /* (292) cmd ::= ANALYZE nm dbnm */ + -6, /* (293) cmd ::= ALTER TABLE fullname RENAME TO nm */ + -7, /* (294) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ -6, /* (295) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ - -8, /* (296) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ - -6, /* (297) cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm */ - -9, /* (298) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL */ - -10, /* (299) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf */ - -11, /* (300) cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf */ - -9, /* (301) cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf */ - -1, /* (302) cmd ::= create_vtab */ - -4, /* (303) cmd ::= create_vtab LP vtabarglist RP */ - -8, /* (304) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ - 0, /* (305) vtabarg ::= */ - -1, /* (306) vtabargtoken ::= ANY */ - -3, /* (307) vtabargtoken ::= lp anylist RP */ - -1, /* (308) lp ::= LP */ - -2, /* (309) with ::= WITH wqlist */ - -3, /* (310) with ::= WITH RECURSIVE wqlist */ - -1, /* (311) wqas ::= AS */ - -2, /* (312) wqas ::= AS MATERIALIZED */ - -3, /* (313) wqas ::= AS NOT MATERIALIZED */ - -6, /* (314) wqitem ::= withnm eidlist_opt wqas LP select RP */ - -1, /* (315) withnm ::= nm */ - -1, /* (316) wqlist ::= wqitem */ - -3, /* (317) wqlist ::= wqlist COMMA wqitem */ - -3, /* (318) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - -5, /* (319) windowdefn ::= nm AS LP window RP */ - -5, /* (320) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - -6, /* (321) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - -4, /* (322) window ::= ORDER BY sortlist frame_opt */ - -5, /* (323) window ::= nm ORDER BY sortlist frame_opt */ - -2, /* (324) window ::= nm frame_opt */ - 0, /* (325) frame_opt ::= */ - -3, /* (326) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - -6, /* (327) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - -1, /* (328) range_or_rows ::= RANGE|ROWS|GROUPS */ - -1, /* (329) frame_bound_s ::= frame_bound */ - -2, /* (330) frame_bound_s ::= UNBOUNDED PRECEDING */ - -1, /* (331) frame_bound_e ::= frame_bound */ - -2, /* (332) frame_bound_e ::= UNBOUNDED FOLLOWING */ - -2, /* (333) frame_bound ::= expr PRECEDING|FOLLOWING */ - -2, /* (334) frame_bound ::= CURRENT ROW */ - 0, /* (335) frame_exclude_opt ::= */ - -2, /* (336) frame_exclude_opt ::= EXCLUDE frame_exclude */ - -2, /* (337) frame_exclude ::= NO OTHERS */ - -2, /* (338) frame_exclude ::= CURRENT ROW */ - -1, /* (339) frame_exclude ::= GROUP|TIES */ - -2, /* (340) window_clause ::= WINDOW windowdefn_list */ - -2, /* (341) filter_over ::= filter_clause over_clause */ - -1, /* (342) filter_over ::= over_clause */ - -1, /* (343) filter_over ::= filter_clause */ - -4, /* (344) over_clause ::= OVER LP window RP */ - -2, /* (345) over_clause ::= OVER nm */ - -5, /* (346) filter_clause ::= FILTER LP WHERE expr RP */ - -1, /* (347) term ::= QNUMBER */ - -1, /* (348) input ::= cmdlist */ - -2, /* (349) cmdlist ::= cmdlist ecmd */ - -1, /* (350) cmdlist ::= ecmd */ - -1, /* (351) ecmd ::= SEMI */ - -2, /* (352) ecmd ::= cmdx SEMI */ - -3, /* (353) ecmd ::= explain cmdx SEMI */ - 0, /* (354) trans_opt ::= */ - -1, /* (355) trans_opt ::= TRANSACTION */ - -2, /* (356) trans_opt ::= TRANSACTION nm */ - -1, /* (357) savepoint_opt ::= SAVEPOINT */ - 0, /* (358) savepoint_opt ::= */ - -2, /* (359) cmd ::= create_table create_table_args */ - -1, /* (360) table_option_set ::= table_option */ - -4, /* (361) columnlist ::= columnlist COMMA columnname carglist */ - -2, /* (362) columnlist ::= columnname carglist */ - -1, /* (363) nm ::= ID|INDEXED|JOIN_KW */ - -1, /* (364) nm ::= STRING */ - -1, /* (365) typetoken ::= typename */ - -1, /* (366) typename ::= ID|STRING */ - -1, /* (367) signed ::= plus_num */ - -1, /* (368) signed ::= minus_num */ - -2, /* (369) carglist ::= carglist ccons */ - 0, /* (370) carglist ::= */ - -2, /* (371) ccons ::= NULL onconf */ - -4, /* (372) ccons ::= GENERATED ALWAYS AS generated */ - -2, /* (373) ccons ::= AS generated */ - -2, /* (374) conslist_opt ::= COMMA conslist */ - -3, /* (375) conslist ::= conslist tconscomma tcons */ - -1, /* (376) conslist ::= tcons */ - 0, /* (377) tconscomma ::= */ - -1, /* (378) defer_subclause_opt ::= defer_subclause */ - -1, /* (379) resolvetype ::= raisetype */ - -1, /* (380) selectnowith ::= oneselect */ - -1, /* (381) oneselect ::= values */ - -2, /* (382) sclp ::= selcollist COMMA */ - -1, /* (383) as ::= ID|STRING */ - -1, /* (384) indexed_opt ::= indexed_by */ - 0, /* (385) returning ::= */ - -1, /* (386) expr ::= term */ - -1, /* (387) likeop ::= LIKE_KW|MATCH */ - -1, /* (388) case_operand ::= expr */ - -1, /* (389) exprlist ::= nexprlist */ - -1, /* (390) nmnum ::= plus_num */ - -1, /* (391) nmnum ::= nm */ - -1, /* (392) nmnum ::= ON */ - -1, /* (393) nmnum ::= DELETE */ - -1, /* (394) nmnum ::= DEFAULT */ - -1, /* (395) plus_num ::= INTEGER|FLOAT */ - 0, /* (396) foreach_clause ::= */ - -3, /* (397) foreach_clause ::= FOR EACH ROW */ - 0, /* (398) tridxby ::= */ - -1, /* (399) database_kw_opt ::= DATABASE */ - 0, /* (400) database_kw_opt ::= */ - 0, /* (401) kwcolumn_opt ::= */ - -1, /* (402) kwcolumn_opt ::= COLUMNKW */ - -1, /* (403) vtabarglist ::= vtabarg */ - -3, /* (404) vtabarglist ::= vtabarglist COMMA vtabarg */ - -2, /* (405) vtabarg ::= vtabarg vtabargtoken */ - 0, /* (406) anylist ::= */ - -4, /* (407) anylist ::= anylist LP anylist RP */ - -2, /* (408) anylist ::= anylist ANY */ - 0, /* (409) with ::= */ - -1, /* (410) windowdefn_list ::= windowdefn */ - -1, /* (411) window ::= frame_opt */ + -1, /* (296) add_column_fullname ::= fullname */ + -8, /* (297) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + -1, /* (298) cmd ::= create_vtab */ + -4, /* (299) cmd ::= create_vtab LP vtabarglist RP */ + -8, /* (300) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 0, /* (301) vtabarg ::= */ + -1, /* (302) vtabargtoken ::= ANY */ + -3, /* (303) vtabargtoken ::= lp anylist RP */ + -1, /* (304) lp ::= LP */ + -2, /* (305) with ::= WITH wqlist */ + -3, /* (306) with ::= WITH RECURSIVE wqlist */ + -1, /* (307) wqas ::= AS */ + -2, /* (308) wqas ::= AS MATERIALIZED */ + -3, /* (309) wqas ::= AS NOT MATERIALIZED */ + -6, /* (310) wqitem ::= withnm eidlist_opt wqas LP select RP */ + -1, /* (311) withnm ::= nm */ + -1, /* (312) wqlist ::= wqitem */ + -3, /* (313) wqlist ::= wqlist COMMA wqitem */ + -3, /* (314) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + -5, /* (315) windowdefn ::= nm AS LP window RP */ + -5, /* (316) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + -6, /* (317) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + -4, /* (318) window ::= ORDER BY sortlist frame_opt */ + -5, /* (319) window ::= nm ORDER BY sortlist frame_opt */ + -2, /* (320) window ::= nm frame_opt */ + 0, /* (321) frame_opt ::= */ + -3, /* (322) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + -6, /* (323) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + -1, /* (324) range_or_rows ::= RANGE|ROWS|GROUPS */ + -1, /* (325) frame_bound_s ::= frame_bound */ + -2, /* (326) frame_bound_s ::= UNBOUNDED PRECEDING */ + -1, /* (327) frame_bound_e ::= frame_bound */ + -2, /* (328) frame_bound_e ::= UNBOUNDED FOLLOWING */ + -2, /* (329) frame_bound ::= expr PRECEDING|FOLLOWING */ + -2, /* (330) frame_bound ::= CURRENT ROW */ + 0, /* (331) frame_exclude_opt ::= */ + -2, /* (332) frame_exclude_opt ::= EXCLUDE frame_exclude */ + -2, /* (333) frame_exclude ::= NO OTHERS */ + -2, /* (334) frame_exclude ::= CURRENT ROW */ + -1, /* (335) frame_exclude ::= GROUP|TIES */ + -2, /* (336) window_clause ::= WINDOW windowdefn_list */ + -2, /* (337) filter_over ::= filter_clause over_clause */ + -1, /* (338) filter_over ::= over_clause */ + -1, /* (339) filter_over ::= filter_clause */ + -4, /* (340) over_clause ::= OVER LP window RP */ + -2, /* (341) over_clause ::= OVER nm */ + -5, /* (342) filter_clause ::= FILTER LP WHERE expr RP */ + -1, /* (343) term ::= QNUMBER */ + -1, /* (344) input ::= cmdlist */ + -2, /* (345) cmdlist ::= cmdlist ecmd */ + -1, /* (346) cmdlist ::= ecmd */ + -1, /* (347) ecmd ::= SEMI */ + -2, /* (348) ecmd ::= cmdx SEMI */ + -3, /* (349) ecmd ::= explain cmdx SEMI */ + 0, /* (350) trans_opt ::= */ + -1, /* (351) trans_opt ::= TRANSACTION */ + -2, /* (352) trans_opt ::= TRANSACTION nm */ + -1, /* (353) savepoint_opt ::= SAVEPOINT */ + 0, /* (354) savepoint_opt ::= */ + -2, /* (355) cmd ::= create_table create_table_args */ + -1, /* (356) table_option_set ::= table_option */ + -4, /* (357) columnlist ::= columnlist COMMA columnname carglist */ + -2, /* (358) columnlist ::= columnname carglist */ + -1, /* (359) nm ::= ID|INDEXED|JOIN_KW */ + -1, /* (360) nm ::= STRING */ + -1, /* (361) typetoken ::= typename */ + -1, /* (362) typename ::= ID|STRING */ + -1, /* (363) signed ::= plus_num */ + -1, /* (364) signed ::= minus_num */ + -2, /* (365) carglist ::= carglist ccons */ + 0, /* (366) carglist ::= */ + -2, /* (367) ccons ::= NULL onconf */ + -4, /* (368) ccons ::= GENERATED ALWAYS AS generated */ + -2, /* (369) ccons ::= AS generated */ + -2, /* (370) conslist_opt ::= COMMA conslist */ + -3, /* (371) conslist ::= conslist tconscomma tcons */ + -1, /* (372) conslist ::= tcons */ + 0, /* (373) tconscomma ::= */ + -1, /* (374) defer_subclause_opt ::= defer_subclause */ + -1, /* (375) resolvetype ::= raisetype */ + -1, /* (376) selectnowith ::= oneselect */ + -1, /* (377) oneselect ::= values */ + -2, /* (378) sclp ::= selcollist COMMA */ + -1, /* (379) as ::= ID|STRING */ + -1, /* (380) indexed_opt ::= indexed_by */ + 0, /* (381) returning ::= */ + -1, /* (382) expr ::= term */ + -1, /* (383) likeop ::= LIKE_KW|MATCH */ + -1, /* (384) case_operand ::= expr */ + -1, /* (385) exprlist ::= nexprlist */ + -1, /* (386) nmnum ::= plus_num */ + -1, /* (387) nmnum ::= nm */ + -1, /* (388) nmnum ::= ON */ + -1, /* (389) nmnum ::= DELETE */ + -1, /* (390) nmnum ::= DEFAULT */ + -1, /* (391) plus_num ::= INTEGER|FLOAT */ + 0, /* (392) foreach_clause ::= */ + -3, /* (393) foreach_clause ::= FOR EACH ROW */ + -1, /* (394) trnm ::= nm */ + 0, /* (395) tridxby ::= */ + -1, /* (396) database_kw_opt ::= DATABASE */ + 0, /* (397) database_kw_opt ::= */ + 0, /* (398) kwcolumn_opt ::= */ + -1, /* (399) kwcolumn_opt ::= COLUMNKW */ + -1, /* (400) vtabarglist ::= vtabarg */ + -3, /* (401) vtabarglist ::= vtabarglist COMMA vtabarg */ + -2, /* (402) vtabarg ::= vtabarg vtabargtoken */ + 0, /* (403) anylist ::= */ + -4, /* (404) anylist ::= anylist LP anylist RP */ + -2, /* (405) anylist ::= anylist ANY */ + 0, /* (406) with ::= */ + -1, /* (407) windowdefn_list ::= windowdefn */ + -1, /* (408) window ::= frame_opt */ }; static void yy_accept(yyParser*); /* Forward Declaration */ @@ -182514,16 +181235,16 @@ static YYACTIONTYPE yy_reduce( { sqlite3FinishCoding(pParse); } break; case 3: /* cmd ::= BEGIN transtype trans_opt */ -{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy144);} +{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy502);} break; case 4: /* transtype ::= */ -{yymsp[1].minor.yy144 = TK_DEFERRED;} +{yymsp[1].minor.yy502 = TK_DEFERRED;} break; case 5: /* transtype ::= DEFERRED */ case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6); case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7); - case 328: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==328); -{yymsp[0].minor.yy144 = yymsp[0].major; /*A-overwrites-X*/} + case 324: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==324); +{yymsp[0].minor.yy502 = yymsp[0].major; /*A-overwrites-X*/} break; case 8: /* cmd ::= COMMIT|END trans_opt */ case 9: /* cmd ::= ROLLBACK trans_opt */ yytestcase(yyruleno==9); @@ -182546,7 +181267,7 @@ static YYACTIONTYPE yy_reduce( break; case 13: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ { - sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy144,0,0,yymsp[-2].minor.yy144); + sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy502,0,0,yymsp[-2].minor.yy502); } break; case 14: /* createkw ::= CREATE */ @@ -182562,38 +181283,38 @@ static YYACTIONTYPE yy_reduce( case 81: /* ifexists ::= */ yytestcase(yyruleno==81); case 100: /* distinct ::= */ yytestcase(yyruleno==100); case 246: /* collate ::= */ yytestcase(yyruleno==246); -{yymsp[1].minor.yy144 = 0;} +{yymsp[1].minor.yy502 = 0;} break; case 16: /* ifnotexists ::= IF NOT EXISTS */ -{yymsp[-2].minor.yy144 = 1;} +{yymsp[-2].minor.yy502 = 1;} break; case 17: /* temp ::= TEMP */ -{yymsp[0].minor.yy144 = pParse->db->init.busy==0;} +{yymsp[0].minor.yy502 = pParse->db->init.busy==0;} break; case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_option_set */ { - sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy391,0); + sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy9,0); } break; case 20: /* create_table_args ::= AS select */ { - sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy555); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555); + sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy637); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy637); } break; case 21: /* table_option_set ::= */ -{yymsp[1].minor.yy391 = 0;} +{yymsp[1].minor.yy9 = 0;} break; case 22: /* table_option_set ::= table_option_set COMMA table_option */ -{yylhsminor.yy391 = yymsp[-2].minor.yy391|yymsp[0].minor.yy391;} - yymsp[-2].minor.yy391 = yylhsminor.yy391; +{yylhsminor.yy9 = yymsp[-2].minor.yy9|yymsp[0].minor.yy9;} + yymsp[-2].minor.yy9 = yylhsminor.yy9; break; case 23: /* table_option ::= WITHOUT nm */ { if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){ - yymsp[-1].minor.yy391 = TF_WithoutRowid | TF_NoVisibleRowid; + yymsp[-1].minor.yy9 = TF_WithoutRowid | TF_NoVisibleRowid; }else{ - yymsp[-1].minor.yy391 = 0; + yymsp[-1].minor.yy9 = 0; sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); } } @@ -182601,13 +181322,13 @@ static YYACTIONTYPE yy_reduce( case 24: /* table_option ::= nm */ { if( yymsp[0].minor.yy0.n==6 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"strict",6)==0 ){ - yylhsminor.yy391 = TF_Strict; + yylhsminor.yy9 = TF_Strict; }else{ - yylhsminor.yy391 = 0; + yylhsminor.yy9 = 0; sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); } } - yymsp[0].minor.yy391 = yylhsminor.yy391; + yymsp[0].minor.yy9 = yylhsminor.yy9; break; case 25: /* columnname ::= nm typetoken */ {sqlite3AddColumn(pParse,yymsp[-1].minor.yy0,yymsp[0].minor.yy0);} @@ -182633,7 +181354,7 @@ static YYACTIONTYPE yy_reduce( case 30: /* scanpt ::= */ { assert( yyLookahead!=YYNOCODE ); - yymsp[1].minor.yy168 = yyLookaheadToken.z; + yymsp[1].minor.yy342 = yyLookaheadToken.z; } break; case 31: /* scantok ::= */ @@ -182647,17 +181368,17 @@ static YYACTIONTYPE yy_reduce( {ASSERT_IS_CREATE; pParse->u1.cr.constraintName = yymsp[0].minor.yy0;} break; case 33: /* ccons ::= DEFAULT scantok term */ -{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy454,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy590,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} break; case 34: /* ccons ::= DEFAULT LP expr RP */ -{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy454,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);} +{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy590,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);} break; case 35: /* ccons ::= DEFAULT PLUS scantok term */ -{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy454,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy590,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} break; case 36: /* ccons ::= DEFAULT MINUS scantok term */ { - Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy454, 0); + Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy590, 0); sqlite3AddDefaultValue(pParse,p,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]); } break; @@ -182672,133 +181393,133 @@ static YYACTIONTYPE yy_reduce( } break; case 38: /* ccons ::= NOT NULL onconf */ -{sqlite3AddNotNull(pParse, yymsp[0].minor.yy144);} +{sqlite3AddNotNull(pParse, yymsp[0].minor.yy502);} break; case 39: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ -{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy144,yymsp[0].minor.yy144,yymsp[-2].minor.yy144);} +{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy502,yymsp[0].minor.yy502,yymsp[-2].minor.yy502);} break; case 40: /* ccons ::= UNIQUE onconf */ -{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy144,0,0,0,0, +{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy502,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; case 41: /* ccons ::= CHECK LP expr RP */ -{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy454,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy0.z);} +{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy590,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy0.z);} break; case 42: /* ccons ::= REFERENCES nm eidlist_opt refargs */ -{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy14,yymsp[0].minor.yy144);} +{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy402,yymsp[0].minor.yy502);} break; case 43: /* ccons ::= defer_subclause */ -{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy144);} +{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy502);} break; case 44: /* ccons ::= COLLATE ID|STRING */ {sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);} break; case 45: /* generated ::= LP expr RP */ -{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy454,0);} +{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy590,0);} break; case 46: /* generated ::= LP expr RP ID */ -{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy454,&yymsp[0].minor.yy0);} +{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy590,&yymsp[0].minor.yy0);} break; case 48: /* autoinc ::= AUTOINCR */ -{yymsp[0].minor.yy144 = 1;} +{yymsp[0].minor.yy502 = 1;} break; case 49: /* refargs ::= */ -{ yymsp[1].minor.yy144 = OE_None*0x0101; /* EV: R-19803-45884 */} +{ yymsp[1].minor.yy502 = OE_None*0x0101; /* EV: R-19803-45884 */} break; case 50: /* refargs ::= refargs refarg */ -{ yymsp[-1].minor.yy144 = (yymsp[-1].minor.yy144 & ~yymsp[0].minor.yy383.mask) | yymsp[0].minor.yy383.value; } +{ yymsp[-1].minor.yy502 = (yymsp[-1].minor.yy502 & ~yymsp[0].minor.yy481.mask) | yymsp[0].minor.yy481.value; } break; case 51: /* refarg ::= MATCH nm */ -{ yymsp[-1].minor.yy383.value = 0; yymsp[-1].minor.yy383.mask = 0x000000; } +{ yymsp[-1].minor.yy481.value = 0; yymsp[-1].minor.yy481.mask = 0x000000; } break; case 52: /* refarg ::= ON INSERT refact */ -{ yymsp[-2].minor.yy383.value = 0; yymsp[-2].minor.yy383.mask = 0x000000; } +{ yymsp[-2].minor.yy481.value = 0; yymsp[-2].minor.yy481.mask = 0x000000; } break; case 53: /* refarg ::= ON DELETE refact */ -{ yymsp[-2].minor.yy383.value = yymsp[0].minor.yy144; yymsp[-2].minor.yy383.mask = 0x0000ff; } +{ yymsp[-2].minor.yy481.value = yymsp[0].minor.yy502; yymsp[-2].minor.yy481.mask = 0x0000ff; } break; case 54: /* refarg ::= ON UPDATE refact */ -{ yymsp[-2].minor.yy383.value = yymsp[0].minor.yy144<<8; yymsp[-2].minor.yy383.mask = 0x00ff00; } +{ yymsp[-2].minor.yy481.value = yymsp[0].minor.yy502<<8; yymsp[-2].minor.yy481.mask = 0x00ff00; } break; case 55: /* refact ::= SET NULL */ -{ yymsp[-1].minor.yy144 = OE_SetNull; /* EV: R-33326-45252 */} +{ yymsp[-1].minor.yy502 = OE_SetNull; /* EV: R-33326-45252 */} break; case 56: /* refact ::= SET DEFAULT */ -{ yymsp[-1].minor.yy144 = OE_SetDflt; /* EV: R-33326-45252 */} +{ yymsp[-1].minor.yy502 = OE_SetDflt; /* EV: R-33326-45252 */} break; case 57: /* refact ::= CASCADE */ -{ yymsp[0].minor.yy144 = OE_Cascade; /* EV: R-33326-45252 */} +{ yymsp[0].minor.yy502 = OE_Cascade; /* EV: R-33326-45252 */} break; case 58: /* refact ::= RESTRICT */ -{ yymsp[0].minor.yy144 = OE_Restrict; /* EV: R-33326-45252 */} +{ yymsp[0].minor.yy502 = OE_Restrict; /* EV: R-33326-45252 */} break; case 59: /* refact ::= NO ACTION */ -{ yymsp[-1].minor.yy144 = OE_None; /* EV: R-33326-45252 */} +{ yymsp[-1].minor.yy502 = OE_None; /* EV: R-33326-45252 */} break; case 60: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ -{yymsp[-2].minor.yy144 = 0;} +{yymsp[-2].minor.yy502 = 0;} break; case 61: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ case 76: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==76); case 173: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==173); -{yymsp[-1].minor.yy144 = yymsp[0].minor.yy144;} +{yymsp[-1].minor.yy502 = yymsp[0].minor.yy502;} break; case 63: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ case 80: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==80); case 219: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==219); case 222: /* in_op ::= NOT IN */ yytestcase(yyruleno==222); case 247: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==247); -{yymsp[-1].minor.yy144 = 1;} +{yymsp[-1].minor.yy502 = 1;} break; case 64: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ -{yymsp[-1].minor.yy144 = 0;} +{yymsp[-1].minor.yy502 = 0;} break; case 66: /* tconscomma ::= COMMA */ {ASSERT_IS_CREATE; pParse->u1.cr.constraintName.n = 0;} break; case 68: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ -{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy14,yymsp[0].minor.yy144,yymsp[-2].minor.yy144,0);} +{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy402,yymsp[0].minor.yy502,yymsp[-2].minor.yy502,0);} break; case 69: /* tcons ::= UNIQUE LP sortlist RP onconf */ -{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy14,yymsp[0].minor.yy144,0,0,0,0, +{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy402,yymsp[0].minor.yy502,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; case 70: /* tcons ::= CHECK LP expr RP onconf */ -{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy454,yymsp[-3].minor.yy0.z,yymsp[-1].minor.yy0.z);} +{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy590,yymsp[-3].minor.yy0.z,yymsp[-1].minor.yy0.z);} break; case 71: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ { - sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy14, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy14, yymsp[-1].minor.yy144); - sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy144); + sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy402, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy402, yymsp[-1].minor.yy502); + sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy502); } break; case 73: /* onconf ::= */ case 75: /* orconf ::= */ yytestcase(yyruleno==75); -{yymsp[1].minor.yy144 = OE_Default;} +{yymsp[1].minor.yy502 = OE_Default;} break; case 74: /* onconf ::= ON CONFLICT resolvetype */ -{yymsp[-2].minor.yy144 = yymsp[0].minor.yy144;} +{yymsp[-2].minor.yy502 = yymsp[0].minor.yy502;} break; case 77: /* resolvetype ::= IGNORE */ -{yymsp[0].minor.yy144 = OE_Ignore;} +{yymsp[0].minor.yy502 = OE_Ignore;} break; case 78: /* resolvetype ::= REPLACE */ case 174: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==174); -{yymsp[0].minor.yy144 = OE_Replace;} +{yymsp[0].minor.yy502 = OE_Replace;} break; case 79: /* cmd ::= DROP TABLE ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy203, 0, yymsp[-1].minor.yy144); + sqlite3DropTable(pParse, yymsp[0].minor.yy563, 0, yymsp[-1].minor.yy502); } break; case 82: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ { - sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy14, yymsp[0].minor.yy555, yymsp[-7].minor.yy144, yymsp[-5].minor.yy144); + sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy402, yymsp[0].minor.yy637, yymsp[-7].minor.yy502, yymsp[-5].minor.yy502); } break; case 83: /* cmd ::= DROP VIEW ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy203, 1, yymsp[-1].minor.yy144); + sqlite3DropTable(pParse, yymsp[0].minor.yy563, 1, yymsp[-1].minor.yy502); } break; case 84: /* cmd ::= select */ @@ -182807,20 +181528,20 @@ static YYACTIONTYPE yy_reduce( if( (pParse->db->mDbFlags & DBFLAG_EncodingFixed)!=0 || sqlite3ReadSchema(pParse)==SQLITE_OK ){ - sqlite3Select(pParse, yymsp[0].minor.yy555, &dest); + sqlite3Select(pParse, yymsp[0].minor.yy637, &dest); } - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy637); } break; case 85: /* select ::= WITH wqlist selectnowith */ -{yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);} +{yymsp[-2].minor.yy637 = attachWithToSelect(pParse,yymsp[0].minor.yy637,yymsp[-1].minor.yy125);} break; case 86: /* select ::= WITH RECURSIVE wqlist selectnowith */ -{yymsp[-3].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);} +{yymsp[-3].minor.yy637 = attachWithToSelect(pParse,yymsp[0].minor.yy637,yymsp[-1].minor.yy125);} break; case 87: /* select ::= selectnowith */ { - Select *p = yymsp[0].minor.yy555; + Select *p = yymsp[0].minor.yy637; if( p ){ parserDoubleLinkSelect(pParse, p); } @@ -182828,8 +181549,8 @@ static YYACTIONTYPE yy_reduce( break; case 88: /* selectnowith ::= selectnowith multiselect_op oneselect */ { - Select *pRhs = yymsp[0].minor.yy555; - Select *pLhs = yymsp[-2].minor.yy555; + Select *pRhs = yymsp[0].minor.yy637; + Select *pLhs = yymsp[-2].minor.yy637; if( pRhs && pRhs->pPrior ){ SrcList *pFrom; Token x; @@ -182839,60 +181560,60 @@ static YYACTIONTYPE yy_reduce( pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0); } if( pRhs ){ - pRhs->op = (u8)yymsp[-1].minor.yy144; + pRhs->op = (u8)yymsp[-1].minor.yy502; pRhs->pPrior = pLhs; if( ALWAYS(pLhs) ) pLhs->selFlags &= ~(u32)SF_MultiValue; pRhs->selFlags &= ~(u32)SF_MultiValue; - if( yymsp[-1].minor.yy144!=TK_ALL ) pParse->hasCompound = 1; + if( yymsp[-1].minor.yy502!=TK_ALL ) pParse->hasCompound = 1; }else{ sqlite3SelectDelete(pParse->db, pLhs); } - yymsp[-2].minor.yy555 = pRhs; + yymsp[-2].minor.yy637 = pRhs; } break; case 89: /* multiselect_op ::= UNION */ case 91: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==91); -{yymsp[0].minor.yy144 = yymsp[0].major; /*A-overwrites-OP*/} +{yymsp[0].minor.yy502 = yymsp[0].major; /*A-overwrites-OP*/} break; case 90: /* multiselect_op ::= UNION ALL */ -{yymsp[-1].minor.yy144 = TK_ALL;} +{yymsp[-1].minor.yy502 = TK_ALL;} break; case 92: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ { - yymsp[-8].minor.yy555 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy14,yymsp[-5].minor.yy203,yymsp[-4].minor.yy454,yymsp[-3].minor.yy14,yymsp[-2].minor.yy454,yymsp[-1].minor.yy14,yymsp[-7].minor.yy144,yymsp[0].minor.yy454); + yymsp[-8].minor.yy637 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy402,yymsp[-5].minor.yy563,yymsp[-4].minor.yy590,yymsp[-3].minor.yy402,yymsp[-2].minor.yy590,yymsp[-1].minor.yy402,yymsp[-7].minor.yy502,yymsp[0].minor.yy590); } break; case 93: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ { - yymsp[-9].minor.yy555 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy14,yymsp[-6].minor.yy203,yymsp[-5].minor.yy454,yymsp[-4].minor.yy14,yymsp[-3].minor.yy454,yymsp[-1].minor.yy14,yymsp[-8].minor.yy144,yymsp[0].minor.yy454); - if( yymsp[-9].minor.yy555 ){ - yymsp[-9].minor.yy555->pWinDefn = yymsp[-2].minor.yy211; + yymsp[-9].minor.yy637 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy402,yymsp[-6].minor.yy563,yymsp[-5].minor.yy590,yymsp[-4].minor.yy402,yymsp[-3].minor.yy590,yymsp[-1].minor.yy402,yymsp[-8].minor.yy502,yymsp[0].minor.yy590); + if( yymsp[-9].minor.yy637 ){ + yymsp[-9].minor.yy637->pWinDefn = yymsp[-2].minor.yy483; }else{ - sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy211); + sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy483); } } break; case 94: /* values ::= VALUES LP nexprlist RP */ { - yymsp[-3].minor.yy555 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy14,0,0,0,0,0,SF_Values,0); + yymsp[-3].minor.yy637 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy402,0,0,0,0,0,SF_Values,0); } break; case 95: /* oneselect ::= mvalues */ { - sqlite3MultiValuesEnd(pParse, yymsp[0].minor.yy555); + sqlite3MultiValuesEnd(pParse, yymsp[0].minor.yy637); } break; case 96: /* mvalues ::= values COMMA LP nexprlist RP */ case 97: /* mvalues ::= mvalues COMMA LP nexprlist RP */ yytestcase(yyruleno==97); { - yymsp[-4].minor.yy555 = sqlite3MultiValues(pParse, yymsp[-4].minor.yy555, yymsp[-1].minor.yy14); + yymsp[-4].minor.yy637 = sqlite3MultiValues(pParse, yymsp[-4].minor.yy637, yymsp[-1].minor.yy402); } break; case 98: /* distinct ::= DISTINCT */ -{yymsp[0].minor.yy144 = SF_Distinct;} +{yymsp[0].minor.yy502 = SF_Distinct;} break; case 99: /* distinct ::= ALL */ -{yymsp[0].minor.yy144 = SF_All;} +{yymsp[0].minor.yy502 = SF_All;} break; case 101: /* sclp ::= */ case 134: /* orderby_opt ::= */ yytestcase(yyruleno==134); @@ -182900,20 +181621,20 @@ static YYACTIONTYPE yy_reduce( case 234: /* exprlist ::= */ yytestcase(yyruleno==234); case 237: /* paren_exprlist ::= */ yytestcase(yyruleno==237); case 242: /* eidlist_opt ::= */ yytestcase(yyruleno==242); -{yymsp[1].minor.yy14 = 0;} +{yymsp[1].minor.yy402 = 0;} break; case 102: /* selcollist ::= sclp scanpt expr scanpt as */ { - yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy14, yymsp[-2].minor.yy454); - if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy14, &yymsp[0].minor.yy0, 1); - sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy14,yymsp[-3].minor.yy168,yymsp[-1].minor.yy168); + yymsp[-4].minor.yy402 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy402, yymsp[-2].minor.yy590); + if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy402, &yymsp[0].minor.yy0, 1); + sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy402,yymsp[-3].minor.yy342,yymsp[-1].minor.yy342); } break; case 103: /* selcollist ::= sclp scanpt STAR */ { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); sqlite3ExprSetErrorOffset(p, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); - yymsp[-2].minor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy14, p); + yymsp[-2].minor.yy402 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy402, p); } break; case 104: /* selcollist ::= sclp scanpt nm DOT STAR */ @@ -182923,7 +181644,7 @@ static YYACTIONTYPE yy_reduce( sqlite3ExprSetErrorOffset(pRight, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); pLeft = tokenExpr(pParse, TK_ID, yymsp[-2].minor.yy0); pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); - yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, pDot); + yymsp[-4].minor.yy402 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy402, pDot); } break; case 105: /* as ::= AS nm */ @@ -182934,50 +181655,50 @@ static YYACTIONTYPE yy_reduce( break; case 107: /* from ::= */ case 110: /* stl_prefix ::= */ yytestcase(yyruleno==110); -{yymsp[1].minor.yy203 = 0;} +{yymsp[1].minor.yy563 = 0;} break; case 108: /* from ::= FROM seltablist */ { - yymsp[-1].minor.yy203 = yymsp[0].minor.yy203; - sqlite3SrcListShiftJoinType(pParse,yymsp[-1].minor.yy203); + yymsp[-1].minor.yy563 = yymsp[0].minor.yy563; + sqlite3SrcListShiftJoinType(pParse,yymsp[-1].minor.yy563); } break; case 109: /* stl_prefix ::= seltablist joinop */ { - if( ALWAYS(yymsp[-1].minor.yy203 && yymsp[-1].minor.yy203->nSrc>0) ) yymsp[-1].minor.yy203->a[yymsp[-1].minor.yy203->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy144; + if( ALWAYS(yymsp[-1].minor.yy563 && yymsp[-1].minor.yy563->nSrc>0) ) yymsp[-1].minor.yy563->a[yymsp[-1].minor.yy563->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy502; } break; case 111: /* seltablist ::= stl_prefix nm dbnm as on_using */ { - yymsp[-4].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-4].minor.yy203,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy269); + yymsp[-4].minor.yy563 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-4].minor.yy563,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy421); } break; case 112: /* seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ { - yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,0,&yymsp[0].minor.yy269); - sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy203, &yymsp[-1].minor.yy0); + yymsp[-5].minor.yy563 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy563,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,0,&yymsp[0].minor.yy421); + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy563, &yymsp[-1].minor.yy0); } break; case 113: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ { - yymsp[-7].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-7].minor.yy203,&yymsp[-6].minor.yy0,&yymsp[-5].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy269); - sqlite3SrcListFuncArgs(pParse, yymsp[-7].minor.yy203, yymsp[-3].minor.yy14); + yymsp[-7].minor.yy563 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-7].minor.yy563,&yymsp[-6].minor.yy0,&yymsp[-5].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy421); + sqlite3SrcListFuncArgs(pParse, yymsp[-7].minor.yy563, yymsp[-3].minor.yy402); } break; case 114: /* seltablist ::= stl_prefix LP select RP as on_using */ { - yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,0,0,&yymsp[-1].minor.yy0,yymsp[-3].minor.yy555,&yymsp[0].minor.yy269); + yymsp[-5].minor.yy563 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy563,0,0,&yymsp[-1].minor.yy0,yymsp[-3].minor.yy637,&yymsp[0].minor.yy421); } break; case 115: /* seltablist ::= stl_prefix LP seltablist RP as on_using */ { - if( yymsp[-5].minor.yy203==0 && yymsp[-1].minor.yy0.n==0 && yymsp[0].minor.yy269.pOn==0 && yymsp[0].minor.yy269.pUsing==0 ){ - yymsp[-5].minor.yy203 = yymsp[-3].minor.yy203; - }else if( ALWAYS(yymsp[-3].minor.yy203!=0) && yymsp[-3].minor.yy203->nSrc==1 ){ - yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,0,0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy269); - if( yymsp[-5].minor.yy203 ){ - SrcItem *pNew = &yymsp[-5].minor.yy203->a[yymsp[-5].minor.yy203->nSrc-1]; - SrcItem *pOld = yymsp[-3].minor.yy203->a; + if( yymsp[-5].minor.yy563==0 && yymsp[-1].minor.yy0.n==0 && yymsp[0].minor.yy421.pOn==0 && yymsp[0].minor.yy421.pUsing==0 ){ + yymsp[-5].minor.yy563 = yymsp[-3].minor.yy563; + }else if( ALWAYS(yymsp[-3].minor.yy563!=0) && yymsp[-3].minor.yy563->nSrc==1 ){ + yymsp[-5].minor.yy563 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy563,0,0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy421); + if( yymsp[-5].minor.yy563 ){ + SrcItem *pNew = &yymsp[-5].minor.yy563->a[yymsp[-5].minor.yy563->nSrc-1]; + SrcItem *pOld = yymsp[-3].minor.yy563->a; assert( pOld->fg.fixedSchema==0 ); pNew->zName = pOld->zName; assert( pOld->fg.fixedSchema==0 ); @@ -183002,12 +181723,12 @@ static YYACTIONTYPE yy_reduce( } pOld->zName = 0; } - sqlite3SrcListDelete(pParse->db, yymsp[-3].minor.yy203); + sqlite3SrcListDelete(pParse->db, yymsp[-3].minor.yy563); }else{ Select *pSubquery; - sqlite3SrcListShiftJoinType(pParse,yymsp[-3].minor.yy203); - pSubquery = sqlite3SelectNew(pParse,0,yymsp[-3].minor.yy203,0,0,0,0,SF_NestedFrom,0); - yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,0,0,&yymsp[-1].minor.yy0,pSubquery,&yymsp[0].minor.yy269); + sqlite3SrcListShiftJoinType(pParse,yymsp[-3].minor.yy563); + pSubquery = sqlite3SelectNew(pParse,0,yymsp[-3].minor.yy563,0,0,0,0,SF_NestedFrom,0); + yymsp[-5].minor.yy563 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy563,0,0,&yymsp[-1].minor.yy0,pSubquery,&yymsp[0].minor.yy421); } } break; @@ -183016,67 +181737,57 @@ static YYACTIONTYPE yy_reduce( {yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;} break; case 118: /* fullname ::= nm */ - case 120: /* xfullname ::= nm */ yytestcase(yyruleno==120); { - yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); - if( IN_RENAME_OBJECT && yylhsminor.yy203 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[0].minor.yy0); + yylhsminor.yy563 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); + if( IN_RENAME_OBJECT && yylhsminor.yy563 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy563->a[0].zName, &yymsp[0].minor.yy0); } - yymsp[0].minor.yy203 = yylhsminor.yy203; + yymsp[0].minor.yy563 = yylhsminor.yy563; break; case 119: /* fullname ::= nm DOT nm */ - case 121: /* xfullname ::= nm DOT nm */ yytestcase(yyruleno==121); { - yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); - if( IN_RENAME_OBJECT && yylhsminor.yy203 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[0].minor.yy0); + yylhsminor.yy563 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); + if( IN_RENAME_OBJECT && yylhsminor.yy563 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy563->a[0].zName, &yymsp[0].minor.yy0); } - yymsp[-2].minor.yy203 = yylhsminor.yy203; + yymsp[-2].minor.yy563 = yylhsminor.yy563; break; - case 122: /* xfullname ::= nm AS nm */ + case 120: /* xfullname ::= nm */ +{yymsp[0].minor.yy563 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/} + break; + case 121: /* xfullname ::= nm DOT nm */ +{yymsp[-2].minor.yy563 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/} + break; + case 122: /* xfullname ::= nm DOT nm AS nm */ { - yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); - if( yylhsminor.yy203 ){ - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[-2].minor.yy0); - }else{ - yylhsminor.yy203->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); - } - } + yymsp[-4].minor.yy563 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/ + if( yymsp[-4].minor.yy563 ) yymsp[-4].minor.yy563->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); } - yymsp[-2].minor.yy203 = yylhsminor.yy203; break; - case 123: /* xfullname ::= nm DOT nm AS nm */ + case 123: /* xfullname ::= nm AS nm */ { - yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); - if( yylhsminor.yy203 ){ - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[-2].minor.yy0); - }else{ - yylhsminor.yy203->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); - } - } + yymsp[-2].minor.yy563 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/ + if( yymsp[-2].minor.yy563 ) yymsp[-2].minor.yy563->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); } - yymsp[-4].minor.yy203 = yylhsminor.yy203; break; case 124: /* joinop ::= COMMA|JOIN */ -{ yymsp[0].minor.yy144 = JT_INNER; } +{ yymsp[0].minor.yy502 = JT_INNER; } break; case 125: /* joinop ::= JOIN_KW JOIN */ -{yymsp[-1].minor.yy144 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} +{yymsp[-1].minor.yy502 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} break; case 126: /* joinop ::= JOIN_KW nm JOIN */ -{yymsp[-2].minor.yy144 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} +{yymsp[-2].minor.yy502 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} break; case 127: /* joinop ::= JOIN_KW nm nm JOIN */ -{yymsp[-3].minor.yy144 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} +{yymsp[-3].minor.yy502 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} break; case 128: /* on_using ::= ON expr */ -{yymsp[-1].minor.yy269.pOn = yymsp[0].minor.yy454; yymsp[-1].minor.yy269.pUsing = 0;} +{yymsp[-1].minor.yy421.pOn = yymsp[0].minor.yy590; yymsp[-1].minor.yy421.pUsing = 0;} break; case 129: /* on_using ::= USING LP idlist RP */ -{yymsp[-3].minor.yy269.pOn = 0; yymsp[-3].minor.yy269.pUsing = yymsp[-1].minor.yy132;} +{yymsp[-3].minor.yy421.pOn = 0; yymsp[-3].minor.yy421.pUsing = yymsp[-1].minor.yy204;} break; case 130: /* on_using ::= */ -{yymsp[1].minor.yy269.pOn = 0; yymsp[1].minor.yy269.pUsing = 0;} +{yymsp[1].minor.yy421.pOn = 0; yymsp[1].minor.yy421.pUsing = 0;} break; case 132: /* indexed_by ::= INDEXED BY nm */ {yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;} @@ -183086,35 +181797,35 @@ static YYACTIONTYPE yy_reduce( break; case 135: /* orderby_opt ::= ORDER BY sortlist */ case 145: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==145); -{yymsp[-2].minor.yy14 = yymsp[0].minor.yy14;} +{yymsp[-2].minor.yy402 = yymsp[0].minor.yy402;} break; case 136: /* sortlist ::= sortlist COMMA expr sortorder nulls */ { - yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14,yymsp[-2].minor.yy454); - sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy14,yymsp[-1].minor.yy144,yymsp[0].minor.yy144); + yymsp[-4].minor.yy402 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy402,yymsp[-2].minor.yy590); + sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy402,yymsp[-1].minor.yy502,yymsp[0].minor.yy502); } break; case 137: /* sortlist ::= expr sortorder nulls */ { - yymsp[-2].minor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy454); /*A-overwrites-Y*/ - sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy14,yymsp[-1].minor.yy144,yymsp[0].minor.yy144); + yymsp[-2].minor.yy402 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy590); /*A-overwrites-Y*/ + sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy402,yymsp[-1].minor.yy502,yymsp[0].minor.yy502); } break; case 138: /* sortorder ::= ASC */ -{yymsp[0].minor.yy144 = SQLITE_SO_ASC;} +{yymsp[0].minor.yy502 = SQLITE_SO_ASC;} break; case 139: /* sortorder ::= DESC */ -{yymsp[0].minor.yy144 = SQLITE_SO_DESC;} +{yymsp[0].minor.yy502 = SQLITE_SO_DESC;} break; case 140: /* sortorder ::= */ case 143: /* nulls ::= */ yytestcase(yyruleno==143); -{yymsp[1].minor.yy144 = SQLITE_SO_UNDEFINED;} +{yymsp[1].minor.yy502 = SQLITE_SO_UNDEFINED;} break; case 141: /* nulls ::= NULLS FIRST */ -{yymsp[-1].minor.yy144 = SQLITE_SO_ASC;} +{yymsp[-1].minor.yy502 = SQLITE_SO_ASC;} break; case 142: /* nulls ::= NULLS LAST */ -{yymsp[-1].minor.yy144 = SQLITE_SO_DESC;} +{yymsp[-1].minor.yy502 = SQLITE_SO_DESC;} break; case 146: /* having_opt ::= */ case 148: /* limit_opt ::= */ yytestcase(yyruleno==148); @@ -183123,42 +181834,42 @@ static YYACTIONTYPE yy_reduce( case 232: /* case_else ::= */ yytestcase(yyruleno==232); case 233: /* case_operand ::= */ yytestcase(yyruleno==233); case 252: /* vinto ::= */ yytestcase(yyruleno==252); -{yymsp[1].minor.yy454 = 0;} +{yymsp[1].minor.yy590 = 0;} break; case 147: /* having_opt ::= HAVING expr */ case 154: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==154); case 156: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==156); case 231: /* case_else ::= ELSE expr */ yytestcase(yyruleno==231); case 251: /* vinto ::= INTO expr */ yytestcase(yyruleno==251); -{yymsp[-1].minor.yy454 = yymsp[0].minor.yy454;} +{yymsp[-1].minor.yy590 = yymsp[0].minor.yy590;} break; case 149: /* limit_opt ::= LIMIT expr */ -{yymsp[-1].minor.yy454 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy454,0);} +{yymsp[-1].minor.yy590 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy590,0);} break; case 150: /* limit_opt ::= LIMIT expr OFFSET expr */ -{yymsp[-3].minor.yy454 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy454,yymsp[0].minor.yy454);} +{yymsp[-3].minor.yy590 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy590,yymsp[0].minor.yy590);} break; case 151: /* limit_opt ::= LIMIT expr COMMA expr */ -{yymsp[-3].minor.yy454 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy454,yymsp[-2].minor.yy454);} +{yymsp[-3].minor.yy590 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy590,yymsp[-2].minor.yy590);} break; case 152: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ { - sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy203, &yymsp[-1].minor.yy0); - sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy203,yymsp[0].minor.yy454,0,0); + sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy563, &yymsp[-1].minor.yy0); + sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy563,yymsp[0].minor.yy590,0,0); } break; case 157: /* where_opt_ret ::= RETURNING selcollist */ -{sqlite3AddReturning(pParse,yymsp[0].minor.yy14); yymsp[-1].minor.yy454 = 0;} +{sqlite3AddReturning(pParse,yymsp[0].minor.yy402); yymsp[-1].minor.yy590 = 0;} break; case 158: /* where_opt_ret ::= WHERE expr RETURNING selcollist */ -{sqlite3AddReturning(pParse,yymsp[0].minor.yy14); yymsp[-3].minor.yy454 = yymsp[-2].minor.yy454;} +{sqlite3AddReturning(pParse,yymsp[0].minor.yy402); yymsp[-3].minor.yy590 = yymsp[-2].minor.yy590;} break; case 159: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ { - sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy203, &yymsp[-4].minor.yy0); - sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy14,"set list"); - if( yymsp[-1].minor.yy203 ){ - SrcList *pFromClause = yymsp[-1].minor.yy203; + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy563, &yymsp[-4].minor.yy0); + sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy402,"set list"); + if( yymsp[-1].minor.yy563 ){ + SrcList *pFromClause = yymsp[-1].minor.yy563; if( pFromClause->nSrc>1 ){ Select *pSubquery; Token as; @@ -183167,90 +181878,90 @@ static YYACTIONTYPE yy_reduce( as.z = 0; pFromClause = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); } - yymsp[-5].minor.yy203 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy203, pFromClause); + yymsp[-5].minor.yy563 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy563, pFromClause); } - sqlite3Update(pParse,yymsp[-5].minor.yy203,yymsp[-2].minor.yy14,yymsp[0].minor.yy454,yymsp[-6].minor.yy144,0,0,0); + sqlite3Update(pParse,yymsp[-5].minor.yy563,yymsp[-2].minor.yy402,yymsp[0].minor.yy590,yymsp[-6].minor.yy502,0,0,0); } break; case 160: /* setlist ::= setlist COMMA nm EQ expr */ { - yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy14, yymsp[0].minor.yy454); - sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy14, &yymsp[-2].minor.yy0, 1); + yymsp[-4].minor.yy402 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy402, yymsp[0].minor.yy590); + sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy402, &yymsp[-2].minor.yy0, 1); } break; case 161: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ { - yymsp[-6].minor.yy14 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy14, yymsp[-3].minor.yy132, yymsp[0].minor.yy454); + yymsp[-6].minor.yy402 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy402, yymsp[-3].minor.yy204, yymsp[0].minor.yy590); } break; case 162: /* setlist ::= nm EQ expr */ { - yylhsminor.yy14 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy454); - sqlite3ExprListSetName(pParse, yylhsminor.yy14, &yymsp[-2].minor.yy0, 1); + yylhsminor.yy402 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy590); + sqlite3ExprListSetName(pParse, yylhsminor.yy402, &yymsp[-2].minor.yy0, 1); } - yymsp[-2].minor.yy14 = yylhsminor.yy14; + yymsp[-2].minor.yy402 = yylhsminor.yy402; break; case 163: /* setlist ::= LP idlist RP EQ expr */ { - yymsp[-4].minor.yy14 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy132, yymsp[0].minor.yy454); + yymsp[-4].minor.yy402 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy204, yymsp[0].minor.yy590); } break; case 164: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ { - sqlite3Insert(pParse, yymsp[-3].minor.yy203, yymsp[-1].minor.yy555, yymsp[-2].minor.yy132, yymsp[-5].minor.yy144, yymsp[0].minor.yy122); + sqlite3Insert(pParse, yymsp[-3].minor.yy563, yymsp[-1].minor.yy637, yymsp[-2].minor.yy204, yymsp[-5].minor.yy502, yymsp[0].minor.yy403); } break; case 165: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ { - sqlite3Insert(pParse, yymsp[-4].minor.yy203, 0, yymsp[-3].minor.yy132, yymsp[-6].minor.yy144, 0); + sqlite3Insert(pParse, yymsp[-4].minor.yy563, 0, yymsp[-3].minor.yy204, yymsp[-6].minor.yy502, 0); } break; case 166: /* upsert ::= */ -{ yymsp[1].minor.yy122 = 0; } +{ yymsp[1].minor.yy403 = 0; } break; case 167: /* upsert ::= RETURNING selcollist */ -{ yymsp[-1].minor.yy122 = 0; sqlite3AddReturning(pParse,yymsp[0].minor.yy14); } +{ yymsp[-1].minor.yy403 = 0; sqlite3AddReturning(pParse,yymsp[0].minor.yy402); } break; case 168: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ -{ yymsp[-11].minor.yy122 = sqlite3UpsertNew(pParse->db,yymsp[-8].minor.yy14,yymsp[-6].minor.yy454,yymsp[-2].minor.yy14,yymsp[-1].minor.yy454,yymsp[0].minor.yy122);} +{ yymsp[-11].minor.yy403 = sqlite3UpsertNew(pParse->db,yymsp[-8].minor.yy402,yymsp[-6].minor.yy590,yymsp[-2].minor.yy402,yymsp[-1].minor.yy590,yymsp[0].minor.yy403);} break; case 169: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ -{ yymsp[-8].minor.yy122 = sqlite3UpsertNew(pParse->db,yymsp[-5].minor.yy14,yymsp[-3].minor.yy454,0,0,yymsp[0].minor.yy122); } +{ yymsp[-8].minor.yy403 = sqlite3UpsertNew(pParse->db,yymsp[-5].minor.yy402,yymsp[-3].minor.yy590,0,0,yymsp[0].minor.yy403); } break; case 170: /* upsert ::= ON CONFLICT DO NOTHING returning */ -{ yymsp[-4].minor.yy122 = sqlite3UpsertNew(pParse->db,0,0,0,0,0); } +{ yymsp[-4].minor.yy403 = sqlite3UpsertNew(pParse->db,0,0,0,0,0); } break; case 171: /* upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ -{ yymsp[-7].minor.yy122 = sqlite3UpsertNew(pParse->db,0,0,yymsp[-2].minor.yy14,yymsp[-1].minor.yy454,0);} +{ yymsp[-7].minor.yy403 = sqlite3UpsertNew(pParse->db,0,0,yymsp[-2].minor.yy402,yymsp[-1].minor.yy590,0);} break; case 172: /* returning ::= RETURNING selcollist */ -{sqlite3AddReturning(pParse,yymsp[0].minor.yy14);} +{sqlite3AddReturning(pParse,yymsp[0].minor.yy402);} break; case 175: /* idlist_opt ::= */ -{yymsp[1].minor.yy132 = 0;} +{yymsp[1].minor.yy204 = 0;} break; case 176: /* idlist_opt ::= LP idlist RP */ -{yymsp[-2].minor.yy132 = yymsp[-1].minor.yy132;} +{yymsp[-2].minor.yy204 = yymsp[-1].minor.yy204;} break; case 177: /* idlist ::= idlist COMMA nm */ -{yymsp[-2].minor.yy132 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy132,&yymsp[0].minor.yy0);} +{yymsp[-2].minor.yy204 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy204,&yymsp[0].minor.yy0);} break; case 178: /* idlist ::= nm */ -{yymsp[0].minor.yy132 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} +{yymsp[0].minor.yy204 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} break; case 179: /* expr ::= LP expr RP */ -{yymsp[-2].minor.yy454 = yymsp[-1].minor.yy454;} +{yymsp[-2].minor.yy590 = yymsp[-1].minor.yy590;} break; case 180: /* expr ::= ID|INDEXED|JOIN_KW */ -{yymsp[0].minor.yy454=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} +{yymsp[0].minor.yy590=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} break; case 181: /* expr ::= nm DOT nm */ { Expr *temp1 = tokenExpr(pParse,TK_ID,yymsp[-2].minor.yy0); Expr *temp2 = tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); - yylhsminor.yy454 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); + yylhsminor.yy590 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); } - yymsp[-2].minor.yy454 = yylhsminor.yy454; + yymsp[-2].minor.yy590 = yylhsminor.yy590; break; case 182: /* expr ::= nm DOT nm DOT nm */ { @@ -183261,32 +181972,27 @@ static YYACTIONTYPE yy_reduce( if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, 0, temp1); } - yylhsminor.yy454 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); + yylhsminor.yy590 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); } - yymsp[-4].minor.yy454 = yylhsminor.yy454; + yymsp[-4].minor.yy590 = yylhsminor.yy590; break; case 183: /* term ::= NULL|FLOAT|BLOB */ case 184: /* term ::= STRING */ yytestcase(yyruleno==184); -{yymsp[0].minor.yy454=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} +{yymsp[0].minor.yy590=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} break; case 185: /* term ::= INTEGER */ { - int iValue; - if( sqlite3GetInt32(yymsp[0].minor.yy0.z, &iValue)==0 ){ - yylhsminor.yy454 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 0); - }else{ - yylhsminor.yy454 = sqlite3ExprInt32(pParse->db, iValue); - } - if( yylhsminor.yy454 ) yylhsminor.yy454->w.iOfst = (int)(yymsp[0].minor.yy0.z - pParse->zTail); + yylhsminor.yy590 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1); + if( yylhsminor.yy590 ) yylhsminor.yy590->w.iOfst = (int)(yymsp[0].minor.yy0.z - pParse->zTail); } - yymsp[0].minor.yy454 = yylhsminor.yy454; + yymsp[0].minor.yy590 = yylhsminor.yy590; break; case 186: /* expr ::= VARIABLE */ { if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){ u32 n = yymsp[0].minor.yy0.n; - yymsp[0].minor.yy454 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0); - sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy454, n); + yymsp[0].minor.yy590 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0); + sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy590, n); }else{ /* When doing a nested parse, one can include terms in an expression ** that look like this: #1 #2 ... These terms refer to registers @@ -183295,80 +182001,80 @@ static YYACTIONTYPE yy_reduce( assert( t.n>=2 ); if( pParse->nested==0 ){ parserSyntaxError(pParse, &t); - yymsp[0].minor.yy454 = 0; + yymsp[0].minor.yy590 = 0; }else{ - yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); - if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable); + yymsp[0].minor.yy590 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); + if( yymsp[0].minor.yy590 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy590->iTable); } } } break; case 187: /* expr ::= expr COLLATE ID|STRING */ { - yymsp[-2].minor.yy454 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy454, &yymsp[0].minor.yy0, 1); + yymsp[-2].minor.yy590 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy590, &yymsp[0].minor.yy0, 1); } break; case 188: /* expr ::= CAST LP expr AS typetoken RP */ { - yymsp[-5].minor.yy454 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); - sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy454, yymsp[-3].minor.yy454, 0); + yymsp[-5].minor.yy590 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); + sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy590, yymsp[-3].minor.yy590, 0); } break; case 189: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP */ { - yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy14, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy144); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy402, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy502); } - yymsp[-4].minor.yy454 = yylhsminor.yy454; + yymsp[-4].minor.yy590 = yylhsminor.yy590; break; case 190: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP */ { - yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-4].minor.yy14, &yymsp[-7].minor.yy0, yymsp[-5].minor.yy144); - sqlite3ExprAddFunctionOrderBy(pParse, yylhsminor.yy454, yymsp[-1].minor.yy14); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, yymsp[-4].minor.yy402, &yymsp[-7].minor.yy0, yymsp[-5].minor.yy502); + sqlite3ExprAddFunctionOrderBy(pParse, yylhsminor.yy590, yymsp[-1].minor.yy402); } - yymsp[-7].minor.yy454 = yylhsminor.yy454; + yymsp[-7].minor.yy590 = yylhsminor.yy590; break; case 191: /* expr ::= ID|INDEXED|JOIN_KW LP STAR RP */ { - yylhsminor.yy454 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0); } - yymsp[-3].minor.yy454 = yylhsminor.yy454; + yymsp[-3].minor.yy590 = yylhsminor.yy590; break; case 192: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over */ { - yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy14, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy144); - sqlite3WindowAttach(pParse, yylhsminor.yy454, yymsp[0].minor.yy211); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy402, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy502); + sqlite3WindowAttach(pParse, yylhsminor.yy590, yymsp[0].minor.yy483); } - yymsp[-5].minor.yy454 = yylhsminor.yy454; + yymsp[-5].minor.yy590 = yylhsminor.yy590; break; case 193: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP filter_over */ { - yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-5].minor.yy14, &yymsp[-8].minor.yy0, yymsp[-6].minor.yy144); - sqlite3WindowAttach(pParse, yylhsminor.yy454, yymsp[0].minor.yy211); - sqlite3ExprAddFunctionOrderBy(pParse, yylhsminor.yy454, yymsp[-2].minor.yy14); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, yymsp[-5].minor.yy402, &yymsp[-8].minor.yy0, yymsp[-6].minor.yy502); + sqlite3WindowAttach(pParse, yylhsminor.yy590, yymsp[0].minor.yy483); + sqlite3ExprAddFunctionOrderBy(pParse, yylhsminor.yy590, yymsp[-2].minor.yy402); } - yymsp[-8].minor.yy454 = yylhsminor.yy454; + yymsp[-8].minor.yy590 = yylhsminor.yy590; break; case 194: /* expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over */ { - yylhsminor.yy454 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0); - sqlite3WindowAttach(pParse, yylhsminor.yy454, yymsp[0].minor.yy211); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0); + sqlite3WindowAttach(pParse, yylhsminor.yy590, yymsp[0].minor.yy483); } - yymsp[-4].minor.yy454 = yylhsminor.yy454; + yymsp[-4].minor.yy590 = yylhsminor.yy590; break; case 195: /* term ::= CTIME_KW */ { - yylhsminor.yy454 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0); } - yymsp[0].minor.yy454 = yylhsminor.yy454; + yymsp[0].minor.yy590 = yylhsminor.yy590; break; case 196: /* expr ::= LP nexprlist COMMA expr RP */ { - ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy14, yymsp[-1].minor.yy454); - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); - if( yymsp[-4].minor.yy454 ){ - yymsp[-4].minor.yy454->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy402, yymsp[-1].minor.yy590); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( yymsp[-4].minor.yy590 ){ + yymsp[-4].minor.yy590->x.pList = pList; if( ALWAYS(pList->nExpr) ){ - yymsp[-4].minor.yy454->flags |= pList->a[0].pExpr->flags & EP_Propagate; + yymsp[-4].minor.yy590->flags |= pList->a[0].pExpr->flags & EP_Propagate; } }else{ sqlite3ExprListDelete(pParse->db, pList); @@ -183376,7 +182082,7 @@ static YYACTIONTYPE yy_reduce( } break; case 197: /* expr ::= expr AND expr */ -{yymsp[-2].minor.yy454=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy454,yymsp[0].minor.yy454);} +{yymsp[-2].minor.yy590=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy590,yymsp[0].minor.yy590);} break; case 198: /* expr ::= expr OR expr */ case 199: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==199); @@ -183385,7 +182091,7 @@ static YYACTIONTYPE yy_reduce( case 202: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==202); case 203: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==203); case 204: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==204); -{yymsp[-2].minor.yy454=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy454,yymsp[0].minor.yy454);} +{yymsp[-2].minor.yy590=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy590,yymsp[0].minor.yy590);} break; case 205: /* likeop ::= NOT LIKE_KW|MATCH */ {yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/} @@ -183395,11 +182101,11 @@ static YYACTIONTYPE yy_reduce( ExprList *pList; int bNot = yymsp[-1].minor.yy0.n & 0x80000000; yymsp[-1].minor.yy0.n &= 0x7fffffff; - pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy454); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy454); - yymsp[-2].minor.yy454 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); - if( bNot ) yymsp[-2].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy454, 0); - if( yymsp[-2].minor.yy454 ) yymsp[-2].minor.yy454->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy590); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy590); + yymsp[-2].minor.yy590 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); + if( bNot ) yymsp[-2].minor.yy590 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy590, 0); + if( yymsp[-2].minor.yy590 ) yymsp[-2].minor.yy590->flags |= EP_InfixFunc; } break; case 207: /* expr ::= expr likeop expr ESCAPE expr */ @@ -183407,87 +182113,91 @@ static YYACTIONTYPE yy_reduce( ExprList *pList; int bNot = yymsp[-3].minor.yy0.n & 0x80000000; yymsp[-3].minor.yy0.n &= 0x7fffffff; - pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy454); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy454); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy454); - yymsp[-4].minor.yy454 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0); - if( bNot ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); - if( yymsp[-4].minor.yy454 ) yymsp[-4].minor.yy454->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy590); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy590); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy590); + yymsp[-4].minor.yy590 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0); + if( bNot ) yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy590, 0); + if( yymsp[-4].minor.yy590 ) yymsp[-4].minor.yy590->flags |= EP_InfixFunc; } break; case 208: /* expr ::= expr ISNULL|NOTNULL */ -{yymsp[-1].minor.yy454 = sqlite3PExprIsNull(pParse,yymsp[0].major,yymsp[-1].minor.yy454);} +{yymsp[-1].minor.yy590 = sqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy590,0);} break; case 209: /* expr ::= expr NOT NULL */ -{yymsp[-2].minor.yy454 = sqlite3PExprIsNull(pParse,TK_NOTNULL,yymsp[-2].minor.yy454);} +{yymsp[-2].minor.yy590 = sqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy590,0);} break; case 210: /* expr ::= expr IS expr */ { - yymsp[-2].minor.yy454 = sqlite3PExprIs(pParse, TK_IS, yymsp[-2].minor.yy454, yymsp[0].minor.yy454); + yymsp[-2].minor.yy590 = sqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy590,yymsp[0].minor.yy590); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy590, yymsp[-2].minor.yy590, TK_ISNULL); } break; case 211: /* expr ::= expr IS NOT expr */ { - yymsp[-3].minor.yy454 = sqlite3PExprIs(pParse, TK_ISNOT, yymsp[-3].minor.yy454, yymsp[0].minor.yy454); + yymsp[-3].minor.yy590 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy590,yymsp[0].minor.yy590); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy590, yymsp[-3].minor.yy590, TK_NOTNULL); } break; case 212: /* expr ::= expr IS NOT DISTINCT FROM expr */ { - yymsp[-5].minor.yy454 = sqlite3PExprIs(pParse, TK_IS, yymsp[-5].minor.yy454, yymsp[0].minor.yy454); + yymsp[-5].minor.yy590 = sqlite3PExpr(pParse,TK_IS,yymsp[-5].minor.yy590,yymsp[0].minor.yy590); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy590, yymsp[-5].minor.yy590, TK_ISNULL); } break; case 213: /* expr ::= expr IS DISTINCT FROM expr */ { - yymsp[-4].minor.yy454 = sqlite3PExprIs(pParse, TK_ISNOT, yymsp[-4].minor.yy454, yymsp[0].minor.yy454); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-4].minor.yy590,yymsp[0].minor.yy590); + binaryToUnaryIfNull(pParse, yymsp[0].minor.yy590, yymsp[-4].minor.yy590, TK_NOTNULL); } break; case 214: /* expr ::= NOT expr */ case 215: /* expr ::= BITNOT expr */ yytestcase(yyruleno==215); -{yymsp[-1].minor.yy454 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy454, 0);/*A-overwrites-B*/} +{yymsp[-1].minor.yy590 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy590, 0);/*A-overwrites-B*/} break; case 216: /* expr ::= PLUS|MINUS expr */ { - Expr *p = yymsp[0].minor.yy454; + Expr *p = yymsp[0].minor.yy590; u8 op = yymsp[-1].major + (TK_UPLUS-TK_PLUS); assert( TK_UPLUS>TK_PLUS ); assert( TK_UMINUS == TK_MINUS + (TK_UPLUS - TK_PLUS) ); if( p && p->op==TK_UPLUS ){ p->op = op; - yymsp[-1].minor.yy454 = p; + yymsp[-1].minor.yy590 = p; }else{ - yymsp[-1].minor.yy454 = sqlite3PExpr(pParse, op, p, 0); + yymsp[-1].minor.yy590 = sqlite3PExpr(pParse, op, p, 0); /*A-overwrites-B*/ } } break; case 217: /* expr ::= expr PTR expr */ { - ExprList *pList = sqlite3ExprListAppend(pParse, 0, yymsp[-2].minor.yy454); - pList = sqlite3ExprListAppend(pParse, pList, yymsp[0].minor.yy454); - yylhsminor.yy454 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); + ExprList *pList = sqlite3ExprListAppend(pParse, 0, yymsp[-2].minor.yy590); + pList = sqlite3ExprListAppend(pParse, pList, yymsp[0].minor.yy590); + yylhsminor.yy590 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); } - yymsp[-2].minor.yy454 = yylhsminor.yy454; + yymsp[-2].minor.yy590 = yylhsminor.yy590; break; case 218: /* between_op ::= BETWEEN */ case 221: /* in_op ::= IN */ yytestcase(yyruleno==221); -{yymsp[0].minor.yy144 = 0;} +{yymsp[0].minor.yy502 = 0;} break; case 220: /* expr ::= expr between_op expr AND expr */ { - ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy454); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy454); - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy454, 0); - if( yymsp[-4].minor.yy454 ){ - yymsp[-4].minor.yy454->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy590); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy590); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy590, 0); + if( yymsp[-4].minor.yy590 ){ + yymsp[-4].minor.yy590->x.pList = pList; }else{ sqlite3ExprListDelete(pParse->db, pList); } - if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); + if( yymsp[-3].minor.yy502 ) yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy590, 0); } break; case 223: /* expr ::= expr in_op LP exprlist RP */ { - if( yymsp[-1].minor.yy14==0 ){ + if( yymsp[-1].minor.yy402==0 ){ /* Expressions of the form ** ** expr1 IN () @@ -183500,145 +182210,145 @@ static YYACTIONTYPE yy_reduce( ** it is or not) and if it is an aggregate, that could change the meaning ** of the whole query. */ - Expr *pB = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy144 ? "true" : "false"); + Expr *pB = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy502 ? "true" : "false"); if( pB ) sqlite3ExprIdToTrueFalse(pB); - if( !ExprHasProperty(yymsp[-4].minor.yy454, EP_HasFunc) ){ - sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy454); - yymsp[-4].minor.yy454 = pB; + if( !ExprHasProperty(yymsp[-4].minor.yy590, EP_HasFunc) ){ + sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy590); + yymsp[-4].minor.yy590 = pB; }else{ - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, yymsp[-3].minor.yy144 ? TK_OR : TK_AND, pB, yymsp[-4].minor.yy454); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, yymsp[-3].minor.yy502 ? TK_OR : TK_AND, pB, yymsp[-4].minor.yy590); } }else{ - Expr *pRHS = yymsp[-1].minor.yy14->a[0].pExpr; - if( yymsp[-1].minor.yy14->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && yymsp[-4].minor.yy454->op!=TK_VECTOR ){ - yymsp[-1].minor.yy14->a[0].pExpr = 0; - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + Expr *pRHS = yymsp[-1].minor.yy402->a[0].pExpr; + if( yymsp[-1].minor.yy402->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && yymsp[-4].minor.yy590->op!=TK_VECTOR ){ + yymsp[-1].minor.yy402->a[0].pExpr = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy402); pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy454, pRHS); - }else if( yymsp[-1].minor.yy14->nExpr==1 && pRHS->op==TK_SELECT ){ - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, pRHS->x.pSelect); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy590, pRHS); + }else if( yymsp[-1].minor.yy402->nExpr==1 && pRHS->op==TK_SELECT ){ + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy590, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy590, pRHS->x.pSelect); pRHS->x.pSelect = 0; - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy402); }else{ - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); - if( yymsp[-4].minor.yy454==0 ){ - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); - }else if( yymsp[-4].minor.yy454->pLeft->op==TK_VECTOR ){ - int nExpr = yymsp[-4].minor.yy454->pLeft->x.pList->nExpr; - Select *pSelectRHS = sqlite3ExprListToValues(pParse, nExpr, yymsp[-1].minor.yy14); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy590, 0); + if( yymsp[-4].minor.yy590==0 ){ + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy402); + }else if( yymsp[-4].minor.yy590->pLeft->op==TK_VECTOR ){ + int nExpr = yymsp[-4].minor.yy590->pLeft->x.pList->nExpr; + Select *pSelectRHS = sqlite3ExprListToValues(pParse, nExpr, yymsp[-1].minor.yy402); if( pSelectRHS ){ parserDoubleLinkSelect(pParse, pSelectRHS); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, pSelectRHS); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy590, pSelectRHS); } }else{ - yymsp[-4].minor.yy454->x.pList = yymsp[-1].minor.yy14; - sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy454); + yymsp[-4].minor.yy590->x.pList = yymsp[-1].minor.yy402; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy590); } } - if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); + if( yymsp[-3].minor.yy502 ) yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy590, 0); } } break; case 224: /* expr ::= LP select RP */ { - yymsp[-2].minor.yy454 = sqlite3PExpr(pParse, TK_SELECT, 0, 0); - sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy454, yymsp[-1].minor.yy555); + yymsp[-2].minor.yy590 = sqlite3PExpr(pParse, TK_SELECT, 0, 0); + sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy590, yymsp[-1].minor.yy637); } break; case 225: /* expr ::= expr in_op LP select RP */ { - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, yymsp[-1].minor.yy555); - if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy590, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy590, yymsp[-1].minor.yy637); + if( yymsp[-3].minor.yy502 ) yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy590, 0); } break; case 226: /* expr ::= expr in_op nm dbnm paren_exprlist */ { SrcList *pSrc = sqlite3SrcListAppend(pParse, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0); - if( yymsp[0].minor.yy14 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy14); - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, pSelect); - if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); + if( yymsp[0].minor.yy402 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy402); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy590, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy590, pSelect); + if( yymsp[-3].minor.yy502 ) yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy590, 0); } break; case 227: /* expr ::= EXISTS LP select RP */ { Expr *p; - p = yymsp[-3].minor.yy454 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); - sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy555); + p = yymsp[-3].minor.yy590 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); + sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy637); } break; case 228: /* expr ::= CASE case_operand case_exprlist case_else END */ { - yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy454, 0); - if( yymsp[-4].minor.yy454 ){ - yymsp[-4].minor.yy454->x.pList = yymsp[-1].minor.yy454 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[-1].minor.yy454) : yymsp[-2].minor.yy14; - sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy454); + yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy590, 0); + if( yymsp[-4].minor.yy590 ){ + yymsp[-4].minor.yy590->x.pList = yymsp[-1].minor.yy590 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy402,yymsp[-1].minor.yy590) : yymsp[-2].minor.yy402; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy590); }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy14); - sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy454); + sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy402); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy590); } } break; case 229: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ { - yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, yymsp[-2].minor.yy454); - yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, yymsp[0].minor.yy454); + yymsp[-4].minor.yy402 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy402, yymsp[-2].minor.yy590); + yymsp[-4].minor.yy402 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy402, yymsp[0].minor.yy590); } break; case 230: /* case_exprlist ::= WHEN expr THEN expr */ { - yymsp[-3].minor.yy14 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy454); - yymsp[-3].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy14, yymsp[0].minor.yy454); + yymsp[-3].minor.yy402 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy590); + yymsp[-3].minor.yy402 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy402, yymsp[0].minor.yy590); } break; case 235: /* nexprlist ::= nexprlist COMMA expr */ -{yymsp[-2].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[0].minor.yy454);} +{yymsp[-2].minor.yy402 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy402,yymsp[0].minor.yy590);} break; case 236: /* nexprlist ::= expr */ -{yymsp[0].minor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy454); /*A-overwrites-Y*/} +{yymsp[0].minor.yy402 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy590); /*A-overwrites-Y*/} break; case 238: /* paren_exprlist ::= LP exprlist RP */ case 243: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==243); -{yymsp[-2].minor.yy14 = yymsp[-1].minor.yy14;} +{yymsp[-2].minor.yy402 = yymsp[-1].minor.yy402;} break; case 239: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ { sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, - sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy14, yymsp[-10].minor.yy144, - &yymsp[-11].minor.yy0, yymsp[0].minor.yy454, SQLITE_SO_ASC, yymsp[-8].minor.yy144, SQLITE_IDXTYPE_APPDEF); + sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy402, yymsp[-10].minor.yy502, + &yymsp[-11].minor.yy0, yymsp[0].minor.yy590, SQLITE_SO_ASC, yymsp[-8].minor.yy502, SQLITE_IDXTYPE_APPDEF); if( IN_RENAME_OBJECT && pParse->pNewIndex ){ sqlite3RenameTokenMap(pParse, pParse->pNewIndex->zName, &yymsp[-4].minor.yy0); } } break; case 240: /* uniqueflag ::= UNIQUE */ - case 281: /* raisetype ::= ABORT */ yytestcase(yyruleno==281); -{yymsp[0].minor.yy144 = OE_Abort;} + case 282: /* raisetype ::= ABORT */ yytestcase(yyruleno==282); +{yymsp[0].minor.yy502 = OE_Abort;} break; case 241: /* uniqueflag ::= */ -{yymsp[1].minor.yy144 = OE_None;} +{yymsp[1].minor.yy502 = OE_None;} break; case 244: /* eidlist ::= eidlist COMMA nm collate sortorder */ { - yymsp[-4].minor.yy14 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy14, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy144, yymsp[0].minor.yy144); + yymsp[-4].minor.yy402 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy402, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy502, yymsp[0].minor.yy502); } break; case 245: /* eidlist ::= nm collate sortorder */ { - yymsp[-2].minor.yy14 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy144, yymsp[0].minor.yy144); /*A-overwrites-Y*/ + yymsp[-2].minor.yy402 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy502, yymsp[0].minor.yy502); /*A-overwrites-Y*/ } break; case 248: /* cmd ::= DROP INDEX ifexists fullname */ -{sqlite3DropIndex(pParse, yymsp[0].minor.yy203, yymsp[-1].minor.yy144);} +{sqlite3DropIndex(pParse, yymsp[0].minor.yy563, yymsp[-1].minor.yy502);} break; case 249: /* cmd ::= VACUUM vinto */ -{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy454);} +{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy590);} break; case 250: /* cmd ::= VACUUM nm vinto */ -{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy454);} +{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy590);} break; case 253: /* cmd ::= PRAGMA nm dbnm */ {sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} @@ -183660,12 +182370,12 @@ static YYACTIONTYPE yy_reduce( Token all; all.z = yymsp[-3].minor.yy0.z; all.n = (int)(yymsp[0].minor.yy0.z - yymsp[-3].minor.yy0.z) + yymsp[0].minor.yy0.n; - sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy427, &all); + sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy319, &all); } break; case 261: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ { - sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy144, yymsp[-4].minor.yy286.a, yymsp[-4].minor.yy286.b, yymsp[-2].minor.yy203, yymsp[0].minor.yy454, yymsp[-10].minor.yy144, yymsp[-8].minor.yy144); + sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy502, yymsp[-4].minor.yy28.a, yymsp[-4].minor.yy28.b, yymsp[-2].minor.yy563, yymsp[0].minor.yy590, yymsp[-10].minor.yy502, yymsp[-8].minor.yy502); yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/ #ifdef SQLITE_DEBUG assert( pParse->isCreate ); /* Set by createkw reduce action */ @@ -183674,439 +182384,421 @@ static YYACTIONTYPE yy_reduce( } break; case 262: /* trigger_time ::= BEFORE|AFTER */ -{ yymsp[0].minor.yy144 = yymsp[0].major; /*A-overwrites-X*/ } +{ yymsp[0].minor.yy502 = yymsp[0].major; /*A-overwrites-X*/ } break; case 263: /* trigger_time ::= INSTEAD OF */ -{ yymsp[-1].minor.yy144 = TK_INSTEAD;} +{ yymsp[-1].minor.yy502 = TK_INSTEAD;} break; case 264: /* trigger_time ::= */ -{ yymsp[1].minor.yy144 = TK_BEFORE; } +{ yymsp[1].minor.yy502 = TK_BEFORE; } break; case 265: /* trigger_event ::= DELETE|INSERT */ case 266: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==266); -{yymsp[0].minor.yy286.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy286.b = 0;} +{yymsp[0].minor.yy28.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy28.b = 0;} break; case 267: /* trigger_event ::= UPDATE OF idlist */ -{yymsp[-2].minor.yy286.a = TK_UPDATE; yymsp[-2].minor.yy286.b = yymsp[0].minor.yy132;} +{yymsp[-2].minor.yy28.a = TK_UPDATE; yymsp[-2].minor.yy28.b = yymsp[0].minor.yy204;} break; case 268: /* when_clause ::= */ - case 286: /* key_opt ::= */ yytestcase(yyruleno==286); -{ yymsp[1].minor.yy454 = 0; } + case 287: /* key_opt ::= */ yytestcase(yyruleno==287); +{ yymsp[1].minor.yy590 = 0; } break; case 269: /* when_clause ::= WHEN expr */ - case 287: /* key_opt ::= KEY expr */ yytestcase(yyruleno==287); -{ yymsp[-1].minor.yy454 = yymsp[0].minor.yy454; } + case 288: /* key_opt ::= KEY expr */ yytestcase(yyruleno==288); +{ yymsp[-1].minor.yy590 = yymsp[0].minor.yy590; } break; case 270: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ { - yymsp[-2].minor.yy427->pLast->pNext = yymsp[-1].minor.yy427; - yymsp[-2].minor.yy427->pLast = yymsp[-1].minor.yy427; + assert( yymsp[-2].minor.yy319!=0 ); + yymsp[-2].minor.yy319->pLast->pNext = yymsp[-1].minor.yy319; + yymsp[-2].minor.yy319->pLast = yymsp[-1].minor.yy319; } break; case 271: /* trigger_cmd_list ::= trigger_cmd SEMI */ { - yymsp[-1].minor.yy427->pLast = yymsp[-1].minor.yy427; + assert( yymsp[-1].minor.yy319!=0 ); + yymsp[-1].minor.yy319->pLast = yymsp[-1].minor.yy319; } break; - case 272: /* tridxby ::= INDEXED BY nm */ + case 272: /* trnm ::= nm DOT nm */ +{ + yymsp[-2].minor.yy0 = yymsp[0].minor.yy0; + sqlite3ErrorMsg(pParse, + "qualified table names are not allowed on INSERT, UPDATE, and DELETE " + "statements within triggers"); +} + break; + case 273: /* tridxby ::= INDEXED BY nm */ { sqlite3ErrorMsg(pParse, "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 273: /* tridxby ::= NOT INDEXED */ + case 274: /* tridxby ::= NOT INDEXED */ { sqlite3ErrorMsg(pParse, "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 274: /* trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt */ -{yylhsminor.yy427 = sqlite3TriggerUpdateStep(pParse, yymsp[-6].minor.yy203, yymsp[-2].minor.yy203, yymsp[-3].minor.yy14, yymsp[-1].minor.yy454, yymsp[-7].minor.yy144, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy168);} - yymsp[-8].minor.yy427 = yylhsminor.yy427; + case 275: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist from where_opt scanpt */ +{yylhsminor.yy319 = sqlite3TriggerUpdateStep(pParse, &yymsp[-6].minor.yy0, yymsp[-2].minor.yy563, yymsp[-3].minor.yy402, yymsp[-1].minor.yy590, yymsp[-7].minor.yy502, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy342);} + yymsp[-8].minor.yy319 = yylhsminor.yy319; break; - case 275: /* trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt */ + case 276: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ { - yylhsminor.yy427 = sqlite3TriggerInsertStep(pParse,yymsp[-4].minor.yy203,yymsp[-3].minor.yy132,yymsp[-2].minor.yy555,yymsp[-6].minor.yy144,yymsp[-1].minor.yy122,yymsp[-7].minor.yy168,yymsp[0].minor.yy168);/*yylhsminor.yy427-overwrites-yymsp[-6].minor.yy144*/ + yylhsminor.yy319 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy204,yymsp[-2].minor.yy637,yymsp[-6].minor.yy502,yymsp[-1].minor.yy403,yymsp[-7].minor.yy342,yymsp[0].minor.yy342);/*yylhsminor.yy319-overwrites-yymsp[-6].minor.yy502*/ } - yymsp[-7].minor.yy427 = yylhsminor.yy427; + yymsp[-7].minor.yy319 = yylhsminor.yy319; break; - case 276: /* trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt */ -{yylhsminor.yy427 = sqlite3TriggerDeleteStep(pParse, yymsp[-3].minor.yy203, yymsp[-1].minor.yy454, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy168);} - yymsp[-5].minor.yy427 = yylhsminor.yy427; + case 277: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ +{yylhsminor.yy319 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy590, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy342);} + yymsp[-5].minor.yy319 = yylhsminor.yy319; break; - case 277: /* trigger_cmd ::= scanpt select scanpt */ -{yylhsminor.yy427 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy555, yymsp[-2].minor.yy168, yymsp[0].minor.yy168); /*yylhsminor.yy427-overwrites-yymsp[-1].minor.yy555*/} - yymsp[-2].minor.yy427 = yylhsminor.yy427; + case 278: /* trigger_cmd ::= scanpt select scanpt */ +{yylhsminor.yy319 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy637, yymsp[-2].minor.yy342, yymsp[0].minor.yy342); /*yylhsminor.yy319-overwrites-yymsp[-1].minor.yy637*/} + yymsp[-2].minor.yy319 = yylhsminor.yy319; break; - case 278: /* expr ::= RAISE LP IGNORE RP */ + case 279: /* expr ::= RAISE LP IGNORE RP */ { - yymsp[-3].minor.yy454 = sqlite3PExpr(pParse, TK_RAISE, 0, 0); - if( yymsp[-3].minor.yy454 ){ - yymsp[-3].minor.yy454->affExpr = OE_Ignore; + yymsp[-3].minor.yy590 = sqlite3PExpr(pParse, TK_RAISE, 0, 0); + if( yymsp[-3].minor.yy590 ){ + yymsp[-3].minor.yy590->affExpr = OE_Ignore; } } break; - case 279: /* expr ::= RAISE LP raisetype COMMA expr RP */ + case 280: /* expr ::= RAISE LP raisetype COMMA expr RP */ { - yymsp[-5].minor.yy454 = sqlite3PExpr(pParse, TK_RAISE, yymsp[-1].minor.yy454, 0); - if( yymsp[-5].minor.yy454 ) { - yymsp[-5].minor.yy454->affExpr = (char)yymsp[-3].minor.yy144; + yymsp[-5].minor.yy590 = sqlite3PExpr(pParse, TK_RAISE, yymsp[-1].minor.yy590, 0); + if( yymsp[-5].minor.yy590 ) { + yymsp[-5].minor.yy590->affExpr = (char)yymsp[-3].minor.yy502; } } break; - case 280: /* raisetype ::= ROLLBACK */ -{yymsp[0].minor.yy144 = OE_Rollback;} + case 281: /* raisetype ::= ROLLBACK */ +{yymsp[0].minor.yy502 = OE_Rollback;} break; - case 282: /* raisetype ::= FAIL */ -{yymsp[0].minor.yy144 = OE_Fail;} + case 283: /* raisetype ::= FAIL */ +{yymsp[0].minor.yy502 = OE_Fail;} break; - case 283: /* cmd ::= DROP TRIGGER ifexists fullname */ + case 284: /* cmd ::= DROP TRIGGER ifexists fullname */ { - sqlite3DropTrigger(pParse,yymsp[0].minor.yy203,yymsp[-1].minor.yy144); + sqlite3DropTrigger(pParse,yymsp[0].minor.yy563,yymsp[-1].minor.yy502); } break; - case 284: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + case 285: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ { - sqlite3Attach(pParse, yymsp[-3].minor.yy454, yymsp[-1].minor.yy454, yymsp[0].minor.yy454); + sqlite3Attach(pParse, yymsp[-3].minor.yy590, yymsp[-1].minor.yy590, yymsp[0].minor.yy590); } break; - case 285: /* cmd ::= DETACH database_kw_opt expr */ + case 286: /* cmd ::= DETACH database_kw_opt expr */ { - sqlite3Detach(pParse, yymsp[0].minor.yy454); + sqlite3Detach(pParse, yymsp[0].minor.yy590); } break; - case 288: /* cmd ::= REINDEX */ + case 289: /* cmd ::= REINDEX */ {sqlite3Reindex(pParse, 0, 0);} break; - case 289: /* cmd ::= REINDEX nm dbnm */ + case 290: /* cmd ::= REINDEX nm dbnm */ {sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 290: /* cmd ::= ANALYZE */ + case 291: /* cmd ::= ANALYZE */ {sqlite3Analyze(pParse, 0, 0);} break; - case 291: /* cmd ::= ANALYZE nm dbnm */ + case 292: /* cmd ::= ANALYZE nm dbnm */ {sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 292: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ + case 293: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ { - sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy203,&yymsp[0].minor.yy0); + sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy563,&yymsp[0].minor.yy0); } break; - case 293: /* cmd ::= alter_add carglist */ + case 294: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ { yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0); -} - break; - case 294: /* alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken */ -{ - disableLookaside(pParse); - sqlite3AlterBeginAddColumn(pParse, yymsp[-4].minor.yy203); - sqlite3AddColumn(pParse, yymsp[-1].minor.yy0, yymsp[0].minor.yy0); - yymsp[-6].minor.yy0 = yymsp[-1].minor.yy0; } break; case 295: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ { - sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy203, &yymsp[0].minor.yy0); -} - break; - case 296: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ -{ - sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy203, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0); + sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy563, &yymsp[0].minor.yy0); } break; - case 297: /* cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm */ + case 296: /* add_column_fullname ::= fullname */ { - sqlite3AlterDropConstraint(pParse, yymsp[-3].minor.yy203, &yymsp[0].minor.yy0, 0); -} - break; - case 298: /* cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL */ -{ - sqlite3AlterDropConstraint(pParse, yymsp[-6].minor.yy203, 0, &yymsp[-3].minor.yy0); -} - break; - case 299: /* cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf */ -{ - sqlite3AlterSetNotNull(pParse, yymsp[-7].minor.yy203, &yymsp[-4].minor.yy0, &yymsp[-2].minor.yy0); -} - break; - case 300: /* cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf */ -{ - sqlite3AlterAddConstraint(pParse, yymsp[-8].minor.yy203, &yymsp[-6].minor.yy0, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1)); + disableLookaside(pParse); + sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy563); } - yy_destructor(yypParser,219,&yymsp[-2].minor); break; - case 301: /* cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf */ + case 297: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ { - sqlite3AlterAddConstraint(pParse, yymsp[-6].minor.yy203, &yymsp[-4].minor.yy0, 0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1)); + sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy563, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0); } - yy_destructor(yypParser,219,&yymsp[-2].minor); break; - case 302: /* cmd ::= create_vtab */ + case 298: /* cmd ::= create_vtab */ {sqlite3VtabFinishParse(pParse,0);} break; - case 303: /* cmd ::= create_vtab LP vtabarglist RP */ + case 299: /* cmd ::= create_vtab LP vtabarglist RP */ {sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} break; - case 304: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + case 300: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ { - sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy144); + sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy502); } break; - case 305: /* vtabarg ::= */ + case 301: /* vtabarg ::= */ {sqlite3VtabArgInit(pParse);} break; - case 306: /* vtabargtoken ::= ANY */ - case 307: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==307); - case 308: /* lp ::= LP */ yytestcase(yyruleno==308); + case 302: /* vtabargtoken ::= ANY */ + case 303: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==303); + case 304: /* lp ::= LP */ yytestcase(yyruleno==304); {sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} break; - case 309: /* with ::= WITH wqlist */ - case 310: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==310); -{ sqlite3WithPush(pParse, yymsp[0].minor.yy59, 1); } + case 305: /* with ::= WITH wqlist */ + case 306: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==306); +{ sqlite3WithPush(pParse, yymsp[0].minor.yy125, 1); } break; - case 311: /* wqas ::= AS */ -{yymsp[0].minor.yy462 = M10d_Any;} + case 307: /* wqas ::= AS */ +{yymsp[0].minor.yy444 = M10d_Any;} break; - case 312: /* wqas ::= AS MATERIALIZED */ -{yymsp[-1].minor.yy462 = M10d_Yes;} + case 308: /* wqas ::= AS MATERIALIZED */ +{yymsp[-1].minor.yy444 = M10d_Yes;} break; - case 313: /* wqas ::= AS NOT MATERIALIZED */ -{yymsp[-2].minor.yy462 = M10d_No;} + case 309: /* wqas ::= AS NOT MATERIALIZED */ +{yymsp[-2].minor.yy444 = M10d_No;} break; - case 314: /* wqitem ::= withnm eidlist_opt wqas LP select RP */ + case 310: /* wqitem ::= withnm eidlist_opt wqas LP select RP */ { - yymsp[-5].minor.yy67 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy14, yymsp[-1].minor.yy555, yymsp[-3].minor.yy462); /*A-overwrites-X*/ + yymsp[-5].minor.yy361 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy402, yymsp[-1].minor.yy637, yymsp[-3].minor.yy444); /*A-overwrites-X*/ } break; - case 315: /* withnm ::= nm */ + case 311: /* withnm ::= nm */ {pParse->bHasWith = 1;} break; - case 316: /* wqlist ::= wqitem */ + case 312: /* wqlist ::= wqitem */ { - yymsp[0].minor.yy59 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy67); /*A-overwrites-X*/ + yymsp[0].minor.yy125 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy361); /*A-overwrites-X*/ } break; - case 317: /* wqlist ::= wqlist COMMA wqitem */ + case 313: /* wqlist ::= wqlist COMMA wqitem */ { - yymsp[-2].minor.yy59 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy59, yymsp[0].minor.yy67); + yymsp[-2].minor.yy125 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy125, yymsp[0].minor.yy361); } break; - case 318: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ + case 314: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ { - assert( yymsp[0].minor.yy211!=0 ); - sqlite3WindowChain(pParse, yymsp[0].minor.yy211, yymsp[-2].minor.yy211); - yymsp[0].minor.yy211->pNextWin = yymsp[-2].minor.yy211; - yylhsminor.yy211 = yymsp[0].minor.yy211; + assert( yymsp[0].minor.yy483!=0 ); + sqlite3WindowChain(pParse, yymsp[0].minor.yy483, yymsp[-2].minor.yy483); + yymsp[0].minor.yy483->pNextWin = yymsp[-2].minor.yy483; + yylhsminor.yy483 = yymsp[0].minor.yy483; } - yymsp[-2].minor.yy211 = yylhsminor.yy211; + yymsp[-2].minor.yy483 = yylhsminor.yy483; break; - case 319: /* windowdefn ::= nm AS LP window RP */ + case 315: /* windowdefn ::= nm AS LP window RP */ { - if( ALWAYS(yymsp[-1].minor.yy211) ){ - yymsp[-1].minor.yy211->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); + if( ALWAYS(yymsp[-1].minor.yy483) ){ + yymsp[-1].minor.yy483->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); } - yylhsminor.yy211 = yymsp[-1].minor.yy211; + yylhsminor.yy483 = yymsp[-1].minor.yy483; } - yymsp[-4].minor.yy211 = yylhsminor.yy211; + yymsp[-4].minor.yy483 = yylhsminor.yy483; break; - case 320: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + case 316: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ { - yymsp[-4].minor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, yymsp[-2].minor.yy14, yymsp[-1].minor.yy14, 0); + yymsp[-4].minor.yy483 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy483, yymsp[-2].minor.yy402, yymsp[-1].minor.yy402, 0); } break; - case 321: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + case 317: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ { - yylhsminor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, yymsp[-2].minor.yy14, yymsp[-1].minor.yy14, &yymsp[-5].minor.yy0); + yylhsminor.yy483 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy483, yymsp[-2].minor.yy402, yymsp[-1].minor.yy402, &yymsp[-5].minor.yy0); } - yymsp[-5].minor.yy211 = yylhsminor.yy211; + yymsp[-5].minor.yy483 = yylhsminor.yy483; break; - case 322: /* window ::= ORDER BY sortlist frame_opt */ + case 318: /* window ::= ORDER BY sortlist frame_opt */ { - yymsp[-3].minor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, 0, yymsp[-1].minor.yy14, 0); + yymsp[-3].minor.yy483 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy483, 0, yymsp[-1].minor.yy402, 0); } break; - case 323: /* window ::= nm ORDER BY sortlist frame_opt */ + case 319: /* window ::= nm ORDER BY sortlist frame_opt */ { - yylhsminor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, 0, yymsp[-1].minor.yy14, &yymsp[-4].minor.yy0); + yylhsminor.yy483 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy483, 0, yymsp[-1].minor.yy402, &yymsp[-4].minor.yy0); } - yymsp[-4].minor.yy211 = yylhsminor.yy211; + yymsp[-4].minor.yy483 = yylhsminor.yy483; break; - case 324: /* window ::= nm frame_opt */ + case 320: /* window ::= nm frame_opt */ { - yylhsminor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, 0, 0, &yymsp[-1].minor.yy0); + yylhsminor.yy483 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy483, 0, 0, &yymsp[-1].minor.yy0); } - yymsp[-1].minor.yy211 = yylhsminor.yy211; + yymsp[-1].minor.yy483 = yylhsminor.yy483; break; - case 325: /* frame_opt ::= */ + case 321: /* frame_opt ::= */ { - yymsp[1].minor.yy211 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); + yymsp[1].minor.yy483 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); } break; - case 326: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + case 322: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ { - yylhsminor.yy211 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy144, yymsp[-1].minor.yy509.eType, yymsp[-1].minor.yy509.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy462); + yylhsminor.yy483 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy502, yymsp[-1].minor.yy205.eType, yymsp[-1].minor.yy205.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy444); } - yymsp[-2].minor.yy211 = yylhsminor.yy211; + yymsp[-2].minor.yy483 = yylhsminor.yy483; break; - case 327: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + case 323: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ { - yylhsminor.yy211 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy144, yymsp[-3].minor.yy509.eType, yymsp[-3].minor.yy509.pExpr, yymsp[-1].minor.yy509.eType, yymsp[-1].minor.yy509.pExpr, yymsp[0].minor.yy462); + yylhsminor.yy483 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy502, yymsp[-3].minor.yy205.eType, yymsp[-3].minor.yy205.pExpr, yymsp[-1].minor.yy205.eType, yymsp[-1].minor.yy205.pExpr, yymsp[0].minor.yy444); } - yymsp[-5].minor.yy211 = yylhsminor.yy211; + yymsp[-5].minor.yy483 = yylhsminor.yy483; break; - case 329: /* frame_bound_s ::= frame_bound */ - case 331: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==331); -{yylhsminor.yy509 = yymsp[0].minor.yy509;} - yymsp[0].minor.yy509 = yylhsminor.yy509; + case 325: /* frame_bound_s ::= frame_bound */ + case 327: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==327); +{yylhsminor.yy205 = yymsp[0].minor.yy205;} + yymsp[0].minor.yy205 = yylhsminor.yy205; break; - case 330: /* frame_bound_s ::= UNBOUNDED PRECEDING */ - case 332: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==332); - case 334: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==334); -{yylhsminor.yy509.eType = yymsp[-1].major; yylhsminor.yy509.pExpr = 0;} - yymsp[-1].minor.yy509 = yylhsminor.yy509; + case 326: /* frame_bound_s ::= UNBOUNDED PRECEDING */ + case 328: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==328); + case 330: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==330); +{yylhsminor.yy205.eType = yymsp[-1].major; yylhsminor.yy205.pExpr = 0;} + yymsp[-1].minor.yy205 = yylhsminor.yy205; break; - case 333: /* frame_bound ::= expr PRECEDING|FOLLOWING */ -{yylhsminor.yy509.eType = yymsp[0].major; yylhsminor.yy509.pExpr = yymsp[-1].minor.yy454;} - yymsp[-1].minor.yy509 = yylhsminor.yy509; + case 329: /* frame_bound ::= expr PRECEDING|FOLLOWING */ +{yylhsminor.yy205.eType = yymsp[0].major; yylhsminor.yy205.pExpr = yymsp[-1].minor.yy590;} + yymsp[-1].minor.yy205 = yylhsminor.yy205; break; - case 335: /* frame_exclude_opt ::= */ -{yymsp[1].minor.yy462 = 0;} + case 331: /* frame_exclude_opt ::= */ +{yymsp[1].minor.yy444 = 0;} break; - case 336: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ -{yymsp[-1].minor.yy462 = yymsp[0].minor.yy462;} + case 332: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ +{yymsp[-1].minor.yy444 = yymsp[0].minor.yy444;} break; - case 337: /* frame_exclude ::= NO OTHERS */ - case 338: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==338); -{yymsp[-1].minor.yy462 = yymsp[-1].major; /*A-overwrites-X*/} + case 333: /* frame_exclude ::= NO OTHERS */ + case 334: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==334); +{yymsp[-1].minor.yy444 = yymsp[-1].major; /*A-overwrites-X*/} break; - case 339: /* frame_exclude ::= GROUP|TIES */ -{yymsp[0].minor.yy462 = yymsp[0].major; /*A-overwrites-X*/} + case 335: /* frame_exclude ::= GROUP|TIES */ +{yymsp[0].minor.yy444 = yymsp[0].major; /*A-overwrites-X*/} break; - case 340: /* window_clause ::= WINDOW windowdefn_list */ -{ yymsp[-1].minor.yy211 = yymsp[0].minor.yy211; } + case 336: /* window_clause ::= WINDOW windowdefn_list */ +{ yymsp[-1].minor.yy483 = yymsp[0].minor.yy483; } break; - case 341: /* filter_over ::= filter_clause over_clause */ + case 337: /* filter_over ::= filter_clause over_clause */ { - if( yymsp[0].minor.yy211 ){ - yymsp[0].minor.yy211->pFilter = yymsp[-1].minor.yy454; + if( yymsp[0].minor.yy483 ){ + yymsp[0].minor.yy483->pFilter = yymsp[-1].minor.yy590; }else{ - sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy454); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy590); } - yylhsminor.yy211 = yymsp[0].minor.yy211; + yylhsminor.yy483 = yymsp[0].minor.yy483; } - yymsp[-1].minor.yy211 = yylhsminor.yy211; + yymsp[-1].minor.yy483 = yylhsminor.yy483; break; - case 342: /* filter_over ::= over_clause */ + case 338: /* filter_over ::= over_clause */ { - yylhsminor.yy211 = yymsp[0].minor.yy211; + yylhsminor.yy483 = yymsp[0].minor.yy483; } - yymsp[0].minor.yy211 = yylhsminor.yy211; + yymsp[0].minor.yy483 = yylhsminor.yy483; break; - case 343: /* filter_over ::= filter_clause */ + case 339: /* filter_over ::= filter_clause */ { - yylhsminor.yy211 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); - if( yylhsminor.yy211 ){ - yylhsminor.yy211->eFrmType = TK_FILTER; - yylhsminor.yy211->pFilter = yymsp[0].minor.yy454; + yylhsminor.yy483 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yylhsminor.yy483 ){ + yylhsminor.yy483->eFrmType = TK_FILTER; + yylhsminor.yy483->pFilter = yymsp[0].minor.yy590; }else{ - sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy454); + sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy590); } } - yymsp[0].minor.yy211 = yylhsminor.yy211; + yymsp[0].minor.yy483 = yylhsminor.yy483; break; - case 344: /* over_clause ::= OVER LP window RP */ + case 340: /* over_clause ::= OVER LP window RP */ { - yymsp[-3].minor.yy211 = yymsp[-1].minor.yy211; - assert( yymsp[-3].minor.yy211!=0 ); + yymsp[-3].minor.yy483 = yymsp[-1].minor.yy483; + assert( yymsp[-3].minor.yy483!=0 ); } break; - case 345: /* over_clause ::= OVER nm */ + case 341: /* over_clause ::= OVER nm */ { - yymsp[-1].minor.yy211 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); - if( yymsp[-1].minor.yy211 ){ - yymsp[-1].minor.yy211->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n); + yymsp[-1].minor.yy483 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yymsp[-1].minor.yy483 ){ + yymsp[-1].minor.yy483->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n); } } break; - case 346: /* filter_clause ::= FILTER LP WHERE expr RP */ -{ yymsp[-4].minor.yy454 = yymsp[-1].minor.yy454; } + case 342: /* filter_clause ::= FILTER LP WHERE expr RP */ +{ yymsp[-4].minor.yy590 = yymsp[-1].minor.yy590; } break; - case 347: /* term ::= QNUMBER */ + case 343: /* term ::= QNUMBER */ { - yylhsminor.yy454=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); - sqlite3DequoteNumber(pParse, yylhsminor.yy454); + yylhsminor.yy590=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); + sqlite3DequoteNumber(pParse, yylhsminor.yy590); } - yymsp[0].minor.yy454 = yylhsminor.yy454; + yymsp[0].minor.yy590 = yylhsminor.yy590; break; default: - /* (348) input ::= cmdlist */ yytestcase(yyruleno==348); - /* (349) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==349); - /* (350) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=350); - /* (351) ecmd ::= SEMI */ yytestcase(yyruleno==351); - /* (352) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==352); - /* (353) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=353); - /* (354) trans_opt ::= */ yytestcase(yyruleno==354); - /* (355) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==355); - /* (356) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==356); - /* (357) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==357); - /* (358) savepoint_opt ::= */ yytestcase(yyruleno==358); - /* (359) cmd ::= create_table create_table_args */ yytestcase(yyruleno==359); - /* (360) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=360); - /* (361) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==361); - /* (362) columnlist ::= columnname carglist */ yytestcase(yyruleno==362); - /* (363) nm ::= ID|INDEXED|JOIN_KW */ yytestcase(yyruleno==363); - /* (364) nm ::= STRING */ yytestcase(yyruleno==364); - /* (365) typetoken ::= typename */ yytestcase(yyruleno==365); - /* (366) typename ::= ID|STRING */ yytestcase(yyruleno==366); - /* (367) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=367); - /* (368) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=368); - /* (369) carglist ::= carglist ccons */ yytestcase(yyruleno==369); - /* (370) carglist ::= */ yytestcase(yyruleno==370); - /* (371) ccons ::= NULL onconf */ yytestcase(yyruleno==371); - /* (372) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==372); - /* (373) ccons ::= AS generated */ yytestcase(yyruleno==373); - /* (374) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==374); - /* (375) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==375); - /* (376) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=376); - /* (377) tconscomma ::= */ yytestcase(yyruleno==377); - /* (378) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=378); - /* (379) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=379); - /* (380) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=380); - /* (381) oneselect ::= values */ yytestcase(yyruleno==381); - /* (382) sclp ::= selcollist COMMA */ yytestcase(yyruleno==382); - /* (383) as ::= ID|STRING */ yytestcase(yyruleno==383); - /* (384) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=384); - /* (385) returning ::= */ yytestcase(yyruleno==385); - /* (386) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=386); - /* (387) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==387); - /* (388) case_operand ::= expr */ yytestcase(yyruleno==388); - /* (389) exprlist ::= nexprlist */ yytestcase(yyruleno==389); - /* (390) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=390); - /* (391) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=391); - /* (392) nmnum ::= ON */ yytestcase(yyruleno==392); - /* (393) nmnum ::= DELETE */ yytestcase(yyruleno==393); - /* (394) nmnum ::= DEFAULT */ yytestcase(yyruleno==394); - /* (395) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==395); - /* (396) foreach_clause ::= */ yytestcase(yyruleno==396); - /* (397) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==397); - /* (398) tridxby ::= */ yytestcase(yyruleno==398); - /* (399) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==399); - /* (400) database_kw_opt ::= */ yytestcase(yyruleno==400); - /* (401) kwcolumn_opt ::= */ yytestcase(yyruleno==401); - /* (402) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==402); - /* (403) vtabarglist ::= vtabarg */ yytestcase(yyruleno==403); - /* (404) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==404); - /* (405) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==405); - /* (406) anylist ::= */ yytestcase(yyruleno==406); - /* (407) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==407); - /* (408) anylist ::= anylist ANY */ yytestcase(yyruleno==408); - /* (409) with ::= */ yytestcase(yyruleno==409); - /* (410) windowdefn_list ::= windowdefn (OPTIMIZED OUT) */ assert(yyruleno!=410); - /* (411) window ::= frame_opt (OPTIMIZED OUT) */ assert(yyruleno!=411); + /* (344) input ::= cmdlist */ yytestcase(yyruleno==344); + /* (345) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==345); + /* (346) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=346); + /* (347) ecmd ::= SEMI */ yytestcase(yyruleno==347); + /* (348) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==348); + /* (349) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=349); + /* (350) trans_opt ::= */ yytestcase(yyruleno==350); + /* (351) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==351); + /* (352) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==352); + /* (353) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==353); + /* (354) savepoint_opt ::= */ yytestcase(yyruleno==354); + /* (355) cmd ::= create_table create_table_args */ yytestcase(yyruleno==355); + /* (356) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=356); + /* (357) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==357); + /* (358) columnlist ::= columnname carglist */ yytestcase(yyruleno==358); + /* (359) nm ::= ID|INDEXED|JOIN_KW */ yytestcase(yyruleno==359); + /* (360) nm ::= STRING */ yytestcase(yyruleno==360); + /* (361) typetoken ::= typename */ yytestcase(yyruleno==361); + /* (362) typename ::= ID|STRING */ yytestcase(yyruleno==362); + /* (363) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=363); + /* (364) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=364); + /* (365) carglist ::= carglist ccons */ yytestcase(yyruleno==365); + /* (366) carglist ::= */ yytestcase(yyruleno==366); + /* (367) ccons ::= NULL onconf */ yytestcase(yyruleno==367); + /* (368) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==368); + /* (369) ccons ::= AS generated */ yytestcase(yyruleno==369); + /* (370) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==370); + /* (371) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==371); + /* (372) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=372); + /* (373) tconscomma ::= */ yytestcase(yyruleno==373); + /* (374) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=374); + /* (375) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=375); + /* (376) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=376); + /* (377) oneselect ::= values */ yytestcase(yyruleno==377); + /* (378) sclp ::= selcollist COMMA */ yytestcase(yyruleno==378); + /* (379) as ::= ID|STRING */ yytestcase(yyruleno==379); + /* (380) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=380); + /* (381) returning ::= */ yytestcase(yyruleno==381); + /* (382) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=382); + /* (383) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==383); + /* (384) case_operand ::= expr */ yytestcase(yyruleno==384); + /* (385) exprlist ::= nexprlist */ yytestcase(yyruleno==385); + /* (386) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=386); + /* (387) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=387); + /* (388) nmnum ::= ON */ yytestcase(yyruleno==388); + /* (389) nmnum ::= DELETE */ yytestcase(yyruleno==389); + /* (390) nmnum ::= DEFAULT */ yytestcase(yyruleno==390); + /* (391) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==391); + /* (392) foreach_clause ::= */ yytestcase(yyruleno==392); + /* (393) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==393); + /* (394) trnm ::= nm */ yytestcase(yyruleno==394); + /* (395) tridxby ::= */ yytestcase(yyruleno==395); + /* (396) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==396); + /* (397) database_kw_opt ::= */ yytestcase(yyruleno==397); + /* (398) kwcolumn_opt ::= */ yytestcase(yyruleno==398); + /* (399) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==399); + /* (400) vtabarglist ::= vtabarg */ yytestcase(yyruleno==400); + /* (401) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==401); + /* (402) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==402); + /* (403) anylist ::= */ yytestcase(yyruleno==403); + /* (404) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==404); + /* (405) anylist ::= anylist ANY */ yytestcase(yyruleno==405); + /* (406) with ::= */ yytestcase(yyruleno==406); + /* (407) windowdefn_list ::= windowdefn (OPTIMIZED OUT) */ assert(yyruleno!=407); + /* (408) window ::= frame_opt (OPTIMIZED OUT) */ assert(yyruleno!=408); break; /********** End reduce actions ************************************************/ }; @@ -184885,8 +183577,8 @@ static const unsigned char aKWCode[148] = {0, /* Check to see if z[0..n-1] is a keyword. If it is, write the ** parser symbol code for that keyword into *pType. Always ** return the integer n (the length of the token). */ -static i64 keywordCode(const char *z, i64 n, int *pType){ - i64 i, j; +static int keywordCode(const char *z, int n, int *pType){ + int i, j; const char *zKW; assert( n>=2 ); i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n*1) % 127; @@ -185440,7 +184132,7 @@ SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *z, int *tokenType){ } case CC_DOLLAR: case CC_VARALPHA: { - i64 n = 0; + int n = 0; testcase( z[0]=='$' ); testcase( z[0]=='@' ); testcase( z[0]==':' ); testcase( z[0]=='#' ); *tokenType = TK_VARIABLE; @@ -185536,7 +184228,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){ int tokenType; /* type of the next token */ int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ - i64 mxSqlLen; /* Max length of an SQL string */ + int mxSqlLen; /* Max length of an SQL string */ Parse *pParentParse = 0; /* Outer parse context, if any */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ @@ -187192,14 +185884,6 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ rc = setupLookaside(db, pBuf, sz, cnt); break; } - case SQLITE_DBCONFIG_FP_DIGITS: { - int nIn = va_arg(ap, int); - int *pOut = va_arg(ap, int*); - if( nIn>3 && nIn<24 ) db->nFpDigit = (u8)nIn; - if( pOut ) *pOut = db->nFpDigit; - rc = SQLITE_OK; - break; - } default: { static const struct { int op; /* The opcode */ @@ -188755,9 +187439,6 @@ SQLITE_API void *sqlite3_wal_hook( sqlite3_mutex_leave(db->mutex); return pRet; #else - UNUSED_PARAMETER(db); - UNUSED_PARAMETER(xCallback); - UNUSED_PARAMETER(pArg); return 0; #endif } @@ -188773,11 +187454,6 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( int *pnCkpt /* OUT: Total number of frames checkpointed */ ){ #ifdef SQLITE_OMIT_WAL - UNUSED_PARAMETER(db); - UNUSED_PARAMETER(zDb); - UNUSED_PARAMETER(eMode); - UNUSED_PARAMETER(pnLog); - UNUSED_PARAMETER(pnCkpt); return SQLITE_OK; #else int rc; /* Return code */ @@ -188791,12 +187467,11 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( if( pnLog ) *pnLog = -1; if( pnCkpt ) *pnCkpt = -1; - assert( SQLITE_CHECKPOINT_NOOP==-1 ); assert( SQLITE_CHECKPOINT_PASSIVE==0 ); assert( SQLITE_CHECKPOINT_FULL==1 ); assert( SQLITE_CHECKPOINT_RESTART==2 ); assert( SQLITE_CHECKPOINT_TRUNCATE==3 ); - if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ + if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint ** mode: */ return SQLITE_MISUSE_BKPT; @@ -189160,7 +187835,6 @@ static const int aHardLimit[] = { SQLITE_MAX_VARIABLE_NUMBER, /* IMP: R-38091-32352 */ SQLITE_MAX_TRIGGER_DEPTH, SQLITE_MAX_WORKER_THREADS, - SQLITE_MAX_PARSER_DEPTH, }; /* @@ -189175,9 +187849,6 @@ static const int aHardLimit[] = { #if SQLITE_MAX_SQL_LENGTH>SQLITE_MAX_LENGTH # error SQLITE_MAX_SQL_LENGTH must not be greater than SQLITE_MAX_LENGTH #endif -#if SQLITE_MAX_SQL_LENGTH>2147482624 /* 1024 less than 2^31 */ -# error SQLITE_MAX_SQL_LENGTH must not be greater than 2147482624 -#endif #if SQLITE_MAX_COMPOUND_SELECT<2 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2 #endif @@ -189233,7 +187904,6 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_SQL_LENGTH]==SQLITE_MAX_SQL_LENGTH ); assert( aHardLimit[SQLITE_LIMIT_COLUMN]==SQLITE_MAX_COLUMN ); assert( aHardLimit[SQLITE_LIMIT_EXPR_DEPTH]==SQLITE_MAX_EXPR_DEPTH ); - assert( aHardLimit[SQLITE_LIMIT_PARSER_DEPTH]==SQLITE_MAX_PARSER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_COMPOUND_SELECT]==SQLITE_MAX_COMPOUND_SELECT); assert( aHardLimit[SQLITE_LIMIT_VDBE_OP]==SQLITE_MAX_VDBE_OP ); assert( aHardLimit[SQLITE_LIMIT_FUNCTION_ARG]==SQLITE_MAX_FUNCTION_ARG ); @@ -189243,7 +187913,7 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_VARIABLE_NUMBER]==SQLITE_MAX_VARIABLE_NUMBER); assert( aHardLimit[SQLITE_LIMIT_TRIGGER_DEPTH]==SQLITE_MAX_TRIGGER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_WORKER_THREADS]==SQLITE_MAX_WORKER_THREADS ); - assert( SQLITE_LIMIT_PARSER_DEPTH==(SQLITE_N_LIMIT-1) ); + assert( SQLITE_LIMIT_WORKER_THREADS==(SQLITE_N_LIMIT-1) ); if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ @@ -189607,7 +188277,7 @@ static int openDatabase( db = sqlite3MallocZero( sizeof(sqlite3) ); if( db==0 ) goto opendb_out; if( isThreadsafe -#if defined(SQLITE_THREAD_MISUSE_WARNINGS) +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS || sqlite3GlobalConfig.bCoreMutex #endif ){ @@ -189628,7 +188298,6 @@ static int openDatabase( db->aDb = db->aDbStatic; db->lookaside.bDisable = 1; db->lookaside.sz = 0; - db->nFpDigit = 17; assert( sizeof(db->aLimit)==sizeof(aHardLimit) ); memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); @@ -190074,12 +188743,6 @@ SQLITE_API int sqlite3_collation_needed16( */ SQLITE_API void *sqlite3_get_clientdata(sqlite3 *db, const char *zName){ DbClientData *p; -#ifdef SQLITE_ENABLE_API_ARMOR - if( !zName || !sqlite3SafetyCheckOk(db) ){ - (void)SQLITE_MISUSE_BKPT; - return 0; - } -#endif sqlite3_mutex_enter(db->mutex); for(p=db->pDbData; p; p=p->pNext){ if( strcmp(p->zName, zName)==0 ){ @@ -191153,7 +189816,6 @@ SQLITE_API const char *sqlite3_filename_journal(const char *zFilename){ } SQLITE_API const char *sqlite3_filename_wal(const char *zFilename){ #ifdef SQLITE_OMIT_WAL - UNUSED_PARAMETER(zFilename); return 0; #else zFilename = sqlite3_filename_journal(zFilename); @@ -192925,15 +191587,6 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table*,int,int); (*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \ ) -SQLITE_PRIVATE int sqlite3Fts3PrepareStmt( - Fts3Table *p, /* Prepare for this connection */ - const char *zSql, /* SQL to prepare */ - int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */ - int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */ - sqlite3_stmt **pp /* OUT: Prepared statement */ -); - - /* fts3.c */ SQLITE_PRIVATE void sqlite3Fts3ErrMsg(char**,const char*,...); SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *, sqlite3_int64); @@ -194541,7 +193194,9 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr){ zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); if( !zSql ) return SQLITE_NOMEM; p->bLock++; - rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt); + rc = sqlite3_prepare_v3( + p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0 + ); p->bLock--; sqlite3_free(zSql); } @@ -196116,7 +194771,9 @@ static int fts3FilterMethod( } if( zSql ){ p->bLock++; - rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt); + rc = sqlite3_prepare_v3( + p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0 + ); p->bLock--; sqlite3_free(zSql); }else{ @@ -196739,7 +195396,6 @@ static int fts3IntegrityMethod( UNUSED_PARAMETER(isQuick); rc = sqlite3Fts3IntegrityCheck(p, &bOk); - assert( pVtab->zErrMsg==0 || rc!=SQLITE_OK ); assert( rc!=SQLITE_CORRUPT_VTAB ); if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){ *pzErr = sqlite3_mprintf("unable to validate the inverted index for" @@ -203177,9 +201833,9 @@ typedef struct SegmentWriter SegmentWriter; ** incrementally. See function fts3PendingListAppend() for details. */ struct PendingList { - sqlite3_int64 nData; + int nData; char *aData; - sqlite3_int64 nSpace; + int nSpace; sqlite3_int64 iLastDocid; sqlite3_int64 iLastCol; sqlite3_int64 iLastPos; @@ -203352,24 +202008,6 @@ struct SegmentNode { #define SQL_UPDATE_LEVEL_IDX 38 #define SQL_UPDATE_LEVEL 39 -/* -** Wrapper around sqlite3_prepare_v3() to ensure that SQLITE_PREPARE_FROM_DDL -** is always set. -*/ -SQLITE_PRIVATE int sqlite3Fts3PrepareStmt( - Fts3Table *p, /* Prepare for this connection */ - const char *zSql, /* SQL to prepare */ - int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */ - int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */ - sqlite3_stmt **pp /* OUT: Prepared statement */ -){ - int f = SQLITE_PREPARE_FROM_DDL - |((bAllowVtab==0) ? SQLITE_PREPARE_NO_VTAB : 0) - |(bPersist ? SQLITE_PREPARE_PERSISTENT : 0); - - return sqlite3_prepare_v3(p->db, zSql, -1, f, pp, NULL); -} - /* ** This function is used to obtain an SQLite prepared statement handle ** for the statement identified by the second argument. If successful, @@ -203495,12 +202133,12 @@ static int fts3SqlStmt( pStmt = p->aStmt[eStmt]; if( !pStmt ){ - int bAllowVtab = 0; + int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB; char *zSql; if( eStmt==SQL_CONTENT_INSERT ){ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist); }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){ - bAllowVtab = 1; + f &= ~SQLITE_PREPARE_NO_VTAB; zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist); }else{ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName); @@ -203508,7 +202146,7 @@ static int fts3SqlStmt( if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3Fts3PrepareStmt(p, zSql, 1, bAllowVtab, &pStmt); + rc = sqlite3_prepare_v3(p->db, zSql, -1, f, &pStmt, NULL); sqlite3_free(zSql); assert( rc==SQLITE_OK || pStmt==0 ); p->aStmt[eStmt] = pStmt; @@ -203857,9 +202495,7 @@ static int fts3PendingTermsAddOne( pList = (PendingList *)fts3HashFind(pHash, zToken, nToken); if( pList ){ - assert( (i64)pList->nData+(i64)nToken+(i64)sizeof(Fts3HashElem) - <= (i64)p->nPendingData ); - p->nPendingData -= (int)(pList->nData + nToken + sizeof(Fts3HashElem)); + p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem)); } if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){ if( pList==fts3HashInsert(pHash, zToken, nToken, pList) ){ @@ -203872,9 +202508,7 @@ static int fts3PendingTermsAddOne( } } if( rc==SQLITE_OK ){ - assert( (i64)p->nPendingData + pList->nData + nToken - + sizeof(Fts3HashElem) <= 0x3fffffff ); - p->nPendingData += (int)(pList->nData + nToken + sizeof(Fts3HashElem)); + p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem)); } return rc; } @@ -206675,7 +205309,7 @@ static int fts3DoRebuild(Fts3Table *p){ if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); } @@ -208428,7 +207062,7 @@ SQLITE_PRIVATE int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){ if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); } @@ -208558,7 +207192,7 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ v = atoi(&zVal[9]); if( v>=24 && v<=p->nPgsz-35 ) p->nNodeSize = v; rc = SQLITE_OK; - }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 11) ){ + }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){ v = atoi(&zVal[11]); if( v>=64 && v<=FTS3_MAX_PENDING_DATA ) p->nMaxPendingData = v; rc = SQLITE_OK; @@ -211833,10 +210467,7 @@ struct JsonString { #define JSON_SQL 0x02 /* Result is always SQL */ #define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ #define JSON_ISSET 0x04 /* json_set(), not json_insert() */ -#define JSON_AINS 0x08 /* json_array_insert(), not json_insert() */ -#define JSON_BLOB 0x10 /* Use the BLOB output format */ - -#define JSON_INSERT_TYPE(X) (((X)&0xC)>>2) +#define JSON_BLOB 0x08 /* Use the BLOB output format */ /* A parsed JSON value. Lifecycle: @@ -211882,7 +210513,6 @@ struct JsonParse { #define JEDIT_REPL 2 /* Overwrite if exists */ #define JEDIT_INS 3 /* Insert if not exists */ #define JEDIT_SET 4 /* Insert or overwrite */ -#define JEDIT_AINS 5 /* array_insert() */ /* ** Maximum nesting depth of JSON for this implementation. @@ -214379,8 +213009,7 @@ static int jsonLabelCompare( */ #define JSON_LOOKUP_ERROR 0xffffffff #define JSON_LOOKUP_NOTFOUND 0xfffffffe -#define JSON_LOOKUP_NOTARRAY 0xfffffffd -#define JSON_LOOKUP_PATHERROR 0xfffffffc +#define JSON_LOOKUP_PATHERROR 0xfffffffd #define JSON_LOOKUP_ISERROR(x) ((x)>=JSON_LOOKUP_PATHERROR) /* Forward declaration */ @@ -214409,7 +213038,7 @@ static u32 jsonLookupStep(JsonParse*,u32,const char*,u32); static u32 jsonCreateEditSubstructure( JsonParse *pParse, /* The original JSONB that is being edited */ JsonParse *pIns, /* Populate this with the blob data to insert */ - const char *zTail /* Tail of the path that determines substructure */ + const char *zTail /* Tail of the path that determins substructure */ ){ static const u8 emptyObject[] = { JSONB_ARRAY, JSONB_OBJECT }; int rc; @@ -214444,9 +213073,9 @@ static u32 jsonCreateEditSubstructure( ** Return one of the JSON_LOOKUP error codes if problems are seen. ** ** This routine will also modify the blob. If pParse->eEdit is one of -** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, JEDIT_SET, or JEDIT_AINS, then changes -** might be made to the selected value. If an edit is performed, then the -** return value does not necessarily point to the select element. If an edit +** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, or JEDIT_SET, then changes might be +** made to the selected value. If an edit is performed, then the return +** value does not necessarily point to the select element. If an edit ** is performed, the return value is only useful for detecting error ** conditions. */ @@ -214472,13 +213101,6 @@ static u32 jsonLookupStep( jsonBlobEdit(pParse, iRoot, sz, 0, 0); }else if( pParse->eEdit==JEDIT_INS ){ /* Already exists, so json_insert() is a no-op */ - }else if( pParse->eEdit==JEDIT_AINS ){ - /* json_array_insert() */ - if( zPath[-1]!=']' ){ - return JSON_LOOKUP_NOTARRAY; - }else{ - jsonBlobEdit(pParse, iRoot, 0, pParse->aIns, pParse->nIns); - } }else{ /* json_set() or json_replace() */ jsonBlobEdit(pParse, iRoot, sz, pParse->aIns, pParse->nIns); @@ -214550,10 +213172,6 @@ static u32 jsonLookupStep( JsonParse ix; /* Header of the label to be inserted */ testcase( pParse->eEdit==JEDIT_INS ); testcase( pParse->eEdit==JEDIT_SET ); - testcase( pParse->eEdit==JEDIT_AINS ); - if( pParse->eEdit==JEDIT_AINS && sqlite3_strglob("*]",&zPath[i])!=0 ){ - return JSON_LOOKUP_NOTARRAY; - } memset(&ix, 0, sizeof(ix)); ix.db = pParse->db; jsonBlobAppendNode(&ix, rawKey?JSONB_TEXTRAW:JSONB_TEXT5, nKey, 0); @@ -214581,32 +213199,28 @@ static u32 jsonLookupStep( return rc; } }else if( zPath[0]=='[' ){ - u64 kk = 0; x = pParse->aBlob[iRoot] & 0x0f; if( x!=JSONB_ARRAY ) return JSON_LOOKUP_NOTFOUND; n = jsonbPayloadSize(pParse, iRoot, &sz); + k = 0; i = 1; while( sqlite3Isdigit(zPath[i]) ){ - if( kk<0xffffffff ) kk = kk*10 + zPath[i] - '0'; - /* ^^^^^^^^^^--- Allow kk to be bigger than any JSON array so that - ** we get NOTFOUND instead of PATHERROR, without overflowing kk. */ + k = k*10 + zPath[i] - '0'; i++; } if( i<2 || zPath[i]!=']' ){ if( zPath[1]=='#' ){ - kk = jsonbArrayCount(pParse, iRoot); + k = jsonbArrayCount(pParse, iRoot); i = 2; if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ - u64 nn = 0; + unsigned int nn = 0; i = 3; do{ - if( nn<0xffffffff ) nn = nn*10 + zPath[i] - '0'; - /* ^^^^^^^^^^--- Allow nn to be bigger than any JSON array to - ** get NOTFOUND instead of PATHERROR, without overflowing nn. */ + nn = nn*10 + zPath[i] - '0'; i++; }while( sqlite3Isdigit(zPath[i]) ); - if( nn>kk ) return JSON_LOOKUP_NOTFOUND; - kk -= nn; + if( nn>k ) return JSON_LOOKUP_NOTFOUND; + k -= nn; } if( zPath[i]!=']' ){ return JSON_LOOKUP_PATHERROR; @@ -214618,22 +213232,21 @@ static u32 jsonLookupStep( j = iRoot+n; iEnd = j+sz; while( jdelta ) jsonAfterEditSizeAdjust(pParse, iRoot); return rc; } - kk--; + k--; n = jsonbPayloadSize(pParse, j, &sz); if( n==0 ) return JSON_LOOKUP_ERROR; j += n+sz; } if( j>iEnd ) return JSON_LOOKUP_ERROR; - if( kk>0 ) return JSON_LOOKUP_NOTFOUND; + if( k>0 ) return JSON_LOOKUP_NOTFOUND; if( pParse->eEdit>=JEDIT_INS ){ JsonParse v; testcase( pParse->eEdit==JEDIT_INS ); - testcase( pParse->eEdit==JEDIT_AINS ); testcase( pParse->eEdit==JEDIT_SET ); rc = jsonCreateEditSubstructure(pParse, &v, &zPath[i+1]); if( !JSON_LOOKUP_ISERROR(rc) @@ -214771,7 +213384,7 @@ static void jsonReturnFromBlob( to_double: z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); if( z==0 ) goto returnfromblob_oom; - rc = sqlite3AtoF(z, &r); + rc = sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); sqlite3DbFree(db, z); if( rc<=0 ) goto returnfromblob_malformed; sqlite3_result_double(pCtx, r); @@ -214958,15 +213571,9 @@ static int jsonFunctionArgToBlob( */ static char *jsonBadPathError( sqlite3_context *ctx, /* The function call containing the error */ - const char *zPath, /* The path with the problem */ - int rc /* Maybe JSON_LOOKUP_NOTARRAY */ + const char *zPath /* The path with the problem */ ){ - char *zMsg; - if( rc==(int)JSON_LOOKUP_NOTARRAY ){ - zMsg = sqlite3_mprintf("not an array element: %Q", zPath); - }else{ - zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); - } + char *zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); if( ctx==0 ) return zMsg; if( zMsg ){ sqlite3_result_error(ctx, zMsg, -1); @@ -214983,13 +213590,13 @@ static char *jsonBadPathError( ** and return the result. ** ** The specific operation is determined by eEdit, which can be one -** of JEDIT_INS, JEDIT_REPL, JEDIT_SET, or JEDIT_AINS. +** of JEDIT_INS, JEDIT_REPL, or JEDIT_SET. */ static void jsonInsertIntoBlob( sqlite3_context *ctx, int argc, sqlite3_value **argv, - int eEdit /* JEDIT_INS, JEDIT_REPL, JEDIT_SET, JEDIT_AINS */ + int eEdit /* JEDIT_INS, JEDIT_REPL, or JEDIT_SET */ ){ int i; u32 rc = 0; @@ -215041,7 +213648,7 @@ static void jsonInsertIntoBlob( if( rc==JSON_LOOKUP_ERROR ){ sqlite3_result_error(ctx, "malformed JSON", -1); }else{ - jsonBadPathError(ctx, zPath, rc); + jsonBadPathError(ctx, zPath); } return; } @@ -215483,7 +214090,7 @@ static void jsonArrayLengthFunc( if( i==JSON_LOOKUP_NOTFOUND ){ /* no-op */ }else if( i==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -215588,7 +214195,7 @@ static void jsonExtractFunc( j = jsonLookupStep(p, 0, jx.zBuf, 0); jsonStringReset(&jx); }else{ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); goto json_extract_error; } if( jnBlob ){ @@ -215623,7 +214230,7 @@ static void jsonExtractFunc( sqlite3_result_error(ctx, "malformed JSON", -1); goto json_extract_error; }else{ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); goto json_extract_error; } } @@ -215952,7 +214559,7 @@ static void jsonRemoveFunc( if( rc==JSON_LOOKUP_NOTFOUND ){ continue; /* No-op */ }else if( rc==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath, rc); + jsonBadPathError(ctx, zPath); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -215964,7 +214571,7 @@ static void jsonRemoveFunc( return; json_remove_patherror: - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); json_remove_done: jsonParseFree(p); @@ -216008,18 +214615,16 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); - int eInsType = JSON_INSERT_TYPE(flags); - static const char *azInsType[] = { "insert", "set", "array_insert" }; - static const u8 aEditType[] = { JEDIT_INS, JEDIT_SET, JEDIT_AINS }; + int bIsSet = (flags&JSON_ISSET)!=0; if( argc<1 ) return; - assert( eInsType>=0 && eInsType<=2 ); if( (argc&1)==0 ) { - jsonWrongNumArgs(ctx, azInsType[eInsType]); + jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); return; } - jsonInsertIntoBlob(ctx, argc, argv, aEditType[eInsType]); + jsonInsertIntoBlob(ctx, argc, argv, bIsSet ? JEDIT_SET : JEDIT_INS); } /* @@ -216044,7 +214649,7 @@ static void jsonTypeFunc( zPath = (const char*)sqlite3_value_text(argv[1]); if( zPath==0 ) goto json_type_done; if( zPath[0]!='$' ){ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); goto json_type_done; } i = jsonLookupStep(p, 0, zPath+1, 0); @@ -216052,7 +214657,7 @@ static void jsonTypeFunc( if( i==JSON_LOOKUP_NOTFOUND ){ /* no-op */ }else if( i==JSON_LOOKUP_PATHERROR ){ - jsonBadPathError(ctx, zPath, 0); + jsonBadPathError(ctx, zPath); }else{ sqlite3_result_error(ctx, "malformed JSON", -1); } @@ -216308,11 +214913,12 @@ static void jsonArrayStep( } static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; - int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ + int flags; pStr->pCtx = ctx; jsonAppendChar(pStr, ']'); + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); if( pStr->eErr ){ jsonReturnString(pStr, 0, 0); return; @@ -216333,9 +214939,6 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); jsonStringTrimOneChar(pStr); } - }else if( flags & JSON_BLOB ){ - static const u8 emptyArray = 0x0b; - sqlite3_result_blob(ctx, &emptyArray, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC); } @@ -216432,11 +215035,12 @@ static void jsonObjectStep( } static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; - int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ + int flags; jsonAppendChar(pStr, '}'); pStr->pCtx = ctx; + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); if( pStr->eErr ){ jsonReturnString(pStr, 0, 0); return; @@ -216457,9 +215061,6 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); jsonStringTrimOneChar(pStr); } - }else if( flags & JSON_BLOB ){ - static const unsigned char emptyObject = 0x0c; - sqlite3_result_blob(ctx, &emptyObject, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC); } @@ -216960,7 +215561,7 @@ static int jsonEachFilter( if( zRoot==0 ) return SQLITE_OK; if( zRoot[0]!='$' ){ sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } @@ -216978,7 +215579,7 @@ static int jsonEachFilter( return SQLITE_OK; } sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } @@ -217068,8 +215669,6 @@ SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void){ JFUNCTION(jsonb, 1,1,0, 0,1,0, jsonRemoveFunc), JFUNCTION(json_array, -1,0,1, 1,0,0, jsonArrayFunc), JFUNCTION(jsonb_array, -1,0,1, 1,1,0, jsonArrayFunc), - JFUNCTION(json_array_insert, -1,1,1, 1,0,JSON_AINS, jsonSetFunc), - JFUNCTION(jsonb_array_insert,-1,1,0, 1,1,JSON_AINS, jsonSetFunc), JFUNCTION(json_array_length, 1,1,0, 0,0,0, jsonArrayLengthFunc), JFUNCTION(json_array_length, 2,1,0, 0,0,0, jsonArrayLengthFunc), JFUNCTION(json_error_position,1,1,0, 0,0,0, jsonErrorFunc), @@ -221650,7 +220249,7 @@ static int geopolyParseNumber(GeoParse *p, GeoCoord *pVal){ /* The sqlite3AtoF() routine is much much faster than atof(), if it ** is available */ double r; - (void)sqlite3AtoF((const char*)p->z, &r); + (void)sqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8); *pVal = r; #else *pVal = (GeoCoord)atof((const char*)p->z); @@ -227235,8 +225834,8 @@ static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){ /* If necessary, grow the pIter->aIdxCol[] array */ if( iIdxCol==nIdxAlloc ){ - RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc64( - pIter->aIdxCol, nIdxAlloc*sizeof(RbuSpan) + 16*sizeof(RbuSpan) + RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc( + pIter->aIdxCol, (nIdxAlloc+16)*sizeof(RbuSpan) ); if( aIdxCol==0 ){ rc = SQLITE_NOMEM; @@ -231909,7 +230508,6 @@ struct carray_bind { int nData; /* Number of elements */ int mFlags; /* Control flags */ void (*xDel)(void*); /* Destructor for aData */ - void *pDel; /* Alternative argument to xDel() */ }; @@ -232242,7 +230840,7 @@ static sqlite3_module carrayModule = { static void carrayBindDel(void *pPtr){ carray_bind *p = (carray_bind*)pPtr; if( p->xDel!=SQLITE_STATIC ){ - p->xDel(p->pDel); + p->xDel(p->aData); } sqlite3_free(p); } @@ -232250,26 +230848,14 @@ static void carrayBindDel(void *pPtr){ /* ** Invoke this interface in order to bind to the single-argument ** version of CARRAY(). -** -** pStmt The prepared statement to which to bind -** idx The index of the parameter of pStmt to which to bind -** aData The data to be bound -** nData The number of elements in aData -** mFlags One of SQLITE_CARRAY_xxxx indicating datatype of aData -** xDestroy Destructor for pDestroy or aData if pDestroy==NULL. -** pDestroy Invoke xDestroy on this pointer if not NULL -** -** The destructor is called pDestroy if pDestroy!=NULL, or against -** aData if pDestroy==NULL. */ -SQLITE_API int sqlite3_carray_bind_v2( +SQLITE_API int sqlite3_carray_bind( sqlite3_stmt *pStmt, int idx, void *aData, int nData, int mFlags, - void (*xDestroy)(void*), - void *pDestroy + void (*xDestroy)(void*) ){ carray_bind *pNew = 0; int i; @@ -232346,38 +230932,20 @@ SQLITE_API int sqlite3_carray_bind_v2( memcpy(pNew->aData, aData, sz); } pNew->xDel = sqlite3_free; - pNew->pDel = pNew->aData; }else{ pNew->aData = aData; pNew->xDel = xDestroy; - pNew->pDel = pDestroy; } return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel); carray_bind_error: if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){ - xDestroy(pDestroy); + xDestroy(aData); } sqlite3_free(pNew); return rc; } -/* -** Invoke this interface in order to bind to the single-argument -** version of CARRAY(). Same as sqlite3_carray_bind_v2() with the -** pDestroy parameter set to NULL. -*/ -SQLITE_API int sqlite3_carray_bind( - sqlite3_stmt *pStmt, - int idx, - void *aData, - int nData, - int mFlags, - void (*xDestroy)(void*) -){ - return sqlite3_carray_bind_v2(pStmt,idx,aData,nData,mFlags,xDestroy,aData); -} - /* ** Invoke this routine to register the carray() function. */ @@ -232740,20 +231308,6 @@ static int sessionVarintGet(const u8 *aBuf, int *piVal){ return getVarint32(aBuf, *piVal); } -/* -** Read a varint value from buffer aBuf[], size nBuf bytes, into *piVal. -** Return the number of bytes read. -*/ -static int sessionVarintGetSafe(const u8 *aBuf, int nBuf, int *piVal){ - u8 aCopy[5]; - const u8 *aRead = aBuf; - if( nBuf<5 ){ - memcpy(aCopy, aBuf, nBuf); - aRead = aCopy; - } - return getVarint32(aRead, *piVal); -} - /* Load an unaligned and unsigned 32-bit integer */ #define SESSION_UINT32(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) @@ -233048,10 +231602,14 @@ static unsigned int sessionChangeHash( int isPK = pTab->abPK[i]; if( bPkOnly && isPK==0 ) continue; + /* It is not possible for eType to be SQLITE_NULL here. The session + ** module does not record changes for rows with NULL values stored in + ** primary key columns. */ assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT || eType==SQLITE_TEXT || eType==SQLITE_BLOB || eType==SQLITE_NULL || eType==0 ); + assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) ); if( isPK ){ a++; @@ -233059,16 +231617,12 @@ static unsigned int sessionChangeHash( if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ h = sessionHashAppendI64(h, sessionGetI64(a)); a += 8; - }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + }else{ int n; a += sessionVarintGet(a, &n); h = sessionHashAppendBlob(h, n, a); a += n; } - /* It should not be possible for eType to be SQLITE_NULL or 0x00 here, - ** as the session module does not record changes for rows with NULL - ** values stored in primary key columns. But a corrupt changesets - ** may contain such a value. */ }else{ a += sessionSerialLen(a); } @@ -235477,13 +234031,10 @@ static int sessionGenerateChangeset( } if( pSession->rc ) return pSession->rc; + rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0); + if( rc!=SQLITE_OK ) return rc; sqlite3_mutex_enter(sqlite3_db_mutex(db)); - rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0); - if( rc!=SQLITE_OK ){ - sqlite3_mutex_leave(sqlite3_db_mutex(db)); - return rc; - } for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ if( pTab->nEntry ){ @@ -235966,8 +234517,7 @@ static int sessionReadRecord( u8 *aVal = &pIn->aData[pIn->iNext]; if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ int nByte; - int nRem = pIn->nData - pIn->iNext; - pIn->iNext += sessionVarintGetSafe(aVal, nRem, &nByte); + pIn->iNext += sessionVarintGet(aVal, &nByte); rc = sessionInputBuffer(pIn, nByte); if( rc==SQLITE_OK ){ if( nByte<0 || nByte>pIn->nData-pIn->iNext ){ @@ -236020,8 +234570,7 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ rc = sessionInputBuffer(pIn, 9); if( rc==SQLITE_OK ){ - int nBuf = pIn->nData - pIn->iNext; - nRead += sessionVarintGetSafe(&pIn->aData[pIn->iNext], nBuf, &nCol); + nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol); /* The hard upper limit for the number of columns in an SQLite ** database table is, according to sqliteLimit.h, 32676. So ** consider any table-header that purports to have more than 65536 @@ -236041,15 +234590,8 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ while( (pIn->iNext + nRead)nData && pIn->aData[pIn->iNext + nRead] ){ nRead++; } - - /* Break out of the loop if if the nul-terminator byte has been found. - ** Otherwise, read some more input data and keep seeking. If there is - ** no more input data, consider the changeset corrupt. */ if( (pIn->iNext + nRead)nData ) break; rc = sessionInputBuffer(pIn, nRead + 100); - if( rc==SQLITE_OK && (pIn->iNext + nRead)>=pIn->nData ){ - rc = SQLITE_CORRUPT_BKPT; - } } *pnByte = nRead+1; return rc; @@ -236181,10 +234723,10 @@ static int sessionChangesetNextOne( memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); } - /* Make sure the buffer contains at least 2 bytes of input data, or all - ** remaining data if there are less than 2 bytes available. This is - ** sufficient either for the 'T' or 'P' byte that begins a new table, - ** or for the "op" and "bIndirect" single bytes otherwise. */ + /* Make sure the buffer contains at least 10 bytes of input data, or all + ** remaining data if there are less than 10 bytes available. This is + ** sufficient either for the 'T' or 'P' byte and the varint that follows + ** it, or for the two single byte values otherwise. */ p->rc = sessionInputBuffer(&p->in, 2); if( p->rc!=SQLITE_OK ) return p->rc; @@ -236214,13 +234756,11 @@ static int sessionChangesetNextOne( return (p->rc = SQLITE_CORRUPT_BKPT); } - if( (op!=SQLITE_UPDATE && op!=SQLITE_DELETE && op!=SQLITE_INSERT) - || (p->in.iNext>=p->in.nData) - ){ - return (p->rc = SQLITE_CORRUPT_BKPT); - } p->op = op; p->bIndirect = p->in.aData[p->in.iNext++]; + if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ + return (p->rc = SQLITE_CORRUPT_BKPT); + } if( paRec ){ int nVal; /* Number of values to buffer */ @@ -241108,22 +239648,14 @@ typedef union { #define sqlite3Fts5ParserARG_PARAM ,pParse #define sqlite3Fts5ParserARG_FETCH Fts5Parse *pParse=fts5yypParser->pParse; #define sqlite3Fts5ParserARG_STORE fts5yypParser->pParse=pParse; -#undef fts5YYREALLOC #define fts5YYREALLOC realloc -#undef fts5YYFREE #define fts5YYFREE free -#undef fts5YYDYNSTACK #define fts5YYDYNSTACK 0 -#undef fts5YYSIZELIMIT -#define sqlite3Fts5ParserCTX(P) 0 #define sqlite3Fts5ParserCTX_SDECL #define sqlite3Fts5ParserCTX_PDECL #define sqlite3Fts5ParserCTX_PARAM #define sqlite3Fts5ParserCTX_FETCH #define sqlite3Fts5ParserCTX_STORE -#undef fts5YYERRORSYMBOL -#undef fts5YYERRSYMDT -#undef fts5YYFALLBACK #define fts5YYNSTATE 35 #define fts5YYNRULE 28 #define fts5YYNRULE_WITH_ACTION 28 @@ -241448,24 +239980,15 @@ static int fts5yyGrowStack(fts5yyParser *p){ int newSize; int idx; fts5yyStackEntry *pNew; -#ifdef fts5YYSIZELIMIT - int nLimit = fts5YYSIZELIMIT(sqlite3Fts5ParserCTX(p)); -#endif newSize = oldSize*2 + 100; -#ifdef fts5YYSIZELIMIT - if( newSize>nLimit ){ - newSize = nLimit; - if( newSize<=oldSize ) return 1; - } -#endif idx = (int)(p->fts5yytos - p->fts5yystack); if( p->fts5yystack==p->fts5yystk0 ){ - pNew = fts5YYREALLOC(0, newSize*sizeof(pNew[0]), sqlite3Fts5ParserCTX(p)); + pNew = fts5YYREALLOC(0, newSize*sizeof(pNew[0])); if( pNew==0 ) return 1; memcpy(pNew, p->fts5yystack, oldSize*sizeof(pNew[0])); }else{ - pNew = fts5YYREALLOC(p->fts5yystack, newSize*sizeof(pNew[0]), sqlite3Fts5ParserCTX(p)); + pNew = fts5YYREALLOC(p->fts5yystack, newSize*sizeof(pNew[0])); if( pNew==0 ) return 1; } p->fts5yystack = pNew; @@ -241645,9 +240168,7 @@ static void sqlite3Fts5ParserFinalize(void *p){ } #if fts5YYGROWABLESTACK - if( pParser->fts5yystack!=pParser->fts5yystk0 ){ - fts5YYFREE(pParser->fts5yystack, sqlite3Fts5ParserCTX(pParser)); - } + if( pParser->fts5yystack!=pParser->fts5yystk0 ) fts5YYFREE(pParser->fts5yystack); #endif } @@ -242929,7 +241450,7 @@ static void fts5SnippetFunction( iBestCol = (iCol>=0 ? iCol : 0); nPhrase = pApi->xPhraseCount(pFts); - aSeen = sqlite3_malloc64(nPhrase); + aSeen = sqlite3_malloc(nPhrase); if( aSeen==0 ){ rc = SQLITE_NOMEM; } @@ -243584,7 +242105,7 @@ static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){ if( nIn<0 ){ nIn = (int)strlen(pIn); } - zRet = (char*)sqlite3_malloc64((i64)nIn+1); + zRet = (char*)sqlite3_malloc(nIn+1); if( zRet ){ memcpy(zRet, pIn, nIn); zRet[nIn] = '\0'; @@ -244284,7 +242805,7 @@ static int sqlite3Fts5ConfigParse( sqlite3_int64 nByte; int bUnindexed = 0; /* True if there are one or more UNINDEXED */ - *ppOut = pRet = (Fts5Config*)sqlite3_malloc64(sizeof(Fts5Config)); + *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config)); if( pRet==0 ) return SQLITE_NOMEM; memset(pRet, 0, sizeof(Fts5Config)); pRet->pGlobal = pGlobal; @@ -244832,6 +243353,8 @@ static void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...){ va_end(ap); } + + /* ** 2014 May 31 ** @@ -245148,7 +243671,7 @@ static int sqlite3Fts5ExprNew( assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 ); if( sParse.rc==SQLITE_OK ){ - *ppNew = pNew = sqlite3_malloc64(sizeof(Fts5Expr)); + *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr)); if( pNew==0 ){ sParse.rc = SQLITE_NOMEM; sqlite3Fts5ParseNodeFree(sParse.pExpr); @@ -245300,7 +243823,7 @@ static int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){ p2->pRoot = 0; if( sParse.rc==SQLITE_OK ){ - Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc64( + Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc( p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*) ); if( ap==0 ){ @@ -248212,7 +246735,7 @@ static int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte int rc = SQLITE_OK; Fts5Hash *pNew; - *ppNew = pNew = (Fts5Hash*)sqlite3_malloc64(sizeof(Fts5Hash)); + *ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash)); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ @@ -250805,7 +249328,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){ /* If necessary, grow the pIter->aRowidOffset[] array. */ if( iRowidOffset>=pIter->nRowidOffset ){ - i64 nNew = pIter->nRowidOffset + 8; + int nNew = pIter->nRowidOffset + 8; int *aNew = (int*)sqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof(int)); if( aNew==0 ){ p->rc = SQLITE_NOMEM; @@ -254111,31 +252634,31 @@ static void fts5DoSecureDelete( ** is another term following it on this page. So the subsequent term ** needs to be moved to replace the term associated with the entry ** being removed. */ - u64 nPrefix = 0; - u64 nSuffix = 0; - u64 nPrefix2 = 0; - u64 nSuffix2 = 0; + int nPrefix = 0; + int nSuffix = 0; + int nPrefix2 = 0; + int nSuffix2 = 0; iDelKeyOff = iNextOff; - iNextOff += fts5GetVarint(&aPg[iNextOff], &nPrefix2); - iNextOff += fts5GetVarint(&aPg[iNextOff], &nSuffix2); + iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2); + iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2); if( iKey!=1 ){ - iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nPrefix); + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix); } - iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nSuffix); + iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix); nPrefix = MIN(nPrefix, nPrefix2); nSuffix = (nPrefix2 + nSuffix2) - nPrefix; - if( (iKeyOff+nSuffix)>(u64)iPgIdx || (iNextOff+nSuffix2)>(u64)iPgIdx ){ + if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){ FTS5_CORRUPT_IDX(p); }else{ if( iKey!=1 ){ iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); } iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); - if( nPrefix2>(u64)pSeg->term.n ){ + if( nPrefix2>pSeg->term.n ){ FTS5_CORRUPT_IDX(p); }else if( nPrefix2>nPrefix ){ memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); @@ -254166,7 +252689,7 @@ static void fts5DoSecureDelete( u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; int nTermIdx = pTerm->nn - pTerm->szLeaf; int iTermIdx = 0; - i64 iTermOff = 0; + int iTermOff = 0; while( 1 ){ u32 iVal = 0; @@ -254177,15 +252700,12 @@ static void fts5DoSecureDelete( } nTermIdx = iTermIdx; - if( iTermOff>pTerm->szLeaf ){ - FTS5_CORRUPT_IDX(p); - }else{ - memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); - fts5PutU16(&pTerm->p[2], iTermOff); - fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); - if( nTermIdx==0 ){ - fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); - } + memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); + fts5PutU16(&pTerm->p[2], iTermOff); + + fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); + if( nTermIdx==0 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); } } fts5DataRelease(pTerm); @@ -254208,9 +252728,7 @@ static void fts5DoSecureDelete( int iPrevKeyOut = 0; int iKeyIn = 0; - if( nMove>0 ){ - memmove(&aPg[iOff], &aPg[iNextOff], nMove); - } + memmove(&aPg[iOff], &aPg[iNextOff], nMove); iPgIdx -= nShift; nPg = iPgIdx; fts5PutU16(&aPg[2], iPgIdx); @@ -255130,16 +253648,16 @@ struct Fts5TokenDataMap { ** aMap[] variables. */ struct Fts5TokenDataIter { - i64 nMapAlloc; /* Allocated size of aMap[] in entries */ - i64 nMap; /* Number of valid entries in aMap[] */ + int nMapAlloc; /* Allocated size of aMap[] in entries */ + int nMap; /* Number of valid entries in aMap[] */ Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */ /* The following are used for prefix-queries only. */ Fts5Buffer terms; /* The following are used for other full-token tokendata queries only. */ - i64 nIter; - i64 nIterAlloc; + int nIter; + int nIterAlloc; Fts5PoslistReader *aPoslistReader; int *aPoslistToIter; Fts5Iter *apIter[FLEXARRAY]; @@ -255195,11 +253713,11 @@ static void fts5TokendataIterAppendMap( ){ if( p->rc==SQLITE_OK ){ if( pT->nMap==pT->nMapAlloc ){ - i64 nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; - i64 nAlloc = nNew * sizeof(Fts5TokenDataMap); + int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; + int nAlloc = nNew * sizeof(Fts5TokenDataMap); Fts5TokenDataMap *aNew; - aNew = (Fts5TokenDataMap*)sqlite3_realloc64(pT->aMap, nAlloc); + aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc); if( aNew==0 ){ p->rc = SQLITE_NOMEM; return; @@ -255225,7 +253743,7 @@ static void fts5TokendataIterAppendMap( */ static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){ Fts5TokenDataMap *aTmp = 0; - i64 nByte = pT->nMap * sizeof(Fts5TokenDataMap); + int nByte = pT->nMap * sizeof(Fts5TokenDataMap); aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte); if( aTmp ){ @@ -255759,10 +254277,9 @@ static Fts5TokenDataIter *fts5AppendTokendataIter( if( p->rc==SQLITE_OK ){ if( pIn==0 || pIn->nIter==pIn->nIterAlloc ){ - i64 nAlloc = pIn ? pIn->nIterAlloc*2 : 16; - i64 nByte = SZ_FTS5TOKENDATAITER(nAlloc+1); - Fts5TokenDataIter *pNew; - pNew = (Fts5TokenDataIter*)sqlite3_realloc64(pIn, nByte); + int nAlloc = pIn ? pIn->nIterAlloc*2 : 16; + int nByte = SZ_FTS5TOKENDATAITER(nAlloc+1); + Fts5TokenDataIter *pNew = (Fts5TokenDataIter*)sqlite3_realloc(pIn, nByte); if( pNew==0 ){ p->rc = SQLITE_NOMEM; @@ -255859,8 +254376,8 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){ /* Ensure the token-mapping is large enough */ if( eDetail==FTS5_DETAIL_FULL && pT->nMapAlloc<(pT->nMap + nByte) ){ - i64 nNew = (pT->nMapAlloc + nByte) * 2; - Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc64( + int nNew = (pT->nMapAlloc + nByte) * 2; + Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc( pT->aMap, nNew*sizeof(Fts5TokenDataMap) ); if( aNew==0 ){ @@ -258889,7 +257406,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ return SQLITE_ERROR; } - idxStr = (char*)sqlite3_malloc64((i64)pInfo->nConstraint * 8 + 1); + idxStr = (char*)sqlite3_malloc(pInfo->nConstraint * 8 + 1); if( idxStr==0 ) return SQLITE_NOMEM; pInfo->idxStr = idxStr; pInfo->needToFreeIdxStr = 1; @@ -260339,7 +258856,6 @@ static int fts5UpdateMethod( } update_out: - sqlite3Fts5IndexCloseReader(pTab->p.pIndex); pTab->p.pConfig->pzErrmsg = 0; return rc; } @@ -261857,7 +260373,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2026-03-06 16:01:44 557aeb43869d3585137b17690cb3b64f7de6921774daae9e56403c3717dceab6", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2026-03-13 10:38:09 737ae4a34738ffa0c3ff7f9bb18df914dd1cad163f28fd6b6e114a344fe6d618", -1, SQLITE_TRANSIENT); } /* @@ -262021,7 +260537,7 @@ static int fts5Init(sqlite3 *db){ int rc; Fts5Global *pGlobal = 0; - pGlobal = (Fts5Global*)sqlite3_malloc64(sizeof(Fts5Global)); + pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global)); if( pGlobal==0 ){ rc = SQLITE_NOMEM; }else{ @@ -263737,7 +262253,7 @@ static int fts5AsciiCreate( if( nArg%2 ){ rc = SQLITE_ERROR; }else{ - p = sqlite3_malloc64(sizeof(AsciiTokenizer)); + p = sqlite3_malloc(sizeof(AsciiTokenizer)); if( p==0 ){ rc = SQLITE_NOMEM; }else{ @@ -264032,7 +262548,7 @@ static int fts5UnicodeCreate( if( nArg%2 ){ rc = SQLITE_ERROR; }else{ - p = (Unicode61Tokenizer*)sqlite3_malloc64(sizeof(Unicode61Tokenizer)); + p = (Unicode61Tokenizer*)sqlite3_malloc(sizeof(Unicode61Tokenizer)); if( p ){ const char *zCat = "L* N* Co"; int i; @@ -264255,7 +262771,7 @@ static int fts5PorterCreate( zBase = azArg[0]; } - pRet = (PorterTokenizer*)sqlite3_malloc64(sizeof(PorterTokenizer)); + pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer)); if( pRet ){ memset(pRet, 0, sizeof(PorterTokenizer)); rc = pApi->xFindTokenizer_v2(pApi, zBase, &pUserdata, &pV2); @@ -264962,7 +263478,7 @@ static int fts5TriCreate( rc = SQLITE_ERROR; }else{ int i; - pNew = (TrigramTokenizer*)sqlite3_malloc64(sizeof(*pNew)); + pNew = (TrigramTokenizer*)sqlite3_malloc(sizeof(*pNew)); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ @@ -266948,7 +265464,7 @@ static int fts5VocabFilterMethod( const char *zCopy = (const char *)sqlite3_value_text(pLe); if( zCopy==0 ) zCopy = ""; pCsr->nLeTerm = sqlite3_value_bytes(pLe); - pCsr->zLeTerm = sqlite3_malloc64((i64)pCsr->nLeTerm+1); + pCsr->zLeTerm = sqlite3_malloc(pCsr->nLeTerm+1); if( pCsr->zLeTerm==0 ){ rc = SQLITE_NOMEM; }else{ diff --git a/deps/sqlite/sqlite3.h b/deps/sqlite/sqlite3.h index 33856750dd35a9..302f0f5b1698d1 100644 --- a/deps/sqlite/sqlite3.h +++ b/deps/sqlite/sqlite3.h @@ -146,12 +146,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.52.0" -#define SQLITE_VERSION_NUMBER 3052000 -#define SQLITE_SOURCE_ID "2026-03-06 16:01:44 557aeb43869d3585137b17690cb3b64f7de6921774daae9e56403c3717dceab6" -#define SQLITE_SCM_BRANCH "trunk" -#define SQLITE_SCM_TAGS "release major-release version-3.52.0" -#define SQLITE_SCM_DATETIME "2026-03-06T16:01:44.367Z" +#define SQLITE_VERSION "3.51.3" +#define SQLITE_VERSION_NUMBER 3051003 +#define SQLITE_SOURCE_ID "2026-03-13 10:38:09 737ae4a34738ffa0c3ff7f9bb18df914dd1cad163f28fd6b6e114a344fe6d618" +#define SQLITE_SCM_BRANCH "branch-3.51" +#define SQLITE_SCM_TAGS "release version-3.51.3" +#define SQLITE_SCM_DATETIME "2026-03-13T10:38:09.694Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -1490,7 +1490,7 @@ typedef const char *sqlite3_filename; ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** -** ^The xSetSystemCall(), xGetSystemCall(), and xNextSystemCall() interfaces +** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided ** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can @@ -2567,15 +2567,12 @@ struct sqlite3_mem_methods { ** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] **
            SQLITE_DBCONFIG_STMT_SCANSTATUS
            **
            The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in -** [SQLITE_ENABLE_STMT_SCANSTATUS] builds. In this case, it sets or clears -** a flag that enables collection of run-time performance statistics -** used by [sqlite3_stmt_scanstatus_v2()] and the [nexec and ncycle] -** columns of the [bytecode virtual table]. -** For statistics to be collected, the flag must be set on -** the database handle both when the SQL statement is -** [sqlite3_prepare|prepared] and when it is [sqlite3_step|stepped]. -** The flag is set (collection of statistics is enabled) by default. -**

            This option takes two arguments: an integer and a pointer to +** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears +** a flag that enables collection of the sqlite3_stmt_scanstatus_v2() +** statistics. For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is prepared and when it +** is stepped. The flag is set (collection of statistics is enabled) +** by default.

            This option takes two arguments: an integer and a pointer to ** an integer. The first argument is 1, 0, or -1 to enable, disable, or ** leave unchanged the statement scanstatus option. If the second argument ** is not NULL, then the value of the statement scanstatus setting after @@ -2648,22 +2645,6 @@ struct sqlite3_mem_methods { ** comments are allowed in SQL text after processing the first argument. **

            ** -** [[SQLITE_DBCONFIG_FP_DIGITS]] -**
            SQLITE_DBCONFIG_FP_DIGITS
            -**
            The SQLITE_DBCONFIG_FP_DIGITS setting is a small integer that determines -** the number of significant digits that SQLite will attempt to preserve when -** converting floating point numbers (IEEE 754 "doubles") into text. The -** default value 17, as of SQLite version 3.52.0. The value was 15 in all -** prior versions.

            -** This option takes two arguments which are an integer and a pointer -** to an integer. The first argument is a small integer, between 3 and 23, or -** zero. The FP_DIGITS setting is changed to that small integer, or left -** altered if the first argument is zero or out of range. The second argument -** is a pointer to an integer. If the pointer is not NULL, then the value of -** the FP_DIGITS setting, after possibly being modified by the first -** arguments, is written into the integer to which the second argument points. -**

            -** ** ** ** [[DBCONFIG arguments]]

            Arguments To SQLITE_DBCONFIG Options

            @@ -2681,10 +2662,9 @@ struct sqlite3_mem_methods { ** the first argument. ** **

            While most SQLITE_DBCONFIG options use the argument format -** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME], -** [SQLITE_DBCONFIG_LOOKASIDE], and [SQLITE_DBCONFIG_FP_DIGITS] options -** are different. See the documentation of those exceptional options for -** details. +** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME] +** and [SQLITE_DBCONFIG_LOOKASIDE] options are different. See the +** documentation of those exceptional options for details. */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ @@ -2709,8 +2689,7 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */ -#define SQLITE_DBCONFIG_FP_DIGITS 1023 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1023 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -4192,7 +4171,6 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename); **

          • sqlite3_errmsg() **
          • sqlite3_errmsg16() **
          • sqlite3_error_offset() -**
          • sqlite3_db_handle() **
          ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language @@ -4239,7 +4217,7 @@ SQLITE_API const char *sqlite3_errstr(int); SQLITE_API int sqlite3_error_offset(sqlite3 *db); /* -** CAPI3REF: Set Error Code And Message +** CAPI3REF: Set Error Codes And Message ** METHOD: sqlite3 ** ** Set the error code of the database handle passed as the first argument @@ -4358,10 +4336,6 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
          SQLITE_LIMIT_EXPR_DEPTH
          **
          The maximum depth of the parse tree on any expression.
          )^ ** -** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
          SQLITE_LIMIT_PARSER_DEPTH
          -**
          The maximum depth of the LALR(1) parser stack used to analyze -** input SQL statements.
          )^ -** ** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(
          SQLITE_LIMIT_COMPOUND_SELECT
          **
          The maximum number of terms in a compound SELECT statement.
          )^ ** @@ -4406,7 +4380,6 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 -#define SQLITE_LIMIT_PARSER_DEPTH 12 /* ** CAPI3REF: Prepare Flags @@ -4451,29 +4424,12 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** fails, the sqlite3_prepare_v3() call returns the same error indications ** with or without this flag; it just omits the call to [sqlite3_log()] that ** logs the error. -** -** [[SQLITE_PREPARE_FROM_DDL]]
          SQLITE_PREPARE_FROM_DDL
          -**
          The SQLITE_PREPARE_FROM_DDL flag causes the SQL compiler to enforce -** security constraints that would otherwise only be enforced when parsing -** the database schema. In other words, the SQLITE_PREPARE_FROM_DDL flag -** causes the SQL compiler to treat the SQL statement being prepared as if -** it had come from an attacker. When SQLITE_PREPARE_FROM_DDL is used and -** [SQLITE_DBCONFIG_TRUSTED_SCHEMA] is off, SQL functions may only be called -** if they are tagged with [SQLITE_INNOCUOUS] and virtual tables may only -** be used if they are tagged with [SQLITE_VTAB_INNOCUOUS]. Best practice -** is to use the SQLITE_PREPARE_FROM_DDL option when preparing any SQL that -** is derived from parts of the database schema. In particular, virtual -** table implementations that run SQL statements that are derived from -** arguments to their CREATE VIRTUAL TABLE statement should always use -** [sqlite3_prepare_v3()] and set the SQLITE_PREPARE_FROM_DDL flag to -** prevent bypass of the [SQLITE_DBCONFIG_TRUSTED_SCHEMA] security checks. ** */ #define SQLITE_PREPARE_PERSISTENT 0x01 #define SQLITE_PREPARE_NORMALIZE 0x02 #define SQLITE_PREPARE_NO_VTAB 0x04 #define SQLITE_PREPARE_DONT_LOG 0x10 -#define SQLITE_PREPARE_FROM_DDL 0x20 /* ** CAPI3REF: Compiling An SQL Statement @@ -4487,9 +4443,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** The preferred routine to use is [sqlite3_prepare_v2()]. The ** [sqlite3_prepare()] interface is legacy and should be avoided. -** [sqlite3_prepare_v3()] has an extra -** [SQLITE_PREPARE_FROM_DDL|"prepFlags" option] that is some times -** needed for special purpose or to pass along security restrictions. +** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used +** for special purposes. ** ** The use of the UTF-8 interfaces is preferred, as SQLite currently ** does all parsing using UTF-8. The UTF-16 interfaces are provided @@ -4894,8 +4849,8 @@ typedef struct sqlite3_context sqlite3_context; ** it should be a pointer to well-formed UTF16 text. ** ^If the third parameter to sqlite3_bind_text64() is not NULL, then ** it should be a pointer to a well-formed unicode string that is -** either UTF8 if the sixth parameter is SQLITE_UTF8 or SQLITE_UTF8_ZT, -** or UTF16 otherwise. +** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16 +** otherwise. ** ** [[byte-order determination rules]] ^The byte-order of ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) @@ -4941,15 +4896,10 @@ typedef struct sqlite3_context sqlite3_context; ** object and pointer to it must remain valid until then. ^SQLite will then ** manage the lifetime of its private copy. ** -** ^The sixth argument (the E argument) -** to sqlite3_bind_text64(S,K,Z,N,D,E) must be one of -** [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], -** or [SQLITE_UTF16LE] to specify the encoding of the text in the -** third parameter, Z. The special value [SQLITE_UTF8_ZT] means that the -** string argument is both UTF-8 encoded and is zero-terminated. In other -** words, SQLITE_UTF8_ZT means that the Z array is allocated to hold at -** least N+1 bytes and that the Z[N] byte is zero. If -** the E argument to sqlite3_bind_text64(S,K,Z,N,D,E) is not one of the +** ^The sixth argument to sqlite3_bind_text64() must be one of +** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] +** to specify the encoding of the text in the third parameter. If +** the sixth argument to sqlite3_bind_text64() is not one of the ** allowed values shown above, or if the text encoding is different ** from the encoding specified by the sixth parameter, then the behavior ** is undefined. @@ -5816,51 +5766,6 @@ SQLITE_API int sqlite3_create_window_function( ** ** These constants define integer codes that represent the various ** text encodings supported by SQLite. -** -**
          -** [[SQLITE_UTF8]]
          SQLITE_UTF8
          Text is encoding as UTF-8
          -** -** [[SQLITE_UTF16LE]]
          SQLITE_UTF16LE
          Text is encoding as UTF-16 -** with each code point being expressed "little endian" - the least significant -** byte first. This is the usual encoding, for example on Windows.
          -** -** [[SQLITE_UTF16BE]]
          SQLITE_UTF16BE
          Text is encoding as UTF-16 -** with each code point being expressed "big endian" - the most significant -** byte first. This encoding is less common, but is still sometimes seen, -** specially on older systems. -** -** [[SQLITE_UTF16]]
          SQLITE_UTF16
          Text is encoding as UTF-16 -** with each code point being expressed either little endian or as big -** endian, according to the native endianness of the host computer. -** -** [[SQLITE_ANY]]
          SQLITE_ANY
          This encoding value may only be used -** to declare the preferred text for [application-defined SQL functions] -** created using [sqlite3_create_function()] and similar. If the preferred -** encoding (the 4th parameter to sqlite3_create_function() - the eTextRep -** parameter) is SQLITE_ANY, that indicates that the function does not have -** a preference regarding the text encoding of its parameters and can take -** any text encoding that the SQLite core find convenient to supply. This -** option is deprecated. Please do not use it in new applications. -** -** [[SQLITE_UTF16_ALIGNED]]
          SQLITE_UTF16_ALIGNED
          This encoding -** value may be used as the 3rd parameter (the eTextRep parameter) to -** [sqlite3_create_collation()] and similar. This encoding value means -** that the application-defined collating sequence created expects its -** input strings to be in UTF16 in native byte order, and that the start -** of the strings must be aligned to a 2-byte boundary. -** -** [[SQLITE_UTF8_ZT]]
          SQLITE_UTF8_ZT
          This option can only be -** used to specify the text encoding to strings input to [sqlite3_result_text64()] -** and [sqlite3_bind_text64()]. It means that the input string (call it "z") -** is UTF-8 encoded and that it is zero-terminated. If the length parameter -** (call it "n") is non-negative, this encoding option means that the caller -** guarantees that z array contains at least n+1 bytes and that the z[n] -** byte has a value of zero. -** This option gives the same output as SQLITE_UTF8, but can be more efficient -** by avoiding the need to make a copy of the input string, in some cases. -** However, if z is allocated to hold fewer than n+1 bytes or if the -** z[n] byte is not zero, undefined behavior may result. -**
          */ #define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ #define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ @@ -5868,7 +5773,6 @@ SQLITE_API int sqlite3_create_window_function( #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ -#define SQLITE_UTF8_ZT 16 /* Zero-terminated UTF8 */ /* ** CAPI3REF: Function Flags @@ -6374,14 +6278,10 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi ** ** There is no limit (other than available memory) on the number of different ** client data pointers (with different names) that can be attached to a -** single database connection. However, the current implementation stores -** the content on a linked list. Insert and retrieval performance will -** be proportional to the number of entries. The design use case, and -** the use case for which the implementation is optimized, is -** that an application will store only small number of client data names, -** typically just one or two. This interface is not intended to be a -** generalized key/value store for thousands or millions of keys. It -** will work for that, but performance might be disappointing. +** single database connection. However, the implementation is optimized +** for the case of having only one or two different client data names. +** Applications and wrapper libraries are discouraged from using more than +** one client data name each. ** ** There is no way to enumerate the client data pointers ** associated with a database connection. The N parameter can be thought @@ -6489,14 +6389,10 @@ typedef void (*sqlite3_destructor_type)(void*); ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. -** ^The sqlite3_result_text64(C,Z,N,D,E) interface sets the return value of an +** ^The sqlite3_result_text64() interface sets the return value of an ** application-defined function to be a text string in an encoding -** specified the E parameter, which must be one -** of [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], -** or [SQLITE_UTF16LE]. ^The special value [SQLITE_UTF8_ZT] means that -** the result text is both UTF-8 and zero-terminated. In other words, -** SQLITE_UTF8_ZT means that the Z array holds at least N+1 byes and that -** the Z[N] is zero. +** specified by the fifth (and last) parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. ** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces @@ -6583,7 +6479,7 @@ SQLITE_API void sqlite3_result_int(sqlite3_context*, int); SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); SQLITE_API void sqlite3_result_null(sqlite3_context*); SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char *z, sqlite3_uint64 n, +SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, void(*)(void*), unsigned char encoding); SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); @@ -7522,7 +7418,7 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The sqlite3_load_extension() interface attempts to load an ** [SQLite extension] library contained in the file zFile. If ** the file cannot be loaded directly, attempts are made to load -** with various operating-system specific filename extensions added. +** with various operating-system specific extensions added. ** So for example, if "samplelib" cannot be loaded, then names like ** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might ** be tried also. @@ -7530,10 +7426,10 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it tries names of the form "sqlite3_X_init" -** where X consists of the lower-case equivalent of all ASCII alphabetic -** characters or all ASCII alphanumeric characters in the filename from -** the last "/" to the first following "." and omitting any initial "lib".)^ +** If that does not work, it constructs a name "sqlite3_X_init" where +** X consists of the lower-case equivalent of all ASCII alphabetic +** characters in the filename from the last "/" to the first following +** "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the @@ -8826,22 +8722,17 @@ SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3*); ** pass the returned value to [sqlite3_free()] to avoid a memory leak. ** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any ** errors were encountered during construction of the string. ^The -** [sqlite3_str_finish(X)] interface might also return a NULL pointer if the +** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the ** string in [sqlite3_str] object X is zero bytes long. -** -** ^The [sqlite3_str_free(X)] interface destroys both the sqlite3_str object -** X and the string content it contains. Calling sqlite3_str_free(X) is -** the equivalent of calling [sqlite3_free](sqlite3_str_finish(X)). */ SQLITE_API char *sqlite3_str_finish(sqlite3_str*); -SQLITE_API void sqlite3_str_free(sqlite3_str*); /* ** CAPI3REF: Add Content To A Dynamic String ** METHOD: sqlite3_str ** -** These interfaces add or remove content to an sqlite3_str object -** previously obtained from [sqlite3_str_new()]. +** These interfaces add content to an sqlite3_str object previously obtained +** from [sqlite3_str_new()]. ** ** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] @@ -8864,10 +8755,6 @@ SQLITE_API void sqlite3_str_free(sqlite3_str*); ** ^The [sqlite3_str_reset(X)] method resets the string under construction ** inside [sqlite3_str] object X back to zero bytes in length. ** -** ^The [sqlite3_str_truncate(X,N)] method changes the length of the string -** under construction to be N bytes are less. This routine is a no-op if -** N is negative or if the string is already N bytes or smaller in size. -** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a ** subsequent call to [sqlite3_str_errcode(X)]. @@ -8878,7 +8765,6 @@ SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn); SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C); SQLITE_API void sqlite3_str_reset(sqlite3_str*); -SQLITE_API void sqlite3_str_truncate(sqlite3_str*,int N); /* ** CAPI3REF: Status Of A Dynamic String @@ -10712,9 +10598,9 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value ** ** a variable pointed to by the "pOut" parameter. ** ** The "flags" parameter must be passed a mask of flags. At present only -** one flag is defined - [SQLITE_SCANSTAT_COMPLEX]. If SQLITE_SCANSTAT_COMPLEX +** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX ** is specified, then status information is available for all elements -** of a query plan that are reported by "[EXPLAIN QUERY PLAN]" output. If +** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If ** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements ** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of ** the EXPLAIN QUERY PLAN output) are available. Invoking API @@ -10728,8 +10614,7 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value ** ** elements used to implement the statement - a non-zero value is returned and ** the variable that pOut points to is unchanged. ** -** See also: [sqlite3_stmt_scanstatus_reset()] and the -** [nexec and ncycle] columnes of the [bytecode virtual table]. +** See also: [sqlite3_stmt_scanstatus_reset()] */ SQLITE_API int sqlite3_stmt_scanstatus( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ @@ -11271,41 +11156,19 @@ SQLITE_API int sqlite3_deserialize( /* ** CAPI3REF: Bind array values to the CARRAY table-valued function ** -** The sqlite3_carray_bind_v2(S,I,P,N,F,X,D) interface binds an array value to -** parameter that is the first argument of the [carray() table-valued function]. -** The S parameter is a pointer to the [prepared statement] that uses the carray() -** functions. I is the parameter index to be bound. I must be the index of the -** parameter that is the first argument to the carray() table-valued function. -** P is a pointer to the array to be bound, and N is the number of elements in -** the array. The F argument is one of constants [SQLITE_CARRAY_INT32], -** [SQLITE_CARRAY_INT64], [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], -** or [SQLITE_CARRAY_BLOB] to indicate the datatype of the array P. -** -** If the X argument is not a NULL pointer or one of the special -** values [SQLITE_STATIC] or [SQLITE_TRANSIENT], then SQLite will invoke -** the function X with argument D when it is finished using the data in P. -** The call to X(D) is a destructor for the array P. The destructor X(D) -** is invoked even if the call to sqlite3_carray_bind() fails. If the X -** parameter is the special-case value [SQLITE_STATIC], then SQLite assumes -** that the data static and the destructor is never invoked. If the X -** parameter is the special-case value [SQLITE_TRANSIENT], then -** sqlite3_carray_bind_v2() makes its own private copy of the data prior -** to returning and never invokes the destructor X. -** -** The sqlite3_carray_bind() function works the same as sqlite_carray_bind_v2() -** with a D parameter set to P. In other words, -** sqlite3_carray_bind(S,I,P,N,F,X) is same as -** sqlite3_carray_bind(S,I,P,N,F,X,P). -*/ -SQLITE_API int sqlite3_carray_bind_v2( - sqlite3_stmt *pStmt, /* Statement to be bound */ - int i, /* Parameter index */ - void *aData, /* Pointer to array data */ - int nData, /* Number of data elements */ - int mFlags, /* CARRAY flags */ - void (*xDel)(void*), /* Destructor for aData */ - void *pDel /* Optional argument to xDel() */ -); +** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to +** one of the first argument of the [carray() table-valued function]. The +** S parameter is a pointer to the [prepared statement] that uses the carray() +** functions. I is the parameter index to be bound. P is a pointer to the +** array to be bound, and N is the number of eements in the array. The +** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to +** indicate the datatype of the array being bound. The X argument is not a +** NULL pointer, then SQLite will invoke the function X on the P parameter +** after it has finished using P, even if the call to +** sqlite3_carray_bind() fails. The special-case finalizer +** SQLITE_TRANSIENT has no effect here. +*/ SQLITE_API int sqlite3_carray_bind( sqlite3_stmt *pStmt, /* Statement to be bound */ int i, /* Parameter index */ diff --git a/deps/sqlite/sqlite3ext.h b/deps/sqlite/sqlite3ext.h index cad1a2a0016041..5258faaed313c7 100644 --- a/deps/sqlite/sqlite3ext.h +++ b/deps/sqlite/sqlite3ext.h @@ -371,11 +371,7 @@ struct sqlite3_api_routines { /* Version 3.51.0 and later */ int (*set_errmsg)(sqlite3*,int,const char*); int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); - /* Version 3.52.0 and later */ - void (*str_truncate)(sqlite3_str*,int); - void (*str_free)(sqlite3_str*); - int (*carray_bind)(sqlite3_stmt*,int,void*,int,int,void(*)(void*)); - int (*carray_bind_v2)(sqlite3_stmt*,int,void*,int,int,void(*)(void*),void*); + }; /* @@ -714,11 +710,6 @@ typedef int (*sqlite3_loadext_entry)( /* Version 3.51.0 and later */ #define sqlite3_set_errmsg sqlite3_api->set_errmsg #define sqlite3_db_status64 sqlite3_api->db_status64 -/* Version 3.52.0 and later */ -#define sqlite3_str_truncate sqlite3_api->str_truncate -#define sqlite3_str_free sqlite3_api->str_free -#define sqlite3_carray_bind sqlite3_api->carray_bind -#define sqlite3_carray_bind_v2 sqlite3_api->carray_bind_v2 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/deps/v8/src/objects/string-inl.h b/deps/v8/src/objects/string-inl.h index 4dfbae264daacf..cfe216b28f331d 100644 --- a/deps/v8/src/objects/string-inl.h +++ b/deps/v8/src/objects/string-inl.h @@ -1226,6 +1226,12 @@ size_t String::Utf8Length(Isolate* isolate, DirectHandle string) { reinterpret_cast(vec.begin()), vec.size()); } + base::Vector vec = content.ToUC16Vector(); + const char16_t* data = reinterpret_cast(vec.begin()); + if (simdutf::validate_utf16(data, vec.size())) { + return simdutf::utf8_length_from_utf16(data, vec.size()); + } + // TODO(419496232): Use simdutf once upstream bug is resolved. size_t utf8_length = 0; uint16_t last_character = unibrow::Utf16::kNoPreviousCharacter; diff --git a/deps/v8/src/wasm/constant-expression-interface.cc b/deps/v8/src/wasm/constant-expression-interface.cc index 0bd4c138e0d357..cf6d577e168bcc 100644 --- a/deps/v8/src/wasm/constant-expression-interface.cc +++ b/deps/v8/src/wasm/constant-expression-interface.cc @@ -40,7 +40,17 @@ void ConstantExpressionInterface::S128Const(FullDecoder* decoder, const Simd128Immediate& imm, Value* result) { if (!generate_value()) return; +#if V8_TARGET_BIG_ENDIAN + // Globals are not little endian enforced, they use native byte order and we + // need to reverse the bytes on big endian platforms. + uint8_t value[kSimd128Size]; + for (int i = 0; i < kSimd128Size; i++) { + value[i] = imm.value[kSimd128Size - 1 - i]; + } + result->runtime_value = WasmValue(value, kWasmS128); +#else result->runtime_value = WasmValue(imm.value, kWasmS128); +#endif } void ConstantExpressionInterface::UnOp(FullDecoder* decoder, WasmOpcode opcode, diff --git a/doc/api/async_context.md b/doc/api/async_context.md index dd3b6a834ec950..b72934a25ea802 100644 --- a/doc/api/async_context.md +++ b/doc/api/async_context.md @@ -386,6 +386,110 @@ try { } ``` +### `asyncLocalStorage.withScope(store)` + + + +> Stability: 1 - Experimental + +* `store` {any} +* Returns: {RunScope} + +Creates a disposable scope that enters the given store and automatically +restores the previous store value when the scope is disposed. This method is +designed to work with JavaScript's explicit resource management (`using` syntax). + +Example: + +```mjs +import { AsyncLocalStorage } from 'node:async_hooks'; + +const asyncLocalStorage = new AsyncLocalStorage(); + +{ + using _ = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store +} + +console.log(asyncLocalStorage.getStore()); // Prints: undefined +``` + +```cjs +const { AsyncLocalStorage } = require('node:async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +{ + using _ = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store +} + +console.log(asyncLocalStorage.getStore()); // Prints: undefined +``` + +The `withScope()` method is particularly useful for managing context in +synchronous code where you want to ensure the previous store value is restored +when exiting a block, even if an error is thrown. + +```mjs +import { AsyncLocalStorage } from 'node:async_hooks'; + +const asyncLocalStorage = new AsyncLocalStorage(); + +try { + using _ = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store + throw new Error('test'); +} catch (e) { + // Store is automatically restored even after error + console.log(asyncLocalStorage.getStore()); // Prints: undefined +} +``` + +```cjs +const { AsyncLocalStorage } = require('node:async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +try { + using _ = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store + throw new Error('test'); +} catch (e) { + // Store is automatically restored even after error + console.log(asyncLocalStorage.getStore()); // Prints: undefined +} +``` + +**Important:** When using `withScope()` in async functions before the first +`await`, be aware that the scope change will affect the caller's context. The +synchronous portion of an async function (before the first `await`) runs +immediately when called, and when it reaches the first `await`, it returns the +promise to the caller. At that point, the scope change becomes visible in the +caller's context and will persist in subsequent synchronous code until something +else changes the scope value. For async operations, prefer using `run()` which +properly isolates context across async boundaries. + +```mjs +import { AsyncLocalStorage } from 'node:async_hooks'; + +const asyncLocalStorage = new AsyncLocalStorage(); + +async function example() { + using _ = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store + await someAsyncOperation(); // Function pauses here and returns promise + console.log(asyncLocalStorage.getStore()); // Prints: my-store +} + +// Calling without await +example(); // Synchronous portion runs, then pauses at first await +// After the promise is returned, the scope 'my-store' is now active in caller! +console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!) +``` + ### Usage with `async/await` If, within an async function, only one `await` call is to run within a context, @@ -420,6 +524,64 @@ of `asyncLocalStorage.getStore()` after the calls you suspect are responsible for the loss. When the code logs `undefined`, the last callback called is probably responsible for the context loss. +## Class: `RunScope` + + + +> Stability: 1 - Experimental + +A disposable scope returned by [`asyncLocalStorage.withScope()`][] that +automatically restores the previous store value when disposed. This class +implements the [Explicit Resource Management][] protocol and is designed to work +with JavaScript's `using` syntax. + +The scope automatically restores the previous store value when the `using` block +exits, whether through normal completion or by throwing an error. + +### `scope.dispose()` + + + +Explicitly ends the scope and restores the previous store value. This method +is idempotent: calling it multiple times has the same effect as calling it once. + +The `[Symbol.dispose]()` method defers to `dispose()`. + +If `withScope()` is called without the `using` keyword, `dispose()` must be +called manually to restore the previous store value. Forgetting to call +`dispose()` will cause the store value to persist for the remainder of the +current execution context: + +```mjs +import { AsyncLocalStorage } from 'node:async_hooks'; + +const storage = new AsyncLocalStorage(); + +// Without using, the scope must be disposed manually +const scope = storage.withScope('my-store'); +// storage.getStore() === 'my-store' here + +scope.dispose(); // Restore previous value +// storage.getStore() === undefined here +``` + +```cjs +const { AsyncLocalStorage } = require('node:async_hooks'); + +const storage = new AsyncLocalStorage(); + +// Without using, the scope must be disposed manually +const scope = storage.withScope('my-store'); +// storage.getStore() === 'my-store' here + +scope.dispose(); // Restore previous value +// storage.getStore() === undefined here +``` + ## Class: `AsyncResource` + +> Stability: 1 - Experimental + +Enable the experimental [`node:stream/iter`][] module. + ### `--experimental-test-coverage` @@ -4237,6 +4258,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12 [`import` specifier]: esm.md#import-specifiers [`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout [`node:sqlite`]: sqlite.md +[`node:stream/iter`]: stream_iter.md [`process.setUncaughtExceptionCaptureCallback()`]: process.md#processsetuncaughtexceptioncapturecallbackfn [`tls.DEFAULT_MAX_VERSION`]: tls.md#tlsdefault_max_version [`tls.DEFAULT_MIN_VERSION`]: tls.md#tlsdefault_min_version diff --git a/doc/api/debugger.md b/doc/api/debugger.md index 03e9cd9185027e..ce25c927d8ef81 100644 --- a/doc/api/debugger.md +++ b/doc/api/debugger.md @@ -260,13 +260,5 @@ For help, see: https://nodejs.org/en/docs/inspector at the end of the URL is generated on the fly, it varies in different debugging sessions.) -If the Chrome browser is older than 66.0.3345.0, -use `inspector.html` instead of `js_app.html` in the above URL. - -Chrome DevTools doesn't support debugging [worker threads][] yet. -[ndb][] can be used to debug them. - [Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/ [`debugger`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger -[ndb]: https://github.com/GoogleChromeLabs/ndb/ -[worker threads]: worker_threads.md diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index 1e98f24d478644..f72d9ddb9bf2d4 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -4164,7 +4164,7 @@ Type: Documentation-only `process.features.tls_alpn`, `process.features.tls_ocsp`, and `process.features.tls_sni` are deprecated, as their values are guaranteed to be identical to that of `process.features.tls`. -### DEP0190: Passing `args` to `node:child_process` `execFile`/`spawn` with `shell` option `true` +### DEP0190: Passing `args` to `node:child_process` `execFile`/`spawn` with `shell` option + +Type: Documentation-only + +Passing a [`CryptoKey`][] to `node:crypto` functions is deprecated and +will throw an error in a future version. This includes +[`crypto.createPublicKey()`][], [`crypto.createPrivateKey()`][], +[`crypto.sign()`][], [`crypto.verify()`][], +[`crypto.publicEncrypt()`][], [`crypto.publicDecrypt()`][], +[`crypto.privateEncrypt()`][], [`crypto.privateDecrypt()`][], +[`Sign.prototype.sign()`][], [`Verify.prototype.verify()`][], +[`crypto.createHmac()`][], [`crypto.createCipheriv()`][], +[`crypto.createDecipheriv()`][], [`crypto.encapsulate()`][], and +[`crypto.decapsulate()`][]. + +### DEP0204: `KeyObject.from()` with non-extractable `CryptoKey` + + + +Type: Documentation-only + +Passing a non-extractable [`CryptoKey`][] to [`KeyObject.from()`][] is +deprecated and will throw an error in a future version. + +### DEP0205: `module.register()` + + + +Type: Documentation-only + +[`module.register()`][] is deprecated. Use [`module.registerHooks()`][] +instead. + +The `module.register()` API provides off-thread async hooks for customizing ES modules; +the `module.registerHooks()` API provides similar hooks that are synchronous, in-thread, and +work for all types of modules. +Supporting async hooks has proven to be complex, involving worker threads orchestration, and there are issues +that have proven unresolveable. See [caveats of asynchronous customization hooks][]. Please migrate to +`module.registerHooks()` as soon as possible as `module.register()` will be +removed in a future version of Node.js. + [DEP0142]: #dep0142-repl_builtinlibs [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3 @@ -4477,14 +4536,18 @@ const server = http2.createSecureServer({ [`Buffer.from(buffer)`]: buffer.md#static-method-bufferfrombuffer [`Buffer.isBuffer()`]: buffer.md#static-method-bufferisbufferobj [`Cipheriv`]: crypto.md#class-cipheriv +[`CryptoKey`]: webcrypto.md#class-cryptokey [`Decipheriv`]: crypto.md#class-decipheriv [`Duplex.toWeb()`]: stream.md#streamduplextowebstreamduplex-options [`Error.isError`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError +[`KeyObject.from()`]: crypto.md#static-method-keyobjectfromkey [`REPLServer.clearBufferedCommand()`]: repl.md#replserverclearbufferedcommand [`ReadStream.open()`]: fs.md#class-fsreadstream [`Server.getConnections()`]: net.md#servergetconnectionscallback [`Server.listen({fd: })`]: net.md#serverlistenhandle-backlog-callback +[`Sign.prototype.sign()`]: crypto.md#signsignprivatekey-outputencoding [`String.prototype.toWellFormed`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toWellFormed +[`Verify.prototype.verify()`]: crypto.md#verifyverifyobject-signature-signatureencoding [`WriteStream.open()`]: fs.md#class-fswritestream [`assert`]: assert.md [`asyncResource.runInAsyncScope()`]: async_context.md#asyncresourceruninasyncscopefn-thisarg-args @@ -4502,11 +4565,21 @@ const server = http2.createSecureServer({ [`crypto.createDecipheriv()`]: crypto.md#cryptocreatedecipherivalgorithm-key-iv-options [`crypto.createHash()`]: crypto.md#cryptocreatehashalgorithm-options [`crypto.createHmac()`]: crypto.md#cryptocreatehmacalgorithm-key-options +[`crypto.createPrivateKey()`]: crypto.md#cryptocreateprivatekeykey +[`crypto.createPublicKey()`]: crypto.md#cryptocreatepublickeykey +[`crypto.decapsulate()`]: crypto.md#cryptodecapsulatekey-ciphertext-callback +[`crypto.encapsulate()`]: crypto.md#cryptoencapsulatekey-callback [`crypto.fips`]: crypto.md#cryptofips [`crypto.pbkdf2()`]: crypto.md#cryptopbkdf2password-salt-iterations-keylen-digest-callback +[`crypto.privateDecrypt()`]: crypto.md#cryptoprivatedecryptprivatekey-buffer +[`crypto.privateEncrypt()`]: crypto.md#cryptoprivateencryptprivatekey-buffer +[`crypto.publicDecrypt()`]: crypto.md#cryptopublicdecryptkey-buffer +[`crypto.publicEncrypt()`]: crypto.md#cryptopublicencryptkey-buffer [`crypto.randomBytes()`]: crypto.md#cryptorandombytessize-callback [`crypto.scrypt()`]: crypto.md#cryptoscryptpassword-salt-keylen-options-callback [`crypto.setEngine()`]: crypto.md#cryptosetengineengine-flags +[`crypto.sign()`]: crypto.md#cryptosignalgorithm-data-key-callback +[`crypto.verify()`]: crypto.md#cryptoverifyalgorithm-data-key-signature-callback [`decipher.final()`]: crypto.md#decipherfinaloutputencoding [`decipher.setAuthTag()`]: crypto.md#deciphersetauthtagbuffer-encoding [`dirent.parentPath`]: fs.md#direntparentpath @@ -4550,6 +4623,8 @@ const server = http2.createSecureServer({ [`message.trailersDistinct`]: http.md#messagetrailersdistinct [`message.trailers`]: http.md#messagetrailers [`module.createRequire()`]: module.md#modulecreaterequirefilename +[`module.register()`]: module.md#moduleregisterspecifier-parenturl-options +[`module.registerHooks()`]: module.md#moduleregisterhooksoptions [`os.networkInterfaces()`]: os.md#osnetworkinterfaces [`os.tmpdir()`]: os.md#ostmpdir [`process.env`]: process.md#processenv @@ -4603,6 +4678,7 @@ const server = http2.createSecureServer({ [`zlib.bytesWritten`]: zlib.md#zlibbyteswritten [alloc]: buffer.md#static-method-bufferallocsize-fill-encoding [alloc_unsafe_size]: buffer.md#static-method-bufferallocunsafesize +[caveats of asynchronous customization hooks]: module.md#caveats-of-asynchronous-customization-hooks [from_arraybuffer]: buffer.md#static-method-bufferfromarraybuffer-byteoffset-length [from_string_encoding]: buffer.md#static-method-bufferfromstring-encoding [legacy URL API]: url.md#legacy-url-api diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index 4587ee649b5173..43496b75e2ebc2 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1447,6 +1447,50 @@ Emitted when [`child_process.spawn()`][] encounters an error. Emitted when [`process.execve()`][] is invoked. +#### Web Locks + +> Stability: 1 - Experimental + + + +These channels are emitted for each [`locks.request()`][] call. See +[`worker_threads.locks`][] for details on Web Locks. + +##### Event: `'locks.request.start'` + +* `name` {string} The name of the requested lock resource. +* `mode` {string} The lock mode: `'exclusive'` or `'shared'`. + +Emitted when a lock request is initiated, before the lock is granted. + +##### Event: `'locks.request.grant'` + +* `name` {string} The name of the requested lock resource. +* `mode` {string} The lock mode: `'exclusive'` or `'shared'`. + +Emitted when a lock is successfully granted and the callback is about to run. + +##### Event: `'locks.request.miss'` + +* `name` {string} The name of the requested lock resource. +* `mode` {string} The lock mode: `'exclusive'` or `'shared'`. + +Emitted when `ifAvailable` is `true` and the lock is not immediately available, +and the request callback is invoked with `null` instead of a `Lock` object. + +##### Event: `'locks.request.end'` + +* `name` {string} The name of the requested lock resource. +* `mode` {string} The lock mode: `'exclusive'` or `'shared'`. +* `steal` {boolean} Whether the request uses steal semantics. +* `ifAvailable` {boolean} Whether the request uses ifAvailable semantics. +* `error` {Error|undefined} The error thrown by the callback, if any. + +Emitted when a lock request has finished, whether the callback succeeded, +threw an error, or the lock was stolen. + #### Worker Thread > Stability: 1 - Experimental @@ -1476,7 +1520,9 @@ Emitted when a new thread is created. [`diagnostics_channel.tracingChannel()`]: #diagnostics_channeltracingchannelnameorchannels [`end` event]: #endevent [`error` event]: #errorevent +[`locks.request()`]: worker_threads.md#locksrequestname-options-callback [`net.Server.listen()`]: net.md#serverlisten [`process.execve()`]: process.md#processexecvefile-args-env [`start` event]: #startevent +[`worker_threads.locks`]: worker_threads.md#worker_threadslocks [context loss]: async_context.md#troubleshooting-context-loss diff --git a/doc/api/environment_variables.md b/doc/api/environment_variables.md index 6e114c1b9dcce7..ebaa5c9f5e86ef 100644 --- a/doc/api/environment_variables.md +++ b/doc/api/environment_variables.md @@ -145,7 +145,7 @@ There following two functions allow you to directly interact with `.env` files: * [`process.loadEnvFile`][] loads an `.env` file and populates `process.env` with its variables -* [`util.parseEnv`][] parses the row content of an `.env` file and returns its value in an object +* [`util.parseEnv`][] parses the raw content of an `.env` file and returns its value in an object [CLI Environment Variables documentation]: cli.md#environment-variables_1 [`--env-file-if-exists=file`]: cli.md#--env-file-if-existsfile diff --git a/doc/api/esm.md b/doc/api/esm.md index 5396acfd53d65c..f6ac6f457b8947 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -809,6 +809,15 @@ imports and they cannot be inspected via `WebAssembly.Module.imports(mod)` or virtualized unless recompiling the module using the direct `WebAssembly.compile` API with string builtins disabled. +String constants may also be imported from the `wasm:js/string-constants` builtin +import URL, allowing static JS string globals to be defined: + +```text +(module + (import "wasm:js/string-constants" "hello" (global $hello externref)) +) +``` + Importing a module in the source phase before it has been instantiated will also use the compile-time builtins automatically: diff --git a/doc/api/fs.md b/doc/api/fs.md index 66d29bc80fbf18..02c1e26a57a615 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -377,6 +377,154 @@ added: v10.0.0 * Type: {number} The numeric file descriptor managed by the {FileHandle} object. +#### `filehandle.pull([...transforms][, options])` + + + +> Stability: 1 - Experimental + +* `...transforms` {Function|Object} Optional transforms to apply via + [`stream/iter pull()`][]. +* `options` {Object} + * `signal` {AbortSignal} + * `autoClose` {boolean} Close the file handle when the stream ends. + **Default:** `false`. + * `start` {number} Byte offset to begin reading from. When specified, + reads use explicit positioning (`pread` semantics). **Default:** current + file position. + * `limit` {number} Maximum number of bytes to read before ending the + iterator. Reads stop when `limit` bytes have been delivered or EOF is + reached, whichever comes first. **Default:** read until EOF. + * `chunkSize` {number} Size in bytes of the buffer allocated for each + read operation. **Default:** `131072` (128 KB). +* Returns: {AsyncIterable\} + +Return the file contents as an async iterable using the +[`node:stream/iter`][] pull model. Reads are performed in `chunkSize`-byte +chunks (default 128 KB). If transforms are provided, they are applied +via [`stream/iter pull()`][]. + +The file handle is locked while the iterable is being consumed and unlocked +when iteration completes, an error occurs, or the consumer breaks. + +This function is only available when the `--experimental-stream-iter` flag is +enabled. + +```mjs +import { open } from 'node:fs/promises'; +import { text } from 'node:stream/iter'; +import { compressGzip } from 'node:zlib/iter'; + +const fh = await open('input.txt', 'r'); + +// Read as text +console.log(await text(fh.pull({ autoClose: true }))); + +// Read 1 KB starting at byte 100 +const fh2 = await open('input.txt', 'r'); +console.log(await text(fh2.pull({ start: 100, limit: 1024, autoClose: true }))); + +// Read with compression +const fh3 = await open('input.txt', 'r'); +const compressed = fh3.pull(compressGzip(), { autoClose: true }); +``` + +```cjs +const { open } = require('node:fs/promises'); +const { text } = require('node:stream/iter'); +const { compressGzip } = require('node:zlib/iter'); + +async function run() { + const fh = await open('input.txt', 'r'); + + // Read as text + console.log(await text(fh.pull({ autoClose: true }))); + + // Read 1 KB starting at byte 100 + const fh2 = await open('input.txt', 'r'); + console.log(await text(fh2.pull({ start: 100, limit: 1024, autoClose: true }))); + + // Read with compression + const fh3 = await open('input.txt', 'r'); + const compressed = fh3.pull(compressGzip(), { autoClose: true }); +} + +run().catch(console.error); +``` + +#### `filehandle.pullSync([...transforms][, options])` + + + +> Stability: 1 - Experimental + +* `...transforms` {Function|Object} Optional transforms to apply via + [`stream/iter pullSync()`][]. +* `options` {Object} + * `autoClose` {boolean} Close the file handle when the stream ends. + **Default:** `false`. + * `start` {number} Byte offset to begin reading from. When specified, + reads use explicit positioning. **Default:** current file position. + * `limit` {number} Maximum number of bytes to read before ending the + iterator. **Default:** read until EOF. + * `chunkSize` {number} Size in bytes of the buffer allocated for each + read operation. **Default:** `131072` (128 KB). +* Returns: {Iterable\} + +Synchronous counterpart of [`filehandle.pull()`][]. Returns a sync iterable +that reads the file using synchronous I/O on the main thread. Reads are +performed in `chunkSize`-byte chunks (default 128 KB). + +The file handle is locked while the iterable is being consumed. Unlike the +async `pull()`, this method does not support `AbortSignal` since all +operations are synchronous. + +This function is only available when the `--experimental-stream-iter` flag is +enabled. + +```mjs +import { open } from 'node:fs/promises'; +import { textSync, pipeToSync } from 'node:stream/iter'; +import { compressGzipSync, decompressGzipSync } from 'node:zlib/iter'; + +const fh = await open('input.txt', 'r'); + +// Read as text (sync) +console.log(textSync(fh.pullSync({ autoClose: true }))); + +// Sync compress pipeline: file -> gzip -> file +const src = await open('input.txt', 'r'); +const dst = await open('output.gz', 'w'); +pipeToSync(src.pullSync(compressGzipSync(), { autoClose: true }), dst.writer({ autoClose: true })); +``` + +```cjs +const { open } = require('node:fs/promises'); +const { textSync, pipeToSync } = require('node:stream/iter'); +const { compressGzipSync, decompressGzipSync } = require('node:zlib/iter'); + +async function run() { + const fh = await open('input.txt', 'r'); + + // Read as text (sync) + console.log(textSync(fh.pullSync({ autoClose: true }))); + + // Sync compress pipeline: file -> gzip -> file + const src = await open('input.txt', 'r'); + const dst = await open('output.gz', 'w'); + pipeToSync( + src.pullSync(compressGzipSync(), { autoClose: true }), + dst.writer({ autoClose: true }), + ); +} + +run().catch(console.error); +``` + #### `filehandle.read(buffer, offset, length, position)` + +> Stability: 1 - Experimental + +* `options` {Object} + * `autoClose` {boolean} Close the file handle when the writer ends or + fails. **Default:** `false`. + * `start` {number} Byte offset to start writing at. When specified, + writes use explicit positioning. **Default:** current file position. + * `limit` {number} Maximum number of bytes the writer will accept. + Async writes (`write()`, `writev()`) that would exceed the limit reject + with `ERR_OUT_OF_RANGE`. Sync writes (`writeSync()`, `writevSync()`) + return `false`. **Default:** no limit. + * `chunkSize` {number} Maximum chunk size in bytes for synchronous write + operations. Writes larger than this threshold fall back to async I/O. + Set this to match the reader's `chunkSize` for optimal `pipeTo()` + performance. **Default:** `131072` (128 KB). +* Returns: {Object} + * `write(chunk[, options])` {Function} Returns {Promise\}. + Accepts `Uint8Array`, `Buffer`, or string (UTF-8 encoded). + * `chunk` {Buffer|TypedArray|DataView|string} + * `options` {Object} + * `signal` {AbortSignal} If the signal is already aborted, the write + rejects with `AbortError` without performing I/O. + * `writev(chunks[, options])` {Function} Returns {Promise\}. Uses + scatter/gather I/O via a single `writev()` syscall. Accepts mixed + `Uint8Array`/string arrays. + * `chunks` {Array\} + * `options` {Object} + * `signal` {AbortSignal} If the signal is already aborted, the write + rejects with `AbortError` without performing I/O. + * `writeSync(chunk)` {Function} Returns {boolean}. Attempts a synchronous + write. Returns `true` if the write succeeded, `false` if the caller + should fall back to async `write()`. Returns `false` when: the writer + is closed/errored, an async operation is in flight, the chunk exceeds + `chunkSize`, or the write would exceed `limit`. + * `chunk` {Buffer|TypedArray|DataView|string} + * `writevSync(chunks)` {Function} Returns {boolean}. Synchronous batch + write. Same fallback semantics as `writeSync()`. + * `chunks` {Array\} + * `end([options])` {Function} Returns {Promise\} total bytes + written. Idempotent: returns `totalBytesWritten` if already closed, + returns the pending promise if already closing. Rejects if the writer + is in an errored state. + * `options` {Object} + * `signal` {AbortSignal} If the signal is already aborted, `end()` + rejects with `AbortError` and the writer remains open. + * `endSync()` {Function} Returns {number|number} total bytes written on + success, `-1` if the writer is errored or an async operation is in + flight. Idempotent when already closed. + * `fail(reason)` {Function} Puts the writer into a terminal error state. + Synchronous. If the writer is already closed or errored, this is a + no-op. If `autoClose` is true, closes the file handle synchronously. + +Return a [`node:stream/iter`][] writer backed by this file handle. + +The writer supports both `Symbol.asyncDispose` and `Symbol.dispose`: + +* `await using w = fh.writer()` — if the writer is still open (no `end()` + called), `asyncDispose` calls `fail()`. If `end()` is pending, it waits + for it to complete. +* `using w = fh.writer()` — calls `fail()` unconditionally. + +The `writeSync()` and `writevSync()` methods enable the try-sync fast path +used by [`stream/iter pipeTo()`][]. When the reader's chunk size matches the +writer's `chunkSize`, all writes in a `pipeTo()` pipeline complete +synchronously with zero promise overhead. + +This function is only available when the `--experimental-stream-iter` flag is +enabled. + +```mjs +import { open } from 'node:fs/promises'; +import { from, pipeTo } from 'node:stream/iter'; +import { compressGzip } from 'node:zlib/iter'; + +// Async pipeline +const fh = await open('output.gz', 'w'); +await pipeTo(from('Hello!'), compressGzip(), fh.writer({ autoClose: true })); + +// Sync pipeline with limit +const src = await open('input.txt', 'r'); +const dst = await open('output.txt', 'w'); +const w = dst.writer({ limit: 1024 * 1024 }); // Max 1 MB +await pipeTo(src.pull({ autoClose: true }), w); +await w.end(); +await dst.close(); +``` + +```cjs +const { open } = require('node:fs/promises'); +const { from, pipeTo } = require('node:stream/iter'); +const { compressGzip } = require('node:zlib/iter'); + +async function run() { + // Async pipeline + const fh = await open('output.gz', 'w'); + await pipeTo(from('Hello!'), compressGzip(), fh.writer({ autoClose: true })); + + // Sync pipeline with limit + const src = await open('input.txt', 'r'); + const dst = await open('output.txt', 'w'); + const w = dst.writer({ limit: 1024 * 1024 }); // Max 1 MB + await pipeTo(src.pull({ autoClose: true }), w); + await w.end(); + await dst.close(); +} + +run().catch(console.error); +``` + #### `filehandle[Symbol.asyncDispose]()` -> Stability: 1.1 - Active development +> Stability: 0 - Deprecated: Use [`module.registerHooks()`][] instead. * `specifier` {string|URL} Customization hooks to be registered; this should be the same string that would be passed to `import()`, except that if it is diff --git a/doc/api/packages.md b/doc/api/packages.md index 02bf9664dae0b1..10cc263dbbdff0 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -1010,7 +1010,7 @@ added: v0.4.0 The `"main"` field defines the entry point of a package when imported by name via a `node_modules` lookup. Its value is a path. -When a package has an [`"exports"`][] field, this will take precedence over the +The [`"exports"`][] field, if it exists, takes precedence over the `"main"` field when importing the package by name. It also defines the script that is used when the [package directory is loaded diff --git a/doc/api/process.md b/doc/api/process.md index 20488287920a95..e2a17da1c3d88d 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -736,6 +736,44 @@ generate a core file. This feature is not available in [`Worker`][] threads. +## `process.addUncaughtExceptionCaptureCallback(fn)` + + + +> Stability: 1 - Experimental + +* `fn` {Function} + +The `process.addUncaughtExceptionCaptureCallback()` function adds a callback +that will be invoked when an uncaught exception occurs, receiving the exception +value as its first argument. + +Unlike [`process.setUncaughtExceptionCaptureCallback()`][], this function allows +multiple callbacks to be registered and does not conflict with the +[`domain`][] module. Callbacks are called in reverse order of registration +(most recent first). If a callback returns `true`, subsequent callbacks +and the default uncaught exception handling are skipped. + +```mjs +import process from 'node:process'; + +process.addUncaughtExceptionCaptureCallback((err) => { + console.error('Caught exception:', err.message); + return true; // Indicates exception was handled +}); +``` + +```cjs +const process = require('node:process'); + +process.addUncaughtExceptionCaptureCallback((err) => { + console.error('Caught exception:', err.message); + return true; // Indicates exception was handled +}); +``` + ## `process.allowedNodeEnvironmentFlags` * `fn` {Function|null} @@ -4034,8 +4077,8 @@ To unset the capture function, method with a non-`null` argument while another capture function is set will throw an error. -Using this function is mutually exclusive with using the deprecated -[`domain`][] built-in module. +To register multiple callbacks that can coexist, use +[`process.addUncaughtExceptionCaptureCallback()`][] instead. ## `process.sourceMapsEnabled` @@ -4567,6 +4610,7 @@ cases: [`net.Socket`]: net.md#class-netsocket [`os.constants.dlopen`]: os.md#dlopen-constants [`postMessageToThread()`]: worker_threads.md#worker_threadspostmessagetothreadthreadid-value-transferlist-timeout +[`process.addUncaughtExceptionCaptureCallback()`]: #processadduncaughtexceptioncapturecallbackfn [`process.argv`]: #processargv [`process.config`]: #processconfig [`process.execPath`]: #processexecpath diff --git a/doc/api/quic.md b/doc/api/quic.md index 723c26c5bd0b99..0c41b1e5c0d247 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -519,7 +519,7 @@ added: v23.8.0 Sends an unreliable datagram to the remote peer, returning the datagram ID. If the datagram payload is specified as an `ArrayBufferView`, then ownership of -that view will be transfered to the underlying stream. +that view will be transferred to the underlying stream. ### `session.stats` @@ -1197,9 +1197,13 @@ True to enable TLS keylogging output. -* Type: {KeyObject|CryptoKey|KeyObject\[]|CryptoKey\[]} +* Type: {KeyObject|KeyObject\[]} The TLS crypto keys to use for sessions. diff --git a/doc/api/repl.md b/doc/api/repl.md index 0ebd5ca4e6f465..3c61adb450fd71 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -709,6 +709,9 @@ npx codemod@latest @nodejs/repl-builtin-modules + +> Stability: 1 - Experimental + + + +The `node:stream/iter` module provides a streaming API built on iterables +rather than the event-driven `Readable`/`Writable`/`Transform` class hierarchy, +or the Web Streams `ReadableStream`/`WritableStream`/`TransformStream` interfaces. + +This module is available only when the `--experimental-stream-iter` CLI flag +is enabled. + +Streams are represented as `AsyncIterable` (async) or +`Iterable` (sync). There are no base classes to extend -- any +object implementing the iterable protocol can participate. Transforms are plain +functions or objects with a `transform` method. + +Data flows in **batches** (`Uint8Array[]` per iteration) to amortize the cost +of async operations. + +```mjs +import { from, pull, text } from 'node:stream/iter'; +import { compressGzip, decompressGzip } from 'node:zlib/iter'; + +// Compress and decompress a string +const compressed = pull(from('Hello, world!'), compressGzip()); +const result = await text(pull(compressed, decompressGzip())); +console.log(result); // 'Hello, world!' +``` + +```cjs +const { from, pull, text } = require('node:stream/iter'); +const { compressGzip, decompressGzip } = require('node:zlib/iter'); + +async function run() { + // Compress and decompress a string + const compressed = pull(from('Hello, world!'), compressGzip()); + const result = await text(pull(compressed, decompressGzip())); + console.log(result); // 'Hello, world!' +} + +run().catch(console.error); +``` + +```mjs +import { open } from 'node:fs/promises'; +import { text, pipeTo } from 'node:stream/iter'; +import { compressGzip, decompressGzip } from 'node:zlib/iter'; + +// Read a file, compress, write to another file +const src = await open('input.txt', 'r'); +const dst = await open('output.gz', 'w'); +await pipeTo(src.pull(), compressGzip(), dst.writer({ autoClose: true })); +await src.close(); + +// Read it back +const gz = await open('output.gz', 'r'); +console.log(await text(gz.pull(decompressGzip(), { autoClose: true }))); +``` + +```cjs +const { open } = require('node:fs/promises'); +const { text, pipeTo } = require('node:stream/iter'); +const { compressGzip, decompressGzip } = require('node:zlib/iter'); + +async function run() { + // Read a file, compress, write to another file + const src = await open('input.txt', 'r'); + const dst = await open('output.gz', 'w'); + await pipeTo(src.pull(), compressGzip(), dst.writer({ autoClose: true })); + await src.close(); + + // Read it back + const gz = await open('output.gz', 'r'); + console.log(await text(gz.pull(decompressGzip(), { autoClose: true }))); +} + +run().catch(console.error); +``` + +## Concepts + +### Byte streams + +All data in this API is represented as `Uint8Array` bytes. Strings +are automatically UTF-8 encoded when passed to `from()`, `push()`, or +`pipeTo()`. This removes ambiguity around encodings and enables zero-copy +transfers between streams and native code. + +### Batching + +Each iteration yields a **batch** -- an array of `Uint8Array` chunks +(`Uint8Array[]`). Batching amortizes the cost of `await` and Promise creation +across multiple chunks. A consumer that processes one chunk at a time can +simply iterate the inner array: + +```mjs +for await (const batch of source) { + for (const chunk of batch) { + handle(chunk); + } +} +``` + +```cjs +async function run() { + for await (const batch of source) { + for (const chunk of batch) { + handle(chunk); + } + } +} +``` + +### Transforms + +Transforms come in two forms: + +* **Stateless** -- a function `(chunks, options) => result` called once per + batch. Receives `Uint8Array[]` (or `null` as the flush signal) and an + `options` object. Returns `Uint8Array[]`, `null`, or an iterable of chunks. + +* **Stateful** -- an object `{ transform(source, options) }` where `transform` + is a generator (sync or async) that receives the entire upstream iterable + and an `options` object, and yields output. This form is used for + compression, encryption, and any transform that needs to buffer across + batches. + +Both forms receive an `options` parameter with the following property: + +* `options.signal` {AbortSignal} An AbortSignal that fires when the pipeline + is cancelled, encounters an error, or the consumer stops reading. Transforms + can check `signal.aborted` or listen for the `'abort'` event to perform + early cleanup. + +The flush signal (`null`) is sent after the source ends, giving transforms +a chance to emit trailing data (e.g., compression footers). + +```js +// Stateless: uppercase transform +const upper = (chunks) => { + if (chunks === null) return null; // flush + return chunks.map((c) => new TextEncoder().encode( + new TextDecoder().decode(c).toUpperCase(), + )); +}; + +// Stateful: line splitter +const lines = { + transform: async function*(source) { + let partial = ''; + for await (const chunks of source) { + if (chunks === null) { + if (partial) yield [new TextEncoder().encode(partial)]; + continue; + } + for (const chunk of chunks) { + const str = partial + new TextDecoder().decode(chunk); + const parts = str.split('\n'); + partial = parts.pop(); + for (const line of parts) { + yield [new TextEncoder().encode(`${line}\n`)]; + } + } + } + }, +}; +``` + +### Pull vs. push + +The API supports two models: + +* **Pull** -- data flows on demand. `pull()` and `pullSync()` create lazy + pipelines that only read from the source when the consumer iterates. + +* **Push** -- data is written explicitly. `push()` creates a writer/readable + pair with backpressure. The writer pushes data in; the readable is consumed + as an async iterable. + +### Backpressure + +Pull streams have natural backpressure -- the consumer drives the pace, so +the source is never read faster than the consumer can process. Push streams +need explicit backpressure because the producer and consumer run +independently. The `highWaterMark` and `backpressure` options on `push()`, +`broadcast()`, and `share()` control how this works. + +#### The two-buffer model + +Push streams use a two-part buffering system. Think of it like a bucket +(slots) being filled through a hose (pending writes), with a float valve +that closes when the bucket is full: + +```text + highWaterMark (e.g., 3) + | + Producer v + | +---------+ + v | | + [ write() ] ----+ +--->| slots |---> Consumer pulls + [ write() ] | | | (bucket)| for await (...) + [ write() ] v | +---------+ + +--------+ ^ + | pending| | + | writes | float valve + | (hose) | (backpressure) + +--------+ + ^ + | + 'strict' mode limits this too! +``` + +* **Slots (the bucket)** -- data ready for the consumer, capped at + `highWaterMark`. When the consumer pulls, it drains all slots at once + into a single batch. + +* **Pending writes (the hose)** -- writes waiting for slot space. After + the consumer drains, pending writes are promoted into the now-empty + slots and their promises resolve. + +How each policy uses these buffers: + +| Policy | Slots limit | Pending writes limit | +| --------------- | --------------- | -------------------- | +| `'strict'` | `highWaterMark` | `highWaterMark` | +| `'block'` | `highWaterMark` | Unbounded | +| `'drop-oldest'` | `highWaterMark` | N/A (never waits) | +| `'drop-newest'` | `highWaterMark` | N/A (never waits) | + +#### Strict (default) + +Strict mode catches "fire-and-forget" patterns where the producer calls +`write()` without awaiting, which would cause unbounded memory growth. +It limits both the slots buffer and the pending writes queue to +`highWaterMark`. + +If you properly await each write, you can only ever have one pending +write at a time (yours), so you never hit the pending writes limit. +Unawaited writes accumulate in the pending queue and throw once it +overflows: + +```mjs +import { push, text } from 'node:stream/iter'; + +const { writer, readable } = push({ highWaterMark: 16 }); + +// Consumer must run concurrently -- without it, the first write +// that fills the buffer blocks the producer forever. +const consuming = text(readable); + +// GOOD: awaited writes. The producer waits for the consumer to +// make room when the buffer is full. +for (const item of dataset) { + await writer.write(item); +} +await writer.end(); +console.log(await consuming); +``` + +```cjs +const { push, text } = require('node:stream/iter'); + +async function run() { + const { writer, readable } = push({ highWaterMark: 16 }); + + // Consumer must run concurrently -- without it, the first write + // that fills the buffer blocks the producer forever. + const consuming = text(readable); + + // GOOD: awaited writes. The producer waits for the consumer to + // make room when the buffer is full. + for (const item of dataset) { + await writer.write(item); + } + await writer.end(); + console.log(await consuming); +} + +run().catch(console.error); +``` + +Forgetting to `await` will eventually throw: + +```js +// BAD: fire-and-forget. Strict mode throws once both buffers fill. +for (const item of dataset) { + writer.write(item); // Not awaited -- queues without bound +} +// --> throws "Backpressure violation: too many pending writes" +``` + +#### Block + +Block mode caps slots at `highWaterMark` but places no limit on the +pending writes queue. Awaited writes block until the consumer makes room, +just like strict mode. The difference is that unawaited writes silently +queue forever instead of throwing -- a potential memory leak if the +producer forgets to `await`. + +This is the mode that existing Node.js classic streams and Web Streams +default to. Use it when you control the producer and know it awaits +properly, or when migrating code from those APIs. + +```mjs +import { push, text } from 'node:stream/iter'; + +const { writer, readable } = push({ + highWaterMark: 16, + backpressure: 'block', +}); + +const consuming = text(readable); + +// Safe -- awaited writes block until the consumer reads. +for (const item of dataset) { + await writer.write(item); +} +await writer.end(); +console.log(await consuming); +``` + +```cjs +const { push, text } = require('node:stream/iter'); + +async function run() { + const { writer, readable } = push({ + highWaterMark: 16, + backpressure: 'block', + }); + + const consuming = text(readable); + + // Safe -- awaited writes block until the consumer reads. + for (const item of dataset) { + await writer.write(item); + } + await writer.end(); + console.log(await consuming); +} + +run().catch(console.error); +``` + +#### Drop-oldest + +Writes never wait. When the slots buffer is full, the oldest buffered +chunk is evicted to make room for the incoming write. The consumer +always sees the most recent data. Useful for live feeds, telemetry, or +any scenario where stale data is less valuable than current data. + +```mjs +import { push } from 'node:stream/iter'; + +// Keep only the 5 most recent readings +const { writer, readable } = push({ + highWaterMark: 5, + backpressure: 'drop-oldest', +}); +``` + +```cjs +const { push } = require('node:stream/iter'); + +// Keep only the 5 most recent readings +const { writer, readable } = push({ + highWaterMark: 5, + backpressure: 'drop-oldest', +}); +``` + +#### Drop-newest + +Writes never wait. When the slots buffer is full, the incoming write is +silently discarded. The consumer processes what is already buffered +without being overwhelmed by new data. Useful for rate-limiting or +shedding load under pressure. + +```mjs +import { push } from 'node:stream/iter'; + +// Accept up to 10 buffered items; discard anything beyond that +const { writer, readable } = push({ + highWaterMark: 10, + backpressure: 'drop-newest', +}); +``` + +```cjs +const { push } = require('node:stream/iter'); + +// Accept up to 10 buffered items; discard anything beyond that +const { writer, readable } = push({ + highWaterMark: 10, + backpressure: 'drop-newest', +}); +``` + +### Writer interface + +A writer is any object conforming to the Writer interface. Only `write()` is +required; all other methods are optional. + +Each async method has a synchronous `*Sync` counterpart designed for a +try-fallback pattern: attempt the fast synchronous path first, and fall back +to the async version only when the synchronous call indicates it could not +complete: + +```mjs +if (!writer.writeSync(chunk)) await writer.write(chunk); +if (!writer.writevSync(chunks)) await writer.writev(chunks); +if (writer.endSync() < 0) await writer.end(); +writer.fail(err); // Always synchronous, no fallback needed +``` + +### `writer.desiredSize` + +* {number|null} + +The number of buffer slots available before the high water mark is reached. +Returns `null` if the writer is closed or the consumer has disconnected. + +The value is always non-negative. + +### `writer.end([options])` + +* `options` {Object} + * `signal` {AbortSignal} Cancel just this operation. The signal cancels only + the pending `end()` call; it does not fail the writer itself. +* Returns: {Promise\} Total bytes written. + +Signal that no more data will be written. + +### `writer.endSync()` + +* Returns: {number} Total bytes written, or `-1` if the writer is not open. + +Synchronous variant of `writer.end()`. Returns `-1` if the writer is already +closed or errored. Can be used as a try-fallback pattern: + +```cjs +const result = writer.endSync(); +if (result < 0) { + writer.end(); +} +``` + +### `writer.fail(reason)` + +* `reason` {any} + +Put the writer into a terminal error state. If the writer is already closed +or errored, this is a no-op. Unlike `write()` and `end()`, `fail()` is +unconditionally synchronous because failing a writer is a pure state +transition with no async work to perform. + +### `writer.write(chunk[, options])` + +* `chunk` {Uint8Array|string} +* `options` {Object} + * `signal` {AbortSignal} Cancel just this write operation. The signal cancels + only the pending `write()` call; it does not fail the writer itself. +* Returns: {Promise\} + +Write a chunk. The promise resolves when buffer space is available. + +### `writer.writeSync(chunk)` + +* `chunk` {Uint8Array|string} +* Returns: {boolean} `true` if the write was accepted, `false` if the + buffer is full. + +Synchronous write. Does not block; returns `false` if backpressure is active. + +### `writer.writev(chunks[, options])` + +* `chunks` {Uint8Array\[]|string\[]} +* `options` {Object} + * `signal` {AbortSignal} Cancel just this write operation. The signal cancels + only the pending `writev()` call; it does not fail the writer itself. +* Returns: {Promise\} + +Write multiple chunks as a single batch. + +### `writer.writevSync(chunks)` + +* `chunks` {Uint8Array\[]|string\[]} +* Returns: {boolean} `true` if the write was accepted, `false` if the + buffer is full. + +Synchronous batch write. + +## The `stream/iter` module + +All functions are available both as named exports and as properties of the +`Stream` namespace object: + +```mjs +// Named exports +import { from, pull, bytes, Stream } from 'node:stream/iter'; + +// Namespace access +Stream.from('hello'); +``` + +```cjs +// Named exports +const { from, pull, bytes, Stream } = require('node:stream/iter'); + +// Namespace access +Stream.from('hello'); +``` + +Including the `node:` prefix on the module specifier is optional. + +## Sources + +### `from(input)` + + + +* `input` {string|ArrayBuffer|ArrayBufferView|Iterable|AsyncIterable|Object} + Must not be `null` or `undefined`. +* Returns: {AsyncIterable\} + +Create an async byte stream from the given input. Strings are UTF-8 encoded. +`ArrayBuffer` and `ArrayBufferView` values are wrapped as `Uint8Array`. Arrays +and iterables are recursively flattened and normalized. + +Objects implementing `Symbol.for('Stream.toAsyncStreamable')` or +`Symbol.for('Stream.toStreamable')` are converted via those protocols. The +`toAsyncStreamable` protocol takes precedence over `toStreamable`, which takes +precedence over the iteration protocols (`Symbol.asyncIterator`, +`Symbol.iterator`). + +```mjs +import { Buffer } from 'node:buffer'; +import { from, text } from 'node:stream/iter'; + +console.log(await text(from('hello'))); // 'hello' +console.log(await text(from(Buffer.from('hello')))); // 'hello' +``` + +```cjs +const { Buffer } = require('node:buffer'); +const { from, text } = require('node:stream/iter'); + +async function run() { + console.log(await text(from('hello'))); // 'hello' + console.log(await text(from(Buffer.from('hello')))); // 'hello' +} + +run().catch(console.error); +``` + +### `fromSync(input)` + + + +* `input` {string|ArrayBuffer|ArrayBufferView|Iterable|Object} + Must not be `null` or `undefined`. +* Returns: {Iterable\} + +Synchronous version of [`from()`][]. Returns a sync iterable. Cannot accept +async iterables or promises. Objects implementing +`Symbol.for('Stream.toStreamable')` are converted via that protocol (takes +precedence over `Symbol.iterator`). The `toAsyncStreamable` protocol is +ignored entirely. + +```mjs +import { fromSync, textSync } from 'node:stream/iter'; + +console.log(textSync(fromSync('hello'))); // 'hello' +``` + +```cjs +const { fromSync, textSync } = require('node:stream/iter'); + +console.log(textSync(fromSync('hello'))); // 'hello' +``` + +## Pipelines + +### `pipeTo(source[, ...transforms], writer[, options])` + + + +* `source` {AsyncIterable|Iterable} The data source. +* `...transforms` {Function|Object} Zero or more transforms to apply. +* `writer` {Object} Destination with `write(chunk)` method. +* `options` {Object} + * `signal` {AbortSignal} Abort the pipeline. + * `preventClose` {boolean} If `true`, do not call `writer.end()` when + the source ends. **Default:** `false`. + * `preventFail` {boolean} If `true`, do not call `writer.fail()` on + error. **Default:** `false`. +* Returns: {Promise\} Total bytes written. + +Pipe a source through transforms into a writer. If the writer has a +`writev(chunks)` method, entire batches are passed in a single call (enabling +scatter/gather I/O). + +If the writer implements the optional `*Sync` methods (`writeSync`, `writevSync`, +`endSync`), `pipeTo()` will attempt to use the synchronous methods +first as a fast path, and fall back to the async versions only when the sync +methods indicate they cannot complete (e.g., backpressure or waiting for the +next tick). `fail()` is always called synchronously. + +```mjs +import { from, pipeTo } from 'node:stream/iter'; +import { compressGzip } from 'node:zlib/iter'; +import { open } from 'node:fs/promises'; + +const fh = await open('output.gz', 'w'); +const totalBytes = await pipeTo( + from('Hello, world!'), + compressGzip(), + fh.writer({ autoClose: true }), +); +``` + +```cjs +const { from, pipeTo } = require('node:stream/iter'); +const { compressGzip } = require('node:zlib/iter'); +const { open } = require('node:fs/promises'); + +async function run() { + const fh = await open('output.gz', 'w'); + const totalBytes = await pipeTo( + from('Hello, world!'), + compressGzip(), + fh.writer({ autoClose: true }), + ); +} + +run().catch(console.error); +``` + +### `pipeToSync(source[, ...transforms], writer[, options])` + + + +* `source` {Iterable} The sync data source. +* `...transforms` {Function|Object} Zero or more sync transforms. +* `writer` {Object} Destination with `write(chunk)` method. +* `options` {Object} + * `preventClose` {boolean} **Default:** `false`. + * `preventFail` {boolean} **Default:** `false`. +* Returns: {number} Total bytes written. + +Synchronous version of [`pipeTo()`][]. The `source`, all transforms, and the +`writer` must be synchronous. Cannot accept async iterables or promises. + +The `writer` must have the `*Sync` methods (`writeSync`, `writevSync`, +`endSync`) and `fail()` for this to work. + +### `pull(source[, ...transforms][, options])` + + + +* `source` {AsyncIterable|Iterable} The data source. +* `...transforms` {Function|Object} Zero or more transforms to apply. +* `options` {Object} + * `signal` {AbortSignal} Abort the pipeline. +* Returns: {AsyncIterable\} + +Create a lazy async pipeline. Data is not read from `source` until the +returned iterable is consumed. Transforms are applied in order. + +```mjs +import { from, pull, text } from 'node:stream/iter'; + +const asciiUpper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => { + for (let i = 0; i < c.length; i++) { + c[i] -= (c[i] >= 97 && c[i] <= 122) * 32; + } + return c; + }); +}; + +const result = pull(from('hello'), asciiUpper); +console.log(await text(result)); // 'HELLO' +``` + +```cjs +const { from, pull, text } = require('node:stream/iter'); + +const asciiUpper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => { + for (let i = 0; i < c.length; i++) { + c[i] -= (c[i] >= 97 && c[i] <= 122) * 32; + } + return c; + }); +}; + +async function run() { + const result = pull(from('hello'), asciiUpper); + console.log(await text(result)); // 'HELLO' +} + +run().catch(console.error); +``` + +Using an `AbortSignal`: + +```mjs +import { pull } from 'node:stream/iter'; + +const ac = new AbortController(); +const result = pull(source, transform, { signal: ac.signal }); +ac.abort(); // Pipeline throws AbortError on next iteration +``` + +```cjs +const { pull } = require('node:stream/iter'); + +const ac = new AbortController(); +const result = pull(source, transform, { signal: ac.signal }); +ac.abort(); // Pipeline throws AbortError on next iteration +``` + +### `pullSync(source[, ...transforms])` + + + +* `source` {Iterable} The sync data source. +* `...transforms` {Function|Object} Zero or more sync transforms. +* Returns: {Iterable\} + +Synchronous version of [`pull()`][]. All transforms must be synchronous. + +## Push streams + +### `push([...transforms][, options])` + + + +* `...transforms` {Function|Object} Optional transforms applied to the + readable side. +* `options` {Object} + * `highWaterMark` {number} Maximum number of buffered slots before + backpressure is applied. Must be >= 1; values below 1 are clamped to 1. + **Default:** `4`. + * `backpressure` {string} Backpressure policy: `'strict'`, `'block'`, + `'drop-oldest'`, or `'drop-newest'`. **Default:** `'strict'`. + * `signal` {AbortSignal} Abort the stream. +* Returns: {Object} + * `writer` {PushWriter} The writer side. + * `readable` {AsyncIterable\} The readable side. + +Create a push stream with backpressure. The writer pushes data in; the +readable side is consumed as an async iterable. + +```mjs +import { push, text } from 'node:stream/iter'; + +const { writer, readable } = push(); + +// Producer and consumer must run concurrently. With strict backpressure +// (the default), awaited writes block until the consumer reads. +const producing = (async () => { + await writer.write('hello'); + await writer.write(' world'); + await writer.end(); +})(); + +console.log(await text(readable)); // 'hello world' +await producing; +``` + +```cjs +const { push, text } = require('node:stream/iter'); + +async function run() { + const { writer, readable } = push(); + + // Producer and consumer must run concurrently. With strict backpressure + // (the default), awaited writes block until the consumer reads. + const producing = (async () => { + await writer.write('hello'); + await writer.write(' world'); + await writer.end(); + })(); + + console.log(await text(readable)); // 'hello world' + await producing; +} + +run().catch(console.error); +``` + +The writer returned by `push()` conforms to the \[Writer interface]\[]. + +## Duplex channels + +### `duplex([options])` + + + +* `options` {Object} + * `highWaterMark` {number} Buffer size for both directions. + **Default:** `4`. + * `backpressure` {string} Policy for both directions. + **Default:** `'strict'`. + * `signal` {AbortSignal} Cancellation signal for both channels. + * `a` {Object} Options specific to the A-to-B direction. Overrides + shared options. + * `highWaterMark` {number} + * `backpressure` {string} + * `b` {Object} Options specific to the B-to-A direction. Overrides + shared options. + * `highWaterMark` {number} + * `backpressure` {string} +* Returns: {Array} A pair `[channelA, channelB]` of duplex channels. + +Create a pair of connected duplex channels for bidirectional communication, +similar to `socketpair()`. Data written to one channel's writer appears in +the other channel's readable. + +Each channel has: + +* `writer` — a \[Writer interface]\[] object for sending data to the peer. +* `readable` — an `AsyncIterable` for reading data from + the peer. +* `close()` — close this end of the channel (idempotent). +* `[Symbol.asyncDispose]()` — async dispose support for `await using`. + +```mjs +import { duplex, text } from 'node:stream/iter'; + +const [client, server] = duplex(); + +// Server echoes back +const serving = (async () => { + for await (const chunks of server.readable) { + await server.writer.writev(chunks); + } +})(); + +await client.writer.write('hello'); +await client.writer.end(); + +console.log(await text(server.readable)); // handled by echo +await serving; +``` + +```cjs +const { duplex, text } = require('node:stream/iter'); + +async function run() { + const [client, server] = duplex(); + + // Server echoes back + const serving = (async () => { + for await (const chunks of server.readable) { + await server.writer.writev(chunks); + } + })(); + + await client.writer.write('hello'); + await client.writer.end(); + + console.log(await text(server.readable)); // handled by echo + await serving; +} + +run().catch(console.error); +``` + +## Consumers + +### `array(source[, options])` + + + +* `source` {AsyncIterable\|Iterable\} +* `options` {Object} + * `signal` {AbortSignal} + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {Promise\} + +Collect all chunks as an array of `Uint8Array` values (without concatenating). + +### `arrayBuffer(source[, options])` + + + +* `source` {AsyncIterable\|Iterable\} +* `options` {Object} + * `signal` {AbortSignal} + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {Promise\} + +Collect all bytes into an `ArrayBuffer`. + +### `arrayBufferSync(source[, options])` + + + +* `source` {Iterable\} +* `options` {Object} + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {ArrayBuffer} + +Synchronous version of [`arrayBuffer()`][]. + +### `arraySync(source[, options])` + + + +* `source` {Iterable\} +* `options` {Object} + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {Uint8Array\[]} + +Synchronous version of [`array()`][]. + +### `bytes(source[, options])` + + + +* `source` {AsyncIterable\|Iterable\} +* `options` {Object} + * `signal` {AbortSignal} + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {Promise\} + +Collect all bytes from a stream into a single `Uint8Array`. + +```mjs +import { from, bytes } from 'node:stream/iter'; + +const data = await bytes(from('hello')); +console.log(data); // Uint8Array(5) [ 104, 101, 108, 108, 111 ] +``` + +```cjs +const { from, bytes } = require('node:stream/iter'); + +async function run() { + const data = await bytes(from('hello')); + console.log(data); // Uint8Array(5) [ 104, 101, 108, 108, 111 ] +} + +run().catch(console.error); +``` + +### `bytesSync(source[, options])` + + + +* `source` {Iterable\} +* `options` {Object} + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {Uint8Array} + +Synchronous version of [`bytes()`][]. + +### `text(source[, options])` + + + +* `source` {AsyncIterable\|Iterable\} +* `options` {Object} + * `encoding` {string} Text encoding. **Default:** `'utf-8'`. + * `signal` {AbortSignal} + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {Promise\} + +Collect all bytes and decode as text. + +```mjs +import { from, text } from 'node:stream/iter'; + +console.log(await text(from('hello'))); // 'hello' +``` + +```cjs +const { from, text } = require('node:stream/iter'); + +async function run() { + console.log(await text(from('hello'))); // 'hello' +} + +run().catch(console.error); +``` + +### `textSync(source[, options])` + + + +* `source` {Iterable\} +* `options` {Object} + * `encoding` {string} **Default:** `'utf-8'`. + * `limit` {number} Maximum number of bytes to consume. If the total bytes + collected exceeds limit, an `ERR_OUT_OF_RANGE` error is thrown +* Returns: {string} + +Synchronous version of [`text()`][]. + +## Utilities + +### `ondrain(drainable)` + + + +* `drainable` {Object} An object implementing the drainable protocol. +* Returns: {Promise\|null} + +Wait for a drainable writer's backpressure to clear. Returns a promise that +resolves to `true` when the writer can accept more data, or `null` if the +object does not implement the drainable protocol. + +```mjs +import { push, ondrain, text } from 'node:stream/iter'; + +const { writer, readable } = push({ highWaterMark: 2 }); +writer.writeSync('a'); +writer.writeSync('b'); + +// Start consuming so the buffer can actually drain +const consuming = text(readable); + +// Buffer is full -- wait for drain +const canWrite = await ondrain(writer); +if (canWrite) { + await writer.write('c'); +} +await writer.end(); +await consuming; +``` + +```cjs +const { push, ondrain, text } = require('node:stream/iter'); + +async function run() { + const { writer, readable } = push({ highWaterMark: 2 }); + writer.writeSync('a'); + writer.writeSync('b'); + + // Start consuming so the buffer can actually drain + const consuming = text(readable); + + // Buffer is full -- wait for drain + const canWrite = await ondrain(writer); + if (canWrite) { + await writer.write('c'); + } + await writer.end(); + await consuming; +} + +run().catch(console.error); +``` + +### `merge(...sources[, options])` + + + +* `...sources` {AsyncIterable\|Iterable\} Two or more iterables. +* `options` {Object} + * `signal` {AbortSignal} +* Returns: {AsyncIterable\} + +Merge multiple async iterables by yielding batches in temporal order +(whichever source produces data first). All sources are consumed +concurrently. + +```mjs +import { from, merge, text } from 'node:stream/iter'; + +const merged = merge(from('hello '), from('world')); +console.log(await text(merged)); // Order depends on timing +``` + +```cjs +const { from, merge, text } = require('node:stream/iter'); + +async function run() { + const merged = merge(from('hello '), from('world')); + console.log(await text(merged)); // Order depends on timing +} + +run().catch(console.error); +``` + +### `tap(callback)` + + + +* `callback` {Function} `(chunks) => void` Called with each batch. +* Returns: {Function} A stateless transform. + +Create a pass-through transform that observes batches without modifying them. +Useful for logging, metrics, or debugging. + +```mjs +import { from, pull, text, tap } from 'node:stream/iter'; + +const result = pull( + from('hello'), + tap((chunks) => console.log('Batch size:', chunks.length)), +); +console.log(await text(result)); +``` + +```cjs +const { from, pull, text, tap } = require('node:stream/iter'); + +async function run() { + const result = pull( + from('hello'), + tap((chunks) => console.log('Batch size:', chunks.length)), + ); + console.log(await text(result)); +} + +run().catch(console.error); +``` + +`tap()` intentionally does not prevent in-place modification of the +chunks by the tapping callback; but return values are ignored. + +### `tapSync(callback)` + + + +* `callback` {Function} +* Returns: {Function} + +Synchronous version of [`tap()`][]. + +## Multi-consumer + +### `broadcast([options])` + + + +* `options` {Object} + * `highWaterMark` {number} Buffer size in slots. Must be >= 1; values + below 1 are clamped to 1. **Default:** `16`. + * `backpressure` {string} `'strict'`, `'block'`, `'drop-oldest'`, or + `'drop-newest'`. **Default:** `'strict'`. + * `signal` {AbortSignal} +* Returns: {Object} + * `writer` {BroadcastWriter} + * `broadcast` {Broadcast} + +Create a push-model multi-consumer broadcast channel. A single writer pushes +data to multiple consumers. Each consumer has an independent cursor into a +shared buffer. + +```mjs +import { broadcast, text } from 'node:stream/iter'; + +const { writer, broadcast: bc } = broadcast(); + +// Create consumers before writing +const c1 = bc.push(); // Consumer 1 +const c2 = bc.push(); // Consumer 2 + +// Producer and consumers must run concurrently. Awaited writes +// block when the buffer fills until consumers read. +const producing = (async () => { + await writer.write('hello'); + await writer.end(); +})(); + +const [r1, r2] = await Promise.all([text(c1), text(c2)]); +console.log(r1); // 'hello' +console.log(r2); // 'hello' +await producing; +``` + +```cjs +const { broadcast, text } = require('node:stream/iter'); + +async function run() { + const { writer, broadcast: bc } = broadcast(); + + // Create consumers before writing + const c1 = bc.push(); // Consumer 1 + const c2 = bc.push(); // Consumer 2 + + // Producer and consumers must run concurrently. Awaited writes + // block when the buffer fills until consumers read. + const producing = (async () => { + await writer.write('hello'); + await writer.end(); + })(); + + const [r1, r2] = await Promise.all([text(c1), text(c2)]); + console.log(r1); // 'hello' + console.log(r2); // 'hello' + await producing; +} + +run().catch(console.error); +``` + +#### `broadcast.bufferSize` + +* {number} + +The number of chunks currently buffered. + +#### `broadcast.cancel([reason])` + +* `reason` {Error} + +Cancel the broadcast. All consumers receive an error. + +#### `broadcast.consumerCount` + +* {number} + +The number of active consumers. + +#### `broadcast.push([...transforms][, options])` + +* `...transforms` {Function|Object} +* `options` {Object} + * `signal` {AbortSignal} +* Returns: {AsyncIterable\} + +Create a new consumer. Each consumer receives all data written to the +broadcast from the point of subscription onward. Optional transforms are +applied to this consumer's view of the data. + +#### `broadcast[Symbol.dispose]()` + +Alias for `broadcast.cancel()`. + +### `Broadcast.from(input[, options])` + + + +* `input` {AsyncIterable|Iterable|Broadcastable} +* `options` {Object} Same as `broadcast()`. +* Returns: {Object} `{ writer, broadcast }` + +Create a {Broadcast} from an existing source. The source is consumed +automatically and pushed to all subscribers. + +### `share(source[, options])` + + + +* `source` {AsyncIterable} The source to share. +* `options` {Object} + * `highWaterMark` {number} Buffer size. Must be >= 1; values below 1 + are clamped to 1. **Default:** `16`. + * `backpressure` {string} `'strict'`, `'block'`, `'drop-oldest'`, or + `'drop-newest'`. **Default:** `'strict'`. +* Returns: {Share} + +Create a pull-model multi-consumer shared stream. Unlike `broadcast()`, the +source is only read when a consumer pulls. Multiple consumers share a single +buffer. + +```mjs +import { from, share, text } from 'node:stream/iter'; + +const shared = share(from('hello')); + +const c1 = shared.pull(); +const c2 = shared.pull(); + +// Consume concurrently to avoid deadlock with small buffers. +const [r1, r2] = await Promise.all([text(c1), text(c2)]); +console.log(r1); // 'hello' +console.log(r2); // 'hello' +``` + +```cjs +const { from, share, text } = require('node:stream/iter'); + +async function run() { + const shared = share(from('hello')); + + const c1 = shared.pull(); + const c2 = shared.pull(); + + // Consume concurrently to avoid deadlock with small buffers. + const [r1, r2] = await Promise.all([text(c1), text(c2)]); + console.log(r1); // 'hello' + console.log(r2); // 'hello' +} + +run().catch(console.error); +``` + +#### `share.bufferSize` + +* {number} + +The number of chunks currently buffered. + +#### `share.cancel([reason])` + +* `reason` {Error} + +Cancel the share. All consumers receive an error. + +#### `share.consumerCount` + +* {number} + +The number of active consumers. + +#### `share.pull([...transforms][, options])` + +* `...transforms` {Function|Object} +* `options` {Object} + * `signal` {AbortSignal} +* Returns: {AsyncIterable\} + +Create a new consumer of the shared source. + +#### `share[Symbol.dispose]()` + +Alias for `share.cancel()`. + +### `Share.from(input[, options])` + + + +* `input` {AsyncIterable|Shareable} +* `options` {Object} Same as `share()`. +* Returns: {Share} + +Create a {Share} from an existing source. + +### `shareSync(source[, options])` + + + +* `source` {Iterable} The sync source to share. +* `options` {Object} + * `highWaterMark` {number} Must be >= 1; values below 1 are clamped + to 1. **Default:** `16`. + * `backpressure` {string} **Default:** `'strict'`. +* Returns: {SyncShare} + +Synchronous version of [`share()`][]. + +### `SyncShare.fromSync(input[, options])` + + + +* `input` {Iterable|SyncShareable} +* `options` {Object} +* Returns: {SyncShare} + +## Compression and decompression transforms + +Compression and decompression transforms for use with `pull()`, `pullSync()`, +`pipeTo()`, and `pipeToSync()` are available via the [`node:zlib/iter`][] +module. See the [`node:zlib/iter` documentation][] for details. + +## Protocol symbols + +These well-known symbols allow third-party objects to participate in the +streaming protocol without importing from `node:stream/iter` directly. + +### `Stream.broadcastProtocol` + +* Value: `Symbol.for('Stream.broadcastProtocol')` + +The value must be a function. When called by `Broadcast.from()`, it receives +the options passed to `Broadcast.from()` and must return an object conforming +to the {Broadcast} interface. The implementation is fully custom -- it can +manage consumers, buffering, and backpressure however it wants. + +```mjs +import { Broadcast, text } from 'node:stream/iter'; + +// This example defers to the built-in Broadcast, but a custom +// implementation could use any mechanism. +class MessageBus { + #broadcast; + #writer; + + constructor() { + const { writer, broadcast } = Broadcast(); + this.#writer = writer; + this.#broadcast = broadcast; + } + + [Symbol.for('Stream.broadcastProtocol')](options) { + return this.#broadcast; + } + + send(data) { + this.#writer.write(new TextEncoder().encode(data)); + } + + close() { + this.#writer.end(); + } +} + +const bus = new MessageBus(); +const { broadcast } = Broadcast.from(bus); +const consumer = broadcast.push(); +bus.send('hello'); +bus.close(); +console.log(await text(consumer)); // 'hello' +``` + +```cjs +const { Broadcast, text } = require('node:stream/iter'); + +// This example defers to the built-in Broadcast, but a custom +// implementation could use any mechanism. +class MessageBus { + #broadcast; + #writer; + + constructor() { + const { writer, broadcast } = Broadcast(); + this.#writer = writer; + this.#broadcast = broadcast; + } + + [Symbol.for('Stream.broadcastProtocol')](options) { + return this.#broadcast; + } + + send(data) { + this.#writer.write(new TextEncoder().encode(data)); + } + + close() { + this.#writer.end(); + } +} + +const bus = new MessageBus(); +const { broadcast } = Broadcast.from(bus); +const consumer = broadcast.push(); +bus.send('hello'); +bus.close(); +text(consumer).then(console.log); // 'hello' +``` + +### `Stream.drainableProtocol` + +* Value: `Symbol.for('Stream.drainableProtocol')` + +Implement to make a writer compatible with `ondrain()`. The method should +return a promise that resolves when backpressure clears, or `null` if no +backpressure. + +```mjs +import { ondrain } from 'node:stream/iter'; + +class CustomWriter { + #queue = []; + #drain = null; + #closed = false; + [Symbol.for('Stream.drainableProtocol')]() { + if (this.#closed) return null; + if (this.#queue.length < 3) return Promise.resolve(true); + this.#drain ??= Promise.withResolvers(); + return this.#drain.promise; + } + write(chunk) { + this.#queue.push(chunk); + } + flush() { + this.#queue.length = 0; + this.#drain?.resolve(true); + this.#drain = null; + } + close() { + this.#closed = true; + } +} +const writer = new CustomWriter(); +const ready = ondrain(writer); +console.log(ready); // Promise { true } -- no backpressure +``` + +```cjs +const { ondrain } = require('node:stream/iter'); + +class CustomWriter { + #queue = []; + #drain = null; + #closed = false; + + [Symbol.for('Stream.drainableProtocol')]() { + if (this.#closed) return null; + if (this.#queue.length < 3) return Promise.resolve(true); + this.#drain ??= Promise.withResolvers(); + return this.#drain.promise; + } + + write(chunk) { + this.#queue.push(chunk); + } + + flush() { + this.#queue.length = 0; + this.#drain?.resolve(true); + this.#drain = null; + } + + close() { + this.#closed = true; + } +} + +const writer = new CustomWriter(); +const ready = ondrain(writer); +console.log(ready); // Promise { true } -- no backpressure +``` + +### `Stream.shareProtocol` + +* Value: `Symbol.for('Stream.shareProtocol')` + +The value must be a function. When called by `Share.from()`, it receives the +options passed to `Share.from()` and must return an object conforming the the +{Share} interface. The implementation is fully custom -- it can manage the shared +source, consumers, buffering, and backpressure however it wants. + +```mjs +import { share, Share, text } from 'node:stream/iter'; + +// This example defers to the built-in share(), but a custom +// implementation could use any mechanism. +class DataPool { + #share; + + constructor(source) { + this.#share = share(source); + } + + [Symbol.for('Stream.shareProtocol')](options) { + return this.#share; + } +} + +const pool = new DataPool( + (async function* () { + yield 'hello'; + })(), +); + +const shared = Share.from(pool); +const consumer = shared.pull(); +console.log(await text(consumer)); // 'hello' +``` + +```cjs +const { share, Share, text } = require('node:stream/iter'); + +// This example defers to the built-in share(), but a custom +// implementation could use any mechanism. +class DataPool { + #share; + + constructor(source) { + this.#share = share(source); + } + + [Symbol.for('Stream.shareProtocol')](options) { + return this.#share; + } +} + +const pool = new DataPool( + (async function* () { + yield 'hello'; + })(), +); + +const shared = Share.from(pool); +const consumer = shared.pull(); +text(consumer).then(console.log); // 'hello' +``` + +### `Stream.shareSyncProtocol` + +* Value: `Symbol.for('Stream.shareSyncProtocol')` + +The value must be a function. When called by `SyncShare.fromSync()`, it receives +the options passed to `SyncShare.fromSync()` and must return an object conforming +to the {SyncShare} interface. The implementation is fully custom -- it can manage +the shared source, consumers, and buffering however it wants. + +```mjs +import { shareSync, SyncShare, textSync } from 'node:stream/iter'; + +// This example defers to the built-in shareSync(), but a custom +// implementation could use any mechanism. +class SyncDataPool { + #share; + + constructor(source) { + this.#share = shareSync(source); + } + + [Symbol.for('Stream.shareSyncProtocol')](options) { + return this.#share; + } +} + +const encoder = new TextEncoder(); +const pool = new SyncDataPool( + function* () { + yield [encoder.encode('hello')]; + }(), +); + +const shared = SyncShare.fromSync(pool); +const consumer = shared.pull(); +console.log(textSync(consumer)); // 'hello' +``` + +```cjs +const { shareSync, SyncShare, textSync } = require('node:stream/iter'); + +// This example defers to the built-in shareSync(), but a custom +// implementation could use any mechanism. +class SyncDataPool { + #share; + + constructor(source) { + this.#share = shareSync(source); + } + + [Symbol.for('Stream.shareSyncProtocol')](options) { + return this.#share; + } +} + +const encoder = new TextEncoder(); +const pool = new SyncDataPool( + function* () { + yield [encoder.encode('hello')]; + }(), +); + +const shared = SyncShare.fromSync(pool); +const consumer = shared.pull(); +console.log(textSync(consumer)); // 'hello' +``` + +### `Stream.toAsyncStreamable` + +* Value: `Symbol.for('Stream.toAsyncStreamable')` + +The value must be a function that converts the object into a streamable value. +When the object is encountered anywhere in the streaming pipeline (as a source +passed to `from()`, or as a value returned from a transform), this method is +called to produce the actual data. It may return (or resolve to) any streamable +value: a string, `Uint8Array`, `AsyncIterable`, `Iterable`, or another streamable +object. + +```mjs +import { from, text } from 'node:stream/iter'; + +class Greeting { + #name; + + constructor(name) { + this.#name = name; + } + + [Symbol.for('Stream.toAsyncStreamable')]() { + return `hello ${this.#name}`; + } +} + +const stream = from(new Greeting('world')); +console.log(await text(stream)); // 'hello world' +``` + +```cjs +const { from, text } = require('node:stream/iter'); + +class Greeting { + #name; + + constructor(name) { + this.#name = name; + } + + [Symbol.for('Stream.toAsyncStreamable')]() { + return `hello ${this.#name}`; + } +} + +const stream = from(new Greeting('world')); +text(stream).then(console.log); // 'hello world' +``` + +### `Stream.toStreamable` + +* Value: `Symbol.for('Stream.toStreamable')` + +The value must be a function that synchronously converts the object into a +streamable value. When the object is encountered anywhere in the streaming +pipeline (as a source passed to `fromSync()`, or as a value returned from a +sync transform), this method is called to produce the actual data. It must +synchronously return a streamable value: a string, `Uint8Array`, or `Iterable`. + +```mjs +import { fromSync, textSync } from 'node:stream/iter'; + +class Greeting { + #name; + + constructor(name) { + this.#name = name; + } + + [Symbol.for('Stream.toStreamable')]() { + return `hello ${this.#name}`; + } +} + +const stream = fromSync(new Greeting('world')); +console.log(textSync(stream)); // 'hello world' +``` + +```cjs +const { fromSync, textSync } = require('node:stream/iter'); + +class Greeting { + #name; + + constructor(name) { + this.#name = name; + } + + [Symbol.for('Stream.toStreamable')]() { + return `hello ${this.#name}`; + } +} + +const stream = fromSync(new Greeting('world')); +console.log(textSync(stream)); // 'hello world' +``` + +[`array()`]: #arraysource-options +[`arrayBuffer()`]: #arraybuffersource-options +[`bytes()`]: #bytessource-options +[`from()`]: #frominput +[`node:zlib/iter`]: zlib_iter.md +[`node:zlib/iter` documentation]: zlib_iter.md +[`pipeTo()`]: #pipetosource-transforms-writer-options +[`pull()`]: #pullsource-transforms-options +[`share()`]: #sharesource-options +[`tap()`]: #tapcallback +[`text()`]: #textsource-options diff --git a/doc/api/test.md b/doc/api/test.md index 6a987b36557099..f2905bcf7f8a91 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -2480,16 +2480,32 @@ changes: generates a new mock module. If `true`, subsequent calls will return the same module mock, and the mock module is inserted into the CommonJS cache. **Default:** false. + * `exports` {Object} Optional mocked exports. The `default` property, if + provided, is used as the mocked module's default export. All other own + enumerable properties are used as named exports. + **This option cannot be used with `defaultExport` or `namedExports`.** + * If the mock is a CommonJS or builtin module, `exports.default` is used as + the value of `module.exports`. + * If `exports.default` is not provided for a CommonJS or builtin mock, + `module.exports` defaults to an empty object. + * If named exports are provided with a non-object default export, the mock + throws an exception when used as a CommonJS or builtin module. * `defaultExport` {any} An optional value used as the mocked module's default export. If this value is not provided, ESM mocks do not include a default export. If the mock is a CommonJS or builtin module, this setting is used as the value of `module.exports`. If this value is not provided, CJS and builtin mocks use an empty object as the value of `module.exports`. + **This option cannot be used with `options.exports`.** + This option is deprecated and will be removed in a later version. + Prefer `options.exports.default`. * `namedExports` {Object} An optional object whose keys and values are used to create the named exports of the mock module. If the mock is a CommonJS or builtin module, these values are copied onto `module.exports`. Therefore, if a mock is created with both named exports and a non-object default export, the mock will throw an exception when used as a CJS or builtin module. + **This option cannot be used with `options.exports`.** + This option is deprecated and will be removed in a later version. + Prefer `options.exports`. * Returns: {MockModuleContext} An object that can be used to manipulate the mock. This function is used to mock the exports of ECMAScript modules, CommonJS modules, JSON modules, and @@ -2497,14 +2513,19 @@ Node.js builtin modules. Any references to the original module prior to mocking order to enable module mocking, Node.js must be started with the [`--experimental-test-module-mocks`][] command-line flag. +**Note**: [module customization hooks][] registered via the **synchronous** API effect resolution of +the `specifier` provided to `mock.module`. Customization hooks registered via the **asynchronous** +API are currently ignored (because the test runner's loader is synchronous, and node does not +support multi-chain / cross-chain loading). + The following example demonstrates how a mock is created for a module. ```js test('mocks a builtin module in both module systems', async (t) => { - // Create a mock of 'node:readline' with a named export named 'fn', which + // Create a mock of 'node:readline' with a named export named 'foo', which // does not exist in the original 'node:readline' module. const mock = t.mock.module('node:readline', { - namedExports: { fn() { return 42; } }, + exports: { foo: () => 42 }, }); let esmImpl = await import('node:readline'); @@ -4244,6 +4265,7 @@ Can be used to abort test subtasks when the test has been aborted. [configuration files]: cli.md#--experimental-config-fileconfig [describe options]: #describename-options-fn [it options]: #testname-options-fn +[module customization hooks]: module.md#customization-hooks [running tests from the command line]: #running-tests-from-the-command-line [stream.compose]: stream.md#streamcomposestreams [subtests]: #subtests diff --git a/doc/api/vm.md b/doc/api/vm.md index e31132f85bec03..2e84d4b7ece83c 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -1829,10 +1829,10 @@ It does several things at once: 1. Creates a new context. 2. If `contextObject` is an object, [contextifies][contextified] it with the new context. - If `contextObject` is undefined, creates a new object and [contextifies][contextified] it. + If `contextObject` is undefined, creates a new object and [contextifies][contextified] it. If `contextObject` is [`vm.constants.DONT_CONTEXTIFY`][], don't [contextify][contextified] anything. -3. Compiles the code as a`vm.Script` -4. Runs the compield code within the created context. The code does not have access to the scope in +3. Compiles the code as a `vm.Script` +4. Runs the compiled code within the created context. The code does not have access to the scope in which this method is called. 5. Returns the result. diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index ce31a590f1138b..ece03f4a1208a7 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -2,6 +2,10 @@ -* `algorithm` {string|Algorithm|CShakeParams} +* `algorithm` {string|Algorithm|CShakeParams|TurboShakeParams|KangarooTwelveParams} * `data` {ArrayBuffer|TypedArray|DataView|Buffer} * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. @@ -1019,6 +1035,8 @@ If `algorithm` is provided as a {string}, it must be one of: * `'cSHAKE128'`[^modern-algos] * `'cSHAKE256'`[^modern-algos] +* `'KT128'`[^modern-algos] +* `'KT256'`[^modern-algos] * `'SHA-1'` * `'SHA-256'` * `'SHA-384'` @@ -1026,6 +1044,8 @@ If `algorithm` is provided as a {string}, it must be one of: * `'SHA3-256'`[^modern-algos] * `'SHA3-384'`[^modern-algos] * `'SHA3-512'`[^modern-algos] +* `'TurboSHAKE128'`[^modern-algos] +* `'TurboSHAKE256'`[^modern-algos] If `algorithm` is provided as an {Object}, it must have a `name` property whose value is one of the above. @@ -1260,6 +1280,10 @@ The {CryptoKey} (secret key) generating algorithms supported include: -#### `cShakeParams.customization` +#### `cShakeParams.name` -* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} +* Type: {string} Must be `'cSHAKE128'`[^modern-algos] or `'cSHAKE256'`[^modern-algos] -The `customization` member represents the customization string. -The Node.js Web Crypto API implementation only supports zero-length customization -which is equivalent to not providing customization at all. +#### `cShakeParams.outputLength` + + + +* Type: {number} represents the requested output length in bits. #### `cShakeParams.functionName` @@ -1896,21 +1928,17 @@ functions based on cSHAKE. The Node.js Web Crypto API implementation only supports zero-length functionName which is equivalent to not providing functionName at all. -#### `cShakeParams.length` +#### `cShakeParams.customization` -* Type: {number} represents the requested output length in bits. - -#### `cShakeParams.name` - - +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -* Type: {string} Must be `'cSHAKE128'`[^modern-algos] or `'cSHAKE256'`[^modern-algos] +The `customization` member represents the customization string. +The Node.js Web Crypto API implementation only supports zero-length customization +which is equivalent to not providing customization at all. ### Class: `EcdhKeyDeriveParams` @@ -2308,6 +2336,38 @@ added: v15.0.0 * Type: {string} +### Class: `KangarooTwelveParams` + + + +#### `kangarooTwelveParams.customization` + + + +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} + +The optional customization string for KangarooTwelve. + +#### `kangarooTwelveParams.name` + + + +* Type: {string} Must be `'KT128'`[^modern-algos] or `'KT256'`[^modern-algos] + +#### `kangarooTwelveParams.outputLength` + + + +* Type: {number} represents the requested output length in bits. + ### Class: `KmacImportParams` #### `kmacParams.algorithm` @@ -2397,25 +2461,25 @@ added: v24.8.0 * Type: {string} Must be `'KMAC128'` or `'KMAC256'`. -#### `kmacParams.customization` +#### `kmacParams.outputLength` -* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} +* Type: {number} -The `customization` member represents the optional customization string. +The length of the output in bytes. This must be a positive integer. -#### `kmacParams.length` +#### `kmacParams.customization` -* Type: {number} +* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -The length of the output in bytes. This must be a positive integer. +The `customization` member represents the optional customization string. ### Class: `Pbkdf2Params` @@ -2674,6 +2738,38 @@ added: v15.0.0 The length (in bytes) of the random salt to use. +### Class: `TurboShakeParams` + + + +#### `turboShakeParams.domainSeparation` + + + +* Type: {number|undefined} + +The optional domain separation byte (0x01-0x7f). Defaults to `0x1f`. + +#### `turboShakeParams.name` + + + +* Type: {string} Must be `'TurboSHAKE128'`[^modern-algos] or `'TurboSHAKE256'`[^modern-algos] + +#### `turboShakeParams.outputLength` + + + +* Type: {number} represents the requested output length in bits. + [^secure-curves]: See [Secure Curves in the Web Cryptography API][] [^modern-algos]: See [Modern Algorithms in the Web Cryptography API][] diff --git a/doc/api/zlib_iter.md b/doc/api/zlib_iter.md new file mode 100644 index 00000000000000..99bcdba745203d --- /dev/null +++ b/doc/api/zlib_iter.md @@ -0,0 +1,254 @@ +# Iterable Compression + + + +> Stability: 1 - Experimental + + + +The `node:zlib/iter` module provides compression and decompression transforms +for use with the [`node:stream/iter`][] iterable streams API. + +This module is available only when the `--experimental-stream-iter` CLI flag +is enabled. + +Each algorithm has both an async variant (stateful async generator, for use +with [`pull()`][] and [`pipeTo()`][]) and a sync variant (stateful sync +generator, for use with `pullSync()` and `pipeToSync()`). + +The async transforms run compression on the libuv threadpool, overlapping +I/O with JavaScript execution. The sync transforms run compression directly +on the main thread. + +> Note: The defaults for these transforms are tuned for streaming throughput, +> and differ from the defaults in `node:zlib`. In particular, gzip/deflate +> default to level 4 (not 6) and memLevel 9 (not 8), and Brotli defaults to +> quality 6 (not 11). These choices match common HTTP server configurations +> and provide significantly faster compression with only a small reduction in +> compression ratio. All defaults can be overridden via options. + +```mjs +import { from, pull, bytes, text } from 'node:stream/iter'; +import { compressGzip, decompressGzip } from 'node:zlib/iter'; + +// Async round-trip +const compressed = await bytes(pull(from('hello'), compressGzip())); +const original = await text(pull(from(compressed), decompressGzip())); +console.log(original); // 'hello' +``` + +```cjs +const { from, pull, bytes, text } = require('node:stream/iter'); +const { compressGzip, decompressGzip } = require('node:zlib/iter'); + +async function run() { + const compressed = await bytes(pull(from('hello'), compressGzip())); + const original = await text(pull(from(compressed), decompressGzip())); + console.log(original); // 'hello' +} + +run().catch(console.error); +``` + +```mjs +import { fromSync, pullSync, textSync } from 'node:stream/iter'; +import { compressGzipSync, decompressGzipSync } from 'node:zlib/iter'; + +// Sync round-trip +const compressed = pullSync(fromSync('hello'), compressGzipSync()); +const original = textSync(pullSync(compressed, decompressGzipSync())); +console.log(original); // 'hello' +``` + +```cjs +const { fromSync, pullSync, textSync } = require('node:stream/iter'); +const { compressGzipSync, decompressGzipSync } = require('node:zlib/iter'); + +const compressed = pullSync(fromSync('hello'), compressGzipSync()); +const original = textSync(pullSync(compressed, decompressGzipSync())); +console.log(original); // 'hello' +``` + +## `compressBrotli([options])` + +## `compressBrotliSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `params` {Object} Key-value object where keys and values are + `zlib.constants` entries. The most important compressor parameters are: + * `BROTLI_PARAM_MODE` -- `BROTLI_MODE_GENERIC` (default), + `BROTLI_MODE_TEXT`, or `BROTLI_MODE_FONT`. + * `BROTLI_PARAM_QUALITY` -- ranges from `BROTLI_MIN_QUALITY` to + `BROTLI_MAX_QUALITY`. **Default:** `6` (not `BROTLI_DEFAULT_QUALITY` + which is 11). Quality 6 is appropriate for streaming; quality 11 is + intended for offline/build-time compression. + * `BROTLI_PARAM_SIZE_HINT` -- expected input size. **Default:** `0` + (unknown). + * `BROTLI_PARAM_LGWIN` -- window size (log2). **Default:** `20` (1 MB). + The Brotli library default is 22 (4 MB); the reduced default saves + memory without significant compression impact for streaming workloads. + * `BROTLI_PARAM_LGBLOCK` -- input block size (log2). + See the [Brotli compressor options][] in the zlib documentation for the + full list. + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a Brotli compression transform. Output is compatible with +`zlib.brotliDecompress()` and `decompressBrotli()`/`decompressBrotliSync()`. + +## `compressDeflate([options])` + +## `compressDeflateSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `level` {number} Compression level (`0`-`9`). **Default:** `4`. + * `windowBits` {number} **Default:** `Z_DEFAULT_WINDOWBITS` (15). + * `memLevel` {number} **Default:** `9`. + * `strategy` {number} **Default:** `Z_DEFAULT_STRATEGY`. + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a deflate compression transform. Output is compatible with +`zlib.inflate()` and `decompressDeflate()`/`decompressDeflateSync()`. + +## `compressGzip([options])` + +## `compressGzipSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `level` {number} Compression level (`0`-`9`). **Default:** `4`. + * `windowBits` {number} **Default:** `Z_DEFAULT_WINDOWBITS` (15). + * `memLevel` {number} **Default:** `9`. + * `strategy` {number} **Default:** `Z_DEFAULT_STRATEGY`. + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a gzip compression transform. Output is compatible with `zlib.gunzip()` +and `decompressGzip()`/`decompressGzipSync()`. + +## `compressZstd([options])` + +## `compressZstdSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `params` {Object} Key-value object where keys and values are + `zlib.constants` entries. The most important compressor parameters are: + * `ZSTD_c_compressionLevel` -- **Default:** `ZSTD_CLEVEL_DEFAULT` (3). + * `ZSTD_c_checksumFlag` -- generate a checksum. **Default:** `0`. + * `ZSTD_c_strategy` -- compression strategy. Values include + `ZSTD_fast`, `ZSTD_dfast`, `ZSTD_greedy`, `ZSTD_lazy`, + `ZSTD_lazy2`, `ZSTD_btlazy2`, `ZSTD_btopt`, `ZSTD_btultra`, + `ZSTD_btultra2`. + See the [Zstd compressor options][] in the zlib documentation for the + full list. + * `pledgedSrcSize` {number} Expected uncompressed size (optional hint). + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a Zstandard compression transform. Output is compatible with +`zlib.zstdDecompress()` and `decompressZstd()`/`decompressZstdSync()`. + +## `decompressBrotli([options])` + +## `decompressBrotliSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `params` {Object} Key-value object where keys and values are + `zlib.constants` entries. Available decompressor parameters: + * `BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION` -- boolean + flag affecting internal memory allocation. + * `BROTLI_DECODER_PARAM_LARGE_WINDOW` -- boolean flag enabling "Large + Window Brotli" mode (not compatible with [RFC 7932][]). + See the [Brotli decompressor options][] in the zlib documentation for + details. + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a Brotli decompression transform. + +## `decompressDeflate([options])` + +## `decompressDeflateSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `windowBits` {number} **Default:** `Z_DEFAULT_WINDOWBITS` (15). + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a deflate decompression transform. + +## `decompressGzip([options])` + +## `decompressGzipSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `windowBits` {number} **Default:** `Z_DEFAULT_WINDOWBITS` (15). + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a gzip decompression transform. + +## `decompressZstd([options])` + +## `decompressZstdSync([options])` + + + +* `options` {Object} + * `chunkSize` {number} Output buffer size. **Default:** `65536` (64 KB). + * `params` {Object} Key-value object where keys and values are + `zlib.constants` entries. Available decompressor parameters: + * `ZSTD_d_windowLogMax` -- maximum window size (log2) the decompressor + will allocate. Limits memory usage against malicious input. + See the [Zstd decompressor options][] in the zlib documentation for + details. + * `dictionary` {Buffer|TypedArray|DataView} +* Returns: {Object} A stateful transform. + +Create a Zstandard decompression transform. + +[Brotli compressor options]: zlib.md#compressor-options +[Brotli decompressor options]: zlib.md#decompressor-options +[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932 +[Zstd compressor options]: zlib.md#compressor-options-1 +[Zstd decompressor options]: zlib.md#decompressor-options-1 +[`node:stream/iter`]: stream_iter.md +[`pipeTo()`]: stream_iter.md#pipetosource-transforms-writer-options +[`pull()`]: stream_iter.md#pullsource-transforms-options diff --git a/doc/changelogs/CHANGELOG_V25.md b/doc/changelogs/CHANGELOG_V25.md index fbd24d6c72f2f8..2c2b5bdbbe58d8 100644 --- a/doc/changelogs/CHANGELOG_V25.md +++ b/doc/changelogs/CHANGELOG_V25.md @@ -8,6 +8,7 @@ +25.9.0
          25.8.2
          25.8.1
          25.8.0
          @@ -52,6 +53,148 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + + +## 2026-04-01, Version 25.9.0 (Current), @aduh95 + +### Notable Changes + +#### Test runner module mocking improvements + +`MockModuleOptions.defaultExport` and `MockModuleOptions.namedExports` have been +consolidated into a single option `MockModuleOptions.exports` to align with user +expectations and other test runners. + +A `default` property on `MockModuleOptions.exports` represents the default +export, and own enumerable properties are treated as named exports. + +An automated migration is available to update user code: + + +```bash +npx codemod @nodejs/mock-module-exports +``` + +Contributed by sangwook in [#61727](https://github.com/nodejs/node/pull/61727). + +#### Other notable changes + +* \[[`312476cb84`](https://github.com/nodejs/node/commit/312476cb84)] - **(SEMVER-MINOR)** **async\_hooks**: add using scopes to `AsyncLocalStorage` (Stephen Belanger) [#61674](https://github.com/nodejs/node/pull/61674) +* \[[`62d2cd473b`](https://github.com/nodejs/node/commit/62d2cd473b)] - **(SEMVER-MINOR)** **cli**: add `--max-heap-size` option (tannal) [#58708](https://github.com/nodejs/node/pull/58708) +* \[[`d0ebf0e44b`](https://github.com/nodejs/node/commit/d0ebf0e44b)] - **(SEMVER-MINOR)** **crypto**: add `TurboSHAKE` and `KangarooTwelve` Web Cryptography algorithms (Filip Skokan) [#62183](https://github.com/nodejs/node/pull/62183) +* \[[`f85b9d9fa8`](https://github.com/nodejs/node/commit/f85b9d9fa8)] - **(SEMVER-MINOR)** **repl**: add customizable error handling (Anna Henningsen) [#62188](https://github.com/nodejs/node/pull/62188) +* \[[`67b854d407`](https://github.com/nodejs/node/commit/67b854d407)] - **(SEMVER-MINOR)** **repl**: remove dependency on `node:domain` (Matteo Collina) [#61227](https://github.com/nodejs/node/pull/61227) +* \[[`966b700623`](https://github.com/nodejs/node/commit/966b700623)] - **(SEMVER-MINOR)** **sea**: support code cache for ESM entrypoint in SEA (Joyee Cheung) [#62158](https://github.com/nodejs/node/pull/62158) +* \[[`e1f0d2a014`](https://github.com/nodejs/node/commit/e1f0d2a014)] - **(SEMVER-MINOR)** **stream**: add stream/iter Implementation (James M Snell) [#62066](https://github.com/nodejs/node/pull/62066) + +### Commits + +* \[[`312476cb84`](https://github.com/nodejs/node/commit/312476cb84)] - **(SEMVER-MINOR)** **async\_hooks**: add using scopes to AsyncLocalStorage (Stephen Belanger) [#61674](https://github.com/nodejs/node/pull/61674) +* \[[`bfff8cb2ab`](https://github.com/nodejs/node/commit/bfff8cb2ab)] - **(SEMVER-MINOR)** **benchmark**: add benchmarks for experimental stream/iter (James M Snell) [#62066](https://github.com/nodejs/node/pull/62066) +* \[[`c721d68502`](https://github.com/nodejs/node/commit/c721d68502)] - **benchmark**: fix destructuring in dgram/single-buffer (Ali Hassan) [#62084](https://github.com/nodejs/node/pull/62084) +* \[[`e2f03c8e92`](https://github.com/nodejs/node/commit/e2f03c8e92)] - **buffer**: improve performance of multiple Buffer operations (Ali Hassan) [#61871](https://github.com/nodejs/node/pull/61871) +* \[[`2fcd07f1ba`](https://github.com/nodejs/node/commit/2fcd07f1ba)] - **build**: support empty libname flags in `configure.py` (Antoine du Hamel) [#62477](https://github.com/nodejs/node/pull/62477) +* \[[`b800c57fce`](https://github.com/nodejs/node/commit/b800c57fce)] - **build**: fix timezone-update path references (Chengzhong Wu) [#62280](https://github.com/nodejs/node/pull/62280) +* \[[`7dc5a1e9b4`](https://github.com/nodejs/node/commit/7dc5a1e9b4)] - **build**: skip dockit on IBMi (SRAVANI GUNDEPALLI) [#62189](https://github.com/nodejs/node/pull/62189) +* \[[`f0eea0f905`](https://github.com/nodejs/node/commit/f0eea0f905)] - **build**: fix --node-builtin-modules-path (Filip Skokan) [#62115](https://github.com/nodejs/node/pull/62115) +* \[[`62d2cd473b`](https://github.com/nodejs/node/commit/62d2cd473b)] - **(SEMVER-MINOR)** **cli**: add --max-heap-size option (tannal) [#58708](https://github.com/nodejs/node/pull/58708) +* \[[`ac4b485698`](https://github.com/nodejs/node/commit/ac4b485698)] - **crypto**: update root certificates to NSS 3.121 (Node.js GitHub Bot) [#62485](https://github.com/nodejs/node/pull/62485) +* \[[`d0ebf0e44b`](https://github.com/nodejs/node/commit/d0ebf0e44b)] - **(SEMVER-MINOR)** **crypto**: add TurboSHAKE and KangarooTwelve Web Cryptography algorithms (Filip Skokan) [#62183](https://github.com/nodejs/node/pull/62183) +* \[[`3009980d9d`](https://github.com/nodejs/node/commit/3009980d9d)] - **crypto**: add crypto::GetSSLCtx API for addon access to OpenSSL contexts (Tim Perry) [#62254](https://github.com/nodejs/node/pull/62254) +* \[[`f5725ca81d`](https://github.com/nodejs/node/commit/f5725ca81d)] - **crypto**: reject ML-KEM/ML-DSA PKCS#8 import without seed in SubtleCrypto (Filip Skokan) [#62218](https://github.com/nodejs/node/pull/62218) +* \[[`f69ed4bc3f`](https://github.com/nodejs/node/commit/f69ed4bc3f)] - **crypto**: rename CShakeParams and KmacParams length to outputLength (Filip Skokan) [#61875](https://github.com/nodejs/node/pull/61875) +* \[[`4d96e53570`](https://github.com/nodejs/node/commit/4d96e53570)] - **crypto**: refactor WebCrypto AEAD algorithms auth tag handling (Filip Skokan) [#62169](https://github.com/nodejs/node/pull/62169) +* \[[`93d77719e8`](https://github.com/nodejs/node/commit/93d77719e8)] - **crypto**: read algorithm name property only once in normalizeAlgorithm (Filip Skokan) [#62170](https://github.com/nodejs/node/pull/62170) +* \[[`3d2e23a981`](https://github.com/nodejs/node/commit/3d2e23a981)] - **deps**: update ada to 3.4.4 (Node.js GitHub Bot) [#62414](https://github.com/nodejs/node/pull/62414) +* \[[`176d6d2205`](https://github.com/nodejs/node/commit/176d6d2205)] - **deps**: update timezone to 2026a (Node.js GitHub Bot) [#62164](https://github.com/nodejs/node/pull/62164) +* \[[`95c7fc67ba`](https://github.com/nodejs/node/commit/95c7fc67ba)] - **deps**: update googletest to 2461743991f9aa53e9a3625eafcbacd81a3c74cd (Node.js GitHub Bot) [#62484](https://github.com/nodejs/node/pull/62484) +* \[[`e5e9f2044a`](https://github.com/nodejs/node/commit/e5e9f2044a)] - **deps**: update simdjson to 4.5.0 (Node.js GitHub Bot) [#62382](https://github.com/nodejs/node/pull/62382) +* \[[`905b94266a`](https://github.com/nodejs/node/commit/905b94266a)] - **deps**: update ngtcp2 to 1.21.0 (Node.js GitHub Bot) [#62051](https://github.com/nodejs/node/pull/62051) +* \[[`180c150122`](https://github.com/nodejs/node/commit/180c150122)] - **deps**: V8: cherry-pick cf1bce40a5ef (Richard Lau) [#62449](https://github.com/nodejs/node/pull/62449) +* \[[`bc265aa003`](https://github.com/nodejs/node/commit/bc265aa003)] - **deps**: upgrade npm to 11.12.1 (npm team) [#62448](https://github.com/nodejs/node/pull/62448) +* \[[`f1b28612c4`](https://github.com/nodejs/node/commit/f1b28612c4)] - **deps**: V8: cherry-pick b25cd62c7ba2 (Yagiz Nizipli) [#62354](https://github.com/nodejs/node/pull/62354) +* \[[`757719d2af`](https://github.com/nodejs/node/commit/757719d2af)] - **deps**: disable rust icu compiled\_data features (Chengzhong Wu) [#62284](https://github.com/nodejs/node/pull/62284) +* \[[`3bdc955b63`](https://github.com/nodejs/node/commit/3bdc955b63)] - **deps**: update sqlite to 3.51.3 (Node.js GitHub Bot) [#62256](https://github.com/nodejs/node/pull/62256) +* \[[`a9703d194a`](https://github.com/nodejs/node/commit/a9703d194a)] - **deps**: update googletest to 73a63ea05dc8ca29ec1d2c1d66481dd0de1950f1 (Node.js GitHub Bot) [#61927](https://github.com/nodejs/node/pull/61927) +* \[[`85138935cb`](https://github.com/nodejs/node/commit/85138935cb)] - **deps**: update merve to 1.2.2 (Node.js GitHub Bot) [#62213](https://github.com/nodejs/node/pull/62213) +* \[[`231521e75e`](https://github.com/nodejs/node/commit/231521e75e)] - **diagnostics\_channel**: add diagnostics channels for web locks (Ilyas Shabi) [#62123](https://github.com/nodejs/node/pull/62123) +* \[[`0093863664`](https://github.com/nodejs/node/commit/0093863664)] - **doc**: deprecate `module.register()` (DEP0205) (Geoffrey Booth) [#62395](https://github.com/nodejs/node/pull/62395) +* \[[`0b96ece6be`](https://github.com/nodejs/node/commit/0b96ece6be)] - **doc**: clarify that features cannot be both experimental and deprecated (Antoine du Hamel) [#62456](https://github.com/nodejs/node/pull/62456) +* \[[`8d3ea975f5`](https://github.com/nodejs/node/commit/8d3ea975f5)] - **doc**: fix 'transfered' typo in quic.md (lilianakatrina684-a11y) [#62492](https://github.com/nodejs/node/pull/62492) +* \[[`08ff16e0ba`](https://github.com/nodejs/node/commit/08ff16e0ba)] - **doc**: move sqlite type conversion section to correct level (René) [#62482](https://github.com/nodejs/node/pull/62482) +* \[[`61cc747dd8`](https://github.com/nodejs/node/commit/61cc747dd8)] - **doc**: add Rafael to last security release steward (Rafael Gonzaga) [#62423](https://github.com/nodejs/node/pull/62423) +* \[[`64cfa5a6fa`](https://github.com/nodejs/node/commit/64cfa5a6fa)] - **doc**: use npm-published version of doc-kit (Aviv Keller) [#62139](https://github.com/nodejs/node/pull/62139) +* \[[`1020321fb0`](https://github.com/nodejs/node/commit/1020321fb0)] - **doc**: fix overstated Date header requirement in response.sendDate (Kit Dallege) [#62206](https://github.com/nodejs/node/pull/62206) +* \[[`9caa7855b2`](https://github.com/nodejs/node/commit/9caa7855b2)] - **doc**: fix guaranteed typo (lilianakatrina684-a11y) [#62374](https://github.com/nodejs/node/pull/62374) +* \[[`e254f65306`](https://github.com/nodejs/node/commit/e254f65306)] - **doc**: enhance clarification about the main field (Mowafak Almahaini) [#62302](https://github.com/nodejs/node/pull/62302) +* \[[`9e724b53f8`](https://github.com/nodejs/node/commit/9e724b53f8)] - **doc**: remove spawn with shell example from bat/cmd section (Kit Dallege) [#62243](https://github.com/nodejs/node/pull/62243) +* \[[`7f37c17516`](https://github.com/nodejs/node/commit/7f37c17516)] - **doc**: minor typo fix (Jeff Matson) [#62358](https://github.com/nodejs/node/pull/62358) +* \[[`eb0ca98f01`](https://github.com/nodejs/node/commit/eb0ca98f01)] - **doc**: add path to vulnerabilities.json mention (Rafael Gonzaga) [#62355](https://github.com/nodejs/node/pull/62355) +* \[[`198b6e0932`](https://github.com/nodejs/node/commit/198b6e0932)] - **doc**: deprecate CryptoKey use in node:crypto (Filip Skokan) [#62321](https://github.com/nodejs/node/pull/62321) +* \[[`17e5aee6c5`](https://github.com/nodejs/node/commit/17e5aee6c5)] - **doc**: fix small environment\_variables typo (chris) [#62279](https://github.com/nodejs/node/pull/62279) +* \[[`193d629895`](https://github.com/nodejs/node/commit/193d629895)] - **doc**: test and test-only targets do not run linter (Xavier Stouder) [#62120](https://github.com/nodejs/node/pull/62120) +* \[[`4a1f20ec4a`](https://github.com/nodejs/node/commit/4a1f20ec4a)] - **doc**: clarify fs.ReadStream and fs.WriteStream are not constructable (Kit Dallege) [#62208](https://github.com/nodejs/node/pull/62208) +* \[[`f976c9214d`](https://github.com/nodejs/node/commit/f976c9214d)] - **doc**: clarify that any truthy value of `shell` is part of DEP0190 (Antoine du Hamel) [#62249](https://github.com/nodejs/node/pull/62249) +* \[[`4d83972681`](https://github.com/nodejs/node/commit/4d83972681)] - **doc**: remove outdated Chrome 66 and ndb references from debugger (Kit Dallege) [#62202](https://github.com/nodejs/node/pull/62202) +* \[[`71f2eada5b`](https://github.com/nodejs/node/commit/71f2eada5b)] - **doc**: add throwIfNoEntry version history to fs.stat (kovan) [#62204](https://github.com/nodejs/node/pull/62204) +* \[[`670c80893b`](https://github.com/nodejs/node/commit/670c80893b)] - **doc**: add note (and caveat) for `mock.module` about customization hooks (Jacob Smith) [#62075](https://github.com/nodejs/node/pull/62075) +* \[[`2ff5cb13f5`](https://github.com/nodejs/node/commit/2ff5cb13f5)] - **doc,test**: clarify --eval syntax for leading '-' scripts (kovan) [#62244](https://github.com/nodejs/node/pull/62244) +* \[[`6c6c9004c4`](https://github.com/nodejs/node/commit/6c6c9004c4)] - **esm**: fix typo in worker loader hook comment (jakecastelli) [#62475](https://github.com/nodejs/node/pull/62475) +* \[[`1cdd23c9f3`](https://github.com/nodejs/node/commit/1cdd23c9f3)] - **esm**: fix source phase identity bug in loadCache eviction (Guy Bedford) [#62415](https://github.com/nodejs/node/pull/62415) +* \[[`4f4ff15794`](https://github.com/nodejs/node/commit/4f4ff15794)] - **esm**: fix path normalization in `finalizeResolution` (Antoine du Hamel) [#62080](https://github.com/nodejs/node/pull/62080) +* \[[`088167d102`](https://github.com/nodejs/node/commit/088167d102)] - **events**: avoid cloning listeners array on every emit (Gürgün Dayıoğlu) [#62261](https://github.com/nodejs/node/pull/62261) +* \[[`0250b436ee`](https://github.com/nodejs/node/commit/0250b436ee)] - **fs**: fix cpSync to handle non-ASCII characters (Stefan Stojanovic) [#61950](https://github.com/nodejs/node/pull/61950) +* \[[`b67a8fb171`](https://github.com/nodejs/node/commit/b67a8fb171)] - **inspector**: add Target.getTargets and extract TargetManager (Kohei) [#62487](https://github.com/nodejs/node/pull/62487) +* \[[`ffcc5a5722`](https://github.com/nodejs/node/commit/ffcc5a5722)] - **lib**: make SubtleCrypto.supports enumerable (Filip Skokan) [#62307](https://github.com/nodejs/node/pull/62307) +* \[[`92ef2ad8fa`](https://github.com/nodejs/node/commit/92ef2ad8fa)] - **lib**: prefer primordials in SubtleCrypto (Filip Skokan) [#62226](https://github.com/nodejs/node/pull/62226) +* \[[`40a43ac4d0`](https://github.com/nodejs/node/commit/40a43ac4d0)] - **module**: fix coverage of mocked CJS modules imported from ESM (Marco) [#62133](https://github.com/nodejs/node/pull/62133) +* \[[`3ef0a5b90e`](https://github.com/nodejs/node/commit/3ef0a5b90e)] - **quic**: remove CryptoKey support from session keys option (Filip Skokan) [#62335](https://github.com/nodejs/node/pull/62335) +* \[[`3c8dd8eb8e`](https://github.com/nodejs/node/commit/3c8dd8eb8e)] - **repl**: use vm DONT\_CONTEXTIFY context (Chengzhong Wu) [#62371](https://github.com/nodejs/node/pull/62371) +* \[[`f85b9d9fa8`](https://github.com/nodejs/node/commit/f85b9d9fa8)] - **(SEMVER-MINOR)** **repl**: add customizable error handling (Anna Henningsen) [#62188](https://github.com/nodejs/node/pull/62188) +* \[[`e4c164e045`](https://github.com/nodejs/node/commit/e4c164e045)] - **repl**: handle exceptions from async context after close (Anna Henningsen) [#62165](https://github.com/nodejs/node/pull/62165) +* \[[`67b854d407`](https://github.com/nodejs/node/commit/67b854d407)] - **(SEMVER-MINOR)** **repl**: remove dependency on domain module (Matteo Collina) [#61227](https://github.com/nodejs/node/pull/61227) +* \[[`966b700623`](https://github.com/nodejs/node/commit/966b700623)] - **(SEMVER-MINOR)** **sea**: support code cache for ESM entrypoint in SEA (Joyee Cheung) [#62158](https://github.com/nodejs/node/pull/62158) +* \[[`fe82baf970`](https://github.com/nodejs/node/commit/fe82baf970)] - **src**: improve EC JWK import performance (Filip Skokan) [#62396](https://github.com/nodejs/node/pull/62396) +* \[[`d490b171e0`](https://github.com/nodejs/node/commit/d490b171e0)] - **src**: handle null backing store in ArrayBufferViewContents::Read (Mert Can Altin) [#62343](https://github.com/nodejs/node/pull/62343) +* \[[`0e4af848bc`](https://github.com/nodejs/node/commit/0e4af848bc)] - **src**: convert context\_frame field in AsyncWrap to internal field (Anna Henningsen) [#62103](https://github.com/nodejs/node/pull/62103) +* \[[`02980b8c8f`](https://github.com/nodejs/node/commit/02980b8c8f)] - **src**: enable compilation/linking with OpenSSL 4.0 (Filip Skokan) [#62410](https://github.com/nodejs/node/pull/62410) +* \[[`064f7c2fa6`](https://github.com/nodejs/node/commit/064f7c2fa6)] - **src**: use stack allocation in indexOf latin1 path (Mert Can Altin) [#62268](https://github.com/nodejs/node/pull/62268) +* \[[`ede52bc2dc`](https://github.com/nodejs/node/commit/ede52bc2dc)] - **src,sqlite**: fix filterFunc dangling reference (Edy Silva) [#62281](https://github.com/nodejs/node/pull/62281) +* \[[`e1f0d2a014`](https://github.com/nodejs/node/commit/e1f0d2a014)] - **(SEMVER-MINOR)** **stream**: add stream/iter Implementation (James M Snell) [#62066](https://github.com/nodejs/node/pull/62066) +* \[[`03839fb087`](https://github.com/nodejs/node/commit/03839fb087)] - **stream**: preserve error over AbortError in pipeline (Marco) [#62113](https://github.com/nodejs/node/pull/62113) +* \[[`0000d2f011`](https://github.com/nodejs/node/commit/0000d2f011)] - **stream**: replace bind with arrow function for onwrite callback (Ali Hassan) [#62087](https://github.com/nodejs/node/pull/62087) +* \[[`3796a73719`](https://github.com/nodejs/node/commit/3796a73719)] - **test**: update WPT for WebCryptoAPI to 2cb332d710 (Node.js GitHub Bot) [#62483](https://github.com/nodejs/node/pull/62483) +* \[[`ad8309415b`](https://github.com/nodejs/node/commit/ad8309415b)] - **test**: update WPT for url to fc3e651593 (Node.js GitHub Bot) [#62379](https://github.com/nodejs/node/pull/62379) +* \[[`bed89b037e`](https://github.com/nodejs/node/commit/bed89b037e)] - **test**: wait for reattach before initial break on restart (Yuya Inoue) [#62471](https://github.com/nodejs/node/pull/62471) +* \[[`c9ffffcc55`](https://github.com/nodejs/node/commit/c9ffffcc55)] - **test**: disable flaky WPT Blob test on AIX (James M Snell) [#62470](https://github.com/nodejs/node/pull/62470) +* \[[`fd41ef31f6`](https://github.com/nodejs/node/commit/fd41ef31f6)] - **(SEMVER-MINOR)** **test**: add tests for experimental stream/iter implementation (James M Snell) [#62066](https://github.com/nodejs/node/pull/62066) +* \[[`1b9d8d3eec`](https://github.com/nodejs/node/commit/1b9d8d3eec)] - **test**: avoid flaky run wait in debugger restart test (Yuya Inoue) [#62112](https://github.com/nodejs/node/pull/62112) +* \[[`cb08a29d51`](https://github.com/nodejs/node/commit/cb08a29d51)] - **test**: skip test-cluster-dgram-reuse on AIX 7.3 (Stewart X Addison) [#62238](https://github.com/nodejs/node/pull/62238) +* \[[`abea0af8a9`](https://github.com/nodejs/node/commit/abea0af8a9)] - **test**: add WebCrypto Promise.prototype.then pollution regression tests (Filip Skokan) [#62226](https://github.com/nodejs/node/pull/62226) +* \[[`47a2132269`](https://github.com/nodejs/node/commit/47a2132269)] - **test**: update WPT for WebCryptoAPI to 6a1c545d77 (Node.js GitHub Bot) [#62187](https://github.com/nodejs/node/pull/62187) +* \[[`2c63d3006c`](https://github.com/nodejs/node/commit/2c63d3006c)] - **test\_runner**: add exports option for module mocks (sangwook) [#61727](https://github.com/nodejs/node/pull/61727) +* \[[`44ac0e1302`](https://github.com/nodejs/node/commit/44ac0e1302)] - **test\_runner**: make it compatible with fake timers (Matteo Collina) [#59272](https://github.com/nodejs/node/pull/59272) +* \[[`1865691275`](https://github.com/nodejs/node/commit/1865691275)] - **test\_runner**: set non-zero exit code when suite errors occur (Edy Silva) [#62282](https://github.com/nodejs/node/pull/62282) +* \[[`0252b2bab8`](https://github.com/nodejs/node/commit/0252b2bab8)] - **tools**: bump picomatch from 4.0.3 to 4.0.4 in /tools/eslint (dependabot\[bot]) [#62439](https://github.com/nodejs/node/pull/62439) +* \[[`3368155267`](https://github.com/nodejs/node/commit/3368155267)] - **tools**: bump yaml from 2.8.2 to 2.8.3 in /tools/doc (dependabot\[bot]) [#62437](https://github.com/nodejs/node/pull/62437) +* \[[`5e47c359f5`](https://github.com/nodejs/node/commit/5e47c359f5)] - **tools**: adopt the `--check-for-duplicates` NCU flag (Antoine du Hamel) [#62478](https://github.com/nodejs/node/pull/62478) +* \[[`4a604e82d0`](https://github.com/nodejs/node/commit/4a604e82d0)] - **tools**: bump picomatch in /tools/doc (dependabot\[bot]) [#62438](https://github.com/nodejs/node/pull/62438) +* \[[`d1a98b4ddb`](https://github.com/nodejs/node/commit/d1a98b4ddb)] - **tools**: bump flatted from 3.4.1 to 3.4.2 in /tools/eslint (dependabot\[bot]) [#62375](https://github.com/nodejs/node/pull/62375) +* \[[`c32daa1ab4`](https://github.com/nodejs/node/commit/c32daa1ab4)] - **tools**: bump eslint deps (Huáng Jùnliàng) [#62356](https://github.com/nodejs/node/pull/62356) +* \[[`7a2fcc6d41`](https://github.com/nodejs/node/commit/7a2fcc6d41)] - **tools**: do not swallow error in `lint-nix` workflow (Antoine du Hamel) [#62292](https://github.com/nodejs/node/pull/62292) +* \[[`c41a2871b5`](https://github.com/nodejs/node/commit/c41a2871b5)] - **tools**: add eslint-plugin-regexp (Huáng Jùnliàng) [#62093](https://github.com/nodejs/node/pull/62093) +* \[[`56dfeb06df`](https://github.com/nodejs/node/commit/56dfeb06df)] - **tools**: fix timeout errors in `lint-nix` job (Antoine du Hamel) [#62265](https://github.com/nodejs/node/pull/62265) +* \[[`22fc8078e8`](https://github.com/nodejs/node/commit/22fc8078e8)] - **tools**: bump flatted from 3.3.3 to 3.4.1 in /tools/eslint (dependabot\[bot]) [#62255](https://github.com/nodejs/node/pull/62255) +* \[[`409b0663bd`](https://github.com/nodejs/node/commit/409b0663bd)] - **tools**: bump undici from 6.23.0 to 6.24.1 in /tools/doc (dependabot\[bot]) [#62250](https://github.com/nodejs/node/pull/62250) +* \[[`67c69750f4`](https://github.com/nodejs/node/commit/67c69750f4)] - **tools**: validate all commits that are pushed to `main` (Antoine du Hamel) [#62246](https://github.com/nodejs/node/pull/62246) +* \[[`7d9db8cd21`](https://github.com/nodejs/node/commit/7d9db8cd21)] - **tools**: keep GN files when updating Merve (Antoine du Hamel) [#62167](https://github.com/nodejs/node/pull/62167) +* \[[`6c8fa42ba2`](https://github.com/nodejs/node/commit/6c8fa42ba2)] - **typings**: rationalise TypedArray types (René) [#62174](https://github.com/nodejs/node/pull/62174) +* \[[`531c64d04e`](https://github.com/nodejs/node/commit/531c64d04e)] - **url**: enable simdutf for ada (Yagiz Nizipli) [#61477](https://github.com/nodejs/node/pull/61477) +* \[[`2000caccde`](https://github.com/nodejs/node/commit/2000caccde)] - **util**: allow color aliases in styleText (sangwook) [#62180](https://github.com/nodejs/node/pull/62180) +* \[[`0aed332ab4`](https://github.com/nodejs/node/commit/0aed332ab4)] - **wasm**: support js string constant esm import (Guy Bedford) [#62198](https://github.com/nodejs/node/pull/62198) +* \[[`d3fd4a978b`](https://github.com/nodejs/node/commit/d3fd4a978b)] - **worker**: heap profile optimizations (Ilyas Shabi) [#62201](https://github.com/nodejs/node/pull/62201) +* \[[`e992a34a18`](https://github.com/nodejs/node/commit/e992a34a18)] - **zlib**: fix use-after-free when reset() is called during write (Matteo Collina) [#62325](https://github.com/nodejs/node/pull/62325) + ## 2026-03-24, Version 25.8.2 (Current), @RafaelGSS diff --git a/doc/contributing/collaborator-guide.md b/doc/contributing/collaborator-guide.md index 6f2e400d440181..99b3ec84b3b9e0 100644 --- a/doc/contributing/collaborator-guide.md +++ b/doc/contributing/collaborator-guide.md @@ -387,6 +387,12 @@ undergo deprecation. The exceptions to this rule are: * Changes to errors thrown by dependencies of Node.js, such as V8. * One-time exceptions granted by the TSC. +Experimental and undocumented APIs are not considered stable, therefore are +typically removed without a deprecation cycle. However, if such API has gotten +some non-trivial adoption in the ecosystem, it (or a subset of it) can undergo +deprecation – at which point, changes to that API (or at least, its deprecated +subset) should follow [semantic versioning][] rules. + For more information, see [Deprecations](#deprecations). #### Breaking changes to internal elements @@ -526,8 +532,8 @@ the three Deprecation levels. Documentation-Only Deprecations can land in a minor release. They can not change to a Runtime Deprecation until the next major release. -No API can change to End-of-Life without going through a Runtime Deprecation -cycle. There is no rule that deprecated code must progress to End-of-Life. +No deprecated APIs can change to End-of-Life without going through a Runtime +Deprecation cycle. There is no rule that deprecated code must progress to End-of-Life. Documentation-Only and Runtime Deprecations can remain in place for an unlimited duration. @@ -1059,6 +1065,7 @@ need to be attached anymore, as only important bugfixes will be included. [node-core-utils-issues]: https://github.com/nodejs/node-core-utils/issues [ppc]: https://github.com/orgs/nodejs/teams/platform-ppc [s390]: https://github.com/orgs/nodejs/teams/platform-s390 +[semantic versioning]: https://semver.org/ [smartos]: https://github.com/orgs/nodejs/teams/platform-smartos [unreliable tests]: https://github.com/nodejs/node/issues?q=is%3Aopen+is%3Aissue+label%3A%22CI+%2F+flaky+test%22 [windows]: https://github.com/orgs/nodejs/teams/platform-windows diff --git a/doc/contributing/releases.md b/doc/contributing/releases.md index 48a9a0bb2e9759..439af1fa9e924b 100644 --- a/doc/contributing/releases.md +++ b/doc/contributing/releases.md @@ -314,14 +314,14 @@ git checkout -b v1.2.3-proposal upstream/v1.x-staging You can also run: ```bash -git node release -S --prepare --security --filterLabel vX.x +git node release -S --prepare --security=../vulnerabilities.json --filterLabel vX.x ``` Example: ```bash git checkout v20.x -git node release -S --prepare --security --filterLabel v20.x +git node release -S --prepare --security=../vulnerabilities.json --filterLabel v20.x ``` to automate the remaining steps until step 6 or you can perform it manually @@ -329,7 +329,7 @@ following the below steps. For semver-minors, you can pass the new version explicitly with `--newVersion` arg: ```bash -git node release -S --prepare --security --filterLabel v20.x --newVersion 20.20.0 +git node release -S --prepare --security=../vulnerabilities.json --filterLabel v20.x --newVersion 20.20.0 ```
          diff --git a/doc/contributing/security-release-process.md b/doc/contributing/security-release-process.md index 1107edc5a68976..ba7dbae378f730 100644 --- a/doc/contributing/security-release-process.md +++ b/doc/contributing/security-release-process.md @@ -39,6 +39,7 @@ The current security stewards are documented in the main Node.js | NodeSource | Rafael | 2025-May-14 | | NodeSource | Rafael | 2025-Jul-15 | | HeroDevs and NodeSource | Marco / Rafael | 2026-Jan-13 | +| NodeSource | Rafael | 2026-Mar-24 | | Datadog | Bryan | | | IBM | Joe | | | Platformatic | Matteo | | diff --git a/doc/node.1 b/doc/node.1 index 41cfd8844b3d1d..ac8a287641d9f3 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -720,6 +720,11 @@ top-level awaits, and print their location to help users find them. .It Fl -experimental-quic Enable experimental support for the QUIC protocol. . +.It Fl -experimental-stream-iter +Enable the experimental +.Sy node:stream/iter +module. +. .It Fl -experimental-sea-config Use this flag to generate a blob that can be injected into the Node.js binary to produce a single executable application. See the documentation @@ -2293,7 +2298,7 @@ that run in libuv's threadpool will experience degraded performance. In order to mitigate this issue, one potential solution is to increase the size of libuv's threadpool by setting the \fB'UV_THREADPOOL_SIZE'\fR environment variable to a value greater than \fB4\fR (its current default value). However, setting this from inside -the process using \fBprocess.env.UV_THREADPOOL_SIZE=size\fR is not guranteed to work +the process using \fBprocess.env.UV_THREADPOOL_SIZE=size\fR is not guaranteed to work as the threadpool would have been created as part of the runtime initialisation much before user code is run. For more information, see the libuv threadpool documentation. . diff --git a/eslint.config.mjs b/eslint.config.mjs index cd66c1392b68c7..fb6f9623587583 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -20,6 +20,7 @@ const { default: js } = await importEslintTool('@eslint/js'); const { default: babelEslintParser } = await importEslintTool('@babel/eslint-parser'); const babelPluginSyntaxImportSource = resolveEslintTool('@babel/plugin-syntax-import-source'); const { default: jsdoc } = await importEslintTool('eslint-plugin-jsdoc'); +const { default: regexpPlugin } = await importEslintTool('eslint-plugin-regexp'); const { default: markdown } = await importEslintTool('@eslint/markdown'); const { default: stylisticJs } = await importEslintTool('@stylistic/eslint-plugin'); @@ -84,6 +85,7 @@ export default [ // #region general config js.configs.recommended, jsdoc.configs['flat/recommended'], + regexpPlugin.configs.recommended, { files: ['**/*.js'], languageOptions: { @@ -275,6 +277,42 @@ export default [ 'jsdoc/reject-any-type': 'off', 'jsdoc/reject-function-type': 'off', + // RegExp recommended rules that we disable. + // Todo: Investigate which rules should be enabled. + 'prefer-regex-literals': 'off', + 'regexp/control-character-escape': 'off', + 'regexp/match-any': 'off', + 'regexp/negation': 'off', + 'regexp/no-contradiction-with-assertion': 'off', + 'regexp/no-dupe-characters-character-class': 'off', + 'regexp/no-dupe-disjunctions': 'off', + 'regexp/no-empty-alternative': 'off', + 'regexp/no-legacy-features': 'off', + 'regexp/no-misleading-capturing-group': 'off', + 'regexp/no-obscure-range': 'off', + 'regexp/no-potentially-useless-backreference': 'off', + 'regexp/no-super-linear-backtracking': 'off', + 'regexp/no-trivially-nested-quantifier': 'off', + 'regexp/no-unused-capturing-group': 'off', + 'regexp/no-useless-assertions': 'off', + 'regexp/no-useless-character-class': 'off', + 'regexp/no-useless-escape': 'off', + 'regexp/no-useless-flag': 'off', + 'regexp/no-useless-lazy': 'off', + 'regexp/no-useless-non-capturing-group': 'off', + 'regexp/no-useless-quantifier': 'off', + 'regexp/no-useless-range': 'off', + 'regexp/optimal-lookaround-quantifier': 'off', + 'regexp/optimal-quantifier-concatenation': 'off', + 'regexp/prefer-character-class': 'off', + 'regexp/prefer-d': 'off', + 'regexp/prefer-question-quantifier': 'off', + 'regexp/prefer-star-quantifier': 'off', + 'regexp/prefer-w': 'off', + 'regexp/sort-flags': 'off', + 'regexp/strict': 'off', + 'regexp/use-ignore-case': 'off', + // Stylistic rules. '@stylistic/js/arrow-parens': 'error', '@stylistic/js/arrow-spacing': 'error', diff --git a/lib/buffer.js b/lib/buffer.js index cf9e0ca50c3d7e..4d96a536a15f8b 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -50,7 +50,6 @@ const { TypedArrayPrototypeGetByteOffset, TypedArrayPrototypeGetLength, TypedArrayPrototypeSet, - TypedArrayPrototypeSlice, TypedArrayPrototypeSubarray, Uint8Array, } = primordials; @@ -383,28 +382,33 @@ Buffer.copyBytesFrom = function copyBytesFrom(view, offset, length) { return new FastBuffer(); } - if (offset !== undefined || length !== undefined) { - if (offset !== undefined) { - validateInteger(offset, 'offset', 0); - if (offset >= viewLength) return new FastBuffer(); - } else { - offset = 0; - } - let end; - if (length !== undefined) { - validateInteger(length, 'length', 0); - end = offset + length; - } else { - end = viewLength; - } + let start = 0; + let end = viewLength; - view = TypedArrayPrototypeSlice(view, offset, end); + if (offset !== undefined) { + validateInteger(offset, 'offset', 0); + if (offset >= viewLength) return new FastBuffer(); + start = offset; } + if (length !== undefined) { + validateInteger(length, 'length', 0); + // The old code used TypedArrayPrototypeSlice which clamps internally. + end = MathMin(start + length, viewLength); + } + + if (end <= start) return new FastBuffer(); + + const viewByteLength = TypedArrayPrototypeGetByteLength(view); + const elementSize = viewByteLength / viewLength; + const srcByteOffset = TypedArrayPrototypeGetByteOffset(view) + + start * elementSize; + const srcByteLength = (end - start) * elementSize; + return fromArrayLike(new Uint8Array( TypedArrayPrototypeGetBuffer(view), - TypedArrayPrototypeGetByteOffset(view), - TypedArrayPrototypeGetByteLength(view))); + srcByteOffset, + srcByteLength)); }; // Identical to the built-in %TypedArray%.of(), but avoids using the deprecated @@ -551,14 +555,15 @@ function fromArrayBuffer(obj, byteOffset, length) { } function fromArrayLike(obj) { - if (obj.length <= 0) + const { length } = obj; + if (length <= 0) return new FastBuffer(); - if (obj.length < (Buffer.poolSize >>> 1)) { - if (obj.length > (poolSize - poolOffset)) + if (length < (Buffer.poolSize >>> 1)) { + if (length > (poolSize - poolOffset)) createPool(); - const b = new FastBuffer(allocPool, poolOffset, obj.length); + const b = new FastBuffer(allocPool, poolOffset, length); TypedArrayPrototypeSet(b, obj, 0); - poolOffset += obj.length; + poolOffset += length; alignPool(); return b; } @@ -732,11 +737,7 @@ const encodingOps = { write: asciiWrite, slice: asciiSlice, indexOf: (buf, val, byteOffset, dir) => - indexOfBuffer(buf, - fromStringFast(val, encodingOps.ascii), - byteOffset, - encodingsMap.ascii, - dir), + indexOfString(buf, val, byteOffset, encodingsMap.ascii, dir), }, base64: { encoding: 'base64', @@ -897,17 +898,17 @@ Buffer.prototype.toString = function toString(encoding, start, end) { return utf8Slice(this, 0, this.length); } - const len = this.length; + const bufferLength = TypedArrayPrototypeGetLength(this); if (start <= 0) start = 0; - else if (start >= len) + else if (start >= bufferLength) return ''; else start = MathTrunc(start) || 0; - if (end === undefined || end > len) - end = len; + if (end === undefined || end > bufferLength) + end = bufferLength; else end = MathTrunc(end) || 0; @@ -1118,7 +1119,9 @@ function _fill(buf, value, offset, end, encoding) { value = 0; } else if (value.length === 1) { // Fast path: If `value` fits into a single byte, use that numeric value. - if (normalizedEncoding === 'utf8') { + // ASCII shares this branch with utf8 since code < 128 covers the full + // ASCII range; anything outside falls through to C++ bindingFill. + if (normalizedEncoding === 'utf8' || normalizedEncoding === 'ascii') { const code = StringPrototypeCharCodeAt(value, 0); if (code < 128) { value = code; @@ -1168,21 +1171,22 @@ function _fill(buf, value, offset, end, encoding) { } Buffer.prototype.write = function write(string, offset, length, encoding) { + const bufferLength = TypedArrayPrototypeGetLength(this); // Buffer#write(string); if (offset === undefined) { - return utf8Write(this, string, 0, this.length); + return utf8Write(this, string, 0, bufferLength); } // Buffer#write(string, encoding) if (length === undefined && typeof offset === 'string') { encoding = offset; - length = this.length; + length = bufferLength; offset = 0; // Buffer#write(string, offset[, length][, encoding]) } else { - validateOffset(offset, 'offset', 0, this.length); + validateOffset(offset, 'offset', 0, bufferLength); - const remaining = this.length - offset; + const remaining = bufferLength - offset; if (length === undefined) { length = remaining; @@ -1190,7 +1194,7 @@ Buffer.prototype.write = function write(string, offset, length, encoding) { encoding = length; length = remaining; } else { - validateOffset(length, 'length', 0, this.length); + validateOffset(length, 'length', 0, bufferLength); if (length > remaining) length = remaining; } @@ -1208,9 +1212,10 @@ Buffer.prototype.write = function write(string, offset, length, encoding) { }; Buffer.prototype.toJSON = function toJSON() { - if (this.length > 0) { - const data = new Array(this.length); - for (let i = 0; i < this.length; ++i) + const bufferLength = TypedArrayPrototypeGetLength(this); + if (bufferLength > 0) { + const data = new Array(bufferLength); + for (let i = 0; i < bufferLength; ++i) data[i] = this[i]; return { type: 'Buffer', data }; } @@ -1235,7 +1240,7 @@ function adjustOffset(offset, length) { } Buffer.prototype.subarray = function subarray(start, end) { - const srcLength = this.length; + const srcLength = TypedArrayPrototypeGetLength(this); start = adjustOffset(start, srcLength); end = end !== undefined ? adjustOffset(end, srcLength) : srcLength; const newLength = end > start ? end - start : 0; @@ -1253,45 +1258,52 @@ function swap(b, n, m) { } Buffer.prototype.swap16 = function swap16() { - // For Buffer.length < 128, it's generally faster to + // Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696 + // For Buffer.length <= 32, it's generally faster to // do the swap in javascript. For larger buffers, // dropping down to the native code is faster. - const len = this.length; + const len = TypedArrayPrototypeGetLength(this); if (len % 2 !== 0) throw new ERR_INVALID_BUFFER_SIZE('16-bits'); - if (len < 128) { + if (len <= 32) { for (let i = 0; i < len; i += 2) swap(this, i, i + 1); return this; } - return _swap16(this); + _swap16(this); + return this; }; Buffer.prototype.swap32 = function swap32() { - // For Buffer.length < 192, it's generally faster to + // Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696 + // For Buffer.length <= 32, it's generally faster to // do the swap in javascript. For larger buffers, // dropping down to the native code is faster. - const len = this.length; + const len = TypedArrayPrototypeGetLength(this); if (len % 4 !== 0) throw new ERR_INVALID_BUFFER_SIZE('32-bits'); - if (len < 192) { + if (len <= 32) { for (let i = 0; i < len; i += 4) { swap(this, i, i + 3); swap(this, i + 1, i + 2); } return this; } - return _swap32(this); + _swap32(this); + return this; }; Buffer.prototype.swap64 = function swap64() { - // For Buffer.length < 192, it's generally faster to + // Ref: https://github.com/nodejs/node/pull/61871#discussion_r2889557696 + // For Buffer.length < 48, it's generally faster to // do the swap in javascript. For larger buffers, // dropping down to the native code is faster. - const len = this.length; + // Threshold differs from swap16/swap32 (<=32) because swap64's + // crossover is between 40 and 48 (native wins at 48, loses at 40). + const len = TypedArrayPrototypeGetLength(this); if (len % 8 !== 0) throw new ERR_INVALID_BUFFER_SIZE('64-bits'); - if (len < 192) { + if (len < 48) { for (let i = 0; i < len; i += 8) { swap(this, i, i + 7); swap(this, i + 1, i + 6); @@ -1300,7 +1312,8 @@ Buffer.prototype.swap64 = function swap64() { } return this; } - return _swap64(this); + _swap64(this); + return this; }; Buffer.prototype.toLocaleString = Buffer.prototype.toString; diff --git a/lib/domain.js b/lib/domain.js index 7dd16ee1bf59ef..f3a42271d2326d 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -40,14 +40,11 @@ const { ReflectApply, SafeMap, SafeWeakMap, - StringPrototypeRepeat, Symbol, } = primordials; const EventEmitter = require('events'); const { - ERR_DOMAIN_CALLBACK_NOT_AVAILABLE, - ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE, ERR_UNHANDLED_ERROR, } = require('internal/errors').codes; const { createHook } = require('async_hooks'); @@ -119,22 +116,9 @@ const asyncHook = createHook({ }, }); -// When domains are in use, they claim full ownership of the -// uncaught exception capture callback. -if (process.hasUncaughtExceptionCaptureCallback()) { - throw new ERR_DOMAIN_CALLBACK_NOT_AVAILABLE(); -} - -// Get the stack trace at the point where `domain` was required. -// eslint-disable-next-line no-restricted-syntax -const domainRequireStack = new Error('require(`domain`) at this point').stack; - +// Domain uses the stacking capability of setUncaughtExceptionCaptureCallback +// to coexist with other callbacks (e.g., REPL). const { setUncaughtExceptionCaptureCallback } = process; -process.setUncaughtExceptionCaptureCallback = function(fn) { - const err = new ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE(); - err.stack += `\n${StringPrototypeRepeat('-', 40)}\n${domainRequireStack}`; - throw err; -}; let sendMakeCallbackDeprecation = false; diff --git a/lib/events.js b/lib/events.js index c065e43d6d2565..c6fc170d590aea 100644 --- a/lib/events.js +++ b/lib/events.js @@ -87,6 +87,7 @@ const { addAbortListener } = require('internal/events/abort_listener'); const kCapture = Symbol('kCapture'); const kErrorMonitor = Symbol('events.errorMonitor'); const kShapeMode = Symbol('shapeMode'); +const kEmitting = Symbol('events.emitting'); const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); const kMaxEventTargetListenersWarned = Symbol('events.maxEventTargetListenersWarned'); @@ -514,19 +515,22 @@ EventEmitter.prototype.emit = function emit(type, ...args) { addCatch(this, result, type, args); } } else { - const len = handler.length; - const listeners = arrayClone(handler); - for (let i = 0; i < len; ++i) { - const result = ReflectApply(listeners[i], this, args); - - // We check if result is undefined first because that - // is the most common case so we do not pay any perf - // penalty. - // This code is duplicated because extracting it away - // would make it non-inlineable. - if (result !== undefined && result !== null) { - addCatch(this, result, type, args); + handler[kEmitting]++; + try { + for (let i = 0; i < handler.length; ++i) { + const result = ReflectApply(handler[i], this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } } + } finally { + handler[kEmitting]--; } } @@ -565,13 +569,17 @@ function _addListener(target, type, listener, prepend) { } else { if (typeof existing === 'function') { // Adding the second element, need to change to array. - existing = events[type] = - prepend ? [listener, existing] : [existing, listener]; + existing = prepend ? [listener, existing] : [existing, listener]; + existing[kEmitting] = 0; + events[type] = existing; // If we've already got an array, just append. - } else if (prepend) { - existing.unshift(listener); } else { - existing.push(listener); + existing = ensureMutableListenerArray(events, type, existing); + if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } } // Check for listener leak @@ -674,7 +682,7 @@ EventEmitter.prototype.removeListener = if (events === undefined) return this; - const list = events[type]; + let list = events[type]; if (list === undefined) return this; @@ -692,6 +700,7 @@ EventEmitter.prototype.removeListener = if (events.removeListener !== undefined) this.emit('removeListener', type, list.listener || listener); } else if (typeof list !== 'function') { + list = ensureMutableListenerArray(events, type, list); let position = -1; for (let i = list.length - 1; i >= 0; i--) { @@ -875,6 +884,24 @@ function arrayClone(arr) { return ArrayPrototypeSlice(arr); } +function cloneEventListenerArray(arr) { + const copy = arrayClone(arr); + copy[kEmitting] = 0; + if (arr.warned) { + copy.warned = true; + } + return copy; +} + +function ensureMutableListenerArray(events, type, handler) { + if (handler[kEmitting] > 0) { + const copy = cloneEventListenerArray(handler); + events[type] = copy; + return copy; + } + return handler; +} + function unwrapListeners(arr) { const ret = arrayClone(arr); for (let i = 0; i < ret.length; ++i) { diff --git a/lib/internal/async_local_storage/async_context_frame.js b/lib/internal/async_local_storage/async_context_frame.js index 518e955379ac54..8390ba92cbe848 100644 --- a/lib/internal/async_local_storage/async_context_frame.js +++ b/lib/internal/async_local_storage/async_context_frame.js @@ -12,6 +12,8 @@ const { const AsyncContextFrame = require('internal/async_context_frame'); const { AsyncResource } = require('async_hooks'); +const RunScope = require('internal/async_local_storage/run_scope'); + class AsyncLocalStorage { #defaultValue = undefined; #name = undefined; @@ -77,6 +79,10 @@ class AsyncLocalStorage { } return frame?.get(this); } + + withScope(store) { + return new RunScope(this, store); + } } module.exports = AsyncLocalStorage; diff --git a/lib/internal/async_local_storage/async_hooks.js b/lib/internal/async_local_storage/async_hooks.js index d227549412bf61..552092d89bf305 100644 --- a/lib/internal/async_local_storage/async_hooks.js +++ b/lib/internal/async_local_storage/async_hooks.js @@ -19,6 +19,8 @@ const { executionAsyncResource, } = require('async_hooks'); +const RunScope = require('internal/async_local_storage/run_scope'); + const storageList = []; const storageHook = createHook({ init(asyncId, type, triggerAsyncId, resource) { @@ -142,6 +144,10 @@ class AsyncLocalStorage { } return this.#defaultValue; } + + withScope(store) { + return new RunScope(this, store); + } } module.exports = AsyncLocalStorage; diff --git a/lib/internal/async_local_storage/run_scope.js b/lib/internal/async_local_storage/run_scope.js new file mode 100644 index 00000000000000..a716c27cbb0f79 --- /dev/null +++ b/lib/internal/async_local_storage/run_scope.js @@ -0,0 +1,31 @@ +'use strict'; + +const { + SymbolDispose, +} = primordials; + +class RunScope { + #storage; + #previousStore; + #disposed = false; + + constructor(storage, store) { + this.#storage = storage; + this.#previousStore = storage.getStore(); + storage.enterWith(store); + } + + dispose() { + if (this.#disposed) { + return; + } + this.#disposed = true; + this.#storage.enterWith(this.#previousStore); + } + + [SymbolDispose]() { + this.dispose(); + } +} + +module.exports = RunScope; diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 2fafdb92a78e04..af4027b7324c7b 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -307,6 +307,7 @@ ObjectDefineProperty(process, 'features', { const { onGlobalUncaughtException, setUncaughtExceptionCaptureCallback, + addUncaughtExceptionCaptureCallback, hasUncaughtExceptionCaptureCallback, } = require('internal/process/execution'); @@ -319,6 +320,8 @@ ObjectDefineProperty(process, 'features', { process._fatalException = onGlobalUncaughtException; process.setUncaughtExceptionCaptureCallback = setUncaughtExceptionCaptureCallback; + process.addUncaughtExceptionCaptureCallback = + addUncaughtExceptionCaptureCallback; process.hasUncaughtExceptionCaptureCallback = hasUncaughtExceptionCaptureCallback; } diff --git a/lib/internal/bootstrap/realm.js b/lib/internal/bootstrap/realm.js index f49f0814bbc687..2ccceb493e68bb 100644 --- a/lib/internal/bootstrap/realm.js +++ b/lib/internal/bootstrap/realm.js @@ -131,7 +131,7 @@ const schemelessBlockList = new SafeSet([ 'test/reporters', ]); // Modules that will only be enabled at run time. -const experimentalModuleList = new SafeSet(['sqlite', 'quic']); +const experimentalModuleList = new SafeSet(['sqlite', 'quic', 'stream/iter', 'zlib/iter']); // Set up process.binding() and process._linkedBinding(). { diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 0474060d394c99..c0765f75642189 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -1,12 +1,9 @@ 'use strict'; const { - ArrayBufferIsView, - ArrayBufferPrototypeSlice, ArrayFrom, ArrayPrototypePush, SafeSet, - TypedArrayPrototypeSlice, } = primordials; const { @@ -28,8 +25,6 @@ const { kKeyVariantAES_GCM_256, kKeyVariantAES_KW_256, kKeyVariantAES_OCB_256, - kWebCryptoCipherDecrypt, - kWebCryptoCipherEncrypt, } = internalBinding('crypto'); const { @@ -143,80 +138,33 @@ function asyncAesKwCipher(mode, key, data) { getVariant('AES-KW', key[kAlgorithm].length))); } -async function asyncAesGcmCipher(mode, key, data, algorithm) { +function asyncAesGcmCipher(mode, key, data, algorithm) { const { tagLength = 128 } = algorithm; - const tagByteLength = tagLength / 8; - let tag; - switch (mode) { - case kWebCryptoCipherDecrypt: { - const slice = ArrayBufferIsView(data) ? - TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; - tag = slice(data, -tagByteLength); - - // Refs: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-operations - // - // > If *plaintext* has a length less than *tagLength* bits, then `throw` - // > an `OperationError`. - if (tagByteLength > tag.byteLength) { - throw lazyDOMException( - 'The provided data is too small.', - 'OperationError'); - } - data = slice(data, 0, -tagByteLength); - break; - } - case kWebCryptoCipherEncrypt: - tag = tagByteLength; - break; - } - - return await jobPromise(() => new AESCipherJob( + return jobPromise(() => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], data, getVariant('AES-GCM', key[kAlgorithm].length), algorithm.iv, - tag, + tagByteLength, algorithm.additionalData)); } -async function asyncAesOcbCipher(mode, key, data, algorithm) { +function asyncAesOcbCipher(mode, key, data, algorithm) { const { tagLength = 128 } = algorithm; - const tagByteLength = tagLength / 8; - let tag; - switch (mode) { - case kWebCryptoCipherDecrypt: { - const slice = ArrayBufferIsView(data) ? - TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; - tag = slice(data, -tagByteLength); - - // Similar to GCM, OCB requires the tag to be present for decryption - if (tagByteLength > tag.byteLength) { - throw lazyDOMException( - 'The provided data is too small.', - 'OperationError'); - } - data = slice(data, 0, -tagByteLength); - break; - } - case kWebCryptoCipherEncrypt: - tag = tagByteLength; - break; - } - - return await jobPromise(() => new AESCipherJob( + return jobPromise(() => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], data, getVariant('AES-OCB', key.algorithm.length), algorithm.iv, - tag, + tagByteLength, algorithm.additionalData)); } diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index c5bbaae90cf595..fd3f168435ddcb 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -2,6 +2,7 @@ const { SafeSet, + StringPrototypeToLowerCase, } = primordials; const { Buffer } = require('buffer'); @@ -332,7 +333,7 @@ function cfrgImportKey( return undefined; } - if (keyObject.asymmetricKeyType !== name.toLowerCase()) { + if (keyObject.asymmetricKeyType !== StringPrototypeToLowerCase(name)) { throw lazyDOMException('Invalid key type', 'DataError'); } diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js index 0979d7aaddbb61..a2b7c1fb04fb89 100644 --- a/lib/internal/crypto/chacha20_poly1305.js +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -1,19 +1,14 @@ 'use strict'; const { - ArrayBufferIsView, - ArrayBufferPrototypeSlice, ArrayFrom, SafeSet, - TypedArrayPrototypeSlice, } = primordials; const { ChaCha20Poly1305CipherJob, KeyObjectHandle, kCryptoJobAsync, - kWebCryptoCipherDecrypt, - kWebCryptoCipherEncrypt, } = internalBinding('crypto'); const { @@ -46,35 +41,13 @@ function validateKeyLength(length) { throw lazyDOMException('Invalid key length', 'DataError'); } -async function c20pCipher(mode, key, data, algorithm) { - let tag; - switch (mode) { - case kWebCryptoCipherDecrypt: { - const slice = ArrayBufferIsView(data) ? - TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; - - if (data.byteLength < 16) { - throw lazyDOMException( - 'The provided data is too small.', - 'OperationError'); - } - - tag = slice(data, -16); - data = slice(data, 0, -16); - break; - } - case kWebCryptoCipherEncrypt: - tag = 16; - break; - } - - return await jobPromise(() => new ChaCha20Poly1305CipherJob( +function c20pCipher(mode, key, data, algorithm) { + return jobPromise(() => new ChaCha20Poly1305CipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], data, algorithm.iv, - tag, algorithm.additionalData)); } diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index 3f34468e55e99f..857753c2b39f9c 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -14,6 +14,8 @@ const { Hmac: _Hmac, kCryptoJobAsync, oneShotDigest, + TurboShakeJob, + KangarooTwelveJob, } = internalBinding('crypto'); const { @@ -223,7 +225,25 @@ async function asyncDigest(algorithm, data) { kCryptoJobAsync, normalizeHashName(algorithm.name), data, - algorithm.length)); + algorithm.outputLength)); + case 'TurboSHAKE128': + // Fall through + case 'TurboSHAKE256': + return await jobPromise(() => new TurboShakeJob( + kCryptoJobAsync, + algorithm.name, + algorithm.domainSeparation ?? 0x1f, + algorithm.outputLength / 8, + data)); + case 'KT128': + // Fall through + case 'KT256': + return await jobPromise(() => new KangarooTwelveJob( + kCryptoJobAsync, + algorithm.name, + algorithm.customization, + algorithm.outputLength / 8, + data)); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index a31c3ddb0d9484..1ad4e27c6a8d39 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -234,7 +234,7 @@ function kmacSignVerify(key, data, algorithm, signature) { key[kKeyObject][kHandle], algorithm.name, algorithm.customization, - algorithm.length / 8, + algorithm.outputLength / 8, data, signature)); } diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index ebe3bfe3d17ca0..11a7e8d843a0a7 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -2,6 +2,7 @@ const { SafeSet, + StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeSet, Uint8Array, @@ -191,6 +192,19 @@ function mlDsaImportKey( } case 'pkcs8': { verifyAcceptableMlDsaKeyUse(name, false, usagesSet); + + const privOnlyLengths = { + '__proto__': null, + 'ML-DSA-44': 2588, + 'ML-DSA-65': 4060, + 'ML-DSA-87': 4924, + }; + if (keyData.byteLength === privOnlyLengths[name]) { + throw lazyDOMException( + 'Importing an ML-DSA PKCS#8 key without a seed is not supported', + 'NotSupportedError'); + } + try { keyObject = createPrivateKey({ key: keyData, @@ -276,7 +290,7 @@ function mlDsaImportKey( return undefined; } - if (keyObject.asymmetricKeyType !== name.toLowerCase()) { + if (keyObject.asymmetricKeyType !== StringPrototypeToLowerCase(name)) { throw lazyDOMException('Invalid key type', 'DataError'); } diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index f6eb76cef10b20..9cf44efc37f20a 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -3,6 +3,7 @@ const { PromiseWithResolvers, SafeSet, + StringPrototypeToLowerCase, TypedArrayPrototypeGetBuffer, TypedArrayPrototypeSet, Uint8Array, @@ -181,6 +182,19 @@ function mlKemImportKey( } case 'pkcs8': { verifyAcceptableMlKemKeyUse(name, false, usagesSet); + + const privOnlyLengths = { + '__proto__': null, + 'ML-KEM-512': 1660, + 'ML-KEM-768': 2428, + 'ML-KEM-1024': 3196, + }; + if (keyData.byteLength === privOnlyLengths[name]) { + throw lazyDOMException( + 'Importing an ML-KEM PKCS#8 key without a seed is not supported', + 'NotSupportedError'); + } + try { keyObject = createPrivateKey({ key: keyData, @@ -209,7 +223,7 @@ function mlKemImportKey( return undefined; } - if (keyObject.asymmetricKeyType !== name.toLowerCase()) { + if (keyObject.asymmetricKeyType !== StringPrototypeToLowerCase(name)) { throw lazyDOMException('Invalid key type', 'DataError'); } diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 5ec1d409ec05b9..70e1027946190a 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -15,7 +15,7 @@ const { ObjectEntries, ObjectKeys, ObjectPrototypeHasOwnProperty, - Promise, + PromiseWithResolvers, StringPrototypeToUpperCase, Symbol, TypedArrayPrototypeGetBuffer, @@ -244,6 +244,10 @@ const kAlgorithmDefinitions = { }, 'cSHAKE128': { 'digest': 'CShakeParams' }, 'cSHAKE256': { 'digest': 'CShakeParams' }, + 'KT128': { 'digest': 'KangarooTwelveParams' }, + 'KT256': { 'digest': 'KangarooTwelveParams' }, + 'TurboSHAKE128': { 'digest': 'TurboShakeParams' }, + 'TurboSHAKE256': { 'digest': 'TurboShakeParams' }, 'ECDH': { 'generateKey': 'EcKeyGenParams', 'exportKey': null, @@ -441,6 +445,10 @@ const experimentalAlgorithms = [ 'SHA3-256', 'SHA3-384', 'SHA3-512', + 'TurboSHAKE128', + 'TurboSHAKE256', + 'KT128', + 'KT256', 'X448', ]; @@ -513,6 +521,10 @@ const simpleAlgorithmDictionaries = { KmacParams: { customization: 'BufferSource', }, + KangarooTwelveParams: { + customization: 'BufferSource', + }, + TurboShakeParams: {}, }; function validateMaxBufferLength(data, name) { @@ -565,10 +577,13 @@ function normalizeAlgorithm(algorithm, op) { return { name: algName }; // 6. - const normalizedAlgorithm = webidl.converters[desiredType](algorithm, { - prefix: 'Failed to normalize algorithm', - context: 'passed algorithm', - }); + const normalizedAlgorithm = webidl.converters[desiredType]( + { __proto__: algorithm, name: algName }, + { + prefix: 'Failed to normalize algorithm', + context: 'passed algorithm', + }, + ); // 7. normalizedAlgorithm.name = algName; @@ -653,15 +668,15 @@ function onDone(resolve, reject, err, result) { } function jobPromise(getJob) { - return new Promise((resolve, reject) => { - try { - const job = getJob(); - job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject); - job.run(); - } catch (err) { - onDone(resolve, reject, err); - } - }); + const { promise, resolve, reject } = PromiseWithResolvers(); + try { + const job = getJob(); + job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject); + job.run(); + } catch (err) { + onDone(resolve, reject, err); + } + return promise; } // In WebCrypto, the publicExponent option in RSA is represented as a diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 031bb9a3669fdf..e95c4fd1392bca 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -1894,6 +1894,10 @@ ObjectDefineProperties( }, }); +ObjectDefineProperties(SubtleCrypto, { + supports: kEnumerableProperty, +}); + module.exports = { Crypto, CryptoKey, diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index e72931e9c83cca..4f371955a73bfd 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -195,12 +195,14 @@ converters.object = (V, opts) => { const isNonSharedArrayBuffer = isArrayBuffer; +/** + * @param {string | object} V - The hash algorithm identifier (string or object). + * @param {string} label - The dictionary name for the error message. + */ function ensureSHA(V, label) { - if ( - typeof V === 'string' ? - !StringPrototypeStartsWith(StringPrototypeToLowerCase(V), 'sha') : - V.name?.toLowerCase?.().startsWith('sha') === false - ) + const name = typeof V === 'string' ? V : V.name; + if (typeof name !== 'string' || + !StringPrototypeStartsWith(StringPrototypeToLowerCase(name), 'sha')) throw lazyDOMException( `Only SHA hashes are supported in ${label}`, 'NotSupportedError'); } @@ -589,14 +591,14 @@ converters.CShakeParams = createDictionaryConverter( 'CShakeParams', [ ...new SafeArrayIterator(dictAlgorithm), { - key: 'length', + key: 'outputLength', converter: (V, opts) => converters['unsigned long'](V, { ...opts, enforceRange: true }), validator: (V, opts) => { // The Web Crypto spec allows for SHAKE output length that are not multiples of // 8. We don't. if (V % 8) - throw lazyDOMException('Unsupported CShakeParams length', 'NotSupportedError'); + throw lazyDOMException('Unsupported CShakeParams outputLength', 'NotSupportedError'); }, required: true, }, @@ -879,13 +881,13 @@ converters.KmacParams = createDictionaryConverter( 'KmacParams', [ ...new SafeArrayIterator(dictAlgorithm), { - key: 'length', + key: 'outputLength', converter: (V, opts) => converters['unsigned long'](V, { ...opts, enforceRange: true }), validator: (V, opts) => { // The Web Crypto spec allows for KMAC output length that are not multiples of 8. We don't. if (V % 8) - throw lazyDOMException('Unsupported KmacParams length', 'NotSupportedError'); + throw lazyDOMException('Unsupported KmacParams outputLength', 'NotSupportedError'); }, required: true, }, @@ -895,6 +897,52 @@ converters.KmacParams = createDictionaryConverter( }, ]); +converters.KangarooTwelveParams = createDictionaryConverter( + 'KangarooTwelveParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'outputLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + if (V === 0 || V % 8) + throw lazyDOMException('Invalid KangarooTwelveParams outputLength', 'OperationError'); + }, + required: true, + }, + { + key: 'customization', + converter: converters.BufferSource, + }, + ]); + +converters.TurboShakeParams = createDictionaryConverter( + 'TurboShakeParams', [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: 'outputLength', + converter: (V, opts) => + converters['unsigned long'](V, { ...opts, enforceRange: true }), + validator: (V, opts) => { + if (V === 0 || V % 8) + throw lazyDOMException('Invalid TurboShakeParams outputLength', 'OperationError'); + }, + required: true, + }, + { + key: 'domainSeparation', + converter: (V, opts) => + converters.octet(V, { ...opts, enforceRange: true }), + validator: (V) => { + if (V < 0x01 || V > 0x7F) { + throw lazyDOMException( + 'TurboShakeParams.domainSeparation must be in range 0x01-0x7f', + 'OperationError'); + } + }, + }, + ]); + module.exports = { converters, requiredArguments, diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 51c6293e97e292..5a3e76dd03b816 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -16,6 +16,9 @@ const { SafePromisePrototypeFinally, Symbol, SymbolAsyncDispose, + SymbolAsyncIterator, + SymbolDispose, + SymbolIterator, Uint8Array, uncurryThis, } = primordials; @@ -43,6 +46,8 @@ const { ERR_INVALID_ARG_VALUE, ERR_INVALID_STATE, ERR_METHOD_NOT_IMPLEMENTED, + ERR_OPERATION_FAILED, + ERR_OUT_OF_RANGE, }, } = require('internal/errors'); const { isArrayBufferView } = require('internal/util/types'); @@ -65,6 +70,7 @@ const { stringToFlags, stringToSymlinkType, toUnixTimestamp, + handleErrorFromBinding: handleSyncErrorFromBinding, validateBufferArray, validateCpOptions, validateOffsetLengthRead, @@ -96,6 +102,7 @@ const { isWindows, isMacOS, } = require('internal/util'); +const { getOptionValue } = require('internal/options'); const EventEmitter = require('events'); const { StringDecoder } = require('string_decoder'); const { kFSWatchStart, watch } = require('internal/fs/watchers'); @@ -115,6 +122,7 @@ const kCloseReject = Symbol('kCloseReject'); const kRef = Symbol('kRef'); const kUnref = Symbol('kUnref'); const kLocked = Symbol('kLocked'); +const kCloseSync = Symbol('kCloseSync'); const { kUsePromises } = binding; const { Interface } = require('internal/readline/interface'); @@ -142,6 +150,24 @@ const lazyReadableStream = getLazy(() => require('internal/webstreams/readablestream').ReadableStream, ); +// Lazy loaded to avoid circular dependency with new streams. +let newStreamsPull; +let newStreamsPullSync; +let newStreamsParsePullArgs; +let newStreamsToUint8Array; +let newStreamsConvertChunks; +function lazyNewStreams() { + if (newStreamsPull === undefined) { + const pullModule = require('internal/streams/iter/pull'); + newStreamsPull = pullModule.pull; + newStreamsPullSync = pullModule.pullSync; + const utils = require('internal/streams/iter/utils'); + newStreamsParsePullArgs = utils.parsePullArgs; + newStreamsToUint8Array = utils.toUint8Array; + newStreamsConvertChunks = utils.convertChunks; + } +} + // By the time the C++ land creates an error for a promise rejection (likely from a // libuv callback), there is already no JS frames on the stack. So we need to // wait until V8 resumes execution back to JS land before we have enough information @@ -269,6 +295,16 @@ class FileHandle extends EventEmitter { return this[kClosePromise]; }; + [kCloseSync]() { + if (this[kFd] === -1) return; + if (this[kClosePromise]) { + throw new ERR_INVALID_STATE('The FileHandle is closing'); + } + this[kFd] = -1; + this[kHandle].closeSync(); + this.emit('close'); + } + async [SymbolAsyncDispose]() { await this.close(); } @@ -424,6 +460,612 @@ class FileHandle extends EventEmitter { } } +if (getOptionValue('--experimental-stream-iter')) { + const kNullPrototo = { __proto__: null }; + const kDefaultChunkSize = 131072; + const kNone = -1; + /** + * Return the file contents as an AsyncIterable using the + * new streams pull model. Optional transforms and options (including + * AbortSignal) may be provided as trailing arguments, mirroring the + * Stream.pull() signature. + * @param {...(Function|object)} args - Optional transforms and/or options + * @returns {AsyncIterable} + */ + FileHandle.prototype.pull = function pull(...args) { + if (this[kFd] === kNone) + throw new ERR_INVALID_STATE('The FileHandle is closed'); + if (this[kClosePromise]) + throw new ERR_INVALID_STATE('The FileHandle is closing'); + if (this[kLocked]) + throw new ERR_INVALID_STATE('The FileHandle is locked'); + + lazyNewStreams(); + const { transforms, options = kNullPrototo } = newStreamsParsePullArgs(args); + + const { + autoClose = false, + chunkSize: readSize = kDefaultChunkSize, + signal, + } = options; + let { + start: pos = kNone, + limit: remaining = kNone, + } = options; + + const handle = this; + const fd = this[kFd]; + + validateBoolean(autoClose, 'options.autoClose'); + + if (pos !== kNone) { + validateInteger(pos, 'options.start', 0); + } + if (remaining !== kNone) { + validateInteger(remaining, 'options.limit', 1); + } + if (readSize !== undefined) { + validateInteger(readSize, 'options.chunkSize', 1); + } + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + } + + this[kLocked] = true; + + const source = { + __proto__: null, + async *[SymbolAsyncIterator]() { + handle[kRef](); + try { + if (signal) { + // Signal-aware path + while (remaining !== 0) { + if (signal.aborted) { + throw signal.reason ?? + lazyDOMException('The operation was aborted', + 'AbortError'); + } + const toRead = remaining > 0 ? + MathMin(readSize, remaining) : readSize; + const buf = Buffer.allocUnsafe(toRead); + let bytesRead; + try { + bytesRead = + (await binding.read(fd, buf, 0, + toRead, pos, kUsePromises)) || 0; + } catch (err) { + ErrorCaptureStackTrace(err, handleErrorFromBinding); + throw err; + } + if (bytesRead === 0) break; + if (pos >= 0) pos += bytesRead; + if (remaining > 0) remaining -= bytesRead; + yield [bytesRead < toRead ? buf.subarray(0, bytesRead) : buf]; + } + } else { + // Fast path - no signal check per iteration + while (remaining !== 0) { + const toRead = remaining > 0 ? + MathMin(readSize, remaining) : readSize; + const buf = Buffer.allocUnsafe(toRead); + let bytesRead; + try { + bytesRead = + (await binding.read(fd, buf, 0, + toRead, pos, kUsePromises)) || 0; + } catch (err) { + ErrorCaptureStackTrace(err, handleErrorFromBinding); + throw err; + } + if (bytesRead === 0) break; + if (pos >= 0) pos += bytesRead; + if (remaining > 0) remaining -= bytesRead; + yield [bytesRead < toRead ? buf.subarray(0, bytesRead) : buf]; + } + } + } finally { + handle[kLocked] = false; + handle[kUnref](); + if (autoClose) { + await handle.close(); + } + } + }, + }; + + // If transforms provided, wrap with pull pipeline + if (transforms.length > 0) { + const pullArgs = [...transforms]; + if (options) { + ArrayPrototypePush(pullArgs, options); + } + return newStreamsPull(source, ...pullArgs); + } + return source; + }; + + /** + * Return the file contents as an Iterable using synchronous + * reads. Optional transforms and options may be provided as trailing + * arguments, mirroring the Stream.pullSync() signature. + * @param {...(Function|object)} args - Optional transforms and/or options + * @returns {Iterable} + */ + FileHandle.prototype.pullSync = function pullSync(...args) { + if (this[kFd] === kNone) + throw new ERR_INVALID_STATE('The FileHandle is closed'); + if (this[kClosePromise]) + throw new ERR_INVALID_STATE('The FileHandle is closing'); + if (this[kLocked]) + throw new ERR_INVALID_STATE('The FileHandle is locked'); + + lazyNewStreams(); + const { transforms, options = kNullPrototo } = newStreamsParsePullArgs(args); + + const { + autoClose = false, + chunkSize: readSize = kDefaultChunkSize, + } = options; + let { + start: pos = kNone, + limit: remaining = kNone, + } = options; + + const handle = this; + const fd = this[kFd]; + + validateBoolean(autoClose, 'options.autoClose'); + + if (pos !== kNone) { + validateInteger(pos, 'options.start', 0); + } + if (remaining !== kNone) { + validateInteger(remaining, 'options.limit', 1); + } + if (readSize !== undefined) { + validateInteger(readSize, 'options.chunkSize', 1); + } + + this[kLocked] = true; + + handle[kRef](); + + function cleanup() { + handle[kLocked] = false; + handle[kUnref](); + if (autoClose) { + handle[kCloseSync](); + } + } + + const source = { + __proto__: null, + [SymbolIterator]() { + let done = false; + return { + __proto__: null, + next() { + if (done || remaining === 0) { + if (!done) { + done = true; + cleanup(); + } + return { value: undefined, done: true }; + } + const toRead = remaining > 0 ? + MathMin(readSize, remaining) : readSize; + const buf = Buffer.allocUnsafe(toRead); + let bytesRead; + try { + bytesRead = binding.read(fd, buf, 0, toRead, pos) || 0; + } catch (err) { + done = true; + cleanup(); + throw err; + } + if (bytesRead === 0) { + done = true; + cleanup(); + return { value: undefined, done: true }; + } + if (pos >= 0) pos += bytesRead; + if (remaining > 0) remaining -= bytesRead; + const chunk = bytesRead < toRead ? + buf.subarray(0, bytesRead) : buf; + return { value: [chunk], done: false }; + }, + return() { + if (!done) { + done = true; + cleanup(); + } + return { value: undefined, done: true }; + }, + }; + }, + }; + + if (transforms.length > 0) { + return newStreamsPullSync(source, ...transforms); + } + return source; + }; + + /** + * Return a new-streams Writer backed by this file handle. + * The writer uses direct binding.writeBuffer / binding.writeBuffers + * calls, bypassing the FileHandle.write() validation chain. + * + * Supports writev() for batch writes (single syscall per batch). + * Handles EAGAIN with retry (up to 5 attempts), matching WriteStream. + * @param {{ + * autoClose?: boolean; + * start?: number; + * }} [options] + * @returns {{ write, writev, end, fail }} + */ + FileHandle.prototype.writer = function writer(options = kNullPrototo) { + if (this[kFd] === kNone) + throw new ERR_INVALID_STATE('The FileHandle is closed'); + if (this[kClosePromise]) + throw new ERR_INVALID_STATE('The FileHandle is closing'); + if (this[kLocked]) + throw new ERR_INVALID_STATE('The FileHandle is locked'); + + lazyNewStreams(); + + validateObject(options, 'options'); + const { + autoClose = false, + chunkSize: syncWriteThreshold = kDefaultChunkSize, + } = options; + let { + start: pos = kNone, + limit: bytesRemaining = kNone, + } = options; + + const handle = this; + const fd = this[kFd]; + let totalBytesWritten = 0; + let closed = false; + let closing = false; + let pendingEndPromise = null; + let error = null; + let asyncPending = false; + + validateBoolean(autoClose, 'options.autoClose'); + + if (pos !== kNone) { + validateInteger(pos, 'options.start', 0); + } + if (bytesRemaining !== kNone) { + validateInteger(bytesRemaining, 'options.limit', 1); + } + if (syncWriteThreshold !== undefined) { + validateInteger(syncWriteThreshold, 'options.chunkSize', 1); + } + + this[kLocked] = true; + handle[kRef](); + + // Write a single buffer with EAGAIN retry (up to 5 retries). + async function writeAll(buf, offset, length, position, signal) { + asyncPending = true; + try { + let retries = 0; + while (length > 0) { + const bytesWritten = (await PromisePrototypeThen( + binding.writeBuffer(fd, buf, offset, length, position, + kUsePromises), + undefined, + handleErrorFromBinding, + )) || 0; + + signal?.throwIfAborted(); + + if (bytesWritten === 0) { + if (++retries > 5) { + throw new ERR_OPERATION_FAILED('write failed after retries'); + } + } else { + retries = 0; + } + + totalBytesWritten += bytesWritten; + offset += bytesWritten; + length -= bytesWritten; + if (position >= 0) position += bytesWritten; + } + } finally { + asyncPending = false; + } + } + + // Writev with EAGAIN retry. On partial write, concatenates remaining + // buffers and falls back to writeAll (same approach as WriteStream). + async function writevAll(buffers, position, signal) { + asyncPending = true; + try { + let totalSize = 0; + for (let i = 0; i < buffers.length; i++) { + totalSize += buffers[i].byteLength; + } + + let retries = 0; + while (totalSize > 0) { + const bytesWritten = (await PromisePrototypeThen( + binding.writeBuffers(fd, buffers, position, kUsePromises), + undefined, + handleErrorFromBinding, + )) || 0; + + signal?.throwIfAborted(); + + if (bytesWritten === 0) { + if (++retries > 5) { + throw new ERR_OPERATION_FAILED('writev failed after retries'); + } + } else { + retries = 0; + } + + totalBytesWritten += bytesWritten; + totalSize -= bytesWritten; + if (position >= 0) position += bytesWritten; + + if (totalSize > 0) { + // Partial write - concatenate remaining and use writeAll. + const remaining = Buffer.concat(buffers); + const wrote = bytesWritten; + // writeAll is already inside asyncPending = true, but + // writeAll sets it again - that's fine (idempotent). + await writeAll(remaining, wrote, remaining.length - wrote, + position, signal); + return; + } + } + } finally { + asyncPending = false; + } + } + + // Synchronous write with EAGAIN retry. Throws on I/O error. + // Used by writeSync for the full write, and by writevSync for + // completing a partial writev. + function writeSyncAll(buf, offset, length, position) { + let retries = 0; + while (length > 0) { + const ctx = {}; + const bytesWritten = binding.writeBuffer( + fd, buf, offset, length, position, undefined, ctx) || 0; + if (ctx.errno !== undefined) { + handleSyncErrorFromBinding(ctx); + } + if (bytesWritten === 0) { + if (++retries > 5) { + throw new ERR_OPERATION_FAILED('write failed after retries'); + } + } else { + retries = 0; + } + totalBytesWritten += bytesWritten; + offset += bytesWritten; + length -= bytesWritten; + if (position >= 0) position += bytesWritten; + } + } + + async function cleanup() { + if (closed) return; + closed = true; + handle[kLocked] = false; + handle[kUnref](); + if (autoClose) { + await handle.close(); + } + } + + return { + __proto__: null, + write(chunk, options = kNullPrototo) { + if (error) { + return PromiseReject(error); + } + if (closed) { + return PromiseReject( + new ERR_INVALID_STATE.TypeError('The writer is closed')); + } + validateObject(options, 'options'); + const { + signal, + } = options; + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + if (signal.aborted) { + return PromiseReject(signal.reason); + } + } + chunk = newStreamsToUint8Array(chunk); + if (bytesRemaining >= 0 && chunk.byteLength > bytesRemaining) { + return PromiseReject( + new ERR_OUT_OF_RANGE('write', `<= ${bytesRemaining} bytes`, + chunk.byteLength)); + } + if (bytesRemaining > 0) bytesRemaining -= chunk.byteLength; + const position = pos; + if (pos >= 0) pos += chunk.byteLength; + return writeAll(chunk, 0, chunk.byteLength, position, signal); + }, + + writev(chunks, options = kNullPrototo) { + if (error) { + return PromiseReject(error); + } + if (closed) { + return PromiseReject( + new ERR_INVALID_STATE.TypeError('The writer is closed')); + } + validateObject(options, 'options'); + const { + signal, + } = options; + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + if (signal?.aborted) { + return PromiseReject(signal.reason); + } + } + chunks = newStreamsConvertChunks(chunks); + let totalSize = 0; + for (let i = 0; i < chunks.length; i++) { + totalSize += chunks[i].byteLength; + } + if (bytesRemaining >= 0 && totalSize > bytesRemaining) { + return PromiseReject( + new ERR_OUT_OF_RANGE('writev', `<= ${bytesRemaining} bytes`, + totalSize)); + } + if (bytesRemaining > 0) bytesRemaining -= totalSize; + const position = pos; + if (pos >= 0) pos += totalSize; + return writevAll(chunks, position, signal); + }, + + writeSync(chunk) { + if (error || closed || asyncPending) return false; + chunk = newStreamsToUint8Array(chunk); + const length = chunk.byteLength; + if (length > syncWriteThreshold) return false; + if (length === 0) return true; + if (bytesRemaining >= 0 && length > bytesRemaining) return false; + const position = pos; + // First attempt - if this fails with zero bytes written, + // return false so pipeTo can fall back to async write(). + const ctx = {}; + const bytesWritten = binding.writeBuffer( + fd, chunk, 0, length, position, undefined, ctx) || 0; + if (ctx.errno !== undefined) return false; + totalBytesWritten += bytesWritten; + if (position >= 0) { + pos = position + bytesWritten; + } + if (bytesWritten === length) { + if (bytesRemaining > 0) bytesRemaining -= length; + return true; + } + // Partial write - bytes are on disk. Must complete or throw. + // Cannot return false here because pipeTo would re-send the + // full chunk, causing duplicate data on disk. + writeSyncAll(chunk, bytesWritten, length - bytesWritten, + position >= 0 ? position + bytesWritten : -1); + if (bytesRemaining > 0) bytesRemaining -= length; + return true; + }, + + writevSync(chunks) { + if (error || closed || asyncPending) return false; + chunks = newStreamsConvertChunks(chunks); + let totalSize = 0; + for (let i = 0; i < chunks.length; i++) { + totalSize += chunks[i].byteLength; + } + if (totalSize > syncWriteThreshold) return false; + if (totalSize === 0) return true; + if (bytesRemaining >= 0 && totalSize > bytesRemaining) return false; + const position = pos; + // writeBuffers throws on error (zero bytes written) - safe + // to catch and return false for async fallback. + let bytesWritten; + try { + bytesWritten = binding.writeBuffers(fd, chunks, position) || 0; + } catch { + return false; + } + totalBytesWritten += bytesWritten; + if (position >= 0) { + pos = position + bytesWritten; + } + if (bytesWritten === totalSize) { + if (bytesRemaining > 0) bytesRemaining -= totalSize; + return true; + } + // Partial writev - bytes are on disk. Must complete or throw. + const rest = Buffer.concat(chunks); + writeSyncAll(rest, bytesWritten, + rest.byteLength - bytesWritten, + position >= 0 ? position + bytesWritten : -1); + if (bytesRemaining > 0) bytesRemaining -= totalSize; + return true; + }, + + end(options = kNullPrototo) { + if (error) { + return PromiseReject(error); + } + if (closed) { + return PromiseResolve(totalBytesWritten); + } + if (closing) { + return pendingEndPromise; + } + validateObject(options, 'options'); + const { + signal, + } = options; + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + if (signal.aborted) { + return PromiseReject(signal.reason); + } + } + closing = true; + pendingEndPromise = PromisePrototypeThen( + cleanup(), () => totalBytesWritten); + return pendingEndPromise; + }, + + endSync() { + if (error) return -1; + if (closed) return totalBytesWritten; + if (asyncPending) return -1; + closed = true; + handle[kLocked] = false; + handle[kUnref](); + if (autoClose) { + handle[kCloseSync](); + } + return totalBytesWritten; + }, + + fail(reason) { + if (closed || error) return; + error = reason ?? new ERR_INVALID_STATE('Failed'); + closed = true; + handle[kLocked] = false; + handle[kUnref](); + if (autoClose) { + handle[kCloseSync](); + } + }, + + [SymbolAsyncDispose]() { + if (closing) { + return pendingEndPromise ?? PromiseResolve(); + } + if (!closed && !error) { + this.fail(); + } + return PromiseResolve(); + }, + + [SymbolDispose]() { + this.fail(); + }, + }; + }; +} + async function handleFdClose(fileOpPromise, closeFunc) { return PromisePrototypeThen( fileOpPromise, diff --git a/lib/internal/locks.js b/lib/internal/locks.js index 054197bcaefcc6..05000e933f0b55 100644 --- a/lib/internal/locks.js +++ b/lib/internal/locks.js @@ -29,8 +29,13 @@ const { createEnumConverter, createDictionaryConverter, } = require('internal/webidl'); +const dc = require('diagnostics_channel'); const locks = internalBinding('locks'); +const lockRequestStartChannel = dc.channel('locks.request.start'); +const lockRequestGrantChannel = dc.channel('locks.request.grant'); +const lockRequestMissChannel = dc.channel('locks.request.miss'); +const lockRequestEndChannel = dc.channel('locks.request.end'); const kName = Symbol('kName'); const kMode = Symbol('kMode'); @@ -113,6 +118,30 @@ function convertLockError(error) { return error; } +function publishLockRequestStart(name, mode) { + if (lockRequestStartChannel.hasSubscribers) { + lockRequestStartChannel.publish({ name, mode }); + } +} + +function publishLockRequestGrant(name, mode) { + if (lockRequestGrantChannel.hasSubscribers) { + lockRequestGrantChannel.publish({ name, mode }); + } +} + +function publishLockRequestMiss(name, mode, ifAvailable) { + if (ifAvailable && lockRequestMissChannel.hasSubscribers) { + lockRequestMissChannel.publish({ name, mode }); + } +} + +function publishLockRequestEnd(name, mode, ifAvailable, steal, error) { + if (lockRequestEndChannel.hasSubscribers) { + lockRequestEndChannel.publish({ name, mode, ifAvailable, steal, error }); + } +} + // https://w3c.github.io/web-locks/#api-lock-manager class LockManager { constructor(symbol = undefined) { @@ -192,6 +221,7 @@ class LockManager { } const clientId = `node-${process.pid}-${threadId}`; + publishLockRequestStart(name, mode); // Handle requests with AbortSignal if (signal) { @@ -212,6 +242,8 @@ class LockManager { return undefined; } lockGranted = true; + publishLockRequestGrant(name, mode); + return callback(createLock(lock)); }); }; @@ -228,27 +260,49 @@ class LockManager { // When released promise settles, clean up listener and resolve main promise SafePromisePrototypeFinally( - PromisePrototypeThen(released, resolve, (error) => reject(convertLockError(error))), + PromisePrototypeThen( + released, + (result) => { + publishLockRequestEnd(name, mode, ifAvailable, steal, undefined); + resolve(result); + }, + (error) => { + const convertedError = convertLockError(error); + publishLockRequestEnd(name, mode, ifAvailable, steal, convertedError); + reject(convertedError); + }, + ), () => signal.removeEventListener('abort', abortListener), ); } catch (error) { signal.removeEventListener('abort', abortListener); - reject(convertLockError(error)); + const convertedError = convertLockError(error); + publishLockRequestEnd(name, mode, ifAvailable, steal, convertedError); + reject(convertedError); } }); } // When ifAvailable: true and lock is not available, C++ passes null to indicate no lock granted const wrapCallback = (internalLock) => { + if (internalLock === null) { + publishLockRequestMiss(name, mode, ifAvailable); + } else { + publishLockRequestGrant(name, mode); + } const lock = createLock(internalLock); return callback(lock); }; // Standard request without signal try { - return await locks.request(name, clientId, mode, steal, ifAvailable, wrapCallback); + const result = await locks.request(name, clientId, mode, steal, ifAvailable, wrapCallback); + publishLockRequestEnd(name, mode, ifAvailable, steal, undefined); + + return result; } catch (error) { const convertedError = convertLockError(error); + publishLockRequestEnd(name, mode, ifAvailable, steal, convertedError); throw convertedError; } } diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 929577c0da6d08..22032f79e90d44 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -145,7 +145,9 @@ class ModuleJobBase { */ syncLink(requestType) { // Store itself into the cache first before linking in case there are circular - // references in the linking. + // references in the linking. Track whether we're overwriting an existing entry + // so we know whether to remove the temporary entry in the finally block. + const hadPreviousEntry = this.loader.loadCache.get(this.url, this.type) !== undefined; this.loader.loadCache.set(this.url, this.type, this); const moduleRequests = this.module.getModuleRequests(); // Modules should be aligned with the moduleRequests array in order. @@ -169,9 +171,14 @@ class ModuleJobBase { } this.module.link(modules); } finally { - // Restore it - if it succeeds, we'll reset in the caller; Otherwise it's - // not cached and if the error is caught, subsequent attempt would still fail. - this.loader.loadCache.delete(this.url, this.type); + if (!hadPreviousEntry) { + // Remove the temporary entry. On failure this ensures subsequent attempts + // don't return a broken job. On success the caller + // (#getOrCreateModuleJobAfterResolve) will re-insert under the correct key. + this.loader.loadCache.delete(this.url, this.type); + } + // If there was a previous entry (ensurePhase() path), leave this in cache - + // it is the upgraded job and the caller will not re-insert. } return evaluationDepJobs; diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index d253a3ff67280c..cbdc120302443c 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -245,7 +245,7 @@ function finalizeResolution(resolved, base, preserveSymlinks) { } const stats = internalFsBinding.internalModuleStat( - StringPrototypeEndsWith(internalFsBinding, path, '/') ? StringPrototypeSlice(path, -1) : path, + StringPrototypeEndsWith(path, '/') ? StringPrototypeSlice(path, -1) : path, ); // Check for stats.isDirectory() diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 2af52e4b9435ea..b7b1843ff35572 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -108,7 +108,9 @@ const kShouldNotSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: fals * @param {boolean} isMain - Whether the module is the entrypoint */ function loadCJSModule(module, source, url, filename, isMain) { - const compileResult = compileFunctionForCJSLoader(source, filename, false /* is_sea_main */, false); + // Use the full URL as the V8 resource name so that any search params + // (e.g. ?node-test-mock) are preserved in coverage reports. + const compileResult = compileFunctionForCJSLoader(source, url, false /* is_sea_main */, false); const { function: compiledWrapper, sourceMapURL, sourceURL } = compileResult; // Cache the source map for the cjs module if present. @@ -522,6 +524,7 @@ translators.set('wasm', function(url, translateContext) { try { compiled = new WebAssembly.Module(source, { builtins: ['js-string'], + importedStringConstants: 'wasm:js/string-constants', }); } catch (err) { err.message = errPath(url) + ': ' + err.message; diff --git a/lib/internal/modules/esm/worker.js b/lib/internal/modules/esm/worker.js index fa5fa87d20efb1..e2553b253db830 100644 --- a/lib/internal/modules/esm/worker.js +++ b/lib/internal/modules/esm/worker.js @@ -37,7 +37,7 @@ const { isCascadedLoaderInitialized, getOrInitializeCascadedLoader } = require(' const { AsyncLoaderHooksOnLoaderHookWorker } = require('internal/modules/esm/hooks'); /** - * Register asynchronus module loader customization hooks. This should only be run in the loader + * Register asynchronous module loader customization hooks. This should only be run in the loader * hooks worker. In a non-loader-hooks thread, if any asynchronous loader hook is registered, the * ModuleLoader#asyncLoaderHooks are initialized to be AsyncLoaderHooksProxiedToLoaderHookWorker * which posts the messages to the async loader hook worker thread. diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 9f484e6a611d33..4a2b57790bd7e6 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -1,6 +1,7 @@ 'use strict'; const { + ArrayPrototypePush, RegExpPrototypeExec, StringPrototypeIndexOf, StringPrototypeSlice, @@ -17,6 +18,7 @@ const { ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, }, } = require('internal/errors'); +const { validateFunction } = require('internal/validators'); const { pathToFileURL } = require('internal/url'); const { exitCodes: { kGenericUserError } } = internalBinding('errors'); const { @@ -105,15 +107,18 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) { } const exceptionHandlerState = { - captureFn: null, + captureFn: null, // Primary callback (for domain's exclusive use) + auxiliaryCallbacks: [], // Auxiliary callbacks (for REPL, etc.) - always called reportFlag: false, }; function setUncaughtExceptionCaptureCallback(fn) { if (fn === null) { exceptionHandlerState.captureFn = fn; - shouldAbortOnUncaughtToggle[0] = 1; - process.report.reportOnUncaughtException = exceptionHandlerState.reportFlag; + if (exceptionHandlerState.auxiliaryCallbacks.length === 0) { + shouldAbortOnUncaughtToggle[0] = 1; + process.report.reportOnUncaughtException = exceptionHandlerState.reportFlag; + } return; } if (typeof fn !== 'function') { @@ -129,6 +134,21 @@ function setUncaughtExceptionCaptureCallback(fn) { process.report.reportOnUncaughtException = false; } +// Add an auxiliary callback that coexists with the primary callback. +// Auxiliary callbacks are called first; if any returns true, the error is handled. +// Otherwise, the primary callback (if set) is called. +function addUncaughtExceptionCaptureCallback(fn) { + validateFunction(fn, 'fn'); + if (exceptionHandlerState.auxiliaryCallbacks.length === 0 && + exceptionHandlerState.captureFn === null) { + exceptionHandlerState.reportFlag = + process.report.reportOnUncaughtException === true; + process.report.reportOnUncaughtException = false; + shouldAbortOnUncaughtToggle[0] = 0; + } + ArrayPrototypePush(exceptionHandlerState.auxiliaryCallbacks, fn); +} + function hasUncaughtExceptionCaptureCallback() { return exceptionHandlerState.captureFn !== null; } @@ -154,21 +174,33 @@ function createOnGlobalUncaughtException() { const type = fromPromise ? 'unhandledRejection' : 'uncaughtException'; process.emit('uncaughtExceptionMonitor', er, type); + // Primary callback (e.g., domain) has priority and always handles the exception if (exceptionHandlerState.captureFn !== null) { exceptionHandlerState.captureFn(er); - } else if (!process.emit('uncaughtException', er, type)) { - // If someone handled it, then great. Otherwise, die in C++ land - // since that means that we'll exit the process, emit the 'exit' event. - try { - if (!process._exiting) { - process._exiting = true; - process.exitCode = kGenericUserError; - process.emit('exit', kGenericUserError); + } else { + // If no primary callback, try auxiliary callbacks (e.g., REPL) + // They must return true to indicate handling + let handled = false; + for (let i = exceptionHandlerState.auxiliaryCallbacks.length - 1; i >= 0; i--) { + if (exceptionHandlerState.auxiliaryCallbacks[i](er) === true) { + handled = true; + break; + } + } + if (!handled && !process.emit('uncaughtException', er, type)) { + // If someone handled it, then great. Otherwise, die in C++ land + // since that means that we'll exit the process, emit the 'exit' event. + try { + if (!process._exiting) { + process._exiting = true; + process.exitCode = kGenericUserError; + process.emit('exit', kGenericUserError); + } + } catch { + // Nothing to be done about it at this point. } - } catch { - // Nothing to be done about it at this point. + return false; } - return false; } // If we handled an error, then make sure any ticks get processed @@ -477,5 +509,6 @@ module.exports = { evalScript, onGlobalUncaughtException: createOnGlobalUncaughtException(), setUncaughtExceptionCaptureCallback, + addUncaughtExceptionCaptureCallback, hasUncaughtExceptionCaptureCallback, }; diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 80d42da2267976..6c6d5d350f026c 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -115,6 +115,7 @@ function prepareExecution(options) { setupNavigator(); setupWarningHandler(); setupSQLite(); + setupStreamIter(); setupQuic(); setupWebStorage(); setupWebsocket(); @@ -392,6 +393,16 @@ function initializeConfigFileSupport() { } } +function setupStreamIter() { + if (!getOptionValue('--experimental-stream-iter')) { + return; + } + + const { BuiltinModule } = require('internal/bootstrap/realm'); + BuiltinModule.allowRequireByUsers('stream/iter'); + BuiltinModule.allowRequireByUsers('zlib/iter'); +} + function setupQuic() { if (!getOptionValue('--experimental-quic')) { return; diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index 026c0784f0bbe8..9d394c9cebbf0e 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -98,7 +98,6 @@ const { const { isKeyObject, - isCryptoKey, } = require('internal/crypto/keys'); const { @@ -143,7 +142,6 @@ const { kVersionNegotiation, kInspect, kKeyObjectHandle, - kKeyObjectInner, kWantsHeaders, kWantsTrailers, } = require('internal/quic/symbols'); @@ -187,7 +185,6 @@ const onSessionHandshakeChannel = dc.channel('quic.session.handshake'); /** * @typedef {import('../socketaddress.js').SocketAddress} SocketAddress * @typedef {import('../crypto/keys.js').KeyObject} KeyObject - * @typedef {import('../crypto/keys.js').CryptoKey} CryptoKey */ /** @@ -260,7 +257,7 @@ const onSessionHandshakeChannel = dc.channel('quic.session.handshake'); * @property {boolean} [verifyClient] Verify the client * @property {boolean} [tlsTrace] Enable TLS tracing * @property {boolean} [verifyPrivateKey] Verify the private key - * @property {KeyObject|CryptoKey|Array} [keys] The keys + * @property {KeyObject|KeyObject[]} [keys] The keys * @property {ArrayBuffer|ArrayBufferView|Array} [certs] The certificates * @property {ArrayBuffer|ArrayBufferView|Array} [ca] The certificate authority * @property {ArrayBuffer|ArrayBufferView|Array} [crl] The certificate revocation list @@ -2171,13 +2168,8 @@ function processTlsOptions(tls, forServer) { throw new ERR_INVALID_ARG_VALUE('options.keys', key, 'must be a private key'); } ArrayPrototypePush(keyHandles, key[kKeyObjectHandle]); - } else if (isCryptoKey(key)) { - if (key.type !== 'private') { - throw new ERR_INVALID_ARG_VALUE('options.keys', key, 'must be a private key'); - } - ArrayPrototypePush(keyHandles, key[kKeyObjectInner][kKeyObjectHandle]); } else { - throw new ERR_INVALID_ARG_TYPE('options.keys', ['KeyObject', 'CryptoKey'], key); + throw new ERR_INVALID_ARG_TYPE('options.keys', 'KeyObject', key); } } } diff --git a/lib/internal/quic/symbols.js b/lib/internal/quic/symbols.js index d82bbd984afab6..1fc315ed8c4a72 100644 --- a/lib/internal/quic/symbols.js +++ b/lib/internal/quic/symbols.js @@ -18,7 +18,6 @@ const { const { kHandle: kKeyObjectHandle, - kKeyObject: kKeyObjectInner, } = require('internal/crypto/util'); // Symbols used to hide various private properties and methods from the @@ -61,7 +60,6 @@ module.exports = { kHeaders, kInspect, kKeyObjectHandle, - kKeyObjectInner, kListen, kNewSession, kNewStream, diff --git a/lib/internal/streams/iter/broadcast.js b/lib/internal/streams/iter/broadcast.js new file mode 100644 index 00000000000000..769f93b5404c30 --- /dev/null +++ b/lib/internal/streams/iter/broadcast.js @@ -0,0 +1,762 @@ +'use strict'; + +// New Streams API - Broadcast +// +// Push-model multi-consumer streaming. A single writer can push data to +// multiple consumers. Each consumer has an independent cursor into a +// shared buffer. + +const { + ArrayIsArray, + ArrayPrototypePush, + MathMax, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + PromiseWithResolvers, + SafeSet, + Symbol, + SymbolAsyncDispose, + SymbolAsyncIterator, + SymbolDispose, + TypedArrayPrototypeGetByteLength, +} = primordials; + +const { lazyDOMException } = require('internal/util'); + +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_RETURN_VALUE, + ERR_INVALID_STATE, + }, +} = require('internal/errors'); +const { + validateAbortSignal, + validateInteger, + validateObject, +} = require('internal/validators'); + +const { + broadcastProtocol, + drainableProtocol, +} = require('internal/streams/iter/types'); + +const { + isAsyncIterable, + isSyncIterable, +} = require('internal/streams/iter/from'); + +const { + pull: pullWithTransforms, +} = require('internal/streams/iter/pull'); + +const { + kMultiConsumerDefaultHWM, + kResolvedPromise, + clampHWM, + convertChunks, + getMinCursor, + hasProtocol, + onSignalAbort, + parsePullArgs, + wrapError, + toUint8Array, + validateBackpressure, +} = require('internal/streams/iter/utils'); + +const { + RingBuffer, +} = require('internal/streams/iter/ringbuffer'); + +const kCancelWriter = Symbol('kCancelWriter'); +const kWrite = Symbol('kWrite'); +const kEnd = Symbol('kEnd'); +const kAbort = Symbol('kAbort'); +const kGetDesiredSize = Symbol('kGetDesiredSize'); +const kCanWrite = Symbol('kCanWrite'); +const kOnBufferDrained = Symbol('kOnBufferDrained'); + +// ============================================================================= +// Broadcast Implementation +// ============================================================================= + +class BroadcastImpl { + #buffer = new RingBuffer(); + #bufferStart = 0; + #consumers = new SafeSet(); + #waiters = []; // Consumers with pending resolve (subset of #consumers) + #ended = false; + #error = null; + #cancelled = false; + #options; + #writer = null; + #cachedMinCursor = 0; + #minCursorDirty = false; + + constructor(options) { + this.#options = options; + this[kOnBufferDrained] = null; + } + + setWriter(writer) { + this.#writer = writer; + } + + get backpressurePolicy() { + return this.#options.backpressure; + } + + get highWaterMark() { + return this.#options.highWaterMark; + } + + get consumerCount() { + return this.#consumers.size; + } + + get bufferSize() { + return this.#buffer.length; + } + + push(...args) { + const { transforms, options } = parsePullArgs(args); + const rawConsumer = this.#createRawConsumer(); + + // When transforms are present, delegate to pull() which creates its + // own internal AbortController that follows the external signal. + // When no transforms, return rawConsumer directly (controller elided + // per PULL-02 optimization -- no transforms means no signal recipient). + if (transforms.length > 0) { + const pullArgs = [...transforms]; + if (options?.signal) { + ArrayPrototypePush(pullArgs, + { __proto__: null, signal: options.signal }); + } + return pullWithTransforms(rawConsumer, ...pullArgs); + } + return rawConsumer; + } + + #createRawConsumer() { + const state = { + __proto__: null, + // Start at the oldest buffered entry so late-joining consumers + // can read data already in the buffer. + cursor: this.#bufferStart, + resolve: null, + reject: null, + detached: false, + }; + + this.#consumers.add(state); + // New consumer starts at buffer start; recalculate min cursor + // since this consumer may now be the slowest. + if (this.#consumers.size === 1) { + this.#cachedMinCursor = state.cursor; + this.#minCursorDirty = false; + } else { + this.#minCursorDirty = true; + } + const self = this; + + const kDone = PromiseResolve( + { __proto__: null, done: true, value: undefined }); + + function detach() { + state.detached = true; + state.resolve = null; + state.reject = null; + self.#consumers.delete(state); + self.#minCursorDirty = true; + self.#tryTrimBuffer(); + } + + return { + __proto__: null, + [SymbolAsyncIterator]() { + return { + __proto__: null, + next() { + if (state.detached) { + if (self.#error) return PromiseReject(self.#error); + return kDone; + } + + const bufferIndex = state.cursor - self.#bufferStart; + if (bufferIndex < self.#buffer.length) { + const chunk = self.#buffer.get(bufferIndex); + // If this consumer was at the min cursor, mark dirty + if (state.cursor <= self.#cachedMinCursor) { + self.#minCursorDirty = true; + } + state.cursor++; + self.#tryTrimBuffer(); + return PromiseResolve( + { __proto__: null, done: false, value: chunk }); + } + + if (self.#error) { + state.detached = true; + self.#consumers.delete(state); + return PromiseReject(self.#error); + } + + if (self.#ended || self.#cancelled) { + detach(); + return kDone; + } + + const { promise, resolve, reject } = PromiseWithResolvers(); + state.resolve = resolve; + state.reject = reject; + ArrayPrototypePush(self.#waiters, state); + return promise; + }, + + return() { + detach(); + return kDone; + }, + + throw() { + detach(); + return kDone; + }, + }; + }, + }; + } + + cancel(reason) { + if (this.#cancelled) return; + this.#cancelled = true; + this.#ended = true; // Prevents [kAbort]() from redundantly iterating consumers + + if (reason !== undefined) { + this.#error = reason; + } + + // Reject pending writes on the writer so the pump doesn't hang + this.#writer?.[kCancelWriter](); + + for (const consumer of this.#consumers) { + if (consumer.resolve) { + if (reason !== undefined) { + consumer.reject?.(reason); + } else { + consumer.resolve({ __proto__: null, done: true, value: undefined }); + } + consumer.resolve = null; + consumer.reject = null; + } + consumer.detached = true; + } + this.#consumers.clear(); + } + + [SymbolDispose]() { + this.cancel(); + } + + // Methods accessed by BroadcastWriter via symbol keys + + [kWrite](chunk) { + if (this.#ended || this.#cancelled) return false; + + if (this.#buffer.length >= this.#options.highWaterMark) { + switch (this.#options.backpressure) { + case 'strict': + case 'block': + return false; + case 'drop-oldest': + this.#buffer.shift(); + this.#bufferStart++; + for (const consumer of this.#consumers) { + if (consumer.cursor < this.#bufferStart) { + consumer.cursor = this.#bufferStart; + } + } + break; + case 'drop-newest': + return true; + } + } + + this.#buffer.push(chunk); + this.#notifyConsumers(); + return true; + } + + [kEnd]() { + if (this.#ended) return; + this.#ended = true; + + for (const consumer of this.#consumers) { + if (consumer.resolve) { + const bufferIndex = consumer.cursor - this.#bufferStart; + if (bufferIndex < this.#buffer.length) { + const chunk = this.#buffer.get(bufferIndex); + consumer.cursor++; + consumer.resolve({ __proto__: null, done: false, value: chunk }); + } else { + consumer.resolve({ __proto__: null, done: true, value: undefined }); + } + consumer.resolve = null; + consumer.reject = null; + } + } + } + + [kAbort](reason) { + if (this.#ended || this.#error) return; + this.#error = reason; + this.#ended = true; + + // Notify all waiting consumers and detach them + for (const consumer of this.#consumers) { + if (consumer.reject) { + consumer.reject(reason); + consumer.resolve = null; + consumer.reject = null; + } + consumer.detached = true; + } + this.#consumers.clear(); + } + + [kGetDesiredSize]() { + if (this.#ended || this.#cancelled) return null; + return MathMax(0, this.#options.highWaterMark - this.#buffer.length); + } + + [kCanWrite]() { + if (this.#ended || this.#cancelled) return false; + if ((this.#options.backpressure === 'strict' || + this.#options.backpressure === 'block') && + this.#buffer.length >= this.#options.highWaterMark) { + return false; + } + return true; + } + + // Private methods + + #recomputeMinCursor() { + this.#cachedMinCursor = getMinCursor( + this.#consumers, this.#bufferStart + this.#buffer.length); + this.#minCursorDirty = false; + } + + #tryTrimBuffer() { + if (this.#minCursorDirty) { + this.#recomputeMinCursor(); + } + const trimCount = this.#cachedMinCursor - this.#bufferStart; + if (trimCount > 0) { + this.#buffer.trimFront(trimCount); + this.#bufferStart = this.#cachedMinCursor; + + if (this[kOnBufferDrained] && + this.#buffer.length < this.#options.highWaterMark) { + this[kOnBufferDrained](); + } + } + } + + #notifyConsumers() { + const waiters = this.#waiters; + if (waiters.length === 0) return; + // Swap out the waiters list so consumers that re-wait during + // resolve don't get processed twice in this cycle. + this.#waiters = []; + for (let i = 0; i < waiters.length; i++) { + const consumer = waiters[i]; + if (consumer.resolve) { + const bufferIndex = consumer.cursor - this.#bufferStart; + if (bufferIndex < this.#buffer.length) { + const chunk = this.#buffer.get(bufferIndex); + if (consumer.cursor <= this.#cachedMinCursor) { + this.#minCursorDirty = true; + } + consumer.cursor++; + const resolve = consumer.resolve; + consumer.resolve = null; + consumer.reject = null; + resolve({ __proto__: null, done: false, value: chunk }); + } else { + // Still waiting -- put back + ArrayPrototypePush(this.#waiters, consumer); + } + } + } + } +} + +// ============================================================================= +// BroadcastWriter +// ============================================================================= + +let getBroadcastPendingWrites; + +class BroadcastWriter { + #broadcast; + #totalBytes = 0; + #closed; + #aborted = false; + #pendingWrites = new RingBuffer(); + #pendingDrains = []; + + static { + // Used in wireBroadcastWriteSignal ensure the signal listener can be + // constructed without closing over the chunk data, which may be large. + getBroadcastPendingWrites = (obj) => obj.#pendingWrites; + } + + constructor(broadcastImpl) { + this.#broadcast = broadcastImpl; + + this.#broadcast[kOnBufferDrained] = () => { + this.#resolvePendingWrites(); + this.#resolvePendingDrains(true); + }; + } + + // The drainable protocol works with Stream.ondrain to provide a notification + // when the writer can accept more data after being backpressured. + [drainableProtocol]() { + const desired = this.desiredSize; + if (desired === null) return null; + if (desired > 0) return PromiseResolve(true); + const { promise, resolve, reject } = PromiseWithResolvers(); + ArrayPrototypePush(this.#pendingDrains, { __proto__: null, resolve, reject }); + return promise; + } + + #isClosed() { + return this.#closed !== undefined; + } + + #isClosedOrAborted() { + return this.#isClosed() || this.#aborted; + } + + get desiredSize() { + return this.#isClosedOrAborted() ? null : this.#broadcast[kGetDesiredSize](); + } + + #canUseWriteFastPath(options) { + return !options?.signal && !this.#isClosed() && !this.#aborted && + this.#broadcast[kCanWrite](); + } + + write(chunk, options) { + // Fast path: no signal, writer open, buffer has space + if (this.#canUseWriteFastPath(options)) { + const converted = toUint8Array(chunk); + this.#broadcast[kWrite]([converted]); + this.#totalBytes += TypedArrayPrototypeGetByteLength(converted); + return kResolvedPromise; + } + return this.#writevSlow([chunk], options); + } + + writev(chunks, options) { + // Fast path: no signal, writer open, buffer has space + if (this.#canUseWriteFastPath(options)) { + const converted = convertChunks(chunks); + this.#broadcast[kWrite](converted); + for (let i = 0; i < converted.length; i++) { + this.#totalBytes += TypedArrayPrototypeGetByteLength(converted[i]); + } + return kResolvedPromise; + } + return this.#writevSlow(chunks, options); + } + + async #writevSlow(chunks, options) { + const signal = options?.signal; + + // Check for pre-aborted + signal?.throwIfAborted(); + + if (this.#isClosedOrAborted()) { + throw new ERR_INVALID_STATE.TypeError('Writer is closed'); + } + + const converted = convertChunks(chunks); + + if (this.#broadcast[kWrite](converted)) { + for (let i = 0; i < converted.length; i++) { + this.#totalBytes += TypedArrayPrototypeGetByteLength(converted[i]); + } + return; + } + + const policy = this.#broadcast.backpressurePolicy; + const hwm = this.#broadcast.highWaterMark; + + if (policy === 'strict') { + if (this.#pendingWrites.length >= hwm) { + throw new ERR_INVALID_STATE.TypeError( + 'Backpressure violation: too many pending writes. ' + + 'Await each write() call to respect backpressure.'); + } + return this.#createPendingWrite(converted, signal); + } + + // 'block' policy + return this.#createPendingWrite(converted, signal); + } + + writeSync(chunk) { + if (this.#isClosedOrAborted()) return false; + if (!this.#broadcast[kCanWrite]()) return false; + const converted = + toUint8Array(chunk); + if (this.#broadcast[kWrite]([converted])) { + this.#totalBytes += TypedArrayPrototypeGetByteLength(converted); + return true; + } + return false; + } + + writevSync(chunks) { + if (this.#isClosedOrAborted()) return false; + if (!this.#broadcast[kCanWrite]()) return false; + const converted = convertChunks(chunks); + if (this.#broadcast[kWrite](converted)) { + for (let i = 0; i < converted.length; i++) { + this.#totalBytes += TypedArrayPrototypeGetByteLength(converted[i]); + } + return true; + } + return false; + } + + // end() is synchronous internally - signal accepted for interface compliance. + end(options) { + if (this.#isClosed()) return this.#closed; + this.#closed = PromiseResolve(this.#totalBytes); + this.#broadcast[kEnd](); + this.#resolvePendingDrains(false); + return this.#closed; + } + + endSync() { + if (this.#closed) return this.#totalBytes; + this.#closed = PromiseResolve(this.#totalBytes); + this.#broadcast[kEnd](); + this.#resolvePendingDrains(false); + return this.#totalBytes; + } + + fail(reason) { + if (this.#isClosedOrAborted()) return; + this.#aborted = true; + this.#closed = PromiseResolve(this.#totalBytes); + const error = reason ?? new ERR_INVALID_STATE.TypeError('Failed'); + this.#rejectPendingWrites(error); + this.#rejectPendingDrains(error); + this.#broadcast[kAbort](error); + } + + [SymbolAsyncDispose]() { + this.fail(); + return PromiseResolve(); + } + + [SymbolDispose]() { + this.fail(); + } + + [kCancelWriter]() { + if (this.#isClosed()) return; + this.#closed = PromiseResolve(this.#totalBytes); + this.#rejectPendingWrites( + lazyDOMException('Broadcast cancelled', 'AbortError')); + this.#resolvePendingDrains(false); + } + + /** + * Create a pending write promise, optionally racing against a signal. + * If the signal fires, the entry is removed from pendingWrites and the + * promise rejects. Signal listeners are cleaned up on normal resolution. + * @returns {Promise} + */ + #createPendingWrite(chunk, signal) { + const { promise, resolve, reject } = PromiseWithResolvers(); + const entry = { __proto__: null, chunk, resolve, reject }; + this.#pendingWrites.push(entry); + if (signal) { + wireBroadcastWriteSignal(entry, signal, resolve, reject, this); + } + return promise; + } + + #resolvePendingWrites() { + while (this.#pendingWrites.length > 0 && this.#broadcast[kCanWrite]()) { + const pending = this.#pendingWrites.shift(); + if (this.#broadcast[kWrite](pending.chunk)) { + for (let i = 0; i < pending.chunk.length; i++) { + this.#totalBytes += TypedArrayPrototypeGetByteLength(pending.chunk[i]); + } + pending.resolve(); + } else { + this.#pendingWrites.unshift(pending); + break; + } + } + } + + #rejectPendingWrites(error) { + while (this.#pendingWrites.length > 0) { + this.#pendingWrites.shift().reject(error); + } + } + + #resolvePendingDrains(canWrite) { + const drains = this.#pendingDrains; + this.#pendingDrains = []; + for (let i = 0; i < drains.length; i++) { + drains[i].resolve(canWrite); + } + } + + #rejectPendingDrains(error) { + const drains = this.#pendingDrains; + this.#pendingDrains = []; + for (let i = 0; i < drains.length; i++) { + drains[i].reject(error); + } + } +} + +function wireBroadcastWriteSignal(entry, signal, resolve, reject, self) { + const onAbort = () => { + const pendingWrites = getBroadcastPendingWrites(self); + const idx = pendingWrites.indexOf(entry); + if (idx !== -1) pendingWrites.removeAt(idx); + entry.chunk = null; + reject(signal.reason ?? lazyDOMException('Aborted', 'AbortError')); + }; + entry.resolve = function() { + signal.removeEventListener('abort', onAbort); + entry.chunk = null; + resolve(); + }; + entry.reject = function(reason) { + signal.removeEventListener('abort', onAbort); + entry.chunk = null; + reject(reason); + }; + signal.addEventListener('abort', onAbort, { __proto__: null, once: true }); +} + +// ============================================================================= +// Public API +// ============================================================================= + +/** + * Create a broadcast channel for push-model multi-consumer streaming. + * @param {{ highWaterMark?: number, backpressure?: string, signal?: AbortSignal }} [options] + * @returns {{ writer: Writer, broadcast: Broadcast }} + */ +function broadcast(options = { __proto__: null }) { + validateObject(options, 'options'); + const { + highWaterMark = kMultiConsumerDefaultHWM, + backpressure = 'strict', + signal, + } = options; + validateInteger(highWaterMark, 'options.highWaterMark'); + validateBackpressure(backpressure); + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + } + + const opts = { + __proto__: null, + highWaterMark: clampHWM(highWaterMark), + backpressure, + signal, + }; + + const broadcastImpl = new BroadcastImpl(opts); + const writer = new BroadcastWriter(broadcastImpl); + broadcastImpl.setWriter(writer); + + if (signal) { + onSignalAbort(signal, () => broadcastImpl.cancel()); + } + + return { __proto__: null, writer, broadcast: broadcastImpl }; +} + +function isBroadcastable(value) { + return hasProtocol(value, broadcastProtocol); +} + +const Broadcast = { + __proto__: null, + from(input, options) { + if (isBroadcastable(input)) { + const bc = input[broadcastProtocol](options); + if (bc === null || typeof bc !== 'object') { + throw new ERR_INVALID_RETURN_VALUE( + 'an object', '[Symbol.for(\'Stream.broadcastProtocol\')]', bc); + } + return { __proto__: null, writer: { __proto__: null }, broadcast: bc }; + } + + if (!isAsyncIterable(input) && !isSyncIterable(input)) { + throw new ERR_INVALID_ARG_TYPE( + 'input', ['Broadcastable', 'AsyncIterable', 'Iterable'], input); + } + + const result = broadcast(options); + const signal = options?.signal; + + const pump = async () => { + const w = result.writer; + try { + if (isAsyncIterable(input)) { + for await (const chunks of input) { + signal?.throwIfAborted(); + if (ArrayIsArray(chunks)) { + if (!w.writevSync(chunks)) { + await w.writev(chunks, signal ? { signal } : undefined); + } + } else if (!w.writeSync(chunks)) { + await w.write(chunks, signal ? { signal } : undefined); + } + } + } else if (isSyncIterable(input)) { + for (const chunks of input) { + signal?.throwIfAborted(); + if (ArrayIsArray(chunks)) { + if (!w.writevSync(chunks)) { + await w.writev(chunks, signal ? { signal } : undefined); + } + } else if (!w.writeSync(chunks)) { + await w.write(chunks, signal ? { signal } : undefined); + } + } + } + if (w.endSync() < 0) { + await w.end(signal ? { signal } : undefined); + } + } catch (error) { + w.fail(wrapError(error)); + } + }; + PromisePrototypeThen(pump(), undefined, () => {}); + + return result; + }, +}; + +module.exports = { + Broadcast, + broadcast, +}; diff --git a/lib/internal/streams/iter/consumers.js b/lib/internal/streams/iter/consumers.js new file mode 100644 index 00000000000000..6e47d3cf9638fd --- /dev/null +++ b/lib/internal/streams/iter/consumers.js @@ -0,0 +1,523 @@ +'use strict'; + +// New Streams API - Consumers & Utilities +// +// bytes(), text(), arrayBuffer() - collect entire stream +// tap(), tapSync() - observe without modifying +// merge() - temporal combining of sources +// ondrain() - backpressure drain utility + +const { + ArrayBufferPrototypeGetByteLength, + ArrayBufferPrototypeSlice, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSlice, + Promise, + PromisePrototypeThen, + SafePromiseAllReturnVoid, + SymbolAsyncIterator, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetByteOffset, +} = primordials; + +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, + }, +} = require('internal/errors'); +const { TextDecoder } = require('internal/encoding'); +const { + validateAbortSignal, + validateFunction, + validateInteger, + validateObject, +} = require('internal/validators'); + +const { + from, + fromSync, + isAsyncIterable, + isSyncIterable, +} = require('internal/streams/iter/from'); + +const { + concatBytes, +} = require('internal/streams/iter/utils'); + +const { + drainableProtocol, +} = require('internal/streams/iter/types'); + +const { + isSharedArrayBuffer, +} = require('internal/util/types'); + +// ============================================================================= +// Type Guards +// ============================================================================= + +function isMergeOptions(value) { + return ( + value !== null && + typeof value === 'object' && + !isAsyncIterable(value) && + !isSyncIterable(value) + ); +} + +// ============================================================================= +// Shared chunk collection helpers +// ============================================================================= + +/** + * Collect chunks from a sync source into an array. + * @param {Iterable} source + * @param {number} [limit] + * @returns {Uint8Array[]} + */ +function collectSync(source, limit) { + // Normalize source via fromSync() - accepts strings, ArrayBuffers, protocols, etc. + const normalized = fromSync(source); + const chunks = []; + let totalBytes = 0; + + for (const batch of normalized) { + for (let i = 0; i < batch.length; i++) { + const chunk = batch[i]; + if (limit !== undefined) { + totalBytes += TypedArrayPrototypeGetByteLength(chunk); + if (totalBytes > limit) { + throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes); + } + } + ArrayPrototypePush(chunks, chunk); + } + } + + return chunks; +} + +/** + * Collect chunks from an async or sync source into an array. + * @param {AsyncIterable|Iterable} source + * @param {AbortSignal} [signal] + * @param {number} [limit] + * @returns {Promise} + */ +async function collectAsync(source, signal, limit) { + signal?.throwIfAborted(); + + // Normalize source via from() - accepts strings, ArrayBuffers, protocols, etc. + const normalized = from(source); + const chunks = []; + + // Fast path: no signal and no limit + if (!signal && limit === undefined) { + for await (const batch of normalized) { + for (let i = 0; i < batch.length; i++) { + ArrayPrototypePush(chunks, batch[i]); + } + } + return chunks; + } + + // Slow path: with signal or limit checks + let totalBytes = 0; + + for await (const batch of normalized) { + signal?.throwIfAborted(); + for (let i = 0; i < batch.length; i++) { + const chunk = batch[i]; + if (limit !== undefined) { + totalBytes += TypedArrayPrototypeGetByteLength(chunk); + if (totalBytes > limit) { + throw new ERR_OUT_OF_RANGE('totalBytes', `<= ${limit}`, totalBytes); + } + } + ArrayPrototypePush(chunks, chunk); + } + } + + return chunks; +} + +/** + * Convert a Uint8Array to its backing ArrayBuffer, slicing if necessary. + * Handles both ArrayBuffer and SharedArrayBuffer backing stores. + * @param {Uint8Array} data + * @returns {ArrayBuffer|SharedArrayBuffer} + */ +function toArrayBuffer(data) { + const byteOffset = TypedArrayPrototypeGetByteOffset(data); + const byteLength = TypedArrayPrototypeGetByteLength(data); + const buffer = TypedArrayPrototypeGetBuffer(data); + // SharedArrayBuffer is not available in primordials, so use + // direct property access for its byteLength and slice. + if (isSharedArrayBuffer(buffer)) { + if (byteOffset === 0 && byteLength === buffer.byteLength) { + return buffer; + } + return buffer.slice(byteOffset, byteOffset + byteLength); + } + if (byteOffset === 0 && + byteLength === ArrayBufferPrototypeGetByteLength(buffer)) { + return buffer; + } + return ArrayBufferPrototypeSlice(buffer, byteOffset, + byteOffset + byteLength); +} + +// ============================================================================= +// Shared option validation +// ============================================================================= + +function validateBaseConsumerOptions(options) { + validateObject(options, 'options'); + if (options.limit !== undefined) { + validateInteger(options.limit, 'options.limit', 0); + } + if (options.encoding !== undefined) { + if (typeof options.encoding !== 'string') { + throw new ERR_INVALID_ARG_TYPE('options.encoding', 'string', + options.encoding); + } + try { + new TextDecoder(options.encoding); + } catch { + throw new ERR_INVALID_ARG_VALUE.RangeError( + 'options.encoding', options.encoding); + } + } +} + +function validateConsumerOptions(options) { + validateBaseConsumerOptions(options); + if (options.signal !== undefined) { + validateAbortSignal(options.signal, 'options.signal'); + } +} + +function validateSyncConsumerOptions(options) { + validateBaseConsumerOptions(options); +} + +// ============================================================================= +// Sync Consumers +// ============================================================================= + +const kNullPrototype = { __proto__: null }; + +/** + * Collect all bytes from a sync source. + * @param {Iterable} source + * @param {{ limit?: number }} [options] + * @returns {Uint8Array} + */ +function bytesSync(source, options = kNullPrototype) { + validateSyncConsumerOptions(options); + return concatBytes(collectSync(source, options.limit)); +} + +/** + * Collect and decode text from a sync source. + * @param {Iterable} source + * @param {{ encoding?: string, limit?: number }} [options] + * @returns {string} + */ +function textSync(source, options = kNullPrototype) { + validateSyncConsumerOptions(options); + const data = concatBytes(collectSync(source, options.limit)); + const decoder = new TextDecoder(options.encoding ?? 'utf-8', { + __proto__: null, + fatal: true, + }); + return decoder.decode(data); +} + +/** + * Collect bytes as ArrayBuffer from a sync source. + * @param {Iterable} source + * @param {{ limit?: number }} [options] + * @returns {ArrayBuffer} + */ +function arrayBufferSync(source, options = kNullPrototype) { + validateSyncConsumerOptions(options); + return toArrayBuffer(concatBytes(collectSync(source, options.limit))); +} + +/** + * Collect all chunks as an array from a sync source. + * @param {Iterable} source + * @param {{ limit?: number }} [options] + * @returns {Uint8Array[]} + */ +function arraySync(source, options = kNullPrototype) { + validateSyncConsumerOptions(options); + return collectSync(source, options.limit); +} + +// ============================================================================= +// Async Consumers +// ============================================================================= + +/** + * Collect all bytes from an async or sync source. + * @param {AsyncIterable|Iterable} source + * @param {{ signal?: AbortSignal, limit?: number }} [options] + * @returns {Promise} + */ +async function bytes(source, options = kNullPrototype) { + validateConsumerOptions(options); + const chunks = await collectAsync(source, options.signal, options.limit); + return concatBytes(chunks); +} + +/** + * Collect and decode text from an async or sync source. + * @param {AsyncIterable|Iterable} source + * @param {{ encoding?: string, signal?: AbortSignal, limit?: number }} [options] + * @returns {Promise} + */ +async function text(source, options = kNullPrototype) { + validateConsumerOptions(options); + const chunks = await collectAsync(source, options.signal, options.limit); + const data = concatBytes(chunks); + const decoder = new TextDecoder(options.encoding ?? 'utf-8', { + __proto__: null, + fatal: true, + }); + return decoder.decode(data); +} + +/** + * Collect bytes as ArrayBuffer from an async or sync source. + * @param {AsyncIterable|Iterable} source + * @param {{ signal?: AbortSignal, limit?: number }} [options] + * @returns {Promise} + */ +async function arrayBuffer(source, options = kNullPrototype) { + validateConsumerOptions(options); + const chunks = await collectAsync(source, options.signal, options.limit); + return toArrayBuffer(concatBytes(chunks)); +} + +/** + * Collect all chunks as an array from an async or sync source. + * @param {AsyncIterable|Iterable} source + * @param {{ signal?: AbortSignal, limit?: number }} [options] + * @returns {Promise} + */ +async function array(source, options = kNullPrototype) { + validateConsumerOptions(options); + return collectAsync(source, options.signal, options.limit); +} + +// ============================================================================= +// Tap Utilities +// ============================================================================= + +/** + * Create a pass-through transform that observes chunks without modifying them. + * @param {Function} callback + * @returns {Function} + */ +function tap(callback) { + validateFunction(callback, 'callback'); + return async (chunks, options) => { + await callback(chunks, options); + return chunks; + }; +} + +/** + * Create a sync pass-through transform that observes chunks. + * @param {Function} callback + * @returns {Function} + */ +function tapSync(callback) { + validateFunction(callback, 'callback'); + return (chunks) => { + callback(chunks); + return chunks; + }; +} + +// ============================================================================= +// Drain Utility +// ============================================================================= + +/** + * Wait for a drainable object's backpressure to clear. + * @param {object} drainable + * @returns {Promise|null} + */ +function ondrain(drainable) { + if ( + drainable === null || + drainable === undefined || + typeof drainable !== 'object' + ) { + return null; + } + + if ( + !(drainableProtocol in drainable) || + typeof drainable[drainableProtocol] !== 'function' + ) { + return null; + } + + return drainable[drainableProtocol](); +} + +// ============================================================================= +// Merge Utility +// ============================================================================= + +/** + * Merge multiple async iterables by yielding values in temporal order. + * @param {...(AsyncIterable|object)} args + * @returns {AsyncIterable} + */ +function merge(...args) { + let sources; + let options; + + if (args.length > 0 && isMergeOptions(args[args.length - 1])) { + options = args[args.length - 1]; + sources = ArrayPrototypeSlice(args, 0, -1); + } else { + sources = args; + } + + if (options?.signal !== undefined) { + validateAbortSignal(options.signal, 'options.signal'); + } + + // Normalize each source via from() + const normalized = ArrayPrototypeMap(sources, (source) => from(source)); + + return { + __proto__: null, + async *[SymbolAsyncIterator]() { + const signal = options?.signal; + + signal?.throwIfAborted(); + + if (normalized.length === 0) return; + + if (normalized.length === 1) { + for await (const batch of normalized[0]) { + signal?.throwIfAborted(); + yield batch; + } + return; + } + + // Multiple sources - use a ready queue so that batches that settle + // between consumer pulls are drained synchronously without an extra + // async tick per batch. Each source has at most one pending .next() + // at a time. Every batch from every source is preserved. + const ready = []; + let activeCount = normalized.length; + let waitResolve = null; + + // Called when a source's .next() settles. Pushes the result into + // the ready queue and wakes the consumer if it's waiting. + const onSettled = (iterator, result) => { + if (result.done) { + activeCount--; + } else { + ArrayPrototypePush(ready, result.value); + // Immediately request the next value from this source + // (at most one pending .next() per source) + PromisePrototypeThen( + iterator.next(), + (r) => onSettled(iterator, r), + (err) => { + ArrayPrototypePush(ready, { __proto__: null, error: err }); + if (waitResolve) { + waitResolve(); + waitResolve = null; + } + }, + ); + } + if (waitResolve) { + waitResolve(); + waitResolve = null; + } + }; + + // Start one .next() per source + const iterators = []; + for (let i = 0; i < normalized.length; i++) { + const iterator = normalized[i][SymbolAsyncIterator](); + ArrayPrototypePush(iterators, iterator); + PromisePrototypeThen( + iterator.next(), + (r) => onSettled(iterator, r), + (err) => { + ArrayPrototypePush(ready, { __proto__: null, error: err }); + if (waitResolve) { + waitResolve(); + waitResolve = null; + } + }, + ); + } + + try { + while (activeCount > 0 || ready.length > 0) { + signal?.throwIfAborted(); + + // Drain ready queue synchronously + while (ready.length > 0) { + const item = ready.shift(); + if (item?.error) { + throw item.error; + } + yield item; + } + + // If sources are still active, wait for the next settlement + if (activeCount > 0) { + await new Promise((resolve) => { + waitResolve = resolve; + }); + } + } + } finally { + // Clean up: return all iterators + await SafePromiseAllReturnVoid(iterators, async (iterator) => { + if (iterator.return) { + try { + await iterator.return(); + } catch { + // Ignore return errors + } + } + }); + } + }, + }; +} + +module.exports = { + array, + arrayBuffer, + arrayBufferSync, + arraySync, + bytes, + bytesSync, + merge, + ondrain, + tap, + tapSync, + text, + textSync, +}; diff --git a/lib/internal/streams/iter/duplex.js b/lib/internal/streams/iter/duplex.js new file mode 100644 index 00000000000000..591837f70eb4cb --- /dev/null +++ b/lib/internal/streams/iter/duplex.js @@ -0,0 +1,141 @@ +'use strict'; + +// New Streams API - Duplex Channel +// +// Creates a pair of connected channels where data written to one +// channel's writer appears in the other channel's readable. + +const { + SymbolAsyncDispose, + SymbolAsyncIterator, +} = primordials; + +const { + push, +} = require('internal/streams/iter/push'); +const { + validateAbortSignal, + validateObject, +} = require('internal/validators'); + +/** + * Create a pair of connected duplex channels for bidirectional communication. + * @param {{ highWaterMark?: number, backpressure?: string, signal?: AbortSignal, + * a?: object, b?: object }} [options] + * @returns {[DuplexChannel, DuplexChannel]} + */ +function duplex(options = { __proto__: null }) { + validateObject(options, 'options'); + const { highWaterMark, backpressure, signal, a, b } = options; + if (a !== undefined) { + validateObject(a, 'options.a'); + } + if (b !== undefined) { + validateObject(b, 'options.b'); + } + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + } + + // Channel A writes to B's readable (A->B direction). + // Signal is NOT passed to push() -- we handle abort via close() below. + const { writer: aWriter, readable: bReadable } = push({ + highWaterMark: a?.highWaterMark ?? highWaterMark, + backpressure: a?.backpressure ?? backpressure, + }); + + // Channel B writes to A's readable (B->A direction) + const { writer: bWriter, readable: aReadable } = push({ + highWaterMark: b?.highWaterMark ?? highWaterMark, + backpressure: b?.backpressure ?? backpressure, + }); + + let aClosed = false; + let bClosed = false; + // Track active iterators so close() can call .return() on them + let aReadableIterator = null; + let bReadableIterator = null; + + const channelA = { + __proto__: null, + get writer() { return aWriter; }, + // Wrap readable to track the iterator for cleanup on close() + get readable() { + return { + __proto__: null, + [SymbolAsyncIterator]() { + const iter = aReadable[SymbolAsyncIterator](); + aReadableIterator = iter; + return iter; + }, + }; + }, + async close() { + if (aClosed) return; + aClosed = true; + // End the writer (signals end-of-stream to B's readable) + if (aWriter.endSync() < 0) { + await aWriter.end(); + } + // Stop iteration of this channel's readable + if (aReadableIterator?.return) { + await aReadableIterator.return(); + aReadableIterator = null; + } + }, + [SymbolAsyncDispose]() { + return this.close(); + }, + }; + + const channelB = { + __proto__: null, + get writer() { return bWriter; }, + get readable() { + return { + __proto__: null, + [SymbolAsyncIterator]() { + const iter = bReadable[SymbolAsyncIterator](); + bReadableIterator = iter; + return iter; + }, + }; + }, + async close() { + if (bClosed) return; + bClosed = true; + if (bWriter.endSync() < 0) { + await bWriter.end(); + } + if (bReadableIterator?.return) { + await bReadableIterator.return(); + bReadableIterator = null; + } + }, + [SymbolAsyncDispose]() { + return this.close(); + }, + }; + + // Signal handler: fail both writers with the abort reason so consumers + // see the error. This is an error-path shutdown, not a clean close. + if (signal) { + const abortBoth = () => { + const reason = signal.reason; + aWriter.fail(reason); + bWriter.fail(reason); + }; + if (signal.aborted) { + abortBoth(); + } else { + signal.addEventListener('abort', abortBoth, + { __proto__: null, once: true }); + } + } + + return [channelA, channelB]; +} + +module.exports = { + duplex, +}; diff --git a/lib/internal/streams/iter/from.js b/lib/internal/streams/iter/from.js new file mode 100644 index 00000000000000..d76f430ab0d51e --- /dev/null +++ b/lib/internal/streams/iter/from.js @@ -0,0 +1,577 @@ +'use strict'; + +// New Streams API - from() and fromSync() +// +// Creates normalized byte stream iterables from various input types. +// Handles recursive flattening of nested iterables and protocol conversions. + +const { + ArrayBufferIsView, + ArrayIsArray, + ArrayPrototypeEvery, + ArrayPrototypePush, + ArrayPrototypeSlice, + DataViewPrototypeGetBuffer, + DataViewPrototypeGetByteLength, + DataViewPrototypeGetByteOffset, + FunctionPrototypeCall, + SymbolAsyncIterator, + SymbolIterator, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetByteOffset, + Uint8Array, +} = primordials; + +const { + codes: { + ERR_INVALID_ARG_TYPE, + }, +} = require('internal/errors'); + +const { + isAnyArrayBuffer, + isDataView, + isPromise, + isUint8Array, +} = require('internal/util/types'); + +const { + toStreamable, + toAsyncStreamable, +} = require('internal/streams/iter/types'); + +const { + hasProtocol, + toUint8Array, +} = require('internal/streams/iter/utils'); + +// Maximum number of chunks to yield per batch from from(Uint8Array[]). +// Bounds peak memory when arrays flow through transforms, which must +// allocate output for the entire batch at once. +const FROM_BATCH_SIZE = 128; + +// ============================================================================= +// Type Guards and Detection +// ============================================================================= + +/** + * Check if value is a primitive chunk (string, ArrayBuffer, or ArrayBufferView). + * @returns {boolean} + */ +function isPrimitiveChunk(value) { + return typeof value === 'string' || isAnyArrayBuffer(value) || ArrayBufferIsView(value); +} + +/** + * Check if value is a sync iterable (has Symbol.iterator). + * @returns {boolean} + */ +function isSyncIterable(value) { + // We do not consider regular strings to be sync iterables in this context. + // We don't care about boxed strings (String objects) since they are uncommon. + return typeof value !== 'string' && + typeof value?.[SymbolIterator] === 'function'; +} + +/** + * Check if value is an async iterable (has Symbol.asyncIterator). + * @returns {boolean} + */ +function isAsyncIterable(value) { + return typeof value?.[SymbolAsyncIterator] === 'function'; +} + +// ============================================================================= +// Primitive Conversion +// ============================================================================= + +/** + * Convert a primitive chunk to Uint8Array. + * - string: UTF-8 encoded + * - ArrayBuffer: wrapped as Uint8Array view (no copy) + * - ArrayBufferView: converted to Uint8Array view of same memory + * @param {string|ArrayBuffer|ArrayBufferView} chunk + * @returns {Uint8Array} + */ +function primitiveToUint8Array(chunk) { + if (typeof chunk === 'string') { + return toUint8Array(chunk); + } + if (isAnyArrayBuffer(chunk)) { + return new Uint8Array(chunk); + } + if (isUint8Array(chunk)) { + return chunk; + } + // Other ArrayBufferView types (Int8Array, DataView, etc.) + if (isDataView(chunk)) { + return new Uint8Array( + DataViewPrototypeGetBuffer(chunk), + DataViewPrototypeGetByteOffset(chunk), + DataViewPrototypeGetByteLength(chunk), + ); + } + return new Uint8Array( + TypedArrayPrototypeGetBuffer(chunk), + TypedArrayPrototypeGetByteOffset(chunk), + TypedArrayPrototypeGetByteLength(chunk), + ); +} + +// ============================================================================= +// Sync Normalization (for fromSync and sync contexts) +// ============================================================================= + +/** + * Normalize a sync streamable yield value to Uint8Array chunks. + * Recursively flattens arrays, iterables, and protocol conversions. + * @yields {Uint8Array} + */ +function* normalizeSyncValue(value) { + // Handle primitives + if (isPrimitiveChunk(value)) { + yield primitiveToUint8Array(value); + return; + } + + // Handle ToStreamable protocol + if (hasProtocol(value, toStreamable)) { + const result = FunctionPrototypeCall(value[toStreamable], value); + yield* normalizeSyncValue(result); + return; + } + + // Handle arrays (which are also iterable, but check first for efficiency) + if (ArrayIsArray(value)) { + for (let i = 0; i < value.length; i++) { + yield* normalizeSyncValue(value[i]); + } + return; + } + + // Handle other sync iterables + if (isSyncIterable(value)) { + for (const item of value) { + yield* normalizeSyncValue(item); + } + return; + } + + // Reject: no valid conversion + throw new ERR_INVALID_ARG_TYPE( + 'value', + ['string', 'ArrayBuffer', 'ArrayBufferView', 'Iterable', 'toStreamable'], + value, + ); +} + +/** + * Check if value is already a Uint8Array[] batch (fast path). + * @returns {boolean} + */ +function isUint8ArrayBatch(value) { + if (!ArrayIsArray(value)) return false; + const len = value.length; + if (len === 0) return true; + // Fast path: single-element batch (most common from transforms) + if (len === 1) return isUint8Array(value[0]); + // Check first and last before iterating all elements + if (!isUint8Array(value[0]) || !isUint8Array(value[len - 1])) return false; + if (len === 2) return true; + for (let i = 1; i < len - 1; i++) { + if (!isUint8Array(value[i])) return false; + } + return true; +} + +/** + * Normalize a sync streamable source, yielding batches of Uint8Array. + * @param {Iterable} source + * @yields {Uint8Array[]} + */ +function* normalizeSyncSource(source) { + for (const value of source) { + // Fast path 1: value is already a Uint8Array[] batch + if (isUint8ArrayBatch(value)) { + if (value.length > 0) { + yield value; + } + continue; + } + // Fast path 2: value is a single Uint8Array (very common) + if (isUint8Array(value)) { + yield [value]; + continue; + } + // Slow path: normalize the value + const batch = []; + for (const chunk of normalizeSyncValue(value)) { + ArrayPrototypePush(batch, chunk); + } + if (batch.length > 0) { + yield batch; + } + } +} + +// ============================================================================= +// Async Normalization (for from and async contexts) +// ============================================================================= + +/** + * Normalize an async streamable yield value to Uint8Array chunks. + * Recursively flattens arrays, iterables, async iterables, promises, + * and protocol conversions. + * @yields {Uint8Array} + */ +async function* normalizeAsyncValue(value) { + // Handle promises first + if (isPromise(value)) { + const resolved = await value; + yield* normalizeAsyncValue(resolved); + return; + } + + // Handle primitives + if (isPrimitiveChunk(value)) { + yield primitiveToUint8Array(value); + return; + } + + // Handle ToAsyncStreamable protocol (check before ToStreamable) + if (hasProtocol(value, toAsyncStreamable)) { + const result = FunctionPrototypeCall(value[toAsyncStreamable], value); + if (isPromise(result)) { + yield* normalizeAsyncValue(await result); + } else { + yield* normalizeAsyncValue(result); + } + return; + } + + // Handle ToStreamable protocol + if (hasProtocol(value, toStreamable)) { + const result = FunctionPrototypeCall(value[toStreamable], value); + yield* normalizeAsyncValue(result); + return; + } + + // Handle arrays (which are also iterable, but check first for efficiency) + if (ArrayIsArray(value)) { + for (let i = 0; i < value.length; i++) { + yield* normalizeAsyncValue(value[i]); + } + return; + } + + // Handle async iterables (check before sync iterables since some objects + // have both) + if (isAsyncIterable(value)) { + for await (const item of value) { + yield* normalizeAsyncValue(item); + } + return; + } + + // Handle sync iterables + if (isSyncIterable(value)) { + for (const item of value) { + yield* normalizeAsyncValue(item); + } + return; + } + + // Reject: no valid conversion + throw new ERR_INVALID_ARG_TYPE( + 'value', + ['string', 'ArrayBuffer', 'ArrayBufferView', 'Iterable', 'AsyncIterable', + 'toStreamable', 'toAsyncStreamable'], + value, + ); +} + +/** + * Normalize an async streamable source, yielding batches of Uint8Array. + * @param {AsyncIterable|Iterable} source + * @yields {Uint8Array[]} + */ +async function* normalizeAsyncSource(source) { + // Prefer async iteration if available + if (isAsyncIterable(source)) { + for await (const value of source) { + // Fast path 1: value is already a Uint8Array[] batch + if (isUint8ArrayBatch(value)) { + if (value.length > 0) { + yield value; + } + continue; + } + // Fast path 2: value is a single Uint8Array (very common) + if (isUint8Array(value)) { + yield [value]; + continue; + } + // Slow path: normalize the value + const batch = []; + for await (const chunk of normalizeAsyncValue(value)) { + ArrayPrototypePush(batch, chunk); + } + if (batch.length > 0) { + yield batch; + } + } + return; + } + + // Fall back to sync iteration - batch all sync values together + if (isSyncIterable(source)) { + const batch = []; + + for (const value of source) { + // Fast path 1: value is already a Uint8Array[] batch + if (isUint8ArrayBatch(value)) { + // Flush any accumulated batch first + if (batch.length > 0) { + yield ArrayPrototypeSlice(batch); + batch.length = 0; + } + if (value.length > 0) { + yield value; + } + continue; + } + // Fast path 2: value is a single Uint8Array (very common) + if (isUint8Array(value)) { + ArrayPrototypePush(batch, value); + continue; + } + // Slow path: normalize the value - must flush and yield individually + if (batch.length > 0) { + yield ArrayPrototypeSlice(batch); + batch.length = 0; + } + const asyncBatch = []; + for await (const chunk of normalizeAsyncValue(value)) { + ArrayPrototypePush(asyncBatch, chunk); + } + if (asyncBatch.length > 0) { + yield asyncBatch; + } + } + + // Yield any remaining batched values + if (batch.length > 0) { + yield batch; + } + return; + } + + throw new ERR_INVALID_ARG_TYPE( + 'source', + ['Iterable', 'AsyncIterable'], + source, + ); +} + +// ============================================================================= +// Public API: from() and fromSync() +// ============================================================================= + +/** + * Create a SyncByteStreamReadable from a ByteInput or SyncStreamable. + * @param {string|ArrayBuffer|ArrayBufferView|Iterable} input + * @returns {Iterable} + */ +function fromSync(input) { + if (input == null) { + throw new ERR_INVALID_ARG_TYPE('input', 'a non-null value', input); + } + + // Check for primitives first (ByteInput) + if (isPrimitiveChunk(input)) { + const chunk = primitiveToUint8Array(input); + return { + __proto__: null, + *[SymbolIterator]() { + yield [chunk]; + }, + }; + } + + // Fast path: Uint8Array[] - yield in bounded sub-batches. + // Yielding the entire array as one batch forces downstream transforms + // to process all data at once, causing peak memory proportional to total + // data volume. Sub-batching keeps peak memory bounded while preserving + // the throughput benefit of batched processing. + if (ArrayIsArray(input)) { + if (input.length === 0) { + return { + __proto__: null, + *[SymbolIterator]() { + // Empty - yield nothing + }, + }; + } + // Check if it's an array of Uint8Array (common case) + if (isUint8Array(input[0])) { + const allUint8 = ArrayPrototypeEvery(input, isUint8Array); + if (allUint8) { + const batch = input; + return { + __proto__: null, + *[SymbolIterator]() { + if (batch.length <= FROM_BATCH_SIZE) { + yield batch; + } else { + for (let i = 0; i < batch.length; i += FROM_BATCH_SIZE) { + yield ArrayPrototypeSlice(batch, i, i + FROM_BATCH_SIZE); + } + } + }, + }; + } + } + } + + // Check toStreamable protocol (takes precedence over iteration protocols). + // toAsyncStreamable is ignored entirely in fromSync. + if (typeof input[toStreamable] === 'function') { + return fromSync(input[toStreamable]()); + } + + // Reject explicit async inputs + if (isAsyncIterable(input)) { + throw new ERR_INVALID_ARG_TYPE( + 'input', + 'a synchronous input (not AsyncIterable)', + input, + ); + } + if (typeof input === 'object' && input !== null && typeof input.then === 'function') { + throw new ERR_INVALID_ARG_TYPE( + 'input', + 'a synchronous input (not Promise)', + input, + ); + } + + // Must be a SyncStreamable + if (!isSyncIterable(input)) { + throw new ERR_INVALID_ARG_TYPE( + 'input', + ['string', 'ArrayBuffer', 'ArrayBufferView', 'Iterable', 'toStreamable'], + input, + ); + } + + return { + __proto__: null, + *[SymbolIterator]() { + yield* normalizeSyncSource(input); + }, + }; +} + +/** + * Create a ByteStreamReadable from a ByteInput or Streamable. + * @param {string|ArrayBuffer|ArrayBufferView|Iterable|AsyncIterable} input + * @returns {AsyncIterable} + */ +function from(input) { + if (input == null) { + throw new ERR_INVALID_ARG_TYPE('input', 'a non-null value', input); + } + + // Check for primitives first (ByteInput) + if (isPrimitiveChunk(input)) { + const chunk = primitiveToUint8Array(input); + return { + __proto__: null, + async *[SymbolAsyncIterator]() { + yield [chunk]; + }, + }; + } + + // Fast path: Uint8Array[] - yield in bounded sub-batches. + // Yielding the entire array as one batch forces downstream transforms + // to process all data at once, causing peak memory proportional to total + // data volume. Sub-batching keeps peak memory bounded while preserving + // the throughput benefit of batched processing. + if (ArrayIsArray(input)) { + if (input.length === 0) { + return { + __proto__: null, + async *[SymbolAsyncIterator]() { + // Empty - yield nothing + }, + }; + } + if (isUint8Array(input[0])) { + const allUint8 = ArrayPrototypeEvery(input, isUint8Array); + if (allUint8) { + const batch = input; + return { + __proto__: null, + async *[SymbolAsyncIterator]() { + if (batch.length <= FROM_BATCH_SIZE) { + yield batch; + } else { + for (let i = 0; i < batch.length; i += FROM_BATCH_SIZE) { + yield ArrayPrototypeSlice(batch, i, i + FROM_BATCH_SIZE); + } + } + }, + }; + } + } + } + + // Check toAsyncStreamable protocol (takes precedence over toStreamable and + // iteration protocols) + if (typeof input[toAsyncStreamable] === 'function') { + return { + __proto__: null, + async *[SymbolAsyncIterator]() { + const result = await input[toAsyncStreamable](); + yield* from(result)[SymbolAsyncIterator](); + }, + }; + } + + // Check toStreamable protocol (takes precedence over iteration protocols) + if (typeof input[toStreamable] === 'function') { + return from(input[toStreamable]()); + } + + // Must be a Streamable (sync or async iterable) + if (!isSyncIterable(input) && !isAsyncIterable(input)) { + throw new ERR_INVALID_ARG_TYPE( + 'input', + ['string', 'ArrayBuffer', 'ArrayBufferView', 'Iterable', + 'AsyncIterable', 'toStreamable', 'toAsyncStreamable'], + input, + ); + } + + return normalizeAsyncSource(input); +} + +// ============================================================================= +// Exports +// ============================================================================= + +module.exports = { + from, + fromSync, + isAsyncIterable, + isPrimitiveChunk, + isSyncIterable, + isUint8ArrayBatch, + normalizeAsyncSource, + normalizeAsyncValue, + normalizeSyncSource, + normalizeSyncValue, + primitiveToUint8Array, +}; diff --git a/lib/internal/streams/iter/pull.js b/lib/internal/streams/iter/pull.js new file mode 100644 index 00000000000000..8c49941ddb54ed --- /dev/null +++ b/lib/internal/streams/iter/pull.js @@ -0,0 +1,935 @@ +'use strict'; + +// New Streams API - Pull Pipeline +// +// pull(), pullSync(), pipeTo(), pipeToSync() +// Pull-through pipelines with transforms. Data flows on-demand from source +// through transforms to consumer. + +const { + ArrayBufferIsView, + ArrayPrototypePush, + ArrayPrototypeSlice, + PromisePrototypeThen, + SymbolAsyncIterator, + SymbolIterator, + TypedArrayPrototypeGetByteLength, + Uint8Array, +} = primordials; + +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + }, +} = require('internal/errors'); +const { lazyDOMException } = require('internal/util'); +const { validateAbortSignal } = require('internal/validators'); +const { + isAnyArrayBuffer, + isPromise, + isUint8Array, +} = require('internal/util/types'); +const { AbortController } = require('internal/abort_controller'); + +const { + from, + fromSync, + primitiveToUint8Array, + isSyncIterable, + isAsyncIterable, + isUint8ArrayBatch, +} = require('internal/streams/iter/from'); + +const { + isPullOptions, + isTransform, + isTransformObject, + parsePullArgs, + toUint8Array, + wrapError, +} = require('internal/streams/iter/utils'); + +const { + kTrustedTransform, +} = require('internal/streams/iter/types'); + +// ============================================================================= +// Type Guards and Helpers +// ============================================================================= + +/** + * Check if a value is a Writer (has write method). + * @returns {boolean} + */ +function hasMethod(value, name) { + return typeof value?.[name] === 'function'; +} + +/** + * Parse pipeTo/pipeToSync arguments: [...transforms, writer, options?] + * @param {Array} args + * @param {string} requiredMethod - 'write' for pipeTo, 'writeSync' for pipeToSync + * @returns {{ transforms: Array, writer: object, options: object }} + */ +function parsePipeToArgs(args, requiredMethod) { + if (args.length === 0) { + throw new ERR_INVALID_ARG_VALUE('args', args, 'pipeTo requires a writer argument'); + } + + let options; + let writerIndex = args.length - 1; + + // Check if last arg is options + const last = args[args.length - 1]; + if (isPullOptions(last) && !hasMethod(last, requiredMethod)) { + options = last; + writerIndex = args.length - 2; + } + + if (writerIndex < 0) { + throw new ERR_INVALID_ARG_VALUE('args', args, 'pipeTo requires a writer argument'); + } + + const writer = args[writerIndex]; + if (!hasMethod(writer, requiredMethod)) { + throw new ERR_INVALID_ARG_TYPE( + 'writer', `object with a ${requiredMethod} method`, writer); + } + + const transforms = ArrayPrototypeSlice(args, 0, writerIndex); + for (let i = 0; i < transforms.length; i++) { + if (!isTransform(transforms[i])) { + throw new ERR_INVALID_ARG_TYPE( + `transforms[${i}]`, ['Function', 'Object with transform()'], + transforms[i]); + } + } + + return { + __proto__: null, + transforms, + writer, + options, + }; +} + +// ============================================================================= +// Transform Output Flattening +// ============================================================================= + +/** + * Flatten transform yield to Uint8Array chunks (sync). + * @yields {Uint8Array} + */ +function* flattenTransformYieldSync(value) { + if (isUint8Array(value)) { + yield value; + return; + } + if (typeof value === 'string') { + yield toUint8Array(value); + return; + } + if (isAnyArrayBuffer(value)) { + yield new Uint8Array(value); + return; + } + if (ArrayBufferIsView(value)) { + yield primitiveToUint8Array(value); + return; + } + // Must be Iterable + if (isSyncIterable(value)) { + for (const item of value) { + yield* flattenTransformYieldSync(item); + } + return; + } + throw new ERR_INVALID_ARG_TYPE( + 'value', + ['Uint8Array', 'string', 'ArrayBuffer', 'ArrayBufferView', 'Iterable'], + value); +} + +/** + * Flatten transform yield to Uint8Array chunks (async). + * @yields {Uint8Array} + */ +async function* flattenTransformYieldAsync(value) { + if (isUint8Array(value)) { + yield value; + return; + } + if (typeof value === 'string') { + yield toUint8Array(value); + return; + } + if (isAnyArrayBuffer(value)) { + yield new Uint8Array(value); + return; + } + if (ArrayBufferIsView(value)) { + yield primitiveToUint8Array(value); + return; + } + // Check for async iterable first + if (isAsyncIterable(value)) { + for await (const item of value) { + yield* flattenTransformYieldAsync(item); + } + return; + } + // Must be sync Iterable + if (isSyncIterable(value)) { + for (const item of value) { + yield* flattenTransformYieldAsync(item); + } + return; + } + throw new ERR_INVALID_ARG_TYPE( + 'value', + ['Uint8Array', 'string', 'ArrayBuffer', 'ArrayBufferView', + 'Iterable', 'AsyncIterable'], + value); +} + +/** + * Process transform result (sync). + * @yields {Uint8Array[]} + */ +function* processTransformResultSync(result) { + if (result === null) { + return; + } + // Single Uint8Array -> wrap as batch + if (isUint8Array(result)) { + yield [result]; + return; + } + // String -> UTF-8 encode and wrap as batch + if (typeof result === 'string') { + yield [toUint8Array(result)]; + return; + } + // ArrayBuffer / ArrayBufferView -> convert and wrap + if (isAnyArrayBuffer(result)) { + yield [new Uint8Array(result)]; + return; + } + if (ArrayBufferIsView(result)) { + yield [primitiveToUint8Array(result)]; + return; + } + // Uint8Array[] batch + if (isUint8ArrayBatch(result)) { + if (result.length > 0) { + yield result; + } + return; + } + // Iterable or Generator + if (isSyncIterable(result)) { + const batch = []; + for (const item of result) { + for (const chunk of flattenTransformYieldSync(item)) { + ArrayPrototypePush(batch, chunk); + } + } + if (batch.length > 0) { + yield batch; + } + return; + } + throw new ERR_INVALID_ARG_TYPE( + 'result', + ['null', 'Uint8Array', 'string', 'ArrayBuffer', + 'ArrayBufferView', 'Array', 'Iterable'], + result); +} + +/** + * Process transform result (async). + * @yields {Uint8Array[]} + */ +async function* processTransformResultAsync(result) { + // Handle Promise + if (isPromise(result)) { + const resolved = await result; + yield* processTransformResultAsync(resolved); + return; + } + if (result === null) { + return; + } + // Single Uint8Array -> wrap as batch + if (isUint8Array(result)) { + yield [result]; + return; + } + // String -> UTF-8 encode and wrap as batch + if (typeof result === 'string') { + yield [toUint8Array(result)]; + return; + } + // ArrayBuffer / ArrayBufferView -> convert and wrap + if (isAnyArrayBuffer(result)) { + yield [new Uint8Array(result)]; + return; + } + if (ArrayBufferIsView(result)) { + yield [primitiveToUint8Array(result)]; + return; + } + // Uint8Array[] batch + if (isUint8ArrayBatch(result)) { + if (result.length > 0) { + yield result; + } + return; + } + // Check for async iterable/generator first + if (isAsyncIterable(result)) { + const batch = []; + for await (const item of result) { + if (isUint8Array(item)) { + ArrayPrototypePush(batch, item); + continue; + } + for await (const chunk of flattenTransformYieldAsync(item)) { + ArrayPrototypePush(batch, chunk); + } + } + if (batch.length > 0) { + yield batch; + } + return; + } + // Sync Iterable or Generator + if (isSyncIterable(result)) { + const batch = []; + for (const item of result) { + if (isUint8Array(item)) { + ArrayPrototypePush(batch, item); + continue; + } + for await (const chunk of flattenTransformYieldAsync(item)) { + ArrayPrototypePush(batch, chunk); + } + } + if (batch.length > 0) { + yield batch; + } + return; + } + throw new ERR_INVALID_ARG_TYPE( + 'result', + ['null', 'Uint8Array', 'string', 'ArrayBuffer', + 'ArrayBufferView', 'Array', 'Iterable', 'AsyncIterable', 'Promise'], + result); +} + +// ============================================================================= +// Sync Pipeline Implementation +// ============================================================================= + +/** + * Apply a single stateless sync transform to a source. + * @yields {Uint8Array[]} + */ +/** + * Apply a fused run of stateless sync transforms. + * @param {Iterable} source + * @param {Array} run - Array of stateless transform functions + * @yields {Uint8Array[]} + */ +function* applyFusedStatelessSyncTransforms(source, run) { + for (const chunks of source) { + let current = chunks; + for (let i = 0; i < run.length; i++) { + const result = run[i](current); + if (result === null) { + current = null; + break; + } + current = result; + } + if (current === null) continue; + // Inline normalization with Uint8Array[] batch as the fast path, + // matching the async pipeline's check order. + if (isUint8ArrayBatch(current)) { + if (current.length > 0) yield current; + } else if (isUint8Array(current)) { + yield [current]; + } else if (typeof current === 'string') { + yield [toUint8Array(current)]; + } else if (isAnyArrayBuffer(current)) { + yield [new Uint8Array(current)]; + } else if (ArrayBufferIsView(current)) { + yield [primitiveToUint8Array(current)]; + } else { + yield* processTransformResultSync(current); + } + } + // Flush + let current = null; + for (let i = 0; i < run.length; i++) { + const result = run[i](current); + if (result === null) { + current = null; + continue; + } + current = result; + } + if (current != null) { + yield* processTransformResultSync(current); + } +} + +/** + * Apply a single stateful sync transform to a source. + * @yields {Uint8Array[]} + */ +function* withFlushSync(source) { + yield* source; + yield null; +} + +function* applyStatefulSyncTransform(source, transform) { + const output = transform(withFlushSync(source)); + for (const item of output) { + const batch = []; + for (const chunk of flattenTransformYieldSync(item)) { + ArrayPrototypePush(batch, chunk); + } + if (batch.length > 0) { + yield batch; + } + } +} + +/** + * Create a sync pipeline from source through transforms. + * @yields {Uint8Array[]} + */ +function* createSyncPipeline(source, transforms) { + let current = source; + + // Apply transforms - fuse consecutive stateless transforms into a single + // generator layer to avoid unnecessary generator ticks. + let statelessRun = []; + + for (let i = 0; i < transforms.length; i++) { + const transform = transforms[i]; + if (isTransformObject(transform)) { + if (statelessRun.length > 0) { + current = applyFusedStatelessSyncTransforms(current, statelessRun); + statelessRun = []; + } + current = applyStatefulSyncTransform(current, transform.transform); + } else { + statelessRun.push(transform); + } + } + if (statelessRun.length > 0) { + current = applyFusedStatelessSyncTransforms(current, statelessRun); + } + + yield* current; +} + +// ============================================================================= +// Async Pipeline Implementation +// ============================================================================= + +/** + * Apply a single stateless async transform to a source. + * @yields {Uint8Array[]} + */ +/** + * Apply a fused run of stateless async transforms to a source. + * All transforms in the run are applied in a tight synchronous loop per batch, + * avoiding the overhead of N async generator ticks for N transforms. + * + * INVARIANT: This function accepts a signal, NOT a pre-built options object. + * A fresh { __proto__: null, signal } options object is created for each + * transform invocation to prevent cross-transform mutation. + * @param {AsyncIterable} source + * @param {Array} run - Array of stateless transform functions + * @param {AbortSignal} signal - The pipeline's abort signal + * @yields {Uint8Array[]} + */ +async function* applyFusedStatelessAsyncTransforms(source, run, signal) { + for await (const chunks of source) { + let current = chunks; + for (let i = 0; i < run.length; i++) { + const result = run[i](current, { __proto__: null, signal }); + if (result === null) { + current = null; + break; + } + if (isPromise(result)) { + const resolved = await result; + if (resolved === null) { + current = null; + break; + } + current = resolved; + } else { + current = result; + } + } + if (current === null) continue; + // Normalize the final output + if (isUint8ArrayBatch(current)) { + if (current.length > 0) yield current; + } else if (isUint8Array(current)) { + yield [current]; + } else if (typeof current === 'string') { + yield [toUint8Array(current)]; + } else if (isAnyArrayBuffer(current)) { + yield [new Uint8Array(current)]; + } else if (ArrayBufferIsView(current)) { + yield [primitiveToUint8Array(current)]; + } else { + yield* processTransformResultAsync(current); + } + } + // Flush: send null through each transform in order + let current = null; + for (let i = 0; i < run.length; i++) { + const result = run[i](current, { __proto__: null, signal }); + if (result === null) { + current = null; + continue; + } + if (isPromise(result)) { + current = await result; + } else { + current = result; + } + } + if (current !== null) { + if (isUint8ArrayBatch(current)) { + if (current.length > 0) yield current; + } else if (isUint8Array(current)) { + yield [current]; + } else if (typeof current === 'string') { + yield [toUint8Array(current)]; + } else { + yield* processTransformResultAsync(current); + } + } +} + +/** + * Append a null flush signal after the source is exhausted. + * @yields {Uint8Array[]} + */ +/** + * Append a null flush signal after the source is exhausted. + * @yields {Uint8Array[]} + */ +async function* withFlushAsync(source) { + for await (const batch of source) { + yield batch; + } + yield null; +} + +async function* applyStatefulAsyncTransform(source, transform, options) { + const output = transform(withFlushAsync(source), options); + for await (const item of output) { + // Fast path: item is already a Uint8Array[] batch (e.g. compression transforms) + if (isUint8ArrayBatch(item)) { + if (item.length > 0) { + yield item; + } + continue; + } + // Fast path: single Uint8Array + if (isUint8Array(item)) { + yield [item]; + continue; + } + // Slow path: flatten arbitrary transform yield + const batch = []; + for await (const chunk of flattenTransformYieldAsync(item)) { + ArrayPrototypePush(batch, chunk); + } + if (batch.length > 0) { + yield batch; + } + } +} + +/** + * Fast path for trusted stateful transforms (e.g. compression). + * Skips withFlushAsync (transform handles done internally) and + * skips isUint8ArrayBatch validation (transform guarantees valid output). + * @yields {Uint8Array[]} + */ +async function* applyTrustedStatefulAsyncTransform(source, transform, options) { + const output = transform(source, options); + for await (const batch of output) { + if (batch.length > 0) { + yield batch; + } + } + // Check abort after the transform completes - without the + // withFlushAsync wrapper there is no extra yield to give + // the outer pipeline a chance to see the abort. + options.signal?.throwIfAborted(); +} + +/** + * Create an async pipeline from source through transforms. + * @yields {Uint8Array[]} + */ +async function* createAsyncPipeline(source, transforms, signal) { + // Check for abort + signal?.throwIfAborted(); + + const normalized = source; + + // Fast path: no transforms, just yield normalized source directly + if (transforms.length === 0) { + for await (const batch of normalized) { + signal?.throwIfAborted(); + yield batch; + } + return; + } + + // Create internal controller for transform cancellation. + // Note: if signal was already aborted, we threw above - no need to check here. + const controller = new AbortController(); + let abortHandler; + if (signal) { + abortHandler = () => { + controller.abort(signal.reason ?? + lazyDOMException('Aborted', 'AbortError')); + }; + signal.addEventListener('abort', abortHandler, { __proto__: null, once: true }); + } + + // Apply transforms - fuse consecutive stateless transforms into a single + // generator layer to avoid unnecessary async generator ticks. + // + // INVARIANT: Each transform invocation MUST receive its own fresh options + // object ({ __proto__: null, signal }). Transforms may mutate the options + // object, so sharing a single object across invocations would allow one + // transform to corrupt the options seen by another. The signal is shared + // across calls (mutations to it are acceptable), but the containing options + // object must be unique per call. This is enforced inside + // applyFusedStatelessAsyncTransforms and applyStatefulAsyncTransform, which + // accept the signal directly and create the options object per invocation. + // DO NOT pass a pre-built options object. + let current = normalized; + const transformSignal = controller.signal; + let statelessRun = []; + + for (let i = 0; i < transforms.length; i++) { + const transform = transforms[i]; + if (isTransformObject(transform)) { + // Flush any accumulated stateless run before the stateful transform + if (statelessRun.length > 0) { + current = applyFusedStatelessAsyncTransforms(current, statelessRun, + transformSignal); + statelessRun = []; + } + const opts = { __proto__: null, signal: transformSignal }; + if (transform[kTrustedTransform]) { + current = applyTrustedStatefulAsyncTransform( + current, transform.transform, opts); + } else { + current = applyStatefulAsyncTransform( + current, transform.transform, opts); + } + } else { + statelessRun.push(transform); + } + } + // Flush remaining stateless run + if (statelessRun.length > 0) { + current = applyFusedStatelessAsyncTransforms(current, statelessRun, + transformSignal); + } + + let completed = false; + try { + for await (const batch of current) { + controller.signal.throwIfAborted(); + yield batch; + } + completed = true; + } catch (error) { + if (!controller.signal.aborted) { + controller.abort(wrapError(error)); + } + throw error; + } finally { + if (!completed && !controller.signal.aborted) { + // Consumer stopped early or generator return() was called. + // If a transform listener throws here, let it propagate. + controller.abort(lazyDOMException('Aborted', 'AbortError')); + } + // Clean up user signal listener to prevent holding controller alive + if (signal && abortHandler) { + signal.removeEventListener('abort', abortHandler); + } + } +} + +// ============================================================================= +// Public API: pull() and pullSync() +// ============================================================================= + +/** + * Create a sync pull-through pipeline with transforms. + * @param {Iterable} source - The sync streamable source + * @param {...Function} transforms - Variadic transforms + * @returns {Iterable} + */ +function pullSync(source, ...transforms) { + for (let i = 0; i < transforms.length; i++) { + if (!isTransform(transforms[i])) { + throw new ERR_INVALID_ARG_TYPE( + `transforms[${i}]`, ['Function', 'Object with transform()'], + transforms[i]); + } + } + return { + __proto__: null, + *[SymbolIterator]() { + yield* createSyncPipeline(fromSync(source), transforms); + }, + }; +} + +/** + * Create an async pull-through pipeline with transforms. + * @param {Iterable|AsyncIterable} source - The streamable source + * @param {...(Function|object)} args - Transforms, with optional PullOptions + * as last argument + * @returns {AsyncIterable} + */ +function pull(source, ...args) { + const { transforms, options } = parsePullArgs(args); + const signal = options?.signal; + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + // Eagerly check abort at call time per spec + if (signal.aborted) { + return { + __proto__: null, + // eslint-disable-next-line require-yield + async *[SymbolAsyncIterator]() { + throw signal.reason; + }, + }; + } + } + + return { + __proto__: null, + async *[SymbolAsyncIterator]() { + yield* createAsyncPipeline(from(source), transforms, signal); + }, + }; +} + +// ============================================================================= +// Public API: pipeTo() and pipeToSync() +// ============================================================================= + +/** + * Write a sync source through transforms to a sync writer. + * @param {Iterable} source + * @param {...(Function|object)} args - Transforms, writer, and optional options + * @returns {number} Total bytes written + */ +function pipeToSync(source, ...args) { + const { transforms, writer, options } = parsePipeToArgs(args, 'writeSync'); + + // Handle transform-writer + if (isTransformObject(writer)) { + ArrayPrototypePush(transforms, writer); + } + + // Normalize source and create pipeline + const normalized = fromSync(source); + const pipeline = transforms.length > 0 ? + createSyncPipeline(normalized, transforms) : + normalized; + + let totalBytes = 0; + const hasWriteSync = typeof writer.writeSync === 'function'; + const hasWritevSync = typeof writer.writevSync === 'function'; + const hasEndSync = typeof writer.endSync === 'function'; + + try { + for (const batch of pipeline) { + if (hasWritevSync && batch.length > 1) { + writer.writevSync(batch); + for (let i = 0; i < batch.length; i++) { + totalBytes += TypedArrayPrototypeGetByteLength(batch[i]); + } + } else { + for (let i = 0; i < batch.length; i++) { + const chunk = batch[i]; + if (hasWriteSync) { + writer.writeSync(chunk); + } else { + writer.write(chunk); + } + totalBytes += TypedArrayPrototypeGetByteLength(chunk); + } + } + } + + if (!options?.preventClose) { + if (!hasEndSync || writer.endSync() < 0) { + writer.end?.(); + } + } + } catch (error) { + if (!options?.preventFail) { + writer.fail?.(wrapError(error)); + } + throw error; + } + + return totalBytes; +} + +/** + * Write an async source through transforms to a writer. + * @param {AsyncIterable|Iterable} source + * @param {...(Function|object)} args - Transforms, writer, and optional options + * @returns {Promise} Total bytes written + */ +async function pipeTo(source, ...args) { + const { transforms, writer, options } = parsePipeToArgs(args, 'write'); + if (options?.signal !== undefined) { + validateAbortSignal(options.signal, 'options.signal'); + } + + // Handle transform-writer + if (isTransformObject(writer)) { + ArrayPrototypePush(transforms, writer); + } + + const signal = options?.signal; + + // Check for abort + signal?.throwIfAborted(); + + // Normalize source via from() + const normalized = from(source); + + let totalBytes = 0; + const hasWritev = typeof writer.writev === 'function'; + const hasWriteSync = typeof writer.writeSync === 'function'; + const hasWritevSync = typeof writer.writevSync === 'function'; + const hasEndSync = typeof writer.endSync === 'function'; + // Async fallback for writeBatch when sync write fails partway through. + // Continues writing from batch[startIndex] using async write(). + async function writeBatchAsyncFallback(batch, startIndex) { + for (let i = startIndex; i < batch.length; i++) { + const chunk = batch[i]; + if (hasWriteSync && writer.writeSync(chunk)) { + // Sync retry succeeded + } else { + const result = writer.write( + chunk, signal ? { __proto__: null, signal } : undefined); + if (result !== undefined) { + await result; + } + } + totalBytes += TypedArrayPrototypeGetByteLength(chunk); + } + } + + // Write a batch using try-fallback: sync first, async if needed. + // Returns undefined on sync success, or a Promise when async fallback + // is required. Callers must check: const p = writeBatch(b); if (p) await p; + function writeBatch(batch) { + if (hasWritev && batch.length > 1) { + if (!hasWritevSync || !writer.writevSync(batch)) { + const opts = signal ? { __proto__: null, signal } : undefined; + return PromisePrototypeThen(writer.writev(batch, opts), () => { + for (let i = 0; i < batch.length; i++) { + totalBytes += TypedArrayPrototypeGetByteLength(batch[i]); + } + }); + } + for (let i = 0; i < batch.length; i++) { + totalBytes += TypedArrayPrototypeGetByteLength(batch[i]); + } + return; + } + for (let i = 0; i < batch.length; i++) { + const chunk = batch[i]; + if (!hasWriteSync || !writer.writeSync(chunk)) { + // Sync path failed at index i - fall back to async for the rest. + // Count bytes for chunks already written synchronously (0..i-1). + return writeBatchAsyncFallback(batch, i); + } + totalBytes += TypedArrayPrototypeGetByteLength(chunk); + } + } + + try { + // Fast path: no transforms - iterate normalized source directly + if (transforms.length === 0) { + if (signal) { + for await (const batch of normalized) { + signal.throwIfAborted(); + const p = writeBatch(batch); + if (p) await p; + } + } else { + for await (const batch of normalized) { + const p = writeBatch(batch); + if (p) await p; + } + } + } else { + const pipeline = createAsyncPipeline(normalized, transforms, signal); + + if (signal) { + for await (const batch of pipeline) { + signal.throwIfAborted(); + const p = writeBatch(batch); + if (p) await p; + } + } else { + for await (const batch of pipeline) { + const p = writeBatch(batch); + if (p) await p; + } + } + } + + if (!options?.preventClose) { + if (!hasEndSync || writer.endSync() < 0) { + await writer.end?.(signal ? { __proto__: null, signal } : undefined); + } + } + } catch (error) { + if (!options?.preventFail) { + writer.fail?.(wrapError(error)); + } + throw error; + } + + return totalBytes; +} + +module.exports = { + pipeTo, + pipeToSync, + pull, + pullSync, +}; diff --git a/lib/internal/streams/iter/push.js b/lib/internal/streams/iter/push.js new file mode 100644 index 00000000000000..4c0b3240d45fdb --- /dev/null +++ b/lib/internal/streams/iter/push.js @@ -0,0 +1,721 @@ +'use strict'; + +// New Streams API - Push Stream Implementation +// +// Creates a bonded pair of writer and async iterable for push-based streaming +// with built-in backpressure. + +const { + ArrayIsArray, + ArrayPrototypePush, + MathMax, + PromiseReject, + PromiseResolve, + PromiseWithResolvers, + SymbolAsyncDispose, + SymbolAsyncIterator, + SymbolDispose, + TypedArrayPrototypeGetByteLength, +} = primordials; + +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_STATE, + }, +} = require('internal/errors'); +const { isError, lazyDOMException } = require('internal/util'); +const { + validateAbortSignal, + validateInteger, +} = require('internal/validators'); + +const { + drainableProtocol, +} = require('internal/streams/iter/types'); + +const { + kPushDefaultHWM, + kResolvedPromise, + clampHWM, + onSignalAbort, + toUint8Array, + convertChunks, + parsePullArgs, + validateBackpressure, +} = require('internal/streams/iter/utils'); + +const { + pull: pullWithTransforms, +} = require('internal/streams/iter/pull'); + +const { + RingBuffer, +} = require('internal/streams/iter/ringbuffer'); + +// ============================================================================= +// PushQueue - Internal Queue with Chunk-Based Backpressure +// ============================================================================= + +class PushQueue { + /** Buffered chunks (each slot is from one write/writev call) */ + #slots = new RingBuffer(); + /** Pending writes waiting for buffer space */ + #pendingWrites = new RingBuffer(); + /** Pending reads waiting for data */ + #pendingReads = new RingBuffer(); + /** Pending drains waiting for backpressure to clear */ + #pendingDrains = []; + /** Writer state: 'open' | 'closing' | 'closed' | 'errored' */ + #writerState = 'open'; + /** Consumer state: 'active' | 'returned' | 'thrown' */ + #consumerState = 'active'; + /** Error that closed the stream */ + #error = null; + /** Total bytes written */ + #bytesWritten = 0; + /** Pending end promise (resolves when consumer drains past end sentinel) */ + #pendingEnd = null; + + /** Configuration */ + #highWaterMark; + #backpressure; + #signal; + #abortHandler; + + constructor(options = { __proto__: null }) { + const { + highWaterMark = kPushDefaultHWM, + backpressure = 'strict', + signal, + } = options; + validateInteger(highWaterMark, 'options.highWaterMark'); + validateBackpressure(backpressure); + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + } + this.#highWaterMark = clampHWM(highWaterMark); + this.#backpressure = backpressure; + this.#signal = signal; + this.#abortHandler = undefined; + + if (this.#signal) { + this.#abortHandler = () => { + this.fail(isError(this.#signal.reason) ? + this.#signal.reason : + lazyDOMException('Aborted', 'AbortError')); + }; + onSignalAbort(this.#signal, this.#abortHandler); + } + } + + // =========================================================================== + // Writer Methods + // =========================================================================== + + /** + * Get slots available before hitting highWaterMark. + * Returns null if writer is closed/errored or consumer has terminated. + * @returns {number | null} + */ + get desiredSize() { + if (this.#writerState !== 'open' || this.#consumerState !== 'active') { + return null; + } + return MathMax(0, this.#highWaterMark - this.#slots.length); + } + + /** + * Check if a sync write would be accepted. + * @returns {boolean} + */ + canWriteSync() { + if (this.#writerState !== 'open') return false; + if (this.#consumerState !== 'active') return false; + if ((this.#backpressure === 'strict' || + this.#backpressure === 'block') && + this.#slots.length >= this.#highWaterMark) { + return false; + } + return true; + } + + /** + * Write chunks synchronously if possible. + * Returns true if write completed, false if buffer is full. + * @returns {boolean} + */ + writeSync(chunks) { + if (this.#writerState !== 'open') return false; + if (this.#consumerState !== 'active') return false; + + if (this.#slots.length >= this.#highWaterMark) { + switch (this.#backpressure) { + case 'strict': + return false; + case 'block': + return false; + case 'drop-oldest': + if (this.#slots.length > 0) { + this.#slots.shift(); + } + break; + case 'drop-newest': + // Discard this write, but return true + for (let i = 0; i < chunks.length; i++) { + this.#bytesWritten += TypedArrayPrototypeGetByteLength(chunks[i]); + } + return true; + } + } + + this.#slots.push(chunks); + for (let i = 0; i < chunks.length; i++) { + this.#bytesWritten += TypedArrayPrototypeGetByteLength(chunks[i]); + } + + this.#resolvePendingReads(); + return true; + } + + /** + * Write chunks asynchronously. + * If signal is provided, a write blocked on backpressure will reject + * immediately when the signal fires. The cancelled write is removed from + * pendingWrites so it does not occupy a slot. The queue itself is NOT put + * into an error state - this is per-operation cancellation, not terminal + * failure. + * @returns {Promise} + */ + async writeAsync(chunks, signal) { + // Check writer state before signal (spec order: state, then signal) + if (this.#writerState === 'closed') { + throw new ERR_INVALID_STATE.TypeError('Writer is closed'); + } + if (this.#writerState === 'closing') { + throw new ERR_INVALID_STATE.TypeError('Writer is closing'); + } + if (this.#writerState === 'errored') { + throw this.#error; + } + if (this.#consumerState !== 'active') { + throw this.#consumerState === 'thrown' && this.#error ? + this.#error : + new ERR_INVALID_STATE.TypeError('Stream closed by consumer'); + } + + // Check for pre-aborted signal (after state checks per spec) + signal?.throwIfAborted(); + + // Try sync first + if (this.writeSync(chunks)) { + return; + } + + // Buffer is full + switch (this.#backpressure) { + case 'strict': + if (this.#pendingWrites.length >= this.#highWaterMark) { + throw new ERR_INVALID_STATE.RangeError( + 'Backpressure violation: too many pending writes. ' + + 'Await each write() call to respect backpressure.'); + } + return this.#createPendingWrite(chunks, signal); + case 'block': + return this.#createPendingWrite(chunks, signal); + default: + throw new ERR_INVALID_STATE( + 'Unexpected: writeSync should have handled non-strict policy'); + } + } + + /** + * Create a pending write promise, optionally racing against a signal. + * If the signal fires, the entry is removed from pendingWrites and the + * promise rejects. Signal listeners are cleaned up on normal resolution. + * @returns {Promise} + */ + #createPendingWrite(chunks, signal) { + const { promise, resolve, reject } = PromiseWithResolvers(); + const entry = { __proto__: null, chunks, resolve, reject }; + this.#pendingWrites.push(entry); + + if (signal) { + const onAbort = () => { + // Remove from queue so it doesn't occupy a slot + const idx = this.#pendingWrites.indexOf(entry); + if (idx !== -1) this.#pendingWrites.removeAt(idx); + reject(signal.reason ?? lazyDOMException('Aborted', 'AbortError')); + }; + + // Wrap resolve/reject to clean up signal listener + entry.resolve = function() { + signal.removeEventListener('abort', onAbort); + resolve(); + }; + entry.reject = function(reason) { + signal.removeEventListener('abort', onAbort); + reject(reason); + }; + + signal.addEventListener('abort', onAbort, { __proto__: null, once: true }); + } + + return promise; + } + + /** + * Signal end of stream. Returns total bytes written. + * @returns {number} + */ + end() { + if (this.#writerState === 'errored') { + return -2; // Signal to reject with stored error + } + if (this.#writerState === 'closing' || this.#writerState === 'closed') { + return this.#bytesWritten; // Idempotent + } + + this.#cleanup(); + this.#rejectPendingWrites( + new ERR_INVALID_STATE.TypeError('Writer closed')); + this.#resolvePendingDrains(false); + + // If buffer is empty, close immediately + if (this.#slots.length === 0) { + this.#writerState = 'closed'; + this.#resolvePendingReads(); + return this.#bytesWritten; + } + + // Buffer has data: transition to closing, defer completion until drained + this.#writerState = 'closing'; + return -3; // Signal to PushWriter: create deferred end promise + } + + /** + * Called by the read path when the consumer has drained all data while + * the writer is in the 'closing' state. Transitions to 'closed' and + * resolves the pending end promise. + */ + endDrained() { + if (this.#writerState !== 'closing') return; + this.#writerState = 'closed'; + if (this.#pendingEnd) { + this.#pendingEnd.resolve(this.#bytesWritten); + this.#pendingEnd = null; + } + } + + /** + * Put queue into terminal error state. + * No-op if errored or closed (fully drained). + * If closing (draining), short-circuits the drain. + */ + fail(reason) { + if (this.#writerState === 'errored' || this.#writerState === 'closed') { + return; + } + + const wasClosing = this.#writerState === 'closing'; + this.#writerState = 'errored'; + this.#error = reason ?? new ERR_INVALID_STATE('Failed'); + this.#cleanup(); + this.#rejectPendingReads(this.#error); + this.#rejectPendingDrains(this.#error); + + if (wasClosing) { + // Short-circuit the graceful drain: reject the pending end promise + if (this.#pendingEnd) { + this.#pendingEnd.reject(this.#error); + this.#pendingEnd = null; + } + } else { + this.#rejectPendingWrites(this.#error); + } + } + + get totalBytesWritten() { + return this.#bytesWritten; + } + + get error() { + return this.#error; + } + + get backpressurePolicy() { + return this.#backpressure; + } + + get writerState() { + return this.#writerState; + } + + get pendingEndPromise() { + return this.#pendingEnd?.promise ?? null; + } + + setPendingEnd(pending) { + this.#pendingEnd = pending; + } + + /** + * Force-enqueue chunks into the slots buffer, bypassing capacity checks. + * Used by PushWriter.writeSync() for 'block' policy where the data is + * accepted but false is returned as a backpressure signal. + */ + forceEnqueue(chunks) { + this.#slots.push(chunks); + for (let i = 0; i < chunks.length; i++) { + this.#bytesWritten += TypedArrayPrototypeGetByteLength(chunks[i]); + } + this.#resolvePendingReads(); + } + + /** + * Wait for backpressure to clear (desiredSize > 0). + * @returns {Promise} + */ + waitForDrain() { + const { promise, resolve, reject } = PromiseWithResolvers(); + ArrayPrototypePush(this.#pendingDrains, { __proto__: null, resolve, reject }); + return promise; + } + + // =========================================================================== + // Consumer Methods + // =========================================================================== + + async read() { + // If there's data in the buffer, return it immediately + if (this.#slots.length > 0) { + const result = this.#drain(); + this.#resolvePendingWrites(); + // After draining, check if writer was closing and buffer is now empty + if (this.#writerState === 'closing' && this.#slots.length === 0) { + this.endDrained(); + } + return { __proto__: null, value: result, done: false }; + } + + // Buffer empty and writer closing = drain complete + if (this.#writerState === 'closing') { + this.endDrained(); + return { __proto__: null, value: undefined, done: true }; + } + + if (this.#writerState === 'closed') { + return { __proto__: null, value: undefined, done: true }; + } + + if (this.#writerState === 'errored' && this.#error) { + throw this.#error; + } + + const { promise, resolve, reject } = PromiseWithResolvers(); + this.#pendingReads.push({ __proto__: null, resolve, reject }); + return promise; + } + + consumerReturn() { + if (this.#consumerState !== 'active') return; + this.#consumerState = 'returned'; + this.#cleanup(); + this.#rejectPendingWrites( + new ERR_INVALID_STATE.TypeError('Stream closed by consumer')); + // If closing, reject the pending end promise + if (this.#writerState === 'closing' && this.#pendingEnd) { + this.#pendingEnd.reject( + new ERR_INVALID_STATE.TypeError('Stream closed by consumer')); + this.#pendingEnd = null; + } + // Resolve pending drains with false - no more data will be consumed + this.#resolvePendingDrains(false); + } + + consumerThrow(error) { + if (this.#consumerState !== 'active') return; + this.#consumerState = 'thrown'; + this.#error = error; + this.#cleanup(); + this.#rejectPendingWrites(error); + // Reject pending drains - the consumer errored + this.#rejectPendingDrains(error); + } + + // =========================================================================== + // Private Methods + // =========================================================================== + + #drain() { + const result = []; + for (let i = 0; i < this.#slots.length; i++) { + const slot = this.#slots.get(i); + for (let j = 0; j < slot.length; j++) { + ArrayPrototypePush(result, slot[j]); + } + } + this.#slots.clear(); + return result; + } + + #resolvePendingReads() { + while (this.#pendingReads.length > 0) { + if (this.#slots.length > 0) { + const pending = this.#pendingReads.shift(); + const result = this.#drain(); + this.#resolvePendingWrites(); + pending.resolve({ __proto__: null, value: result, done: false }); + } else if (this.#writerState === 'closing' && this.#slots.length === 0) { + this.endDrained(); + const pending = this.#pendingReads.shift(); + pending.resolve({ __proto__: null, value: undefined, done: true }); + } else if (this.#writerState === 'closed') { + const pending = this.#pendingReads.shift(); + pending.resolve({ __proto__: null, value: undefined, done: true }); + } else if (this.#writerState === 'errored' && this.#error) { + const pending = this.#pendingReads.shift(); + pending.reject(this.#error); + } else { + break; + } + } + } + + #resolvePendingWrites() { + while (this.#pendingWrites.length > 0 && + this.#slots.length < this.#highWaterMark) { + const pending = this.#pendingWrites.shift(); + this.#slots.push(pending.chunks); + for (let i = 0; i < pending.chunks.length; i++) { + this.#bytesWritten += TypedArrayPrototypeGetByteLength(pending.chunks[i]); + } + pending.resolve(); + } + + if (this.#slots.length < this.#highWaterMark) { + this.#resolvePendingDrains(true); + } + } + + #resolvePendingDrains(canWrite) { + const drains = this.#pendingDrains; + this.#pendingDrains = []; + for (let i = 0; i < drains.length; i++) { + drains[i].resolve(canWrite); + } + } + + #rejectPendingDrains(error) { + const drains = this.#pendingDrains; + this.#pendingDrains = []; + for (let i = 0; i < drains.length; i++) { + drains[i].reject(error); + } + } + + #rejectPendingReads(error) { + while (this.#pendingReads.length > 0) { + this.#pendingReads.shift().reject(error); + } + } + + #rejectPendingWrites(error) { + while (this.#pendingWrites.length > 0) { + this.#pendingWrites.shift().reject(error); + } + } + + #cleanup() { + if (this.#signal && this.#abortHandler) { + this.#signal.removeEventListener('abort', this.#abortHandler); + this.#abortHandler = undefined; + } + } +} + +// ============================================================================= +// PushWriter Implementation +// ============================================================================= + +class PushWriter { + #queue; + + constructor(queue) { + this.#queue = queue; + } + + [drainableProtocol]() { + const desired = this.desiredSize; + if (desired === null) return null; + if (desired > 0) return PromiseResolve(true); + return this.#queue.waitForDrain(); + } + + get desiredSize() { + return this.#queue.desiredSize; + } + + write(chunk, options) { + if (!options?.signal && this.#queue.canWriteSync()) { + const bytes = toUint8Array(chunk); + this.#queue.writeSync([bytes]); + return kResolvedPromise; + } + const bytes = toUint8Array(chunk); + return this.#queue.writeAsync([bytes], options?.signal); + } + + writev(chunks, options) { + if (!ArrayIsArray(chunks)) { + throw new ERR_INVALID_ARG_TYPE('chunks', 'Array', chunks); + } + if (!options?.signal && this.#queue.canWriteSync()) { + const bytes = convertChunks(chunks); + this.#queue.writeSync(bytes); + return kResolvedPromise; + } + const bytes = convertChunks(chunks); + return this.#queue.writeAsync(bytes, options?.signal); + } + + writeSync(chunk) { + const bytes = toUint8Array(chunk); + const result = this.#queue.writeSync([bytes]); + if (!result && this.#queue.backpressurePolicy === 'block') { + // Block policy: force-enqueue and return false as backpressure signal. + // Data IS accepted; false tells caller to slow down. + this.#queue.forceEnqueue([bytes]); + return false; + } + return result; + } + + writevSync(chunks) { + if (!ArrayIsArray(chunks)) { + throw new ERR_INVALID_ARG_TYPE('chunks', 'Array', chunks); + } + const bytes = convertChunks(chunks); + const result = this.#queue.writeSync(bytes); + if (!result && this.#queue.backpressurePolicy === 'block') { + this.#queue.forceEnqueue(bytes); + return false; + } + return result; + } + + end(options) { + const result = this.#queue.end(); + if (result === -2) { + // Errored: reject with stored error + return PromiseReject(this.#queue.error); + } + if (result === -3) { + // Closing: buffer has data, create deferred promise that resolves + // when consumer drains past the end sentinel + const { promise, resolve, reject } = PromiseWithResolvers(); + this.#queue.setPendingEnd({ __proto__: null, promise, resolve, reject }); + return promise; + } + // >= 0: byte count (immediate close or idempotent) + return PromiseResolve(result); + } + + endSync() { + const result = this.#queue.end(); + if (result === -2) return -1; // Errored + if (result === -3) return -1; // Buffer not empty, can't wait + return result; + } + + fail(reason) { + this.#queue.fail(reason); + } + + [SymbolAsyncDispose]() { + const state = this.#queue.writerState; + if (state === 'closing') { + // Wait for graceful drain + return this.#queue.pendingEndPromise ?? PromiseResolve(); + } + if (state === 'open') { + this.fail(); + } + return PromiseResolve(); + } + + [SymbolDispose]() { + this.fail(); + } +} + +// ============================================================================= +// Readable Implementation +// ============================================================================= + +function createReadable(queue) { + return { + __proto__: null, + [SymbolAsyncIterator]() { + return { + __proto__: null, + async next() { + return queue.read(); + }, + async return() { + queue.consumerReturn(); + return { __proto__: null, value: undefined, done: true }; + }, + async throw(error) { + queue.consumerThrow(error); + return { __proto__: null, value: undefined, done: true }; + }, + }; + }, + }; +} + +// ============================================================================= +// Stream.push() Factory +// ============================================================================= + +function parseArgs(args) { + const result = parsePullArgs(args); + // PushQueue constructor requires a non-undefined options object. + if (result.options === undefined) { + result.options = { __proto__: null }; + } + return result; +} + +/** + * Create a push stream with optional transforms. + * @param {...(Function|object)} args - Transforms, then options (optional) + * @returns {{ writer: Writer, readable: AsyncIterable }} + */ +function push(...args) { + const { transforms, options } = parseArgs(args); + + const queue = new PushQueue(options); + const writer = new PushWriter(queue); + const rawReadable = createReadable(queue); + + // Apply transforms lazily if provided + let readable; + if (transforms.length > 0) { + if (options.signal) { + readable = pullWithTransforms( + rawReadable, ...transforms, { __proto__: null, signal: options.signal }); + } else { + readable = pullWithTransforms(rawReadable, ...transforms); + } + } else { + readable = rawReadable; + } + + return { __proto__: null, writer, readable }; +} + +module.exports = { + push, +}; diff --git a/lib/internal/streams/iter/ringbuffer.js b/lib/internal/streams/iter/ringbuffer.js new file mode 100644 index 00000000000000..a05b7825fb86ae --- /dev/null +++ b/lib/internal/streams/iter/ringbuffer.js @@ -0,0 +1,151 @@ +'use strict'; + +// RingBuffer - O(1) FIFO queue with indexed access. +// +// Replaces plain JS arrays that are used as queues with shift()/push(). +// Array.shift() is O(n) because it copies all remaining elements; +// RingBuffer.shift() is O(1) -- it just advances a head pointer. +// +// Also provides O(1) trimFront(count) to replace Array.splice(0, count). +// +// Capacity is always a power of 2, so modulo is replaced with bitwise AND. + +const { + Array, +} = primordials; + +class RingBuffer { + #backing; + #head = 0; + #size = 0; + #mask; + + constructor(initialCapacity = 16) { + this.#mask = initialCapacity - 1; + this.#backing = new Array(initialCapacity); + } + + get length() { + return this.#size; + } + + /** + * Append an item to the tail. O(1) amortized. + */ + push(item) { + if (this.#size > this.#mask) { + this.#grow(); + } + this.#backing[(this.#head + this.#size) & this.#mask] = item; + this.#size++; + } + + /** + * Prepend an item to the head. O(1) amortized. + */ + unshift(item) { + if (this.#size > this.#mask) { + this.#grow(); + } + this.#head = (this.#head - 1 + this.#mask + 1) & this.#mask; + this.#backing[this.#head] = item; + this.#size++; + } + + /** + * Remove and return the item at the head. O(1). + * @returns {any} + */ + shift() { + if (this.#size === 0) return undefined; + const item = this.#backing[this.#head]; + this.#backing[this.#head] = undefined; // Help GC + this.#head = (this.#head + 1) & this.#mask; + this.#size--; + return item; + } + + /** + * Read item at a logical index (0 = head). O(1). + * Returns undefined if index is out of bounds. + * @returns {any} + */ + get(index) { + if (index < 0 || index >= this.#size) return undefined; + return this.#backing[(this.#head + index) & this.#mask]; + } + + /** + * Remove `count` items from the head without returning them. + * O(count) for GC cleanup. + */ + trimFront(count) { + if (count <= 0) return; + if (count >= this.#size) { + this.clear(); + return; + } + for (let i = 0; i < count; i++) { + this.#backing[(this.#head + i) & this.#mask] = undefined; + } + this.#head = (this.#head + count) & this.#mask; + this.#size -= count; + } + + /** + * Find the logical index of `item` (reference equality). O(n). + * Returns -1 if not found. + * @returns {number} + */ + indexOf(item) { + for (let i = 0; i < this.#size; i++) { + if (this.#backing[(this.#head + i) & this.#mask] === item) { + return i; + } + } + return -1; + } + + /** + * Remove the item at logical `index`, shifting later elements. O(n) worst case. + * Used only on rare abort-signal cancellation path. + */ + removeAt(index) { + if (index < 0 || index >= this.#size) return; + for (let i = index; i < this.#size - 1; i++) { + const from = (this.#head + i + 1) & this.#mask; + const to = (this.#head + i) & this.#mask; + this.#backing[to] = this.#backing[from]; + } + const last = (this.#head + this.#size - 1) & this.#mask; + this.#backing[last] = undefined; + this.#size--; + } + + /** + * Remove all items. O(n) for GC cleanup. + */ + clear() { + for (let i = 0; i < this.#size; i++) { + this.#backing[(this.#head + i) & this.#mask] = undefined; + } + this.#head = 0; + this.#size = 0; + } + + /** + * Double the backing capacity, linearizing the circular layout. + */ + #grow() { + const newCapacity = (this.#mask + 1) * 2; + const newBacking = new Array(newCapacity); + for (let i = 0; i < this.#size; i++) { + newBacking[i] = this.#backing[(this.#head + i) & this.#mask]; + } + this.#backing = newBacking; + this.#head = 0; + this.#mask = newCapacity - 1; + } +} + +module.exports = { RingBuffer }; diff --git a/lib/internal/streams/iter/share.js b/lib/internal/streams/iter/share.js new file mode 100644 index 00000000000000..752c0bfcbcab8f --- /dev/null +++ b/lib/internal/streams/iter/share.js @@ -0,0 +1,651 @@ +'use strict'; + +// New Streams API - Share +// +// Pull-model multi-consumer streaming. Shares a single source among +// multiple consumers with explicit buffering. + +const { + ArrayPrototypePush, + PromisePrototypeThen, + PromiseResolve, + PromiseWithResolvers, + SafeSet, + SymbolAsyncIterator, + SymbolDispose, + SymbolIterator, +} = primordials; + +const { + shareProtocol, + shareSyncProtocol, +} = require('internal/streams/iter/types'); + +const { + from, + fromSync, + isAsyncIterable, + isSyncIterable, +} = require('internal/streams/iter/from'); + +const { + pull: pullWithTransforms, + pullSync: pullSyncWithTransforms, +} = require('internal/streams/iter/pull'); + +const { + kMultiConsumerDefaultHWM, + clampHWM, + getMinCursor, + hasProtocol, + onSignalAbort, + wrapError, + parsePullArgs, + validateBackpressure, +} = require('internal/streams/iter/utils'); + +const { + RingBuffer, +} = require('internal/streams/iter/ringbuffer'); + +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_RETURN_VALUE, + ERR_OUT_OF_RANGE, + }, +} = require('internal/errors'); +const { + validateAbortSignal, + validateInteger, + validateObject, +} = require('internal/validators'); + +// ============================================================================= +// Async Share Implementation +// ============================================================================= + +class ShareImpl { + #source; + #options; + #buffer = new RingBuffer(); + #bufferStart = 0; + #consumers = new SafeSet(); + #sourceIterator = null; + #sourceExhausted = false; + #sourceError = null; + #cancelled = false; + #pulling = false; + #pullWaiters = []; + + constructor(source, options) { + this.#source = source; + this.#options = options; + } + + get consumerCount() { + return this.#consumers.size; + } + + get bufferSize() { + return this.#buffer.length; + } + + pull(...args) { + const { transforms, options } = parsePullArgs(args); + const rawConsumer = this.#createRawConsumer(); + + if (transforms.length > 0) { + if (options) { + return pullWithTransforms(rawConsumer, ...transforms, options); + } + return pullWithTransforms(rawConsumer, ...transforms); + } + return rawConsumer; + } + + #createRawConsumer() { + const state = { + __proto__: null, + cursor: this.#bufferStart, + resolve: null, + reject: null, + detached: false, + }; + + this.#consumers.add(state); + const self = this; + + return { + __proto__: null, + [SymbolAsyncIterator]() { + return { + __proto__: null, + async next() { + if (self.#sourceError) { + state.detached = true; + self.#consumers.delete(state); + throw self.#sourceError; + } + + // Loop until we get data, source is exhausted, or + // consumer is detached. Multiple consumers may be woken + // after a single pull - those that find no data at their + // cursor must re-pull rather than terminating prematurely. + for (;;) { + if (state.detached) { + return { __proto__: null, done: true, value: undefined }; + } + + if (self.#cancelled) { + state.detached = true; + self.#consumers.delete(state); + return { __proto__: null, done: true, value: undefined }; + } + + // Check if data is available in buffer + const bufferIndex = state.cursor - self.#bufferStart; + if (bufferIndex < self.#buffer.length) { + const chunk = self.#buffer.get(bufferIndex); + state.cursor++; + self.#tryTrimBuffer(); + return { __proto__: null, done: false, value: chunk }; + } + + if (self.#sourceExhausted) { + state.detached = true; + self.#consumers.delete(state); + if (self.#sourceError) throw self.#sourceError; + return { __proto__: null, done: true, value: undefined }; + } + + // Need to pull from source - check buffer limit + const canPull = await self.#waitForBufferSpace(); + if (!canPull) { + state.detached = true; + self.#consumers.delete(state); + if (self.#sourceError) throw self.#sourceError; + return { __proto__: null, done: true, value: undefined }; + } + + await self.#pullFromSource(); + } + }, + + async return() { + state.detached = true; + state.resolve = null; + state.reject = null; + self.#consumers.delete(state); + self.#tryTrimBuffer(); + return { __proto__: null, done: true, value: undefined }; + }, + + async throw() { + state.detached = true; + state.resolve = null; + state.reject = null; + self.#consumers.delete(state); + self.#tryTrimBuffer(); + return { __proto__: null, done: true, value: undefined }; + }, + }; + }, + }; + } + + cancel(reason) { + if (this.#cancelled) return; + this.#cancelled = true; + + if (reason !== undefined) { + this.#sourceError = reason; + } + + if (this.#sourceIterator?.return) { + PromisePrototypeThen(this.#sourceIterator.return(), undefined, () => {}); + } + + for (const consumer of this.#consumers) { + if (consumer.resolve) { + if (reason !== undefined) { + consumer.reject?.(reason); + } else { + consumer.resolve({ __proto__: null, done: true, value: undefined }); + } + consumer.resolve = null; + consumer.reject = null; + } + consumer.detached = true; + } + this.#consumers.clear(); + + for (let i = 0; i < this.#pullWaiters.length; i++) { + this.#pullWaiters[i](); + } + this.#pullWaiters = []; + } + + [SymbolDispose]() { + this.cancel(); + } + + // Internal methods + + async #waitForBufferSpace() { + while (this.#buffer.length >= this.#options.highWaterMark) { + if (this.#cancelled || this.#sourceError || this.#sourceExhausted) { + return !this.#cancelled; + } + + switch (this.#options.backpressure) { + case 'strict': + throw new ERR_OUT_OF_RANGE( + 'buffer size', `<= ${this.#options.highWaterMark}`, + this.#buffer.length); + case 'block': { + const { promise, resolve } = PromiseWithResolvers(); + ArrayPrototypePush(this.#pullWaiters, resolve); + await promise; + break; + } + case 'drop-oldest': + this.#buffer.shift(); + this.#bufferStart++; + for (const consumer of this.#consumers) { + if (consumer.cursor < this.#bufferStart) { + consumer.cursor = this.#bufferStart; + } + } + return true; + case 'drop-newest': + return true; + } + } + return true; + } + + #pullFromSource() { + if (this.#sourceExhausted || this.#cancelled) { + return PromiseResolve(); + } + + if (this.#pulling) { + const { promise, resolve } = PromiseWithResolvers(); + ArrayPrototypePush(this.#pullWaiters, resolve); + return promise; + } + + this.#pulling = true; + + return (async () => { + try { + if (!this.#sourceIterator) { + if (isAsyncIterable(this.#source)) { + this.#sourceIterator = + this.#source[SymbolAsyncIterator](); + } else if (isSyncIterable(this.#source)) { + const syncIterator = + this.#source[SymbolIterator](); + this.#sourceIterator = { + __proto__: null, + async next() { + return syncIterator.next(); + }, + async return() { + return syncIterator.return?.() ?? + { __proto__: null, done: true, value: undefined }; + }, + }; + } else { + throw new ERR_INVALID_ARG_TYPE( + 'source', ['AsyncIterable', 'Iterable'], this.#source); + } + } + + const result = await this.#sourceIterator.next(); + + if (result.done) { + this.#sourceExhausted = true; + } else { + this.#buffer.push(result.value); + } + } catch (error) { + this.#sourceError = wrapError(error); + this.#sourceExhausted = true; + } finally { + this.#pulling = false; + for (let i = 0; i < this.#pullWaiters.length; i++) { + this.#pullWaiters[i](); + } + this.#pullWaiters = []; + } + })(); + } + + #tryTrimBuffer() { + const minCursor = getMinCursor( + this.#consumers, this.#bufferStart + this.#buffer.length); + const trimCount = minCursor - this.#bufferStart; + if (trimCount > 0) { + this.#buffer.trimFront(trimCount); + this.#bufferStart = minCursor; + for (let i = 0; i < this.#pullWaiters.length; i++) { + this.#pullWaiters[i](); + } + this.#pullWaiters = []; + } + } +} + +// ============================================================================= +// Sync Share Implementation +// ============================================================================= + +class SyncShareImpl { + #source; + #options; + #buffer = new RingBuffer(); + #bufferStart = 0; + #consumers = new SafeSet(); + #sourceIterator = null; + #sourceExhausted = false; + #sourceError = null; + #cancelled = false; + + constructor(source, options) { + this.#source = source; + this.#options = options; + } + + get consumerCount() { + return this.#consumers.size; + } + + get bufferSize() { + return this.#buffer.length; + } + + pull(...transforms) { + const rawConsumer = this.#createRawConsumer(); + + if (transforms.length > 0) { + return pullSyncWithTransforms(rawConsumer, ...transforms); + } + return rawConsumer; + } + + #createRawConsumer() { + const state = { + __proto__: null, + cursor: this.#bufferStart, + detached: false, + }; + + this.#consumers.add(state); + const self = this; + + return { + __proto__: null, + [SymbolIterator]() { + return { + __proto__: null, + next() { + if (state.detached) { + return { __proto__: null, done: true, value: undefined }; + } + if (self.#sourceError) { + state.detached = true; + self.#consumers.delete(state); + throw self.#sourceError; + } + if (self.#cancelled) { + state.detached = true; + self.#consumers.delete(state); + return { __proto__: null, done: true, value: undefined }; + } + + const bufferIndex = state.cursor - self.#bufferStart; + if (bufferIndex < self.#buffer.length) { + const chunk = self.#buffer.get(bufferIndex); + state.cursor++; + self.#tryTrimBuffer(); + return { __proto__: null, done: false, value: chunk }; + } + + if (self.#sourceExhausted) { + state.detached = true; + self.#consumers.delete(state); + return { __proto__: null, done: true, value: undefined }; + } + + // Check buffer limit + if (self.#buffer.length >= self.#options.highWaterMark) { + switch (self.#options.backpressure) { + case 'strict': + throw new ERR_OUT_OF_RANGE( + 'buffer size', `<= ${self.#options.highWaterMark}`, + self.#buffer.length); + case 'block': + throw new ERR_OUT_OF_RANGE( + 'buffer size', `<= ${self.#options.highWaterMark} ` + + '(blocking not available in sync context)', + self.#buffer.length); + case 'drop-oldest': + self.#buffer.shift(); + self.#bufferStart++; + for (const consumer of self.#consumers) { + if (consumer.cursor < self.#bufferStart) { + consumer.cursor = self.#bufferStart; + } + } + break; + case 'drop-newest': + state.detached = true; + self.#consumers.delete(state); + return { __proto__: null, done: true, value: undefined }; + } + } + + self.#pullFromSource(); + + if (self.#sourceError) { + state.detached = true; + self.#consumers.delete(state); + throw self.#sourceError; + } + + const newBufferIndex = state.cursor - self.#bufferStart; + if (newBufferIndex < self.#buffer.length) { + const chunk = self.#buffer.get(newBufferIndex); + state.cursor++; + self.#tryTrimBuffer(); + return { __proto__: null, done: false, value: chunk }; + } + + if (self.#sourceExhausted) { + state.detached = true; + self.#consumers.delete(state); + return { __proto__: null, done: true, value: undefined }; + } + + return { __proto__: null, done: true, value: undefined }; + }, + + return() { + state.detached = true; + self.#consumers.delete(state); + self.#tryTrimBuffer(); + return { __proto__: null, done: true, value: undefined }; + }, + + throw() { + state.detached = true; + self.#consumers.delete(state); + self.#tryTrimBuffer(); + return { __proto__: null, done: true, value: undefined }; + }, + }; + }, + }; + } + + cancel(reason) { + if (this.#cancelled) return; + this.#cancelled = true; + + if (reason !== undefined) { + this.#sourceError = reason; + } + + if (this.#sourceIterator?.return) { + this.#sourceIterator.return(); + } + + for (const consumer of this.#consumers) { + consumer.detached = true; + } + this.#consumers.clear(); + } + + [SymbolDispose]() { + this.cancel(); + } + + #pullFromSource() { + if (this.#sourceExhausted || this.#cancelled) return; + + try { + this.#sourceIterator ||= this.#source[SymbolIterator](); + + const result = this.#sourceIterator.next(); + + if (result.done) { + this.#sourceExhausted = true; + } else { + this.#buffer.push(result.value); + } + } catch (error) { + this.#sourceError = wrapError(error); + this.#sourceExhausted = true; + } + } + + #tryTrimBuffer() { + const minCursor = getMinCursor( + this.#consumers, this.#bufferStart + this.#buffer.length); + const trimCount = minCursor - this.#bufferStart; + if (trimCount > 0) { + this.#buffer.trimFront(trimCount); + this.#bufferStart = minCursor; + } + } +} + +// ============================================================================= +// Public API +// ============================================================================= + +function share(source, options = { __proto__: null }) { + // Normalize source via from() - accepts strings, ArrayBuffers, protocols, etc. + const normalized = from(source); + validateObject(options, 'options'); + const { + highWaterMark = kMultiConsumerDefaultHWM, + backpressure = 'strict', + signal, + } = options; + validateInteger(highWaterMark, 'options.highWaterMark'); + validateBackpressure(backpressure); + if (signal !== undefined) { + validateAbortSignal(signal, 'options.signal'); + } + + const opts = { + __proto__: null, + highWaterMark: clampHWM(highWaterMark), + backpressure, + signal, + }; + + const shareImpl = new ShareImpl(normalized, opts); + + if (signal) { + onSignalAbort(signal, () => shareImpl.cancel()); + } + + return shareImpl; +} + +function shareSync(source, options = { __proto__: null }) { + // Normalize source via fromSync() - accepts strings, ArrayBuffers, protocols, etc. + const normalized = fromSync(source); + validateObject(options, 'options'); + const { + highWaterMark = kMultiConsumerDefaultHWM, + backpressure = 'strict', + } = options; + validateInteger(highWaterMark, 'options.highWaterMark'); + validateBackpressure(backpressure); + + const opts = { + __proto__: null, + highWaterMark: clampHWM(highWaterMark), + backpressure, + }; + + return new SyncShareImpl(normalized, opts); +} + +function isShareable(value) { + return hasProtocol(value, shareProtocol); +} + +function isSyncShareable(value) { + return hasProtocol(value, shareSyncProtocol); +} + +const Share = { + __proto__: null, + from(input, options) { + if (isShareable(input)) { + const result = input[shareProtocol](options); + if (result === null || typeof result !== 'object') { + throw new ERR_INVALID_RETURN_VALUE( + 'an object', '[Symbol.for(\'Stream.shareProtocol\')]', result); + } + return result; + } + if (isAsyncIterable(input) || isSyncIterable(input)) { + return share(input, options); + } + throw new ERR_INVALID_ARG_TYPE( + 'input', ['Shareable', 'AsyncIterable', 'Iterable'], input); + }, +}; + +const SyncShare = { + __proto__: null, + fromSync(input, options) { + if (isSyncShareable(input)) { + const result = input[shareSyncProtocol](options); + if (result === null || typeof result !== 'object') { + throw new ERR_INVALID_RETURN_VALUE( + 'an object', '[Symbol.for(\'Stream.shareSyncProtocol\')]', result); + } + return result; + } + if (isSyncIterable(input)) { + return shareSync(input, options); + } + throw new ERR_INVALID_ARG_TYPE( + 'input', ['SyncShareable', 'Iterable'], input); + }, +}; + +module.exports = { + Share, + SyncShare, + share, + shareSync, +}; diff --git a/lib/internal/streams/iter/transform.js b/lib/internal/streams/iter/transform.js new file mode 100644 index 00000000000000..4cb417ed98ce32 --- /dev/null +++ b/lib/internal/streams/iter/transform.js @@ -0,0 +1,830 @@ +'use strict'; + +// Compression / Decompression Transforms +// +// Creates bare native zlib handles via internalBinding('zlib'), bypassing +// the stream.Transform / ZlibBase / EventEmitter machinery entirely. +// Compression runs on the libuv threadpool via handle.write() (async) so +// I/O and upstream transforms can overlap with compression work. +// Each factory returns a transform descriptor that can be passed to pull(). + +const { + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeShift, + MathMax, + NumberIsNaN, + ObjectEntries, + ObjectKeys, + PromiseWithResolvers, + StringPrototypeStartsWith, + SymbolAsyncIterator, + TypedArrayPrototypeFill, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeSlice, + Uint32Array, +} = primordials; + +const { Buffer } = require('buffer'); +const { + codes: { + ERR_BROTLI_INVALID_PARAM, + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + ERR_ZSTD_INVALID_PARAM, + }, + genericNodeError, +} = require('internal/errors'); +const { lazyDOMException } = require('internal/util'); +const { isArrayBufferView, isAnyArrayBuffer } = require('internal/util/types'); +const { kTrustedTransform } = require('internal/streams/iter/types'); +const { + checkRangesOrGetDefault, + validateFiniteNumber, + validateObject, +} = require('internal/validators'); +const binding = internalBinding('zlib'); +const constants = internalBinding('constants').zlib; + +const { + // Zlib modes + DEFLATE, INFLATE, GZIP, GUNZIP, + BROTLI_ENCODE, BROTLI_DECODE, + ZSTD_COMPRESS, ZSTD_DECOMPRESS, + // Zlib flush + Z_NO_FLUSH, Z_FINISH, + // Zlib defaults + Z_DEFAULT_WINDOWBITS, + Z_DEFAULT_STRATEGY, + // Brotli flush + BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FINISH, + // Zlib ranges + Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, + Z_MIN_LEVEL, Z_MAX_LEVEL, + Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, + Z_FIXED, + // Zstd flush + ZSTD_e_continue, ZSTD_e_end, +} = constants; + +// --------------------------------------------------------------------------- +// Option validation helpers (matching lib/zlib.js validation patterns) +// --------------------------------------------------------------------------- + +// Default output buffer size for compression transforms. Larger than +// Z_DEFAULT_CHUNK (16KB) to reduce the number of threadpool re-entries +// when the engine has more output than fits in one buffer. 64KB matches +// BATCH_HWM and the typical input chunk size from pull(). +const DEFAULT_OUTPUT_SIZE = 64 * 1024; + +// Batch high water mark - yield output in chunks of approximately this size. +const BATCH_HWM = DEFAULT_OUTPUT_SIZE; + +// Pre-allocated empty buffer for flush/finalize calls. +const kEmpty = Buffer.alloc(0); + +function validateChunkSize(options) { + let chunkSize = options.chunkSize; + if (!validateFiniteNumber(chunkSize, 'options.chunkSize')) { + chunkSize = DEFAULT_OUTPUT_SIZE; + } else if (chunkSize < Z_MIN_CHUNK) { + throw new ERR_OUT_OF_RANGE('options.chunkSize', + `>= ${Z_MIN_CHUNK}`, chunkSize); + } + return chunkSize; +} + +function validateDictionary(dictionary) { + if (dictionary === undefined) return undefined; + if (isArrayBufferView(dictionary)) return dictionary; + if (isAnyArrayBuffer(dictionary)) return Buffer.from(dictionary); + throw new ERR_INVALID_ARG_TYPE( + 'options.dictionary', + ['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'], + dictionary); +} + +function validateParams(params, maxParam, errClass) { + if (params === undefined) return; + if (typeof params !== 'object' || params === null) { + throw new ERR_INVALID_ARG_TYPE('options.params', 'Object', params); + } + const keys = ObjectKeys(params); + for (let i = 0; i < keys.length; i++) { + const origKey = keys[i]; + const key = +origKey; + if (NumberIsNaN(key) || key < 0 || key > maxParam) { + throw new errClass(origKey); + } + const value = params[origKey]; + if (typeof value !== 'number' && typeof value !== 'boolean') { + throw new ERR_INVALID_ARG_TYPE('options.params[key]', 'number', value); + } + } +} + +// --------------------------------------------------------------------------- +// Brotli / Zstd parameter arrays (computed once, reused per init call). +// Mirrors the pattern in lib/zlib.js. +// --------------------------------------------------------------------------- +const kMaxBrotliParam = MathMax( + ...ArrayPrototypeMap( + ObjectEntries(constants), + ({ 0: key, 1: value }) => + (StringPrototypeStartsWith(key, 'BROTLI_PARAM_') ? value : 0), + ), +); +const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1); + +const kMaxZstdCParam = MathMax( + ...ArrayPrototypeMap( + ObjectKeys(constants), + (key) => (StringPrototypeStartsWith(key, 'ZSTD_c_') ? constants[key] : 0), + ), +); +const zstdInitCParamsArray = new Uint32Array(kMaxZstdCParam + 1); + +const kMaxZstdDParam = MathMax( + ...ArrayPrototypeMap( + ObjectKeys(constants), + (key) => (StringPrototypeStartsWith(key, 'ZSTD_d_') ? constants[key] : 0), + ), +); +const zstdInitDParamsArray = new Uint32Array(kMaxZstdDParam + 1); + +// --------------------------------------------------------------------------- +// Handle creation - bare native handles, no Transform/EventEmitter overhead. +// +// Each factory accepts a processCallback (called from the threadpool +// completion path in C++) and an onError handler. +// --------------------------------------------------------------------------- + +/** + * Create a bare Zlib handle (gzip, gunzip, deflate, inflate). + * @returns {{ handle: object, writeState: Uint32Array, chunkSize: number }} + */ +function createZlibHandle(mode, options, processCallback, onError) { + // Validate all options before creating the native handle to avoid + // "close before init" assertion if validation throws. + const chunkSize = validateChunkSize(options); + const windowBits = checkRangesOrGetDefault( + options.windowBits, 'options.windowBits', + Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_DEFAULT_WINDOWBITS); + // Default compression level 4 (not Z_DEFAULT_COMPRESSION which maps to + // level 6). Level 4 is ~1.5x faster with only ~5-10% worse compression + // ratio - the sweet spot for streaming and HTTP content-encoding. + const level = checkRangesOrGetDefault( + options.level, 'options.level', + Z_MIN_LEVEL, Z_MAX_LEVEL, 4); + // memLevel 9 uses ~128KB more memory than 8 but provides faster hash + // lookups during compression. Negligible memory cost for the speed gain. + const memLevel = checkRangesOrGetDefault( + options.memLevel, 'options.memLevel', + Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, 9); + const strategy = checkRangesOrGetDefault( + options.strategy, 'options.strategy', + Z_DEFAULT_STRATEGY, Z_FIXED, Z_DEFAULT_STRATEGY); + const dictionary = validateDictionary(options.dictionary); + + const handle = new binding.Zlib(mode); + const writeState = new Uint32Array(2); + + handle.onerror = onError; + handle.init( + windowBits, level, memLevel, strategy, + writeState, processCallback, dictionary, + ); + + return { __proto__: null, handle, writeState, chunkSize }; +} + +/** + * Create a bare Brotli handle. + * @returns {{ handle: object, writeState: Uint32Array, chunkSize: number }} + */ +function createBrotliHandle(mode, options, processCallback, onError) { + // Validate before creating native handle. + const chunkSize = validateChunkSize(options); + const dictionary = validateDictionary(options.dictionary); + validateParams(options.params, kMaxBrotliParam, ERR_BROTLI_INVALID_PARAM); + + const handle = mode === BROTLI_ENCODE ? + new binding.BrotliEncoder(mode) : new binding.BrotliDecoder(mode); + const writeState = new Uint32Array(2); + + TypedArrayPrototypeFill(brotliInitParamsArray, -1); + // Streaming-appropriate defaults: quality 6 (not 11) and lgwin 20 (1MB, + // not 4MB). Quality 11 is intended for offline/build-time compression + // and allocates ~400MB of internal state. Quality 6 is ~10x faster with + // only ~10-15% worse compression ratio - the standard for dynamic HTTP + // content-encoding (nginx, Caddy, Cloudflare all use 4-6). + if (mode === BROTLI_ENCODE) { + brotliInitParamsArray[constants.BROTLI_PARAM_QUALITY] = 6; + brotliInitParamsArray[constants.BROTLI_PARAM_LGWIN] = 20; + } + if (options.params) { + // User-supplied params override the defaults above. + const params = options.params; + const keys = ObjectKeys(params); + for (let i = 0; i < keys.length; i++) { + const key = +keys[i]; + brotliInitParamsArray[key] = params[keys[i]]; + } + } + + handle.onerror = onError; + handle.init( + brotliInitParamsArray, + writeState, + processCallback, + dictionary, + ); + + return { __proto__: null, handle, writeState, chunkSize }; +} + +/** + * Create a bare Zstd handle. + * @returns {{ handle: object, writeState: Uint32Array, chunkSize: number }} + */ +function createZstdHandle(mode, options, processCallback, onError) { + const isCompress = mode === ZSTD_COMPRESS; + + // Validate before creating native handle. + const chunkSize = validateChunkSize(options); + const dictionary = validateDictionary(options.dictionary); + const maxParam = isCompress ? kMaxZstdCParam : kMaxZstdDParam; + validateParams(options.params, maxParam, ERR_ZSTD_INVALID_PARAM); + + const pledgedSrcSize = options.pledgedSrcSize; + if (pledgedSrcSize !== undefined) { + if (typeof pledgedSrcSize !== 'number' || NumberIsNaN(pledgedSrcSize)) { + throw new ERR_INVALID_ARG_TYPE('options.pledgedSrcSize', 'number', + pledgedSrcSize); + } + if (pledgedSrcSize < 0) { + throw new ERR_OUT_OF_RANGE('options.pledgedSrcSize', '>= 0', + pledgedSrcSize); + } + } + + const handle = isCompress ? + new binding.ZstdCompress() : new binding.ZstdDecompress(); + const writeState = new Uint32Array(2); + + const initArray = isCompress ? zstdInitCParamsArray : zstdInitDParamsArray; + TypedArrayPrototypeFill(initArray, -1); + if (options.params) { + const params = options.params; + const keys = ObjectKeys(params); + for (let i = 0; i < keys.length; i++) { + const key = +keys[i]; + initArray[key] = params[keys[i]]; + } + } + + handle.onerror = onError; + handle.init( + initArray, + pledgedSrcSize, + writeState, + processCallback, + dictionary, + ); + + return { __proto__: null, handle, writeState, chunkSize }; +} + +// --------------------------------------------------------------------------- +// Core: makeZlibTransform +// +// Uses async handle.write() so compression runs on the libuv threadpool. +// The generator manually iterates the source with pre-reading: the next +// upstream read+transform is started before awaiting the current compression, +// so I/O and upstream work overlap with threadpool compression. +// --------------------------------------------------------------------------- +function makeZlibTransform(createHandleFn, processFlag, finishFlag) { + return { + __proto__: null, + [kTrustedTransform]: true, + transform: async function*(source, options) { + const { signal } = options; + + // Fail fast if already aborted - don't allocate a native handle. + signal?.throwIfAborted(); + + // ---- Per-invocation state shared with the write callback ---- + let outBuf; + let outOffset = 0; + let chunkSize; + let pending = []; + let pendingBytes = 0; + + // Current write operation state (read by the callback for looping). + let resolveWrite, rejectWrite; + let writeInput, writeFlush; + let writeInOff, writeAvailIn, writeAvailOutBefore; + + // processCallback: called by C++ AfterThreadPoolWork when compression + // on the threadpool completes. Collects output, loops if the engine + // has more output to produce (availOut === 0), then resolves the + // promise when all output for this input chunk is collected. + function onWriteComplete() { + const availOut = writeState[0]; + const availInAfter = writeState[1]; + const have = writeAvailOutBefore - availOut; + const bufferExhausted = availOut === 0 || outOffset + have >= chunkSize; + + if (have > 0) { + if (bufferExhausted && outOffset === 0) { + // Entire buffer filled from start - yield directly, no copy. + ArrayPrototypePush(pending, outBuf); + } else if (bufferExhausted) { + // Tail of buffer filled and buffer is being replaced - + // subarray is safe since outBuf reference is overwritten below. + ArrayPrototypePush(pending, + outBuf.subarray(outOffset, outOffset + have)); + } else { + // Partial fill, buffer will be reused - must copy. + ArrayPrototypePush(pending, + TypedArrayPrototypeSlice(outBuf, + outOffset, + outOffset + have)); + } + pendingBytes += have; + outOffset += have; + } + + // Reallocate output buffer if exhausted. + if (bufferExhausted) { + outBuf = Buffer.allocUnsafe(chunkSize); + outOffset = 0; + } + + if (availOut === 0) { + // Engine has more output - but if aborted, don't loop. + if (!resolveWrite) return; + + const consumed = writeAvailIn - availInAfter; + writeInOff += consumed; + writeAvailIn = availInAfter; + writeAvailOutBefore = chunkSize - outOffset; + + handle.write(writeFlush, + writeInput, writeInOff, writeAvailIn, + outBuf, outOffset, writeAvailOutBefore); + return; // Will call onWriteComplete again. + } + + // All input consumed and output collected. + handle.buffer = null; + const resolve = resolveWrite; + resolveWrite = undefined; + rejectWrite = undefined; + if (resolve) resolve(); + } + + // onError: called by C++ when the engine encounters an error. + // Fires instead of onWriteComplete - reject the promise. + function onError(message, errno, code) { + const error = genericNodeError(message, { __proto__: null, errno, code }); + error.errno = errno; + error.code = code; + const reject = rejectWrite; + resolveWrite = undefined; + rejectWrite = undefined; + if (reject) reject(error); + } + + // ---- Create the handle with our callbacks ---- + const result = createHandleFn(onWriteComplete, onError); + const handle = result.handle; + const writeState = result.writeState; + chunkSize = result.chunkSize; + outBuf = Buffer.allocUnsafe(chunkSize); + + // Abort handler: reject any in-flight threadpool operation so the + // generator doesn't block waiting for compression to finish. + const onAbort = () => { + const reject = rejectWrite; + resolveWrite = undefined; + rejectWrite = undefined; + if (reject) { + reject(signal.reason ?? + lazyDOMException('The operation was aborted', 'AbortError')); + } + }; + signal.addEventListener('abort', onAbort, { __proto__: null, once: true }); + + // Dispatch input to the threadpool and return a promise. + function processInputAsync(input, flushFlag) { + const { promise, resolve, reject } = PromiseWithResolvers(); + resolveWrite = resolve; + rejectWrite = reject; + writeInput = input; + writeFlush = flushFlag; + writeInOff = 0; + writeAvailIn = TypedArrayPrototypeGetByteLength(input); + writeAvailOutBefore = chunkSize - outOffset; + + // Keep input alive while the threadpool references it. + handle.buffer = input; + + handle.write(flushFlag, + input, 0, writeAvailIn, + outBuf, outOffset, writeAvailOutBefore); + return promise; + } + + function drainBatch() { + if (pendingBytes <= BATCH_HWM) { + // Swap instead of splice - avoids copying the array. + const batch = pending; + pending = []; + pendingBytes = 0; + return batch; + } + const batch = []; + let batchBytes = 0; + while (pending.length > 0 && batchBytes < BATCH_HWM) { + const buf = ArrayPrototypeShift(pending); + ArrayPrototypePush(batch, buf); + const len = TypedArrayPrototypeGetByteLength(buf); + batchBytes += len; + pendingBytes -= len; + } + return batch; + } + + let finalized = false; + + const iter = source[SymbolAsyncIterator](); + try { + // Manually iterate the source so we can pre-read: calling + // iter.next() starts the upstream read + transform on libuv + // before we await the current compression on the threadpool. + let nextResult = iter.next(); + + while (true) { + const { value: chunks, done } = await nextResult; + if (done) break; + + signal?.throwIfAborted(); + + if (chunks === null) { + // Flush signal - finalize the engine. + if (!finalized) { + finalized = true; + await processInputAsync(kEmpty, finishFlag); + while (pending.length > 0) { + yield drainBatch(); + } + } + nextResult = iter.next(); + continue; + } + + // Pre-read: start upstream I/O + transform for the NEXT batch + // while we compress the current batch on the threadpool. + nextResult = iter.next(); + + for (let i = 0; i < chunks.length; i++) { + await processInputAsync(chunks[i], processFlag); + } + + if (pendingBytes >= BATCH_HWM) { + while (pending.length > 0 && pendingBytes >= BATCH_HWM) { + yield drainBatch(); + } + } + if (pending.length > 0) { + yield drainBatch(); + } + } + + // Source ended - finalize if not already done by a null signal. + if (!finalized && !signal.aborted) { + finalized = true; + await processInputAsync(kEmpty, finishFlag); + while (pending.length > 0) { + yield drainBatch(); + } + } + } finally { + signal.removeEventListener('abort', onAbort); + handle.close(); + // Close the upstream iterator so its finally blocks run promptly + // rather than waiting for GC. + try { await iter.return?.(); } catch { /* Intentional no-op. */ } + } + }, + }; +} + +// --------------------------------------------------------------------------- +// Compression factories +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// Core: makeZlibTransformSync +// +// Synchronous counterpart to makeZlibTransform. Uses handle.writeSync() +// which runs compression directly on the main thread (no threadpool). +// Returns a stateful sync transform (generator function). +// --------------------------------------------------------------------------- +function makeZlibTransformSync(createHandleFn, processFlag, finishFlag) { + return { + __proto__: null, + transform: function*(source) { + // The processCallback is never called in sync mode, but handle.init() + // requires it. Pass a no-op. + let error = null; + function onError(message, errno, code) { + error = genericNodeError(message, { __proto__: null, errno, code }); + error.errno = errno; + error.code = code; + } + + const result = createHandleFn(() => {}, onError); + const handle = result.handle; + const writeState = result.writeState; + const chunkSize = result.chunkSize; + let outBuf = Buffer.allocUnsafe(chunkSize); + let outOffset = 0; + let pending = []; + let pendingBytes = 0; + + function processSyncInput(input, flushFlag) { + let inOff = 0; + let availIn = TypedArrayPrototypeGetByteLength(input); + let availOutBefore = chunkSize - outOffset; + + handle.writeSync(flushFlag, + input, inOff, availIn, + outBuf, outOffset, availOutBefore); + if (error) throw error; + + while (true) { + const availOut = writeState[0]; + const availInAfter = writeState[1]; + const have = availOutBefore - availOut; + const bufferExhausted = availOut === 0 || + outOffset + have >= chunkSize; + + if (have > 0) { + if (bufferExhausted && outOffset === 0) { + // Entire buffer filled - yield directly, no copy. + ArrayPrototypePush(pending, outBuf); + } else if (bufferExhausted) { + // Tail filled, buffer being replaced - subarray is safe. + ArrayPrototypePush(pending, + outBuf.subarray(outOffset, outOffset + have)); + } else { + // Partial fill, buffer reused - must copy. + ArrayPrototypePush(pending, + TypedArrayPrototypeSlice(outBuf, + outOffset, + outOffset + have)); + } + pendingBytes += have; + outOffset += have; + } + + if (bufferExhausted) { + outBuf = Buffer.allocUnsafe(chunkSize); + outOffset = 0; + } + + if (availOut === 0) { + // Engine has more output - loop. + const consumed = availIn - availInAfter; + inOff += consumed; + availIn = availInAfter; + availOutBefore = chunkSize - outOffset; + + handle.writeSync(flushFlag, + input, inOff, availIn, + outBuf, outOffset, availOutBefore); + if (error) throw error; + continue; + } + + // All input consumed. + break; + } + } + + function drainBatch() { + if (pendingBytes <= BATCH_HWM) { + const batch = pending; + pending = []; + pendingBytes = 0; + return batch; + } + const batch = []; + let batchBytes = 0; + while (pending.length > 0 && batchBytes < BATCH_HWM) { + const buf = ArrayPrototypeShift(pending); + const len = TypedArrayPrototypeGetByteLength(buf); + ArrayPrototypePush(batch, buf); + batchBytes += len; + pendingBytes -= len; + } + return batch; + } + + try { + for (const batch of source) { + if (batch === null) { + // Flush signal - finalize the engine. + processSyncInput(Buffer.alloc(0), finishFlag); + while (pending.length > 0) { + yield drainBatch(); + } + continue; + } + + for (let i = 0; i < batch.length; i++) { + processSyncInput(batch[i], processFlag); + } + + if (pendingBytes >= BATCH_HWM) { + while (pending.length > 0 && pendingBytes >= BATCH_HWM) { + yield drainBatch(); + } + } + if (pending.length > 0) { + yield drainBatch(); + } + } + } finally { + handle.close(); + } + }, + }; +} + +// --------------------------------------------------------------------------- +// Async compression factories +// --------------------------------------------------------------------------- + +const kNullPrototype = { __proto__: null }; + +function compressGzip(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createZlibHandle(GZIP, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function compressDeflate(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createZlibHandle(DEFLATE, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function compressBrotli(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createBrotliHandle(BROTLI_ENCODE, options, cb, onErr), + BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FINISH, + ); +} + +function compressZstd(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createZstdHandle(ZSTD_COMPRESS, options, cb, onErr), + ZSTD_e_continue, ZSTD_e_end, + ); +} + +// --------------------------------------------------------------------------- +// Decompression factories +// --------------------------------------------------------------------------- + +function decompressGzip(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createZlibHandle(GUNZIP, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function decompressDeflate(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createZlibHandle(INFLATE, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function decompressBrotli(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createBrotliHandle(BROTLI_DECODE, options, cb, onErr), + BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FINISH, + ); +} + +function decompressZstd(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransform( + (cb, onErr) => createZstdHandle(ZSTD_DECOMPRESS, options, cb, onErr), + ZSTD_e_continue, ZSTD_e_end, + ); +} + +// --------------------------------------------------------------------------- +// Sync compression factories +// --------------------------------------------------------------------------- + +function compressGzipSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createZlibHandle(GZIP, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function compressDeflateSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createZlibHandle(DEFLATE, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function compressBrotliSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createBrotliHandle(BROTLI_ENCODE, options, cb, onErr), + BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FINISH, + ); +} + +function compressZstdSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createZstdHandle(ZSTD_COMPRESS, options, cb, onErr), + ZSTD_e_continue, ZSTD_e_end, + ); +} + +// --------------------------------------------------------------------------- +// Sync decompression factories +// --------------------------------------------------------------------------- + +function decompressGzipSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createZlibHandle(GUNZIP, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function decompressDeflateSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createZlibHandle(INFLATE, options, cb, onErr), + Z_NO_FLUSH, Z_FINISH, + ); +} + +function decompressBrotliSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createBrotliHandle(BROTLI_DECODE, options, cb, onErr), + BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FINISH, + ); +} + +function decompressZstdSync(options = kNullPrototype) { + validateObject(options, 'options'); + return makeZlibTransformSync( + (cb, onErr) => createZstdHandle(ZSTD_DECOMPRESS, options, cb, onErr), + ZSTD_e_continue, ZSTD_e_end, + ); +} + +module.exports = { + compressBrotli, + compressBrotliSync, + compressDeflate, + compressDeflateSync, + compressGzip, + compressGzipSync, + compressZstd, + compressZstdSync, + decompressBrotli, + decompressBrotliSync, + decompressDeflate, + decompressDeflateSync, + decompressGzip, + decompressGzipSync, + decompressZstd, + decompressZstdSync, +}; diff --git a/lib/internal/streams/iter/types.js b/lib/internal/streams/iter/types.js new file mode 100644 index 00000000000000..c205db00e3782a --- /dev/null +++ b/lib/internal/streams/iter/types.js @@ -0,0 +1,66 @@ +'use strict'; + +const { + Symbol, + SymbolFor, +} = primordials; + +/** + * Symbol for sync value-to-streamable conversion protocol. + * Objects implementing this can be written to streams or yielded + * from generators. Works in both sync and async contexts. + * + * Third-party: [Symbol.for('Stream.toStreamable')]() { ... } + */ +const toStreamable = SymbolFor('Stream.toStreamable'); + +/** + * Symbol for async value-to-streamable conversion protocol. + * Objects implementing this can be written to async streams. + * Works in async contexts only. + * + * Third-party: [Symbol.for('Stream.toAsyncStreamable')]() { ... } + */ +const toAsyncStreamable = SymbolFor('Stream.toAsyncStreamable'); + +/** + * Symbol for Broadcastable protocol - object can provide a Broadcast. + */ +const broadcastProtocol = SymbolFor('Stream.broadcastProtocol'); + +/** + * Symbol for Shareable protocol - object can provide a Share. + */ +const shareProtocol = SymbolFor('Stream.shareProtocol'); + +/** + * Symbol for SyncShareable protocol - object can provide a SyncShare. + */ +const shareSyncProtocol = SymbolFor('Stream.shareSyncProtocol'); + +/** + * Symbol for Drainable protocol - object can signal when backpressure + * clears. Used to bridge event-driven sources that need drain notification. + */ +const drainableProtocol = SymbolFor('Stream.drainableProtocol'); + +/** + * Internal sentinel for trusted stateful transforms. A transform object + * with [kTrustedTransform] = true signals that: + * 1. It handles source exhaustion (done) internally - no withFlushAsync + * wrapper needed. + * 2. It always yields valid Uint8Array[] batches - no isUint8ArrayBatch + * validation needed on each yield. + * This is NOT a public protocol symbol - it uses Symbol() not Symbol.for(). + */ +const kTrustedTransform = Symbol('kTrustedTransform'); + +module.exports = { + broadcastProtocol, + drainableProtocol, + kTrustedTransform, + shareProtocol, + shareSyncProtocol, + toAsyncStreamable, + toStreamable, +}; diff --git a/lib/internal/streams/iter/utils.js b/lib/internal/streams/iter/utils.js new file mode 100644 index 00000000000000..2a156d2828a2e0 --- /dev/null +++ b/lib/internal/streams/iter/utils.js @@ -0,0 +1,291 @@ +'use strict'; + +const { + Array, + ArrayBufferPrototypeGetByteLength, + ArrayPrototypeSlice, + MathMax, + MathMin, + NumberMAX_SAFE_INTEGER, + PromiseResolve, + String, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetByteOffset, + Uint8Array, +} = primordials; + +const { TextEncoder } = require('internal/encoding'); +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_OPERATION_FAILED, + }, +} = require('internal/errors'); +const { isError } = require('internal/util'); + +const { Buffer } = require('buffer'); + +const { isSharedArrayBuffer, isUint8Array } = require('internal/util/types'); + +const { validateOneOf } = require('internal/validators'); + +// Cached resolved promise to avoid allocating a new one on every sync fast-path. +const kResolvedPromise = PromiseResolve(); + +// Shared TextEncoder instance for string conversion. +const encoder = new TextEncoder(); + +// Default high water marks for push and multi-consumer streams. These values +// are somewhat arbitrary but have been tested across various workloads and +// appear to yield the best overall throughput/latency balance. + +/** Default high water mark for push streams (single-consumer). */ +const kPushDefaultHWM = 4; + +/** Default high water mark for broadcast and share streams (multi-consumer). */ +const kMultiConsumerDefaultHWM = 16; + +/** + * Clamp a high water mark to [1, MAX_SAFE_INTEGER]. + * @param {number} value + * @returns {number} + */ +function clampHWM(value) { + return MathMax(1, MathMin(NumberMAX_SAFE_INTEGER, value)); +} + +/** + * Register a handler for an AbortSignal, handling the already-aborted case. + * If the signal is already aborted, calls handler immediately. + * Otherwise, adds a one-time 'abort' listener. + * @param {AbortSignal} signal + * @param {Function} handler + */ +function onSignalAbort(signal, handler) { + if (signal.aborted) { + handler(); + } else { + signal.addEventListener('abort', handler, { __proto__: null, once: true }); + } +} + +/** + * Compute the minimum cursor across a set of consumers. + * Returns fallback if the set is empty. + * @param {Set} consumers - Set of objects with a `cursor` property + * @param {number} fallback - Value to return when set is empty + * @returns {number} + */ +function getMinCursor(consumers, fallback) { + let min = Infinity; + for (const consumer of consumers) { + if (consumer.cursor < min) { + min = consumer.cursor; + } + } + return min === Infinity ? fallback : min; +} + +/** + * Convert a chunk (string or Uint8Array) to Uint8Array. + * Strings are UTF-8 encoded. + * @param {Uint8Array|string} chunk + * @returns {Uint8Array} + */ +function toUint8Array(chunk) { + if (typeof chunk === 'string') { + return encoder.encode(chunk); + } + if (!isUint8Array(chunk)) { + throw new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Uint8Array'], chunk); + } + return chunk; +} + +/** + * Check if all chunks in an array are already Uint8Array (no strings). + * Short-circuits on the first string found. + * @param {Array} chunks + * @returns {boolean} + */ +function allUint8Array(chunks) { + // Ok, well, kind of. This is more a check for "no strings"... + for (let i = 0; i < chunks.length; i++) { + if (typeof chunks[i] === 'string') return false; + } + return true; +} + +/** + * Concatenate multiple Uint8Arrays into a single Uint8Array. + * @param {Uint8Array[]} chunks + * @returns {Uint8Array} + */ +function concatBytes(chunks) { + // Empty stream: return zero-length Uint8Array + if (chunks.length === 0) { + return new Uint8Array(0); + } + // Single chunk: return directly if it covers the entire backing buffer + if (chunks.length === 1) { + const chunk = chunks[0]; + const buf = TypedArrayPrototypeGetBuffer(chunk); + // SharedArrayBuffer is not available in primordials, so use + // direct property access for its byteLength. + const bufByteLength = isSharedArrayBuffer(buf) ? + buf.byteLength : + ArrayBufferPrototypeGetByteLength(buf); + if (TypedArrayPrototypeGetByteOffset(chunk) === 0 && + TypedArrayPrototypeGetByteLength(chunk) === bufByteLength) { + return chunk; + } + } + // Multiple chunks or shared buffer: concatenate + const buf = Buffer.concat(chunks); + return new Uint8Array( + TypedArrayPrototypeGetBuffer(buf), + TypedArrayPrototypeGetByteOffset(buf), + TypedArrayPrototypeGetByteLength(buf)); +} + +/** + * Convert an array of chunks (strings or Uint8Arrays) to a Uint8Array[]. + * Always returns a fresh copy of the array. + * @param {Array} chunks + * @returns {Uint8Array[]} + */ +function convertChunks(chunks) { + if (allUint8Array(chunks)) { + return ArrayPrototypeSlice(chunks); + } + const len = chunks.length; + const result = new Array(len); + for (let i = 0; i < len; i++) { + result[i] = toUint8Array(chunks[i]); + } + return result; +} + +/** + * Wrap a caught value as an Error, converting non-Error values. + * @param {unknown} error + * @returns {Error} + */ +function wrapError(error) { + return isError(error) ? error : new ERR_OPERATION_FAILED(String(error)); +} + +/** + * Check if a value implements a Symbol-keyed protocol (has a function + * at the given symbol key). + * @param {unknown} value + * @param {symbol} symbol + * @returns {boolean} + */ +function hasProtocol(value, symbol) { + return ( + value !== null && + typeof value === 'object' && + symbol in value && + typeof value[symbol] === 'function' + ); +} + +/** + * Check if a value is PullOptions (object without transform or write property). + * @param {unknown} value + * @returns {boolean} + */ +function isPullOptions(value) { + return ( + value !== null && + typeof value === 'object' && + !('transform' in value) && + !('write' in value) + ); +} + +/** + * Check if a value is a stateful transform object (has a transform method). + * @param {unknown} value + * @returns {boolean} + */ +function isTransformObject(value) { + return typeof value?.transform === 'function'; +} + +/** + * Check if a value is a valid transform (function or transform object). + * @param {unknown} value + * @returns {boolean} + */ +function isTransform(value) { + return typeof value === 'function' || isTransformObject(value); +} + +/** + * Parse variadic arguments for pull/pullSync. + * Returns { transforms, options } + * @param {Array} args + * @returns {{ transforms: Array, options: object|undefined }} + */ +function parsePullArgs(args) { + if (args.length === 0) { + return { __proto__: null, transforms: [], options: undefined }; + } + + let transforms; + let options; + const last = args[args.length - 1]; + if (isPullOptions(last)) { + transforms = ArrayPrototypeSlice(args, 0, -1); + options = last; + } else { + transforms = args; + options = undefined; + } + + for (let i = 0; i < transforms.length; i++) { + if (!isTransform(transforms[i])) { + throw new ERR_INVALID_ARG_TYPE( + `transforms[${i}]`, ['Function', 'Object with transform()'], + transforms[i]); + } + } + + return { __proto__: null, transforms, options }; +} + +/** + * Validate backpressure option value. + * @param {string} value + */ +function validateBackpressure(value) { + validateOneOf(value, 'options.backpressure', [ + 'strict', + 'block', + 'drop-oldest', + 'drop-newest', + ]); +} + +module.exports = { + kMultiConsumerDefaultHWM, + kPushDefaultHWM, + kResolvedPromise, + allUint8Array, + clampHWM, + concatBytes, + convertChunks, + getMinCursor, + hasProtocol, + isPullOptions, + isTransform, + isTransformObject, + onSignalAbort, + parsePullArgs, + toUint8Array, + validateBackpressure, + wrapError, +}; diff --git a/lib/internal/streams/pipeline.js b/lib/internal/streams/pipeline.js index ad3f0796875aae..546ff579d8ebbd 100644 --- a/lib/internal/streams/pipeline.js +++ b/lib/internal/streams/pipeline.js @@ -227,7 +227,7 @@ function pipelineImpl(streams, callback, opts) { } function finishImpl(err, final) { - if (err && (!error || error.code === 'ERR_STREAM_PREMATURE_CLOSE')) { + if (err && (!error || error.code === 'ERR_STREAM_PREMATURE_CLOSE' || error.name === 'AbortError')) { error = err; } diff --git a/lib/internal/streams/writable.js b/lib/internal/streams/writable.js index a9beecee156ee6..e934c38bbf9acc 100644 --- a/lib/internal/streams/writable.js +++ b/lib/internal/streams/writable.js @@ -347,7 +347,7 @@ function WritableState(options, stream, isDuplex) { this.corked = 0; // The callback that's passed to _write(chunk, cb). - this.onwrite = onwrite.bind(undefined, stream); + this.onwrite = (er) => onwrite(stream, er); // The amount that is being written when _write is called. this.writelen = 0; diff --git a/lib/internal/test_runner/mock/loader.js b/lib/internal/test_runner/mock/loader.js index a7a22539be3093..d64ffdfa096559 100644 --- a/lib/internal/test_runner/mock/loader.js +++ b/lib/internal/test_runner/mock/loader.js @@ -113,10 +113,10 @@ function defaultExportSource(useESM, hasDefaultExport) { if (!hasDefaultExport) { return ''; } else if (useESM) { - return 'export default $__exports.defaultExport;'; + return 'export default $__exports.moduleExports.default;'; } - return 'module.exports = $__exports.defaultExport;'; + return 'module.exports = $__exports.moduleExports.default;'; } function namedExportsSource(useESM, exportNames) { @@ -134,9 +134,9 @@ if (module.exports === null || typeof module.exports !== 'object') { const name = exportNames[i]; if (useESM) { - source += `export let ${name} = $__exports.namedExports[${JSONStringify(name)}];\n`; + source += `export let ${name} = $__exports.moduleExports[${JSONStringify(name)}];\n`; } else { - source += `module.exports[${JSONStringify(name)}] = $__exports.namedExports[${JSONStringify(name)}];\n`; + source += `module.exports[${JSONStringify(name)}] = $__exports.moduleExports[${JSONStringify(name)}];\n`; } } diff --git a/lib/internal/test_runner/mock/mock.js b/lib/internal/test_runner/mock/mock.js index 1af24c77a10731..fb1ed322b414fc 100644 --- a/lib/internal/test_runner/mock/mock.js +++ b/lib/internal/test_runner/mock/mock.js @@ -1,10 +1,12 @@ 'use strict'; const { + ArrayPrototypeFilter, ArrayPrototypePush, ArrayPrototypeSlice, Error, FunctionPrototypeBind, FunctionPrototypeCall, + ObjectAssign, ObjectDefineProperty, ObjectGetOwnPropertyDescriptor, ObjectGetPrototypeOf, @@ -33,6 +35,7 @@ const { URLParse, } = require('internal/url'); const { + deprecateProperty, emitExperimentalWarning, getStructuredStack, kEmptyObject, @@ -61,6 +64,14 @@ const kSupportedFormats = [ 'module', ]; let sharedModuleState; +const deprecateNamedExports = deprecateProperty( + 'namedExports', + 'mock.module(): options.namedExports is deprecated. Use options.exports instead.', +); +const deprecateDefaultExport = deprecateProperty( + 'defaultExport', + 'mock.module(): options.defaultExport is deprecated. Use options.exports.default instead.', +); const { hooks: mockHooks, mocks, @@ -185,20 +196,16 @@ class MockModuleContext { baseURL, cache, caller, - defaultExport, format, fullPath, - hasDefaultExport, - namedExports, + moduleExports, sharedState, specifier, }) { const config = { __proto__: null, cache, - defaultExport, - hasDefaultExport, - namedExports, + moduleExports, caller, }; @@ -230,8 +237,8 @@ class MockModuleContext { __proto__: null, url: baseURL, cache, - exportNames: ObjectKeys(namedExports), - hasDefaultExport, + exportNames: ArrayPrototypeFilter(ObjectKeys(moduleExports), (k) => k !== 'default'), + hasDefaultExport: 'default' in moduleExports, format, localVersion, active: true, @@ -241,8 +248,7 @@ class MockModuleContext { delete Module._cache[fullPath]; sharedState.mockExports.set(baseURL, { __proto__: null, - defaultExport, - namedExports, + moduleExports, }); } @@ -627,14 +633,9 @@ class MockTracker { debug('module mock entry, specifier = "%s", options = %o', specifier, options); const { - cache = false, - namedExports = kEmptyObject, - defaultExport, - } = options; - const hasDefaultExport = 'defaultExport' in options; - - validateBoolean(cache, 'options.cache'); - validateObject(namedExports, 'options.namedExports'); + cache, + moduleExports, + } = normalizeModuleMockOptions(options); const sharedState = setupSharedModuleState(); const mockSpecifier = StringPrototypeStartsWith(specifier, 'node:') ? @@ -673,11 +674,9 @@ class MockTracker { baseURL: baseURL.href, cache, caller, - defaultExport, format, fullPath, - hasDefaultExport, - namedExports, + moduleExports, sharedState, specifier: mockSpecifier, }); @@ -816,6 +815,73 @@ class MockTracker { } } +function normalizeModuleMockOptions(options) { + const { cache = false } = options; + validateBoolean(cache, 'options.cache'); + + const hasExports = 'exports' in options; + const hasNamedExports = 'namedExports' in options; + const hasDefaultExport = 'defaultExport' in options; + + deprecateNamedExports(options); + deprecateDefaultExport(options); + + const moduleExports = { __proto__: null }; + + if (hasExports) { + validateObject(options.exports, 'options.exports'); + } + + if (hasNamedExports) { + validateObject(options.namedExports, 'options.namedExports'); + } + + if (hasExports && (hasNamedExports || hasDefaultExport)) { + let reason = "cannot be used with 'options.namedExports'"; + + if (hasDefaultExport) { + reason = hasNamedExports ? + "cannot be used with 'options.namedExports' or 'options.defaultExport'" : + "cannot be used with 'options.defaultExport'"; + } + + throw new ERR_INVALID_ARG_VALUE('options.exports', options.exports, reason); + } + + if (hasExports) { + copyOwnProperties(options.exports, moduleExports); + } + + if (hasNamedExports) { + copyOwnProperties(options.namedExports, moduleExports); + } + + if (hasDefaultExport) { + ObjectDefineProperty( + moduleExports, + 'default', + ObjectAssign({ __proto__: null }, ObjectGetOwnPropertyDescriptor(options, 'defaultExport')), + ); + } + + return { + __proto__: null, + cache, + moduleExports, + }; +} + + +function copyOwnProperties(from, to) { + const keys = ObjectKeys(from); + + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const descriptor = ObjectGetOwnPropertyDescriptor(from, key); + ObjectDefineProperty(to, key, descriptor); + } +} + function setupSharedModuleState() { if (sharedModuleState === undefined) { const { mock } = require('test'); @@ -855,9 +921,7 @@ function cjsMockModuleLoad(request, parent, isMain) { const { cache, caller, - defaultExport, - hasDefaultExport, - namedExports, + moduleExports, } = config; if (cache && Module._cache[resolved]) { @@ -866,9 +930,10 @@ function cjsMockModuleLoad(request, parent, isMain) { return Module._cache[resolved].exports; } + const hasDefaultExport = 'default' in moduleExports; // eslint-disable-next-line node-core/set-proto-to-null-in-object - const modExports = hasDefaultExport ? defaultExport : {}; - const exportNames = ObjectKeys(namedExports); + const modExports = hasDefaultExport ? moduleExports.default : {}; + const exportNames = ArrayPrototypeFilter(ObjectKeys(moduleExports), (k) => k !== 'default'); if ((typeof modExports !== 'object' || modExports === null) && exportNames.length > 0) { @@ -878,7 +943,7 @@ function cjsMockModuleLoad(request, parent, isMain) { for (let i = 0; i < exportNames.length; ++i) { const name = exportNames[i]; - const descriptor = ObjectGetOwnPropertyDescriptor(namedExports, name); + const descriptor = ObjectGetOwnPropertyDescriptor(moduleExports, name); ObjectDefineProperty(modExports, name, descriptor); } diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index 7cabb3e7525195..0fcf0bdab644d2 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -149,7 +149,7 @@ function stopTest(timeout, signal) { disposeFunction = () => { abortListener[SymbolDispose](); - timer[SymbolDispose](); + clearTimeout(timer); }; } diff --git a/lib/internal/test_runner/utils.js b/lib/internal/test_runner/utils.js index 5b53342933cdcb..db7e2ee50dd8d6 100644 --- a/lib/internal/test_runner/utils.js +++ b/lib/internal/test_runner/utils.js @@ -384,6 +384,9 @@ function countCompletedTest(test, harness = test.root.harness) { } if (test.reportedType === 'suite') { harness.counters.suites++; + if (!test.passed) { + harness.success = false; + } return; } // Check SKIP and TODO tests first, as those should not be counted as diff --git a/lib/repl.js b/lib/repl.js index 12f8074c78e524..60ea4457ab1bf6 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -73,7 +73,6 @@ const { RegExpPrototypeExec, SafePromiseRace, SafeSet, - SafeWeakSet, StringPrototypeCharAt, StringPrototypeEndsWith, StringPrototypeIncludes, @@ -115,7 +114,7 @@ const { const { Console } = require('console'); const { shouldColorize } = require('internal/util/colors'); const CJSModule = require('internal/modules/cjs/loader').Module; -const domain = require('domain'); +const { AsyncLocalStorage } = require('async_hooks'); let debug = require('internal/util/debuglog').debuglog('repl', (fn) => { debug = fn; }); @@ -123,8 +122,10 @@ const { ErrorPrepareStackTrace, codes: { ERR_CANNOT_WATCH_SIGINT, + ERR_INVALID_ARG_VALUE, ERR_INVALID_REPL_EVAL_CONFIG, ERR_INVALID_REPL_INPUT, + ERR_INVALID_STATE, ERR_MISSING_ARGS, ERR_SCRIPT_EXECUTION_INTERRUPTED, }, @@ -178,22 +179,47 @@ const { let processTopLevelAwait; const parentModule = module; -const domainSet = new SafeWeakSet(); + +// AsyncLocalStorage to track which REPL instance owns the current async context +// This replaces the domain-based tracking for error handling +const replContext = new AsyncLocalStorage(); +let exceptionCaptureSetup = false; + +/** + * Sets up the uncaught exception capture callback to route errors + * to the appropriate REPL instance. This replaces domain-based error handling. + * Uses addUncaughtExceptionCaptureCallback to coexist with the primary + * callback (e.g., domain module). + */ +function setupExceptionCapture() { + if (exceptionCaptureSetup) return; + + process.addUncaughtExceptionCaptureCallback((err) => { + const store = replContext.getStore(); + if (store?.replServer) { + const result = store.replServer._handleError(err); + return result !== 'unhandled'; // We handled it + } + // No active REPL context - let other handlers try + }); + + exceptionCaptureSetup = true; +} const kBufferedCommandSymbol = Symbol('bufferedCommand'); const kLoadingSymbol = Symbol('loading'); function processNewListener(event, listener) { - if (event === 'uncaughtException' && - process.domain && - listener.name !== 'domainUncaughtExceptionClear' && - domainSet.has(process.domain)) { - // Throw an error so that the event will not be added and the current - // domain takes over. That way the user is notified about the error - // and the current code evaluation is stopped, just as any other code - // that contains an error. - throw new ERR_INVALID_REPL_INPUT( - 'Listeners for `uncaughtException` cannot be used in the REPL'); + if (event === 'uncaughtException') { + const store = replContext.getStore(); + if (store?.replServer) { + // Throw an error so that the event will not be added and the + // current REPL handles it. That way the user is notified about + // the error and the current code evaluation is stopped, just as + // any other code that contains an error. + throw new ERR_INVALID_REPL_INPUT( + 'Listeners for `uncaughtException` cannot be used in the REPL'); + } } } @@ -341,7 +367,13 @@ class REPLServer extends Interface { this.allowBlockingCompletions = !!options.allowBlockingCompletions; this.useColors = !!options.useColors; - this._domain = options.domain || domain.create(); + this._isStandalone = !!options[kStandaloneREPL]; + + if (options.domain !== undefined) { + throw new ERR_INVALID_ARG_VALUE('options.domain', options.domain, + 'is no longer supported'); + } + this.useGlobal = !!useGlobal; this.ignoreUndefined = !!ignoreUndefined; this.replMode = replMode || module.exports.REPL_MODE_SLOPPY; @@ -351,6 +383,7 @@ class REPLServer extends Interface { this.lastError = undefined; this.breakEvalOnSigint = !!options.breakEvalOnSigint; this.editorMode = false; + this._userErrorHandler = options.handleError; // Context id for use with the inspector protocol. this[kContextId] = undefined; this[kLastCommandErrored] = false; @@ -370,7 +403,8 @@ class REPLServer extends Interface { this.once('exit', removeProcessNewListener); } - domainSet.add(this._domain); + // Set up exception capture for async error handling + setupExceptionCapture(); const savedRegExMatches = ['', '', '', '', '', '', '', '', '', '']; const sep = '\u0000\u0000\u0000'; @@ -591,13 +625,16 @@ class REPLServer extends Interface { } } catch (e) { err = e; - - if (process.domain) { - debug('not recoverable, send to domain'); + // If there's an active domain with error listeners, let it handle the error + if (process.domain?.listenerCount('error') > 0) { + debug('domain handling error'); process.domain.emit('error', err); - process.domain.exit(); return; } + // Handle non-recoverable errors directly + debug('not recoverable, handle error'); + self._handleError(err); + return; } if (awaitPromise && !err) { @@ -623,13 +660,15 @@ class REPLServer extends Interface { const result = (await promise)?.value; finishExecution(null, result); } catch (err) { - if (err && process.domain) { - debug('not recoverable, send to domain'); + // If there's an active domain with error listeners, let it handle the error + if (process.domain?.listenerCount('error') > 0) { + debug('domain handling async error'); process.domain.emit('error', err); - process.domain.exit(); - return; + } else { + // Handle non-recoverable async errors directly + debug('not recoverable, handle error'); + self._handleError(err); } - finishExecution(err); } finally { // Remove prioritized SIGINT listener if it was not called. prioritizedSigintQueue.delete(sigintListener); @@ -644,124 +683,16 @@ class REPLServer extends Interface { } } - self.eval = self._domain.bind(eval_); - - self._domain.on('error', function debugDomainError(e) { - debug('domain error'); - let errStack = ''; - - if (typeof e === 'object' && e !== null) { - overrideStackTrace.set(e, (error, stackFrames) => { - let frames; - if (typeof stackFrames === 'object') { - // Search from the bottom of the call stack to - // find the first frame with a null function name - const idx = ArrayPrototypeFindLastIndex( - stackFrames, - (frame) => frame.getFunctionName() === null, - ); - // If found, get rid of it and everything below it - frames = ArrayPrototypeSlice(stackFrames, 0, idx); - } else { - frames = stackFrames; - } - // FIXME(devsnek): this is inconsistent with the checks - // that the real prepareStackTrace dispatch uses in - // lib/internal/errors.js. - if (typeof MainContextError.prepareStackTrace === 'function') { - return MainContextError.prepareStackTrace(error, frames); - } - return ErrorPrepareStackTrace(error, frames); - }); - decorateErrorStack(e); - - if (e.domainThrown) { - delete e.domain; - delete e.domainThrown; - } - - if (isError(e)) { - if (e.stack) { - if (e.name === 'SyntaxError') { - // Remove stack trace. - e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( - /^\s+at\s.*\n?/gm, - SideEffectFreeRegExpPrototypeSymbolReplace(/^REPL\d+:\d+\r?\n/, e.stack, ''), - ''); - const importErrorStr = 'Cannot use import statement outside a ' + - 'module'; - if (StringPrototypeIncludes(e.message, importErrorStr)) { - e.message = 'Cannot use import statement inside the Node.js ' + - 'REPL, alternatively use dynamic import: ' + toDynamicImport(ArrayPrototypeAt(self.lines, -1)); - e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( - /SyntaxError:.*\n/, - e.stack, - `SyntaxError: ${e.message}\n`); - } - } else if (self.replMode === module.exports.REPL_MODE_STRICT) { - e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( - /(\s+at\s+REPL\d+:)(\d+)/, - e.stack, - (_, pre, line) => pre + (line - 1), - ); - } - } - errStack = self.writer(e); - - // Remove one line error braces to keep the old style in place. - if (errStack[0] === '[' && errStack[errStack.length - 1] === ']') { - errStack = StringPrototypeSlice(errStack, 1, -1); - } - } - } - - if (!self.underscoreErrAssigned) { - self.lastError = e; - } - - if (options[kStandaloneREPL] && - process.listenerCount('uncaughtException') !== 0) { - process.nextTick(() => { - process.emit('uncaughtException', e); - self.clearBufferedCommand(); - self.lines.level = []; - if (!self.closed) { - self.displayPrompt(); - } - }); - } else { - if (errStack === '') { - errStack = self.writer(e); - } - const lines = SideEffectFreeRegExpPrototypeSymbolSplit(/(?<=\n)/, errStack); - let matched = false; - - errStack = ''; - ArrayPrototypeForEach(lines, (line) => { - if (!matched && - RegExpPrototypeExec(/^\[?([A-Z][a-z0-9_]*)*Error/, line) !== null) { - errStack += writer.options.breakLength >= line.length ? - `Uncaught ${line}` : - `Uncaught:\n${line}`; - matched = true; - } else { - errStack += line; - } - }); - if (!matched) { - const ln = lines.length === 1 ? ' ' : ':\n'; - errStack = `Uncaught${ln}${errStack}`; - } - // Normalize line endings. - errStack += StringPrototypeEndsWith(errStack, '\n') ? '' : '\n'; - self.output.write(errStack); - self.clearBufferedCommand(); - self.lines.level = []; - if (!self.closed) { - self.displayPrompt(); - } - } - }); + // Wrap eval to run within the REPL's async context for error tracking. + // The function names are needed for stack trace filtering - they must not + // be anonymous, but we can't use 'eval' as a name since it's reserved. + const originalEval = eval_; + // eslint-disable-next-line func-name-matching + self.eval = function REPLEval(code, context, file, cb) { + replContext.run({ replServer: self }, function REPLEvalInContext() { + originalEval(code, context, file, cb); + }); + }; self.clearBufferedCommand(); @@ -925,7 +856,7 @@ class REPLServer extends Interface { } if (e) { - self._domain.emit('error', e.err || e); + self._handleError(e.err || e); self[kLastCommandErrored] = true; } @@ -1051,6 +982,131 @@ class REPLServer extends Interface { clearBufferedCommand() { this[kBufferedCommandSymbol] = ''; } + _handleError(e) { + debug('handle error'); + if (this._userErrorHandler) { + const state = this._userErrorHandler(e); + if (state !== 'ignore' && state !== 'print' && state !== 'unhandled') { + throw new ERR_INVALID_STATE( + 'External REPL error handler must return either "ignore", "print"' + + `, or "unhandled", but received: ${state}`); + } + if (state === 'ignore') { + return; + } + if (state === 'unhandled') { + return 'unhandled'; + } + } + let errStack = ''; + + if (typeof e === 'object' && e !== null) { + overrideStackTrace.set(e, (error, stackFrames) => { + let frames; + if (typeof stackFrames === 'object') { + // Search from the bottom of the call stack to + // find the first frame with a null function name + const idx = ArrayPrototypeFindLastIndex( + stackFrames, + (frame) => frame.getFunctionName() === null, + ); + // If found, get rid of it and everything below it + frames = ArrayPrototypeSlice(stackFrames, 0, idx); + } else { + frames = stackFrames; + } + // FIXME(devsnek): this is inconsistent with the checks + // that the real prepareStackTrace dispatch uses in + // lib/internal/errors.js. + if (typeof MainContextError.prepareStackTrace === 'function') { + return MainContextError.prepareStackTrace(error, frames); + } + return ErrorPrepareStackTrace(error, frames); + }); + decorateErrorStack(e); + + if (isError(e)) { + if (e.stack) { + if (e.name === 'SyntaxError') { + // Remove stack trace. + e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( + /^\s+at\s.*\n?/gm, + SideEffectFreeRegExpPrototypeSymbolReplace(/^REPL\d+:\d+\r?\n/, e.stack, ''), + ''); + const importErrorStr = 'Cannot use import statement outside a ' + + 'module'; + if (StringPrototypeIncludes(e.message, importErrorStr)) { + e.message = 'Cannot use import statement inside the Node.js ' + + 'REPL, alternatively use dynamic import: ' + toDynamicImport(ArrayPrototypeAt(this.lines, -1)); + e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( + /SyntaxError:.*\n/, + e.stack, + `SyntaxError: ${e.message}\n`); + } + } else if (this.replMode === module.exports.REPL_MODE_STRICT) { + e.stack = SideEffectFreeRegExpPrototypeSymbolReplace( + /(\s+at\s+REPL\d+:)(\d+)/, + e.stack, + (_, pre, line) => pre + (line - 1), + ); + } + } + errStack = this.writer(e); + + // Remove one line error braces to keep the old style in place. + if (errStack[0] === '[' && errStack[errStack.length - 1] === ']') { + errStack = StringPrototypeSlice(errStack, 1, -1); + } + } + } + + if (!this.underscoreErrAssigned) { + this.lastError = e; + } + + if (this._isStandalone && + process.listenerCount('uncaughtException') !== 0) { + process.nextTick(() => { + process.emit('uncaughtException', e); + this.clearBufferedCommand(); + this.lines.level = []; + if (!this.closed) { + this.displayPrompt(); + } + }); + } else { + if (errStack === '') { + errStack = this.writer(e); + } + const lines = SideEffectFreeRegExpPrototypeSymbolSplit(/(?<=\n)/, errStack); + let matched = false; + + errStack = ''; + ArrayPrototypeForEach(lines, (line) => { + if (!matched && + RegExpPrototypeExec(/^\[?([A-Z][a-z0-9_]*)*Error/, line) !== null) { + errStack += writer.options.breakLength >= line.length ? + `Uncaught ${line}` : + `Uncaught:\n${line}`; + matched = true; + } else { + errStack += line; + } + }); + if (!matched) { + const ln = lines.length === 1 ? ' ' : ':\n'; + errStack = `Uncaught${ln}${errStack}`; + } + // Normalize line endings. + errStack += StringPrototypeEndsWith(errStack, '\n') ? '' : '\n'; + this.output.write(errStack); + this.clearBufferedCommand(); + this.lines.level = []; + if (!this.closed) { + this.displayPrompt(); + } + } + } close() { if (this.terminal && this.historyManager?.isFlushing && !this._closingOnFlush) { this._closingOnFlush = true; @@ -1076,10 +1132,10 @@ class REPLServer extends Interface { session.once('Runtime.executionContextCreated', ({ params }) => { this[kContextId] = params.context.id; }); - context = vm.createContext(); + context = vm.createContext(vm.constants.DONT_CONTEXTIFY); session.post('Runtime.disable'); }, () => { - context = vm.createContext(); + context = vm.createContext(vm.constants.DONT_CONTEXTIFY); }); ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis), (name) => { // Only set properties that do not already exist as a global builtin. diff --git a/lib/stream/iter.js b/lib/stream/iter.js new file mode 100644 index 00000000000000..e77e485a7f2bd5 --- /dev/null +++ b/lib/stream/iter.js @@ -0,0 +1,180 @@ +'use strict'; + +// Public entry point for the iterable streams API. +// Usage: require('stream/iter') or require('node:stream/iter') +// Requires: --experimental-stream-iter + +const { + ObjectFreeze, +} = primordials; + +const { emitExperimentalWarning } = require('internal/util'); +emitExperimentalWarning('stream/iter'); + +// Protocol symbols +const { + toStreamable, + toAsyncStreamable, + broadcastProtocol, + shareProtocol, + shareSyncProtocol, + drainableProtocol, +} = require('internal/streams/iter/types'); + +// Factories +const { push } = require('internal/streams/iter/push'); +const { duplex } = require('internal/streams/iter/duplex'); +const { from, fromSync } = require('internal/streams/iter/from'); + +// Pipelines +const { + pull, + pullSync, + pipeTo, + pipeToSync, +} = require('internal/streams/iter/pull'); + +// Consumers +const { + bytes, + bytesSync, + text, + textSync, + arrayBuffer, + arrayBufferSync, + array, + arraySync, + tap, + tapSync, + merge, + ondrain, +} = require('internal/streams/iter/consumers'); + +// Multi-consumer +const { broadcast, Broadcast } = require('internal/streams/iter/broadcast'); +const { + share, + shareSync, + Share, + SyncShare, +} = require('internal/streams/iter/share'); + +/** + * Stream namespace - unified access to all stream functions. + * @example + * const { Stream } = require('stream/iter'); + * + * const { writer, readable } = Stream.push(); + * await writer.write("hello"); + * await writer.end(); + * + * const output = Stream.pull(readable, transform1, transform2); + * const data = await Stream.bytes(output); + */ +const Stream = ObjectFreeze({ + // Factories + push, + duplex, + from, + fromSync, + + // Pipelines + pull, + pullSync, + + // Pipe to destination + pipeTo, + pipeToSync, + + // Consumers (async) + bytes, + text, + arrayBuffer, + array, + + // Consumers (sync) + bytesSync, + textSync, + arrayBufferSync, + arraySync, + + // Combining + merge, + + // Multi-consumer (push model) + broadcast, + + // Multi-consumer (pull model) + share, + shareSync, + + // Utilities + tap, + tapSync, + + // Drain utility for event source integration + ondrain, + + // Protocol symbols + toStreamable, + toAsyncStreamable, + broadcastProtocol, + shareProtocol, + shareSyncProtocol, + drainableProtocol, +}); + +module.exports = { + // The Stream namespace + Stream, + + // Also export everything individually for destructured imports + + // Protocol symbols + toStreamable, + toAsyncStreamable, + broadcastProtocol, + shareProtocol, + shareSyncProtocol, + drainableProtocol, + + // Factories + push, + duplex, + from, + fromSync, + + // Pipelines + pull, + pullSync, + pipeTo, + pipeToSync, + + // Consumers (async) + bytes, + text, + arrayBuffer, + array, + + // Consumers (sync) + bytesSync, + textSync, + arrayBufferSync, + arraySync, + + // Combining + merge, + + // Multi-consumer + broadcast, + Broadcast, + share, + shareSync, + Share, + SyncShare, + + // Utilities + tap, + tapSync, + ondrain, +}; diff --git a/lib/util.js b/lib/util.js index 5d3c0c75a28fc5..54b7e2f9a442f7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -32,6 +32,7 @@ const { ObjectDefineProperties, ObjectDefineProperty, ObjectGetOwnPropertyDescriptors, + ObjectGetOwnPropertyNames, ObjectKeys, ObjectSetPrototypeOf, ObjectValues, @@ -115,7 +116,7 @@ function getStyleCache() { if (styleCache === undefined) { styleCache = { __proto__: null }; const colors = inspect.colors; - for (const key of ObjectKeys(colors)) { + for (const key of ObjectGetOwnPropertyNames(colors)) { const codes = colors[key]; if (codes) { const openNum = codes[0]; @@ -206,7 +207,7 @@ function styleText(format, text, options) { if (key === 'none') continue; const style = cache[key]; if (style === undefined) { - validateOneOf(key, 'format', ObjectKeys(inspect.colors)); + validateOneOf(key, 'format', ObjectGetOwnPropertyNames(inspect.colors)); } openCodes += style.openSeq; closeCodes = style.closeSeq + closeCodes; diff --git a/lib/zlib/iter.js b/lib/zlib/iter.js new file mode 100644 index 00000000000000..7c2d64d75aa63f --- /dev/null +++ b/lib/zlib/iter.js @@ -0,0 +1,53 @@ +'use strict'; + +// Public entry point for the iterable compression/decompression API. +// Usage: require('zlib/iter') or require('node:zlib/iter') +// Requires: --experimental-stream-iter + +const { emitExperimentalWarning } = require('internal/util'); +emitExperimentalWarning('zlib/iter'); + +const { + compressGzip, + compressGzipSync, + compressDeflate, + compressDeflateSync, + compressBrotli, + compressBrotliSync, + compressZstd, + compressZstdSync, + decompressGzip, + decompressGzipSync, + decompressDeflate, + decompressDeflateSync, + decompressBrotli, + decompressBrotliSync, + decompressZstd, + decompressZstdSync, +} = require('internal/streams/iter/transform'); + +module.exports = { + // Compression transforms (async) + compressGzip, + compressDeflate, + compressBrotli, + compressZstd, + + // Compression transforms (sync) + compressGzipSync, + compressDeflateSync, + compressBrotliSync, + compressZstdSync, + + // Decompression transforms (async) + decompressGzip, + decompressDeflate, + decompressBrotli, + decompressZstd, + + // Decompression transforms (sync) + decompressGzipSync, + decompressDeflateSync, + decompressBrotliSync, + decompressZstdSync, +}; diff --git a/node.gyp b/node.gyp index 2dd7eb1af5865a..bd77943b105173 100644 --- a/node.gyp +++ b/node.gyp @@ -390,6 +390,7 @@ 'src/crypto/crypto_kem.cc', 'src/crypto/crypto_hmac.cc', 'src/crypto/crypto_kmac.cc', + 'src/crypto/crypto_turboshake.cc', 'src/crypto/crypto_random.cc', 'src/crypto/crypto_rsa.cc', 'src/crypto/crypto_spkac.cc', @@ -408,6 +409,7 @@ 'src/crypto/crypto_dh.h', 'src/crypto/crypto_hmac.h', 'src/crypto/crypto_kmac.h', + 'src/crypto/crypto_turboshake.h', 'src/crypto/crypto_rsa.h', 'src/crypto/crypto_spkac.h', 'src/crypto/crypto_util.h', @@ -948,7 +950,13 @@ }, }], [ 'node_builtin_modules_path!=""', { - 'defines': [ 'NODE_BUILTIN_MODULES_PATH="<(node_builtin_modules_path)"' ] + 'defines': [ 'NODE_BUILTIN_MODULES_PATH="<(node_builtin_modules_path)"' ], + # When loading builtins from disk, JS source files do not need to + # trigger rebuilds since the binary reads them at runtime. + 'sources!': [ + '<@(library_files)', + '<@(deps_files)', + ], }], [ 'node_shared=="true"', { 'sources': [ @@ -1103,6 +1111,16 @@ '<@(deps_files)', 'config.gypi' ], + 'conditions': [ + [ 'node_builtin_modules_path!=""', { + # When loading builtins from disk, JS source files do not need + # to trigger rebuilds since the binary reads them at runtime. + 'inputs!': [ + '<@(library_files)', + '<@(deps_files)', + ], + }], + ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/node_javascript.cc', ], diff --git a/src/async_wrap-inl.h b/src/async_wrap-inl.h index 432b22e6c8d185..5dd2b72036ce77 100644 --- a/src/async_wrap-inl.h +++ b/src/async_wrap-inl.h @@ -50,7 +50,25 @@ inline double AsyncWrap::get_trigger_async_id() const { } inline v8::Local AsyncWrap::context_frame() const { - return context_frame_.Get(env()->isolate()); + auto as_data = object()->GetInternalField(kAsyncContextFrame); + DCHECK_IMPLIES(!as_data.IsEmpty(), + as_data->IsValue() || as_data->IsPrivate()); + if (as_data->IsPrivate()) { + DCHECK(as_data.As()->Name()->SameValue( + env()->empty_context_frame_sentinel_symbol()->Name())); + return {}; + } + return as_data.As(); +} + +inline void AsyncWrap::set_context_frame(v8::Local value) { + if (value.IsEmpty()) { + // Empty values are not allowed in internal fields + object()->SetInternalField(kAsyncContextFrame, + env()->empty_context_frame_sentinel_symbol()); + } else { + object()->SetInternalField(kAsyncContextFrame, value); + } } inline v8::MaybeLocal AsyncWrap::MakeCallback( diff --git a/src/async_wrap.cc b/src/async_wrap.cc index e8c8db0425704c..5007fd34414abf 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -348,7 +348,7 @@ void AsyncWrap::EmitDestroy(bool from_gc) { HandleScope handle_scope(env()->isolate()); USE(object()->Set(env()->context(), env()->resource_symbol(), object())); } - context_frame_.Reset(); + set_context_frame({}); } } @@ -530,9 +530,9 @@ AsyncWrap::AsyncWrap(Environment* env, } AsyncWrap::AsyncWrap(Environment* env, Local object) - : BaseObject(env, object), - context_frame_(env->isolate(), - async_context_frame::current(env->isolate())) {} + : BaseObject(env, object) { + set_context_frame(async_context_frame::current(env->isolate())); +} // This method is necessary to work around one specific problem: // Before the init() hook runs, if there is one, the BaseObject() constructor @@ -635,7 +635,7 @@ void AsyncWrap::AsyncReset(Local resource, double execution_async_id) { EmitTraceAsyncStart(); - context_frame_.Reset(isolate, async_context_frame::current(isolate)); + set_context_frame(async_context_frame::current(isolate)); EmitAsyncInit(env(), resource, env()->async_hooks()->provider_string(provider_type()), @@ -678,7 +678,7 @@ MaybeLocal AsyncWrap::MakeCallback(const Local cb, EmitTraceEventBefore(); #ifdef DEBUG - if (context_frame_.IsEmpty()) { + if (context_frame().IsEmpty()) { ProcessEmitWarning(env(), "MakeCallback() called without context_frame, " "likely use after destroy of AsyncWrap."); @@ -687,15 +687,8 @@ MaybeLocal AsyncWrap::MakeCallback(const Local cb, ProviderType provider = provider_type(); async_context context { get_async_id(), get_trigger_async_id() }; - MaybeLocal ret = - InternalMakeCallback(env(), - object(), - object(), - cb, - argc, - argv, - context, - context_frame_.Get(env()->isolate())); + MaybeLocal ret = InternalMakeCallback( + env(), object(), object(), cb, argc, argv, context, context_frame()); // This is a static call with cached values because the `this` object may // no longer be alive at this point. diff --git a/src/async_wrap.h b/src/async_wrap.h index 8ca74820da5674..e4884cb88301d4 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -119,7 +119,8 @@ class ExternalReferenceRegistry; class AsyncWrap : public BaseObject { public: enum InternalFields { - kInternalFieldCount = BaseObject::kInternalFieldCount, + kAsyncContextFrame = BaseObject::kInternalFieldCount, + kInternalFieldCount, }; enum ProviderType { @@ -201,6 +202,7 @@ class AsyncWrap : public BaseObject { inline double get_trigger_async_id() const; inline v8::Local context_frame() const; + inline void set_context_frame(v8::Local value); void AsyncReset(v8::Local resource, double execution_async_id = kInvalidAsyncId); @@ -244,8 +246,6 @@ class AsyncWrap : public BaseObject { // Because the values may be Reset(), cannot be made const. double async_id_ = kInvalidAsyncId; double trigger_async_id_ = kInvalidAsyncId; - - v8::Global context_frame_; }; } // namespace node diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index b5495e59737eb6..fa619696ffd5b2 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -76,24 +76,28 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, } size_t tag_len = 0; + size_t data_len = in.size(); if (params.cipher.isGcmMode() || params.cipher.isOcbMode()) { + tag_len = params.length; switch (cipher_mode) { case kWebCryptoCipherDecrypt: { - // If in decrypt mode, the auth tag must be set in the params.tag. - CHECK(params.tag); + // In decrypt mode, the auth tag is appended to the end of the + // ciphertext. Split it off and set it on the cipher context. + if (data_len < tag_len) { + return WebCryptoCipherStatus::FAILED; + } + data_len -= tag_len; - // For OCB mode, we need to set the auth tag length before setting the - // tag if (params.cipher.isOcbMode()) { - if (!ctx.setAeadTagLength(params.tag.size())) { + if (!ctx.setAeadTagLength(tag_len)) { return WebCryptoCipherStatus::FAILED; } } ncrypto::Buffer buffer = { - .data = params.tag.data(), - .len = params.tag.size(), + .data = in.data() + data_len, + .len = tag_len, }; if (!ctx.setAeadTag(buffer)) { return WebCryptoCipherStatus::FAILED; @@ -101,14 +105,6 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, break; } case kWebCryptoCipherEncrypt: { - // In encrypt mode, we grab the tag length here. We'll use it to - // ensure that that allocated buffer has enough room for both the - // final block and the auth tag. Unlike our other AES-GCM implementation - // in CipherBase, in WebCrypto, the auth tag is concatenated to the end - // of the generated ciphertext and returned in the same ArrayBuffer. - tag_len = params.length; - - // For OCB mode, we need to set the auth tag length if (params.cipher.isOcbMode()) { if (!ctx.setAeadTagLength(tag_len)) { return WebCryptoCipherStatus::FAILED; @@ -122,7 +118,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, } size_t total = 0; - int buf_len = in.size() + ctx.getBlockSize() + tag_len; + int buf_len = data_len + ctx.getBlockSize() + (encrypt ? tag_len : 0); int out_len; ncrypto::Buffer buffer = { @@ -148,9 +144,9 @@ WebCryptoCipherStatus AES_Cipher(Environment* env, // Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244 buffer = { .data = in.data(), - .len = in.size(), + .len = data_len, }; - if (in.empty()) { + if (data_len == 0) { out_len = 0; } else if (!ctx.update(buffer, ptr, &out_len)) { return WebCryptoCipherStatus::FAILED; @@ -381,42 +377,17 @@ bool ValidateCounter( return true; } -bool ValidateAuthTag( - Environment* env, - CryptoJobMode mode, - WebCryptoCipherMode cipher_mode, - Local value, - AESCipherConfig* params) { - switch (cipher_mode) { - case kWebCryptoCipherDecrypt: { - if (!IsAnyBufferSource(value)) { - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); - return false; - } - ArrayBufferOrViewContents tag_contents(value); - if (!tag_contents.CheckSizeInt32()) [[unlikely]] { - THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big"); - return false; - } - params->tag = mode == kCryptoJobAsync - ? tag_contents.ToCopy() - : tag_contents.ToByteSource(); - break; - } - case kWebCryptoCipherEncrypt: { - if (!value->IsUint32()) { - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); - return false; - } - params->length = value.As()->Value(); - if (params->length > 128) { - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); - return false; - } - break; - } - default: - UNREACHABLE(); +bool ValidateAuthTag(Environment* env, + Local value, + AESCipherConfig* params) { + if (!value->IsUint32()) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); + return false; + } + params->length = value.As()->Value(); + if (params->length > 128) { + THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env); + return false; } return true; } @@ -451,8 +422,7 @@ AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept cipher(other.cipher), length(other.length), iv(std::move(other.iv)), - additional_data(std::move(other.additional_data)), - tag(std::move(other.tag)) {} + additional_data(std::move(other.additional_data)) {} AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept { if (&other == this) return *this; @@ -466,7 +436,6 @@ void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const { if (mode == kCryptoJobAsync) { tracker->TrackFieldWithSize("iv", iv.size()); tracker->TrackFieldWithSize("additional_data", additional_data.size()); - tracker->TrackFieldWithSize("tag", tag.size()); } } @@ -510,7 +479,7 @@ Maybe AESCipherTraits::AdditionalConfig( return Nothing(); } } else if (params->cipher.isGcmMode() || params->cipher.isOcbMode()) { - if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) || + if (!ValidateAuthTag(env, args[offset + 2], params) || !ValidateAdditionalData(env, mode, args[offset + 3], params)) { return Nothing(); } diff --git a/src/crypto/crypto_aes.h b/src/crypto/crypto_aes.h index 401ef70a5eba9f..5627f9020bad54 100644 --- a/src/crypto/crypto_aes.h +++ b/src/crypto/crypto_aes.h @@ -52,7 +52,6 @@ struct AESCipherConfig final : public MemoryRetainer { size_t length; ByteSource iv; // Used for both iv or counter ByteSource additional_data; - ByteSource tag; // Used only for authenticated modes (GCM) AESCipherConfig() = default; diff --git a/src/crypto/crypto_chacha20_poly1305.cc b/src/crypto/crypto_chacha20_poly1305.cc index bfe904c49ad771..0fd3e0517317ca 100644 --- a/src/crypto/crypto_chacha20_poly1305.cc +++ b/src/crypto/crypto_chacha20_poly1305.cc @@ -54,63 +54,6 @@ bool ValidateIV(Environment* env, return true; } -bool ValidateAuthTag(Environment* env, - CryptoJobMode mode, - WebCryptoCipherMode cipher_mode, - Local value, - ChaCha20Poly1305CipherConfig* params) { - switch (cipher_mode) { - case kWebCryptoCipherDecrypt: { - if (!IsAnyBufferSource(value)) { - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( - env, "Authentication tag must be a buffer"); - return false; - } - - ArrayBufferOrViewContents tag(value); - if (!tag.CheckSizeInt32()) [[unlikely]] { - THROW_ERR_OUT_OF_RANGE(env, "tag is too large"); - return false; - } - - if (tag.size() != kChaCha20Poly1305TagSize) { - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( - env, "Invalid authentication tag length"); - return false; - } - - if (mode == kCryptoJobAsync) { - params->tag = tag.ToCopy(); - } else { - params->tag = tag.ToByteSource(); - } - break; - } - case kWebCryptoCipherEncrypt: { - // For encryption, the value should be the tag length (passed from - // JavaScript) We expect it to be the tag size constant for - // ChaCha20-Poly1305 - if (!value->IsUint32()) { - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env, "Tag length must be a number"); - return false; - } - - uint32_t tag_length = value.As()->Value(); - if (tag_length != kChaCha20Poly1305TagSize) { - THROW_ERR_CRYPTO_INVALID_TAG_LENGTH( - env, "Invalid tag length for ChaCha20-Poly1305"); - return false; - } - // Tag is generated during encryption, not provided - break; - } - default: - UNREACHABLE(); - } - - return true; -} - bool ValidateAdditionalData(Environment* env, CryptoJobMode mode, Local value, @@ -138,8 +81,7 @@ ChaCha20Poly1305CipherConfig::ChaCha20Poly1305CipherConfig( : mode(other.mode), cipher(other.cipher), iv(std::move(other.iv)), - additional_data(std::move(other.additional_data)), - tag(std::move(other.tag)) {} + additional_data(std::move(other.additional_data)) {} ChaCha20Poly1305CipherConfig& ChaCha20Poly1305CipherConfig::operator=( ChaCha20Poly1305CipherConfig&& other) noexcept { @@ -154,7 +96,6 @@ void ChaCha20Poly1305CipherConfig::MemoryInfo(MemoryTracker* tracker) const { if (mode == kCryptoJobAsync) { tracker->TrackFieldWithSize("iv", iv.size()); tracker->TrackFieldWithSize("additional_data", additional_data.size()); - tracker->TrackFieldWithSize("tag", tag.size()); } } @@ -179,17 +120,9 @@ Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig( return Nothing(); } - // Authentication tag parameter (only for decryption) or tag length (for - // encryption) - if (static_cast(args.Length()) > offset + 1) { - if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 1], params)) { - return Nothing(); - } - } - // Additional authenticated data parameter (optional) - if (static_cast(args.Length()) > offset + 2) { - if (!ValidateAdditionalData(env, mode, args[offset + 2], params)) { + if (static_cast(args.Length()) > offset + 1) { + if (!ValidateAdditionalData(env, mode, args[offset + 1], params)) { return Nothing(); } } @@ -229,23 +162,25 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( return WebCryptoCipherStatus::FAILED; } - size_t tag_len = 0; + size_t tag_len = kChaCha20Poly1305TagSize; + size_t data_len = in.size(); switch (cipher_mode) { case kWebCryptoCipherDecrypt: { - if (params.tag.size() != kChaCha20Poly1305TagSize) { + if (data_len < tag_len) { return WebCryptoCipherStatus::FAILED; } + data_len -= tag_len; + if (!ctx.setAeadTag(ncrypto::Buffer{ - .data = params.tag.data(), - .len = params.tag.size(), + .data = in.data() + data_len, + .len = tag_len, })) { return WebCryptoCipherStatus::FAILED; } break; } case kWebCryptoCipherEncrypt: { - tag_len = kChaCha20Poly1305TagSize; break; } default: @@ -253,7 +188,7 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( } size_t total = 0; - int buf_len = in.size() + ctx.getBlockSize() + tag_len; + int buf_len = data_len + ctx.getBlockSize() + (encrypt ? tag_len : 0); int out_len; // Process additional authenticated data if present @@ -271,9 +206,9 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( // Process the input data buffer = { .data = in.data(), - .len = in.size(), + .len = data_len, }; - if (in.empty()) { + if (data_len == 0) { if (!ctx.update({}, ptr, &out_len)) { return WebCryptoCipherStatus::FAILED; } diff --git a/src/crypto/crypto_chacha20_poly1305.h b/src/crypto/crypto_chacha20_poly1305.h index 5b4d5cde2c3929..f56b1a2e74a152 100644 --- a/src/crypto/crypto_chacha20_poly1305.h +++ b/src/crypto/crypto_chacha20_poly1305.h @@ -17,7 +17,6 @@ struct ChaCha20Poly1305CipherConfig final : public MemoryRetainer { ncrypto::Cipher cipher; ByteSource iv; ByteSource additional_data; - ByteSource tag; ChaCha20Poly1305CipherConfig() = default; diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 4c5e0c582ff6a0..9e453c5e173ade 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -2443,5 +2443,30 @@ void UseExtraCaCerts(std::string_view file) { extra_root_certs_file = file; } +NODE_EXTERN SSL_CTX* GetSSLCtx(Local context, Local value) { + Environment* env = Environment::GetCurrent(context); + if (env == nullptr) return nullptr; + + // TryCatchto swallow any exceptions from Get() (e.g. failing getters) + v8::TryCatch try_catch(env->isolate()); + + // Unwrap the .context property from the JS SecureContext wrapper + // (as returned by tls.createSecureContext()). + if (value->IsObject()) { + Local inner; + if (!value.As() + ->Get(context, FIXED_ONE_BYTE_STRING(env->isolate(), "context")) + .ToLocal(&inner)) { + return nullptr; + } + value = inner; + } + + if (!SecureContext::HasInstance(env, value)) return nullptr; + SecureContext* sc = BaseObject::FromJSObject(value); + if (sc == nullptr) return nullptr; + return sc->ctx().get(); +} + } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_turboshake.cc b/src/crypto/crypto_turboshake.cc new file mode 100644 index 00000000000000..60ef7a8dae5e44 --- /dev/null +++ b/src/crypto/crypto_turboshake.cc @@ -0,0 +1,642 @@ +#include "crypto/crypto_turboshake.h" +#include "async_wrap-inl.h" +#include "node_internals.h" +#include "threadpoolwork-inl.h" + +#include +#include + +namespace node::crypto { + +using v8::FunctionCallbackInfo; +using v8::JustVoid; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Nothing; +using v8::Object; +using v8::Uint32; +using v8::Value; + +// ============================================================================ +// Keccak-p[1600, n_r=12] permutation +// Reference: FIPS 202, Section 3.3 and 3.4; RFC 9861 Section 2.2 +// Adapted from OpenSSL's keccak1600.c (KECCAK_REF variant) +// ============================================================================ +namespace { + +inline uint64_t ROL64(uint64_t val, int offset) { + DCHECK(offset >= 0 && offset < 64); + if (offset == 0) return val; + return (val << offset) | (val >> (64 - offset)); +} + +// Load/store 64-bit lanes in little-endian byte order. +// The Keccak state uses LE lane encoding (FIPS 202 Section 1, B.1). +// These helpers ensure correctness on both LE and BE platforms. +inline uint64_t LoadLE64(const uint8_t* src) { + return static_cast(src[0]) | (static_cast(src[1]) << 8) | + (static_cast(src[2]) << 16) | + (static_cast(src[3]) << 24) | + (static_cast(src[4]) << 32) | + (static_cast(src[5]) << 40) | + (static_cast(src[6]) << 48) | + (static_cast(src[7]) << 56); +} + +inline void StoreLE64(uint8_t* dst, uint64_t val) { + dst[0] = static_cast(val); + dst[1] = static_cast(val >> 8); + dst[2] = static_cast(val >> 16); + dst[3] = static_cast(val >> 24); + dst[4] = static_cast(val >> 32); + dst[5] = static_cast(val >> 40); + dst[6] = static_cast(val >> 48); + dst[7] = static_cast(val >> 56); +} + +static const unsigned char rhotates[5][5] = { + {0, 1, 62, 28, 27}, + {36, 44, 6, 55, 20}, + {3, 10, 43, 25, 39}, + {41, 45, 15, 21, 8}, + {18, 2, 61, 56, 14}, +}; + +// Round constants for Keccak-f[1600]. +// TurboSHAKE uses the last 12 rounds (indices 12..23). +static const uint64_t iotas[24] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL, + 0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL, + 0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, + 0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, 0x8000000080008081ULL, + 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL, +}; + +// Keccak-p[1600, 12]: the reduced-round permutation used by TurboSHAKE. +void KeccakP1600_12(uint64_t A[5][5]) { + for (size_t round = 12; round < 24; round++) { + // Theta + uint64_t C[5]; + for (size_t x = 0; x < 5; x++) { + C[x] = A[0][x] ^ A[1][x] ^ A[2][x] ^ A[3][x] ^ A[4][x]; + } + uint64_t D[5]; + for (size_t x = 0; x < 5; x++) { + D[x] = C[(x + 4) % 5] ^ ROL64(C[(x + 1) % 5], 1); + } + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] ^= D[x]; + } + } + + // Rho + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] = ROL64(A[y][x], rhotates[y][x]); + } + } + + // Pi + uint64_t T[5][5]; + memcpy(T, A, sizeof(T)); + for (size_t y = 0; y < 5; y++) { + for (size_t x = 0; x < 5; x++) { + A[y][x] = T[x][(3 * y + x) % 5]; + } + } + + // Chi + for (size_t y = 0; y < 5; y++) { + uint64_t row[5]; + for (size_t x = 0; x < 5; x++) { + row[x] = A[y][x] ^ (~A[y][(x + 1) % 5] & A[y][(x + 2) % 5]); + } + memcpy(A[y], row, sizeof(row)); + } + + // Iota + A[0][0] ^= iotas[round]; + } +} + +// ============================================================================ +// TurboSHAKE sponge construction +// RFC 9861 Section 2.2, Appendix A.2/A.3 +// ============================================================================ + +// TurboSHAKE128 rate = 168 bytes (1344 bits), capacity = 256 bits +// TurboSHAKE256 rate = 136 bytes (1088 bits), capacity = 512 bits +static constexpr size_t kTurboSHAKE128Rate = 168; +static constexpr size_t kTurboSHAKE256Rate = 136; + +void TurboSHAKE(const uint8_t* input, + size_t input_len, + size_t rate, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + uint64_t A[5][5] = {}; + // Both rates (168, 136) are multiples of 8 + size_t lane_count = rate / 8; + + size_t offset = 0; + + // Absorb complete blocks from input + while (offset + rate <= input_len) { + for (size_t i = 0; i < lane_count; i++) { + A[i / 5][i % 5] ^= LoadLE64(input + offset + i * 8); + } + KeccakP1600_12(A); + offset += rate; + } + + // Absorb last (partial) block: remaining input bytes + domain_sep + padding + size_t remaining = input_len - offset; + uint8_t pad[168] = {}; // sized for max rate (TurboSHAKE128) + if (remaining > 0) { + memcpy(pad, input + offset, remaining); + } + pad[remaining] ^= domain_sep; + pad[rate - 1] ^= 0x80; + + for (size_t i = 0; i < lane_count; i++) { + A[i / 5][i % 5] ^= LoadLE64(pad + i * 8); + } + KeccakP1600_12(A); + + // Squeeze output + size_t out_offset = 0; + while (out_offset < output_len) { + size_t block = output_len - out_offset; + if (block > rate) block = rate; + size_t full_lanes = block / 8; + for (size_t i = 0; i < full_lanes; i++) { + StoreLE64(output + out_offset + i * 8, A[i / 5][i % 5]); + } + size_t rem = block % 8; + if (rem > 0) { + uint8_t tmp[8]; + StoreLE64(tmp, A[full_lanes / 5][full_lanes % 5]); + memcpy(output + out_offset + full_lanes * 8, tmp, rem); + } + out_offset += block; + if (out_offset < output_len) { + KeccakP1600_12(A); + } + } +} + +// Convenience wrappers +void TurboSHAKE128(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + TurboSHAKE( + input, input_len, kTurboSHAKE128Rate, domain_sep, output, output_len); +} + +void TurboSHAKE256(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len) { + TurboSHAKE( + input, input_len, kTurboSHAKE256Rate, domain_sep, output, output_len); +} + +// ============================================================================ +// KangarooTwelve tree hashing (RFC 9861 Section 3) +// ============================================================================ + +static constexpr size_t kChunkSize = 8192; + +// length_encode(x): RFC 9861 Section 3.3 +// Returns byte string x_(n-1) || ... || x_0 || n +// where x = sum of 256^i * x_i, n is smallest such that x < 256^n +std::vector LengthEncode(size_t x) { + if (x == 0) { + return {0x00}; + } + + std::vector result; + size_t val = x; + while (val > 0) { + result.push_back(static_cast(val & 0xFF)); + val >>= 8; + } + + // Reverse to get big-endian: x_(n-1) || ... || x_0 + size_t n = result.size(); + for (size_t i = 0; i < n / 2; i++) { + std::swap(result[i], result[n - 1 - i]); + } + + // Append n (the length of the encoding) + result.push_back(static_cast(n)); + return result; +} + +using TurboSHAKEFn = void (*)(const uint8_t* input, + size_t input_len, + uint8_t domain_sep, + uint8_t* output, + size_t output_len); + +void KangarooTwelve(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len, + TurboSHAKEFn turboshake, + size_t cv_len) { + // Build S = M || C || length_encode(|C|) + auto len_enc = LengthEncode(custom_len); + size_t s_len = msg_len + custom_len + len_enc.size(); + + // Short message path: |S| <= 8192 + if (s_len <= kChunkSize) { + // Build S in a contiguous buffer + std::vector s(s_len); + size_t pos = 0; + if (msg_len > 0) { + memcpy(s.data() + pos, message, msg_len); + pos += msg_len; + } + if (custom_len > 0) { + memcpy(s.data() + pos, customization, custom_len); + pos += custom_len; + } + memcpy(s.data() + pos, len_enc.data(), len_enc.size()); + + turboshake(s.data(), s_len, 0x07, output, output_len); + return; + } + + // Long message path: tree hashing + // We need to process S in chunks, but S is virtual (M || C || length_encode) + // Build a helper to read from this virtual concatenation. + + // First chunk is S[0:8192], compute chaining values for rest + // FinalNode = S[0:8192] || 0x03 || 0x00^7 + + // We need to read from S = M || C || length_encode(|C|) + // Helper lambda to copy from virtual S + auto read_s = [&](size_t s_offset, uint8_t* buf, size_t len) { + size_t copied = 0; + // Part 1: message + if (s_offset < msg_len && copied < len) { + size_t avail = msg_len - s_offset; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, message + s_offset, to_copy); + copied += to_copy; + s_offset += to_copy; + } + // Part 2: customization + size_t custom_start = msg_len; + if (s_offset < custom_start + custom_len && copied < len) { + size_t off_in_custom = s_offset - custom_start; + size_t avail = custom_len - off_in_custom; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, customization + off_in_custom, to_copy); + copied += to_copy; + s_offset += to_copy; + } + // Part 3: length_encode + size_t le_start = msg_len + custom_len; + if (s_offset < le_start + len_enc.size() && copied < len) { + size_t off_in_le = s_offset - le_start; + size_t avail = len_enc.size() - off_in_le; + size_t to_copy = avail < (len - copied) ? avail : (len - copied); + memcpy(buf + copied, len_enc.data() + off_in_le, to_copy); + copied += to_copy; + } + }; + + // Start building FinalNode + // FinalNode = S_0 || 0x03 0x00^7 || CV_1 || CV_2 || ... || CV_(n-1) + // || length_encode(n-1) || 0xFF 0xFF + + // Read first chunk S_0 + std::vector first_chunk(kChunkSize); + read_s(0, first_chunk.data(), kChunkSize); + + // Start FinalNode with S_0 || 0x03 || 0x00^7 + std::vector final_node; + final_node.reserve(kChunkSize + 8 + ((s_len / kChunkSize) * cv_len) + 16); + final_node.insert(final_node.end(), first_chunk.begin(), first_chunk.end()); + final_node.push_back(0x03); + final_node.insert(final_node.end(), 7, 0x00); + + // Process remaining chunks + size_t offset = kChunkSize; + size_t num_blocks = 0; + std::vector chunk(kChunkSize); + std::vector cv(cv_len); + + while (offset < s_len) { + size_t block_size = s_len - offset; + if (block_size > kChunkSize) block_size = kChunkSize; + + chunk.resize(block_size); + read_s(offset, chunk.data(), block_size); + + // CV = TurboSHAKE(chunk, 0x0B, cv_len) + turboshake(chunk.data(), block_size, 0x0B, cv.data(), cv_len); + + final_node.insert(final_node.end(), cv.begin(), cv.end()); + num_blocks++; + offset += block_size; + } + + // Append length_encode(num_blocks) || 0xFF 0xFF + auto num_blocks_enc = LengthEncode(num_blocks); + final_node.insert( + final_node.end(), num_blocks_enc.begin(), num_blocks_enc.end()); + final_node.push_back(0xFF); + final_node.push_back(0xFF); + + // Final hash + turboshake(final_node.data(), final_node.size(), 0x06, output, output_len); +} + +void KT128(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len) { + KangarooTwelve(message, + msg_len, + customization, + custom_len, + output, + output_len, + TurboSHAKE128, + 32); +} + +void KT256(const uint8_t* message, + size_t msg_len, + const uint8_t* customization, + size_t custom_len, + uint8_t* output, + size_t output_len) { + KangarooTwelve(message, + msg_len, + customization, + custom_len, + output, + output_len, + TurboSHAKE256, + 64); +} + +} // anonymous namespace + +// ============================================================================ +// TurboShake bindings +// ============================================================================ + +TurboShakeConfig::TurboShakeConfig(TurboShakeConfig&& other) noexcept + : job_mode(other.job_mode), + variant(other.variant), + output_length(other.output_length), + domain_separation(other.domain_separation), + data(std::move(other.data)) {} + +TurboShakeConfig& TurboShakeConfig::operator=( + TurboShakeConfig&& other) noexcept { + if (&other == this) return *this; + this->~TurboShakeConfig(); + return *new (this) TurboShakeConfig(std::move(other)); +} + +void TurboShakeConfig::MemoryInfo(MemoryTracker* tracker) const { + if (job_mode == kCryptoJobAsync) { + // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource + tracker->TrackFieldWithSize("data", data.size()); + } +} + +Maybe TurboShakeTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + TurboShakeConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->job_mode = mode; + + // args[offset + 0] = algorithm name (string) + CHECK(args[offset]->IsString()); + Utf8Value algorithm_name(env->isolate(), args[offset]); + std::string_view alg = algorithm_name.ToStringView(); + + if (alg == "TurboSHAKE128") { + params->variant = TurboShakeVariant::TurboSHAKE128; + } else if (alg == "TurboSHAKE256") { + params->variant = TurboShakeVariant::TurboSHAKE256; + } else { + UNREACHABLE(); + } + + // args[offset + 1] = domain separation byte (uint32) + CHECK(args[offset + 1]->IsUint32()); + params->domain_separation = + static_cast(args[offset + 1].As()->Value()); + CHECK_GE(params->domain_separation, 0x01); + CHECK_LE(params->domain_separation, 0x7F); + + // args[offset + 2] = output length in bytes (uint32) + CHECK(args[offset + 2]->IsUint32()); + params->output_length = args[offset + 2].As()->Value(); + + // args[offset + 3] = data (ArrayBuffer/View) + ArrayBufferOrViewContents data(args[offset + 3]); + if (!data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "data is too big"); + return Nothing(); + } + params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource(); + + return JustVoid(); +} + +bool TurboShakeTraits::DeriveBits(Environment* env, + const TurboShakeConfig& params, + ByteSource* out, + CryptoJobMode mode) { + CHECK_GT(params.output_length, 0); + char* buf = MallocOpenSSL(params.output_length); + + const uint8_t* input = reinterpret_cast(params.data.data()); + size_t input_len = params.data.size(); + + switch (params.variant) { + case TurboShakeVariant::TurboSHAKE128: + TurboSHAKE128(input, + input_len, + params.domain_separation, + reinterpret_cast(buf), + params.output_length); + break; + case TurboShakeVariant::TurboSHAKE256: + TurboSHAKE256(input, + input_len, + params.domain_separation, + reinterpret_cast(buf), + params.output_length); + break; + } + + *out = ByteSource::Allocated(buf, params.output_length); + return true; +} + +MaybeLocal TurboShakeTraits::EncodeOutput(Environment* env, + const TurboShakeConfig& params, + ByteSource* out) { + return out->ToArrayBuffer(env); +} + +// ============================================================================ +// KangarooTwelve bindings +// ============================================================================ + +KangarooTwelveConfig::KangarooTwelveConfig( + KangarooTwelveConfig&& other) noexcept + : job_mode(other.job_mode), + variant(other.variant), + output_length(other.output_length), + data(std::move(other.data)), + customization(std::move(other.customization)) {} + +KangarooTwelveConfig& KangarooTwelveConfig::operator=( + KangarooTwelveConfig&& other) noexcept { + if (&other == this) return *this; + this->~KangarooTwelveConfig(); + return *new (this) KangarooTwelveConfig(std::move(other)); +} + +void KangarooTwelveConfig::MemoryInfo(MemoryTracker* tracker) const { + if (job_mode == kCryptoJobAsync) { + // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource + tracker->TrackFieldWithSize("data", data.size()); + tracker->TrackFieldWithSize("customization", customization.size()); + } +} + +Maybe KangarooTwelveTraits::AdditionalConfig( + CryptoJobMode mode, + const FunctionCallbackInfo& args, + unsigned int offset, + KangarooTwelveConfig* params) { + Environment* env = Environment::GetCurrent(args); + + params->job_mode = mode; + + // args[offset + 0] = algorithm name (string) + CHECK(args[offset]->IsString()); + Utf8Value algorithm_name(env->isolate(), args[offset]); + std::string_view alg = algorithm_name.ToStringView(); + + if (alg == "KT128") { + params->variant = KangarooTwelveVariant::KT128; + } else if (alg == "KT256") { + params->variant = KangarooTwelveVariant::KT256; + } else { + UNREACHABLE(); + } + + // args[offset + 1] = customization (BufferSource or undefined) + if (!args[offset + 1]->IsUndefined()) { + ArrayBufferOrViewContents customization(args[offset + 1]); + if (!customization.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "customization is too big"); + return Nothing(); + } + params->customization = mode == kCryptoJobAsync + ? customization.ToCopy() + : customization.ToByteSource(); + } + + // args[offset + 2] = output length in bytes (uint32) + CHECK(args[offset + 2]->IsUint32()); + params->output_length = args[offset + 2].As()->Value(); + + // args[offset + 3] = data (ArrayBuffer/View) + ArrayBufferOrViewContents data(args[offset + 3]); + if (!data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "data is too big"); + return Nothing(); + } + params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource(); + + return JustVoid(); +} + +bool KangarooTwelveTraits::DeriveBits(Environment* env, + const KangarooTwelveConfig& params, + ByteSource* out, + CryptoJobMode mode) { + CHECK_GT(params.output_length, 0); + char* buf = MallocOpenSSL(params.output_length); + + const uint8_t* input = reinterpret_cast(params.data.data()); + size_t input_len = params.data.size(); + + const uint8_t* custom = + reinterpret_cast(params.customization.data()); + size_t custom_len = params.customization.size(); + + switch (params.variant) { + case KangarooTwelveVariant::KT128: + KT128(input, + input_len, + custom, + custom_len, + reinterpret_cast(buf), + params.output_length); + break; + case KangarooTwelveVariant::KT256: + KT256(input, + input_len, + custom, + custom_len, + reinterpret_cast(buf), + params.output_length); + break; + } + + *out = ByteSource::Allocated(buf, params.output_length); + return true; +} + +MaybeLocal KangarooTwelveTraits::EncodeOutput( + Environment* env, const KangarooTwelveConfig& params, ByteSource* out) { + return out->ToArrayBuffer(env); +} + +// ============================================================================ +// Registration +// ============================================================================ + +void TurboShake::Initialize(Environment* env, Local target) { + TurboShakeJob::Initialize(env, target); + KangarooTwelveJob::Initialize(env, target); +} + +void TurboShake::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + TurboShakeJob::RegisterExternalReferences(registry); + KangarooTwelveJob::RegisterExternalReferences(registry); +} + +} // namespace node::crypto diff --git a/src/crypto/crypto_turboshake.h b/src/crypto/crypto_turboshake.h new file mode 100644 index 00000000000000..53b01eec8bd7c8 --- /dev/null +++ b/src/crypto/crypto_turboshake.h @@ -0,0 +1,105 @@ +#ifndef SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ +#define SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "crypto/crypto_util.h" + +namespace node::crypto { + +enum class TurboShakeVariant { TurboSHAKE128, TurboSHAKE256 }; + +struct TurboShakeConfig final : public MemoryRetainer { + CryptoJobMode job_mode; + TurboShakeVariant variant; + uint32_t output_length; // Output length in bytes + uint8_t domain_separation; // Domain separation byte (0x01–0x7F) + ByteSource data; + + TurboShakeConfig() = default; + + explicit TurboShakeConfig(TurboShakeConfig&& other) noexcept; + + TurboShakeConfig& operator=(TurboShakeConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(TurboShakeConfig) + SET_SELF_SIZE(TurboShakeConfig) +}; + +struct TurboShakeTraits final { + using AdditionalParameters = TurboShakeConfig; + static constexpr const char* JobName = "TurboShakeJob"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + TurboShakeConfig* params); + + static bool DeriveBits(Environment* env, + const TurboShakeConfig& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput(Environment* env, + const TurboShakeConfig& params, + ByteSource* out); +}; + +using TurboShakeJob = DeriveBitsJob; + +enum class KangarooTwelveVariant { KT128, KT256 }; + +struct KangarooTwelveConfig final : public MemoryRetainer { + CryptoJobMode job_mode; + KangarooTwelveVariant variant; + uint32_t output_length; // Output length in bytes + ByteSource data; + ByteSource customization; + + KangarooTwelveConfig() = default; + + explicit KangarooTwelveConfig(KangarooTwelveConfig&& other) noexcept; + + KangarooTwelveConfig& operator=(KangarooTwelveConfig&& other) noexcept; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(KangarooTwelveConfig) + SET_SELF_SIZE(KangarooTwelveConfig) +}; + +struct KangarooTwelveTraits final { + using AdditionalParameters = KangarooTwelveConfig; + static constexpr const char* JobName = "KangarooTwelveJob"; + static constexpr AsyncWrap::ProviderType Provider = + AsyncWrap::PROVIDER_DERIVEBITSREQUEST; + + static v8::Maybe AdditionalConfig( + CryptoJobMode mode, + const v8::FunctionCallbackInfo& args, + unsigned int offset, + KangarooTwelveConfig* params); + + static bool DeriveBits(Environment* env, + const KangarooTwelveConfig& params, + ByteSource* out, + CryptoJobMode mode); + + static v8::MaybeLocal EncodeOutput( + Environment* env, const KangarooTwelveConfig& params, ByteSource* out); +}; + +using KangarooTwelveJob = DeriveBitsJob; + +namespace TurboShake { +void Initialize(Environment* env, v8::Local target); +void RegisterExternalReferences(ExternalReferenceRegistry* registry); +} // namespace TurboShake + +} // namespace node::crypto + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_CRYPTO_CRYPTO_TURBOSHAKE_H_ diff --git a/src/env_properties.h b/src/env_properties.h index 91e2e06c3c2703..7f0abdac9ca452 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -21,6 +21,7 @@ V(arrow_message_private_symbol, "node:arrowMessage") \ V(contextify_context_private_symbol, "node:contextify:context") \ V(decorated_private_symbol, "node:decorated") \ + V(empty_context_frame_sentinel_symbol, "node:empty_context_frame_sentinel") \ V(transfer_mode_private_symbol, "node:transfer_mode") \ V(host_defined_option_symbol, "node:host_defined_option_symbol") \ V(js_transferable_wrapper_private_symbol, "node:js_transferable_wrapper") \ diff --git a/src/inspector/domain_target.pdl b/src/inspector/domain_target.pdl index 3195b3a441d8ab..890e79db8eb6b3 100644 --- a/src/inspector/domain_target.pdl +++ b/src/inspector/domain_target.pdl @@ -20,6 +20,9 @@ experimental domain Target SessionID sessionId TargetInfo targetInfo boolean waitingForDebugger + command getTargets + returns + array of TargetInfo targetInfos command setAutoAttach parameters boolean autoAttach diff --git a/src/inspector/node_inspector.gypi b/src/inspector/node_inspector.gypi index e3e2fc140db7c3..a493f59465d207 100644 --- a/src/inspector/node_inspector.gypi +++ b/src/inspector/node_inspector.gypi @@ -32,6 +32,8 @@ 'src/inspector/network_inspector.h', 'src/inspector/network_agent.cc', 'src/inspector/network_agent.h', + 'src/inspector/target_manager.cc', + 'src/inspector/target_manager.h', 'src/inspector/target_agent.cc', 'src/inspector/target_agent.h', 'src/inspector/worker_inspector.cc', diff --git a/src/inspector/target_agent.cc b/src/inspector/target_agent.cc index 2f841cb2a9c54c..04cbe0b83750ce 100644 --- a/src/inspector/target_agent.cc +++ b/src/inspector/target_agent.cc @@ -8,10 +8,6 @@ namespace node { namespace inspector { namespace protocol { -std::unordered_map> - TargetAgent::target_session_id_worker_map_ = - std::unordered_map>(); -int TargetAgent::next_session_id_ = 1; class WorkerTargetDelegate : public WorkerDelegate { public: explicit WorkerTargetDelegate(std::shared_ptr target_agent) @@ -32,13 +28,14 @@ std::unique_ptr createTargetInfo( const std::string_view target_id, const std::string_view type, const std::string_view title, - const std::string_view url) { + const std::string_view url, + bool attached = false) { return Target::TargetInfo::create() .setTargetId(std::string(target_id)) .setType(std::string(type)) .setTitle(std::string(title)) .setUrl(std::string(url)) - .setAttached(false) + .setAttached(attached) .setCanAccessOpener(true) .build(); } @@ -57,11 +54,11 @@ void TargetAgent::createAndAttachIfNecessary( targetCreated(target_id, type, title, url); bool attached = false; - if (auto_attach_) { + if (target_manager_->auto_attach()) { attached = true; attachedToTarget(worker, target_id, type, title, url); } - targets_.push_back({target_id, type, title, url, worker, attached}); + target_manager_->AddTarget(worker, target_id, type, title, url, attached); } void TargetAgent::listenWorker(std::weak_ptr worker_manager) { @@ -87,12 +84,26 @@ void TargetAgent::targetCreated(const std::string_view target_id, frontend_->targetCreated(createTargetInfo(target_id, type, title, url)); } +crdtp::DispatchResponse TargetAgent::getTargets( + std::unique_ptr>* out_targetInfos) { + auto target_infos = std::make_unique>(); + for (const auto& target : target_manager_->GetTargetsSnapshot()) { + target_infos->push_back(createTargetInfo(target.target_id, + target.type, + target.title, + target.url, + target.attached)); + } + *out_targetInfos = std::move(target_infos); + return DispatchResponse::Success(); +} + int TargetAgent::getNextSessionId() { - return next_session_id_++; + return target_manager_->NextSessionId(); } int TargetAgent::getNextTargetId() { - return next_target_id_++; + return target_manager_->NextTargetId(); } void TargetAgent::attachedToTarget(std::shared_ptr worker, @@ -101,7 +112,7 @@ void TargetAgent::attachedToTarget(std::shared_ptr worker, const std::string& title, const std::string& url) { int session_id = getNextSessionId(); - target_session_id_worker_map_[session_id] = worker; + TargetManager::RegisterSessionWorker(session_id, worker); worker->SetTargetSessionId(session_id); frontend_->attachedToTarget(std::to_string(session_id), createTargetInfo(target_id, type, title, url), @@ -112,11 +123,10 @@ void TargetAgent::attachedToTarget(std::shared_ptr worker, // all threads. Modify it to be managed per worker thread. crdtp::DispatchResponse TargetAgent::setAutoAttach( bool auto_attach, bool wait_for_debugger_on_start) { - auto_attach_ = auto_attach; - wait_for_debugger_on_start_ = wait_for_debugger_on_start; + target_manager_->SetAutoAttach(auto_attach, wait_for_debugger_on_start); if (auto_attach) { - for (auto& target : targets_) { + for (auto& target : target_manager_->targets()) { if (!target.attached) { target.attached = true; attachedToTarget(target.worker, diff --git a/src/inspector/target_agent.h b/src/inspector/target_agent.h index 36a95f80dcad75..f7904e986751f3 100644 --- a/src/inspector/target_agent.h +++ b/src/inspector/target_agent.h @@ -1,9 +1,9 @@ #ifndef SRC_INSPECTOR_TARGET_AGENT_H_ #define SRC_INSPECTOR_TARGET_AGENT_H_ +#include #include -#include -#include +#include "inspector/target_manager.h" #include "inspector/worker_inspector.h" #include "node/inspector/protocol/Target.h" @@ -14,15 +14,6 @@ class TargetInspector; namespace protocol { -struct TargetInfo { - std::string target_id; - std::string type; - std::string title; - std::string url; - std::shared_ptr worker; - bool attached; -}; - class TargetAgent : public Target::Backend, public std::enable_shared_from_this { public: @@ -32,15 +23,14 @@ class TargetAgent : public Target::Backend, const std::string& title, const std::string& url); + DispatchResponse getTargets( + std::unique_ptr>* out_targetInfos) + override; DispatchResponse setAutoAttach(bool auto_attach, bool wait_for_debugger_on_start) override; void listenWorker(std::weak_ptr worker_manager); void reset(); - static std::unordered_map> - target_session_id_worker_map_; - - bool isThisThread(MainThreadHandle* worker) { return worker == main_thread_; } private: int getNextTargetId(); @@ -57,15 +47,9 @@ class TargetAgent : public Target::Backend, std::shared_ptr frontend_; std::weak_ptr worker_manager_; - static int next_session_id_; - int next_target_id_ = 1; std::unique_ptr worker_event_handle_ = nullptr; - bool auto_attach_ = false; - // TODO(islandryu): If false, implement it so that each thread does not wait - // for the worker to execute. - bool wait_for_debugger_on_start_ = true; - std::vector targets_; - MainThreadHandle* main_thread_; + std::unique_ptr target_manager_ = + std::make_unique(); }; } // namespace protocol diff --git a/src/inspector/target_manager.cc b/src/inspector/target_manager.cc new file mode 100644 index 00000000000000..318aa71e4e7526 --- /dev/null +++ b/src/inspector/target_manager.cc @@ -0,0 +1,66 @@ +#include "inspector/target_manager.h" + +#include "inspector/main_thread_interface.h" + +namespace node { +namespace inspector { + +Mutex TargetManager::session_state_lock_; +std::unordered_map> + TargetManager::session_worker_map_; +int TargetManager::next_session_id_ = 1; + +int TargetManager::NextTargetId() { + return next_target_id_++; +} + +int TargetManager::NextSessionId() { + Mutex::ScopedLock scoped_lock(session_state_lock_); + return next_session_id_++; +} + +void TargetManager::SetAutoAttach(bool auto_attach, + bool wait_for_debugger_on_start) { + auto_attach_ = auto_attach; + wait_for_debugger_on_start_ = wait_for_debugger_on_start; +} + +void TargetManager::AddTarget(std::shared_ptr worker, + const std::string& target_id, + const std::string& type, + const std::string& title, + const std::string& url, + bool attached) { + targets_.push_back({target_id, type, title, url, worker, attached}); +} + +std::vector TargetManager::GetTargetsSnapshot() + const { + std::vector result; + result.reserve(targets_.size()); + for (const auto& target : targets_) { + if (target.worker && !target.worker->Expired()) { + result.push_back(target); + } + } + return result; +} + +void TargetManager::RegisterSessionWorker( + int session_id, std::shared_ptr worker) { + Mutex::ScopedLock scoped_lock(session_state_lock_); + session_worker_map_[session_id] = std::move(worker); +} + +std::shared_ptr TargetManager::WorkerForSession( + int session_id) { + Mutex::ScopedLock scoped_lock(session_state_lock_); + auto it = session_worker_map_.find(session_id); + if (it == session_worker_map_.end()) { + return nullptr; + } + return it->second; +} + +} // namespace inspector +} // namespace node diff --git a/src/inspector/target_manager.h b/src/inspector/target_manager.h new file mode 100644 index 00000000000000..3d7984b866665f --- /dev/null +++ b/src/inspector/target_manager.h @@ -0,0 +1,70 @@ +#ifndef SRC_INSPECTOR_TARGET_MANAGER_H_ +#define SRC_INSPECTOR_TARGET_MANAGER_H_ + +#include +#include +#include +#include + +#include "node_mutex.h" + +namespace node { +namespace inspector { + +class MainThreadHandle; + +class TargetManager { + public: + struct TargetInfo { + std::string target_id; + std::string type; + std::string title; + std::string url; + std::shared_ptr worker; + bool attached; + }; + + TargetManager() = default; + + int NextTargetId(); + int NextSessionId(); + + void SetAutoAttach(bool auto_attach, bool wait_for_debugger_on_start); + bool auto_attach() const { return auto_attach_; } + bool wait_for_debugger_on_start() const { + return wait_for_debugger_on_start_; + } + + void AddTarget(std::shared_ptr worker, + const std::string& target_id, + const std::string& type, + const std::string& title, + const std::string& url, + bool attached); + std::vector GetTargetsSnapshot() const; + std::vector& targets() { return targets_; } + const std::vector& targets() const { return targets_; } + + static void RegisterSessionWorker(int session_id, + std::shared_ptr worker); + static std::shared_ptr WorkerForSession(int session_id); + + private: + static Mutex session_state_lock_; + static std::unordered_map> + session_worker_map_; + static int next_session_id_; + + int next_target_id_ = 1; + bool auto_attach_ = false; + // TODO(islandryu): Honor this flag for worker targets. It is stored here + // so Target.setAutoAttach() state can be tracked, but worker startup pause + // behavior does not change based on it yet. + bool wait_for_debugger_on_start_ = true; + std::vector targets_; +}; + +} // namespace inspector +} // namespace node + +#endif // SRC_INSPECTOR_TARGET_MANAGER_H_ diff --git a/src/inspector_io.cc b/src/inspector_io.cc index 361d19349261e5..e0b4b6c3746162 100644 --- a/src/inspector_io.cc +++ b/src/inspector_io.cc @@ -7,6 +7,7 @@ #include "inspector/node_json.h" #include "inspector/node_string.h" #include "inspector/target_agent.h" +#include "inspector/target_manager.h" #include "inspector_socket_server.h" #include "ncrypto.h" #include "node.h" @@ -380,8 +381,7 @@ void InspectorIoDelegate::MessageReceived(int session_id, ::isdigit); if (is_number) { int target_session_id = std::stoi(*target_session_id_str); - worker = protocol::TargetAgent::target_session_id_worker_map_ - [target_session_id]; + worker = TargetManager::WorkerForSession(target_session_id); if (worker) { merged_session_id += target_session_id << 16; } diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 354b45bda9ccc7..ef69fe133fad61 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -1,5 +1,6 @@ #include "module_wrap.h" +#include "debug_utils-inl.h" #include "env.h" #include "memory_tracker-inl.h" #include "node_contextify.h" @@ -7,6 +8,7 @@ #include "node_external_reference.h" #include "node_internals.h" #include "node_process-inl.h" +#include "node_sea.h" #include "node_url.h" #include "node_watchdog.h" #include "util-inl.h" @@ -365,6 +367,20 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { new ScriptCompiler::CachedData(data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); } +#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION + // For embedder ESM in a SEA, use the bundled code cache if available. + if (id_symbol == realm->isolate_data()->embedder_module_hdo() && + sea::IsSingleExecutable()) { + sea::SeaResource sea = sea::FindSingleExecutableResource(); + if (sea.use_code_cache()) { + std::string_view data = sea.code_cache.value(); + user_cached_data = new ScriptCompiler::CachedData( + reinterpret_cast(data.data()), + static_cast(data.size()), + ScriptCompiler::CachedData::BufferNotOwned); + } + } +#endif // !DISABLE_SINGLE_EXECUTABLE_APPLICATION Local source_text = args[2].As(); bool cache_rejected = false; @@ -389,12 +405,26 @@ void ModuleWrap::New(const FunctionCallbackInfo& args) { return; } - if (user_cached_data.has_value() && user_cached_data.value() != nullptr && - cache_rejected) { - THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED( - realm, "cachedData buffer was rejected"); - try_catch.ReThrow(); - return; + if (user_cached_data.has_value() && user_cached_data.value() != nullptr) { +#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION + if (id_symbol == realm->isolate_data()->embedder_module_hdo() && + sea::IsSingleExecutable()) { + if (cache_rejected) { + per_process::Debug(DebugCategory::SEA, + "SEA module code cache rejected\n"); + ProcessEmitWarningSync(realm->env(), "Code cache data rejected."); + } else { + per_process::Debug(DebugCategory::SEA, + "SEA module code cache accepted\n"); + } + } else // NOLINT(readability/braces) +#endif // !DISABLE_SINGLE_EXECUTABLE_APPLICATION + if (cache_rejected) { + THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED( + realm, "cachedData buffer was rejected"); + try_catch.ReThrow(); + return; + } } if (that->Set(context, diff --git a/src/node.h b/src/node.h index 1977cfc5b998c7..b2872f22c6ca36 100644 --- a/src/node.h +++ b/src/node.h @@ -124,6 +124,8 @@ // Forward-declare libuv loop struct uv_loop_s; +struct napi_module; +struct ssl_ctx_st; // Forward declaration of SSL_CTX for OpenSSL. // Forward-declare these functions now to stop MSVS from becoming // terminally confused when it's done in node_internals.h @@ -1656,6 +1658,20 @@ NODE_DEPRECATED( v8::Local object, v8::Object::Wrappable* wrappable)); +namespace crypto { + +// Returns the SSL_CTX* from a SecureContext JS object, as returned by +// tls.createSecureContext(). +// Returns nullptr if the value is not a SecureContext instance, +// or if Node.js was built without OpenSSL. +// +// The returned pointer is not owned by the caller and must not be freed. +// It is valid only while the SecureContext JS object remains alive. +NODE_EXTERN struct ssl_ctx_st* GetSSLCtx(v8::Local context, + v8::Local secure_context); + +} // namespace crypto + } // namespace node #endif // SRC_NODE_H_ diff --git a/src/node_buffer.cc b/src/node_buffer.cc index e40a21288ee79d..362ac268483ea3 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1050,24 +1050,20 @@ void IndexOfString(const FunctionCallbackInfo& args) { needle_length, offset, is_forward); - } else if (enc == LATIN1) { - uint8_t* needle_data = node::UncheckedMalloc(needle_length); - if (needle_data == nullptr) { - return args.GetReturnValue().Set(-1); - } + } else if (enc == ASCII || enc == LATIN1) { + MaybeStackBuffer needle_data(needle_length); StringBytes::Write(isolate, - reinterpret_cast(needle_data), + reinterpret_cast(needle_data.out()), needle_length, needle, enc); result = nbytes::SearchString(reinterpret_cast(haystack), haystack_length, - needle_data, + needle_data.out(), needle_length, offset, is_forward); - free(needle_data); } args.GetReturnValue().Set( @@ -1200,31 +1196,59 @@ int32_t FastIndexOfNumber(Local, static CFunction fast_index_of_number(CFunction::Make(FastIndexOfNumber)); void Swap16(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]); + DCHECK(args[0]->IsArrayBufferView()); SPREAD_BUFFER_ARG(args[0], ts_obj); CHECK(nbytes::SwapBytes16(ts_obj_data, ts_obj_length)); - args.GetReturnValue().Set(args[0]); } +void FastSwap16(Local receiver, + Local buffer_obj, + // NOLINTNEXTLINE(runtime/references) + FastApiCallbackOptions& options) { + TRACK_V8_FAST_API_CALL("buffer.swap16"); + HandleScope scope(options.isolate); + SPREAD_BUFFER_ARG(buffer_obj, ts_obj); + CHECK(nbytes::SwapBytes16(ts_obj_data, ts_obj_length)); +} + +static CFunction fast_swap16(CFunction::Make(FastSwap16)); void Swap32(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]); + DCHECK(args[0]->IsArrayBufferView()); SPREAD_BUFFER_ARG(args[0], ts_obj); CHECK(nbytes::SwapBytes32(ts_obj_data, ts_obj_length)); - args.GetReturnValue().Set(args[0]); } +void FastSwap32(Local receiver, + Local buffer_obj, + // NOLINTNEXTLINE(runtime/references) + FastApiCallbackOptions& options) { + TRACK_V8_FAST_API_CALL("buffer.swap32"); + HandleScope scope(options.isolate); + SPREAD_BUFFER_ARG(buffer_obj, ts_obj); + CHECK(nbytes::SwapBytes32(ts_obj_data, ts_obj_length)); +} + +static CFunction fast_swap32(CFunction::Make(FastSwap32)); void Swap64(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]); + DCHECK(args[0]->IsArrayBufferView()); SPREAD_BUFFER_ARG(args[0], ts_obj); CHECK(nbytes::SwapBytes64(ts_obj_data, ts_obj_length)); - args.GetReturnValue().Set(args[0]); } +void FastSwap64(Local receiver, + Local buffer_obj, + // NOLINTNEXTLINE(runtime/references) + FastApiCallbackOptions& options) { + TRACK_V8_FAST_API_CALL("buffer.swap64"); + HandleScope scope(options.isolate); + SPREAD_BUFFER_ARG(buffer_obj, ts_obj); + CHECK(nbytes::SwapBytes64(ts_obj_data, ts_obj_length)); +} + +static CFunction fast_swap64(CFunction::Make(FastSwap64)); + static void IsUtf8(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); @@ -1623,9 +1647,9 @@ void Initialize(Local target, SetMethodNoSideEffect( context, target, "createUnsafeArrayBuffer", CreateUnsafeArrayBuffer); - SetMethod(context, target, "swap16", Swap16); - SetMethod(context, target, "swap32", Swap32); - SetMethod(context, target, "swap64", Swap64); + SetFastMethod(context, target, "swap16", Swap16, &fast_swap16); + SetFastMethod(context, target, "swap32", Swap32, &fast_swap32); + SetFastMethod(context, target, "swap64", Swap64, &fast_swap64); SetMethodNoSideEffect(context, target, "isUtf8", IsUtf8); SetMethodNoSideEffect(context, target, "isAscii", IsAscii); @@ -1694,8 +1718,11 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(IndexOfString); registry->Register(Swap16); + registry->Register(fast_swap16); registry->Register(Swap32); + registry->Register(fast_swap32); registry->Register(Swap64); + registry->Register(fast_swap64); registry->Register(IsUtf8); registry->Register(IsAscii); diff --git a/src/node_builtins.cc b/src/node_builtins.cc index 5ef8d06933700c..81503ae2a1ff85 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -74,7 +74,8 @@ const BuiltinSource* BuiltinLoader::AddFromDisk(const char* id, const std::string& filename, const UnionBytes& source) { BuiltinSourceType type = GetBuiltinSourceType(id, filename); - auto result = source_.write()->emplace(id, BuiltinSource{id, source, type}); + auto result = + source_.write()->insert_or_assign(id, BuiltinSource{id, source, type}); return &(result.first->second); } @@ -133,15 +134,17 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { "internal/tls/wrap", "internal/tls/secure-context", "internal/http2/core", "internal/http2/compat", "internal/streams/lazy_transform", -#endif // !HAVE_OPENSSL +#endif // !HAVE_OPENSSL #ifndef OPENSSL_NO_QUIC "internal/quic/quic", "internal/quic/symbols", "internal/quic/stats", "internal/quic/state", -#endif // !OPENSSL_NO_QUIC - "quic", // Experimental. - "sqlite", // Experimental. - "sys", // Deprecated. - "wasi", // Experimental. +#endif // !OPENSSL_NO_QUIC + "quic", // Experimental. + "sqlite", // Experimental. + "stream/iter", // Experimental. + "zlib/iter", // Experimental. + "sys", // Deprecated. + "wasi", // Experimental. #if !HAVE_SQLITE "internal/webstorage", // Experimental. #endif diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 991cbf95fbb786..84375f9a737675 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -75,6 +75,8 @@ namespace crypto { #define KMAC_NAMESPACE_LIST(V) #endif +#define TURBOSHAKE_NAMESPACE_LIST(V) V(TurboShake) + #ifdef OPENSSL_NO_SCRYPT #define SCRYPT_NAMESPACE_LIST(V) #else @@ -86,7 +88,8 @@ namespace crypto { ARGON2_NAMESPACE_LIST(V) \ KEM_NAMESPACE_LIST(V) \ KMAC_NAMESPACE_LIST(V) \ - SCRYPT_NAMESPACE_LIST(V) + SCRYPT_NAMESPACE_LIST(V) \ + TURBOSHAKE_NAMESPACE_LIST(V) void Initialize(Local target, Local unused, diff --git a/src/node_crypto.h b/src/node_crypto.h index e5e29544b57a81..cc8fc689f48438 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -55,6 +55,7 @@ #include "crypto/crypto_spkac.h" #include "crypto/crypto_timing.h" #include "crypto/crypto_tls.h" +#include "crypto/crypto_turboshake.h" #include "crypto/crypto_util.h" #include "crypto/crypto_x509.h" diff --git a/src/node_file.cc b/src/node_file.cc index ef83a43d9f07f2..7fb87639b37e96 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -49,7 +49,7 @@ #include #if defined(__MINGW32__) || defined(_MSC_VER) -# include +#include #endif #ifdef _WIN32 @@ -88,7 +88,7 @@ using v8::Undefined; using v8::Value; #ifndef S_ISDIR -# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) #endif #ifdef __POSIX__ @@ -203,8 +203,7 @@ static const char* get_fs_func_name_by_type(uv_fs_type req_type) { // We sometimes need to convert a C++ lambda function to a raw C-style function. // This is helpful, because ReqWrap::Dispatch() does not recognize lambda // functions, and thus does not wrap them properly. -typedef void(*uv_fs_callback_t)(uv_fs_t*); - +typedef void (*uv_fs_callback_t)(uv_fs_t*); void FSContinuationData::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("paths", paths_); @@ -336,7 +335,7 @@ BaseObjectPtr FileHandle::TransferData::Deserialize( int fd = fd_; fd_ = -1; - return BaseObjectPtr { FileHandle::New(bd, fd) }; + return BaseObjectPtr{FileHandle::New(bd, fd)}; } // Throw an exception if the file handle has not yet been closed. @@ -431,7 +430,7 @@ FileHandle::CloseReq::CloseReq(Environment* env, Local obj, Local promise, Local ref) - : ReqWrap(env, obj, AsyncWrap::PROVIDER_FILEHANDLECLOSEREQ) { + : ReqWrap(env, obj, AsyncWrap::PROVIDER_FILEHANDLECLOSEREQ) { promise_.Reset(env->isolate(), promise); ref_.Reset(env->isolate(), ref); } @@ -447,8 +446,6 @@ void FileHandle::CloseReq::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("ref", ref_); } - - // Closes this FileHandle asynchronously and returns a Promise that will be // resolved when the callback is invoked, or rejects with a UVException if // there was a problem closing the fd. This is the preferred mechanism for @@ -476,8 +473,10 @@ MaybeLocal FileHandle::ClosePromise() { Local promise = resolver.As(); Local close_req_obj; - if (!env()->fdclose_constructor_template() - ->NewInstance(env()->context()).ToLocal(&close_req_obj)) { + if (!env() + ->fdclose_constructor_template() + ->NewInstance(env()->context()) + .ToLocal(&close_req_obj)) { return MaybeLocal(); } closing_ = true; @@ -520,6 +519,27 @@ void FileHandle::Close(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(ret); } +void FileHandle::CloseSync(const FunctionCallbackInfo& args) { + FileHandle* fd; + ASSIGN_OR_RETURN_UNWRAP(&fd, args.This()); + + // Already closed or closing - no-op. + if (fd->closed_ || fd->closing_) return; + + uv_fs_t req; + CHECK_NE(fd->fd_, -1); + FS_SYNC_TRACE_BEGIN(close); + int ret = uv_fs_close(fd->env()->event_loop(), &req, fd->fd_, nullptr); + FS_SYNC_TRACE_END(close); + uv_fs_req_cleanup(&req); + + fd->AfterClose(); + + if (ret < 0) { + Environment* env = fd->env(); + env->ThrowUVException(ret, "close"); + } +} void FileHandle::ReleaseFD(const FunctionCallbackInfo& args) { FileHandle* fd; @@ -538,8 +558,7 @@ void FileHandle::AfterClose() { closing_ = false; closed_ = true; fd_ = -1; - if (reading_ && !persistent().IsEmpty()) - EmitRead(UV_EOF); + if (reading_ && !persistent().IsEmpty()) EmitRead(UV_EOF); } void FileHandleReadWrap::MemoryInfo(MemoryTracker* tracker) const { @@ -548,17 +567,15 @@ void FileHandleReadWrap::MemoryInfo(MemoryTracker* tracker) const { } FileHandleReadWrap::FileHandleReadWrap(FileHandle* handle, Local obj) - : ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQCALLBACK), - file_handle_(handle) {} + : ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQCALLBACK), + file_handle_(handle) {} int FileHandle::ReadStart() { - if (!IsAlive() || IsClosing()) - return UV_EOF; + if (!IsAlive() || IsClosing()) return UV_EOF; reading_ = true; - if (current_read_) - return 0; + if (current_read_) return 0; BaseObjectPtr read_wrap; @@ -604,67 +621,65 @@ int FileHandle::ReadStart() { current_read_ = std::move(read_wrap); FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, current_read_.get()) - current_read_->Dispatch(uv_fs_read, - fd_, - ¤t_read_->buffer_, - 1, - read_offset_, - uv_fs_callback_t{[](uv_fs_t* req) { - FileHandle* handle; - { - FileHandleReadWrap* req_wrap = FileHandleReadWrap::from_req(req); - FS_ASYNC_TRACE_END1( - req->fs_type, req_wrap, "result", static_cast(req->result)) - handle = req_wrap->file_handle_; - CHECK_EQ(handle->current_read_.get(), req_wrap); - } - - // ReadStart() checks whether current_read_ is set to determine whether - // a read is in progress. Moving it into a local variable makes sure that - // the ReadStart() call below doesn't think we're still actively reading. - BaseObjectPtr read_wrap = - std::move(handle->current_read_); - - ssize_t result = req->result; - uv_buf_t buffer = read_wrap->buffer_; - - uv_fs_req_cleanup(req); + current_read_->Dispatch( + uv_fs_read, + fd_, + ¤t_read_->buffer_, + 1, + read_offset_, + uv_fs_callback_t{[](uv_fs_t* req) { + FileHandle* handle; + { + FileHandleReadWrap* req_wrap = FileHandleReadWrap::from_req(req); + FS_ASYNC_TRACE_END1( + req->fs_type, req_wrap, "result", static_cast(req->result)) + handle = req_wrap->file_handle_; + CHECK_EQ(handle->current_read_.get(), req_wrap); + } - // Push the read wrap back to the freelist, or let it be destroyed - // once we’re exiting the current scope. - constexpr size_t kWantedFreelistFill = 100; - auto& freelist = handle->binding_data_->file_handle_read_wrap_freelist; - if (freelist.size() < kWantedFreelistFill) { - read_wrap->Reset(); - freelist.emplace_back(std::move(read_wrap)); - } + // ReadStart() checks whether current_read_ is set to determine whether + // a read is in progress. Moving it into a local variable makes sure + // that the ReadStart() call below doesn't think we're still actively + // reading. + BaseObjectPtr read_wrap = + std::move(handle->current_read_); + + ssize_t result = req->result; + uv_buf_t buffer = read_wrap->buffer_; + + uv_fs_req_cleanup(req); + + // Push the read wrap back to the freelist, or let it be destroyed + // once we’re exiting the current scope. + constexpr size_t kWantedFreelistFill = 100; + auto& freelist = handle->binding_data_->file_handle_read_wrap_freelist; + if (freelist.size() < kWantedFreelistFill) { + read_wrap->Reset(); + freelist.emplace_back(std::move(read_wrap)); + } - if (result >= 0) { - // Read at most as many bytes as we originally planned to. - if (handle->read_length_ >= 0 && handle->read_length_ < result) - result = handle->read_length_; + if (result >= 0) { + // Read at most as many bytes as we originally planned to. + if (handle->read_length_ >= 0 && handle->read_length_ < result) + result = handle->read_length_; - // If we read data and we have an expected length, decrease it by - // how much we have read. - if (handle->read_length_ >= 0) - handle->read_length_ -= result; + // If we read data and we have an expected length, decrease it by + // how much we have read. + if (handle->read_length_ >= 0) handle->read_length_ -= result; - // If we have an offset, increase it by how much we have read. - if (handle->read_offset_ >= 0) - handle->read_offset_ += result; - } + // If we have an offset, increase it by how much we have read. + if (handle->read_offset_ >= 0) handle->read_offset_ += result; + } - // Reading 0 bytes from a file always means EOF, or that we reached - // the end of the requested range. - if (result == 0) - result = UV_EOF; + // Reading 0 bytes from a file always means EOF, or that we reached + // the end of the requested range. + if (result == 0) result = UV_EOF; - handle->EmitRead(result, buffer); + handle->EmitRead(result, buffer); - // Start over, if EmitRead() didn’t tell us to stop. - if (handle->reading_) - handle->ReadStart(); - }}); + // Start over, if EmitRead() didn’t tell us to stop. + if (handle->reading_) handle->ReadStart(); + }}); return 0; } @@ -689,23 +704,23 @@ int FileHandle::DoShutdown(ShutdownWrap* req_wrap) { closing_ = true; CHECK_NE(fd_, -1); FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, wrap) - wrap->Dispatch(uv_fs_close, fd_, uv_fs_callback_t{[](uv_fs_t* req) { - FileHandleCloseWrap* wrap = static_cast( - FileHandleCloseWrap::from_req(req)); - FS_ASYNC_TRACE_END1( - req->fs_type, wrap, "result", static_cast(req->result)) - FileHandle* handle = static_cast(wrap->stream()); - handle->AfterClose(); - - int result = static_cast(req->result); - uv_fs_req_cleanup(req); - wrap->Done(result); - }}); + wrap->Dispatch( + uv_fs_close, fd_, uv_fs_callback_t{[](uv_fs_t* req) { + FileHandleCloseWrap* wrap = static_cast( + FileHandleCloseWrap::from_req(req)); + FS_ASYNC_TRACE_END1( + req->fs_type, wrap, "result", static_cast(req->result)) + FileHandle* handle = static_cast(wrap->stream()); + handle->AfterClose(); + + int result = static_cast(req->result); + uv_fs_req_cleanup(req); + wrap->Done(result); + }}); return 0; } - void FSReqCallback::Reject(Local reject) { MakeCallback(env()->oncomplete_string(), 1, &reject); } @@ -719,10 +734,7 @@ void FSReqCallback::ResolveStatFs(const uv_statfs_t* stat) { } void FSReqCallback::Resolve(Local value) { - Local argv[2] { - Null(env()->isolate()), - value - }; + Local argv[2]{Null(env()->isolate()), value}; MakeCallback(env()->oncomplete_string(), value->IsUndefined() ? 1 : arraysize(argv), argv); @@ -768,7 +780,7 @@ void FSReqAfterScope::Clear() { // which is also why the errors should have been constructed // in JS for more flexibility. void FSReqAfterScope::Reject(uv_fs_t* req) { - BaseObjectPtr wrap { wrap_ }; + BaseObjectPtr wrap{wrap_}; Local exception = UVException(wrap_->env()->isolate(), static_cast(req->result), wrap_->syscall(), @@ -796,8 +808,7 @@ void AfterNoArgs(uv_fs_t* req) { FSReqAfterScope after(req_wrap, req); FS_ASYNC_TRACE_END1( req->fs_type, req_wrap, "result", static_cast(req->result)) - if (after.Proceed()) - req_wrap->Resolve(Undefined(req_wrap->env()->isolate())); + if (after.Proceed()) req_wrap->Resolve(Undefined(req_wrap->env()->isolate())); } void AfterStat(uv_fs_t* req) { @@ -949,8 +960,7 @@ void AfterScanDir(uv_fs_t* req) { uv_dirent_t ent; r = uv_fs_scandir_next(req, &ent); - if (r == UV_EOF) - break; + if (r == UV_EOF) break; if (r != 0) { return req_wrap->Reject( UVException(isolate, r, nullptr, req_wrap->syscall(), req->path)); @@ -1005,8 +1015,15 @@ void Access(const FunctionCallbackInfo& args) { path.ToStringView()); FS_ASYNC_TRACE_BEGIN1( UV_FS_ACCESS, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "access", UTF8, AfterNoArgs, - uv_fs_access, *path, mode); + AsyncCall(env, + req_wrap_async, + args, + "access", + UTF8, + AfterNoArgs, + uv_fs_access, + *path, + mode); } else { // access(path, mode) THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); @@ -1033,8 +1050,8 @@ void Close(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 1); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_CLOSE, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "close", UTF8, AfterNoArgs, - uv_fs_close, fd); + AsyncCall( + env, req_wrap_async, args, "close", UTF8, AfterNoArgs, uv_fs_close, fd); } else { // close(fd) FSReqWrapSync req_wrap_sync("close"); FS_SYNC_TRACE_BEGIN(close); @@ -1172,7 +1189,9 @@ static void Stat(const FunctionCallbackInfo& args) { if (is_uv_error(result)) { return; } - Local arr = FillGlobalStatsArray(binding_data, use_bigint, + Local arr = FillGlobalStatsArray( + binding_data, + use_bigint, static_cast(req_wrap_sync.req.ptr)); args.GetReturnValue().Set(arr); } @@ -1196,8 +1215,14 @@ static void LStat(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_LSTAT, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "lstat", UTF8, AfterStat, - uv_fs_lstat, *path); + AsyncCall(env, + req_wrap_async, + args, + "lstat", + UTF8, + AfterStat, + uv_fs_lstat, + *path); } else { // lstat(path, use_bigint, undefined, throw_if_no_entry) bool do_not_throw_if_no_entry = args[3]->IsFalse(); FSReqWrapSync req_wrap_sync("lstat", *path); @@ -1214,7 +1239,9 @@ static void LStat(const FunctionCallbackInfo& args) { return; } - Local arr = FillGlobalStatsArray(binding_data, use_bigint, + Local arr = FillGlobalStatsArray( + binding_data, + use_bigint, static_cast(req_wrap_sync.req.ptr)); args.GetReturnValue().Set(arr); } @@ -1238,8 +1265,8 @@ static void FStat(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_FSTAT, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "fstat", UTF8, AfterStat, - uv_fs_fstat, fd); + AsyncCall( + env, req_wrap_async, args, "fstat", UTF8, AfterStat, uv_fs_fstat, fd); } else { // fstat(fd, use_bigint, undefined, do_not_throw_error) bool do_not_throw_error = args[2]->IsTrue(); const auto should_throw = [do_not_throw_error](int result) { @@ -1254,7 +1281,9 @@ static void FStat(const FunctionCallbackInfo& args) { return; } - Local arr = FillGlobalStatsArray(binding_data, use_bigint, + Local arr = FillGlobalStatsArray( + binding_data, + use_bigint, static_cast(req_wrap_sync.req.ptr)); args.GetReturnValue().Set(arr); } @@ -1345,8 +1374,18 @@ static void Symlink(const FunctionCallbackInfo& args) { TRACE_STR_COPY(*target), "path", TRACE_STR_COPY(*path)) - AsyncDestCall(env, req_wrap_async, args, "symlink", *path, path.length(), - UTF8, AfterNoArgs, uv_fs_symlink, *target, *path, flags); + AsyncDestCall(env, + req_wrap_async, + args, + "symlink", + *path, + path.length(), + UTF8, + AfterNoArgs, + uv_fs_symlink, + *target, + *path, + flags); } else { // symlink(target, path, flags, undefined, ctx) FSReqWrapSync req_wrap_sync("symlink", *target, *path); FS_SYNC_TRACE_BEGIN(symlink); @@ -1401,8 +1440,17 @@ static void Link(const FunctionCallbackInfo& args) { TRACE_STR_COPY(*src), "dest", TRACE_STR_COPY(*dest)) - AsyncDestCall(env, req_wrap_async, args, "link", *dest, dest.length(), UTF8, - AfterNoArgs, uv_fs_link, *src, *dest); + AsyncDestCall(env, + req_wrap_async, + args, + "link", + *dest, + dest.length(), + UTF8, + AfterNoArgs, + uv_fs_link, + *src, + *dest); } else { // link(src, dest) // To avoid bypass the link target should be allowed to read and write THROW_IF_INSUFFICIENT_PERMISSIONS( @@ -1439,8 +1487,14 @@ static void ReadLink(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_READLINK, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "readlink", encoding, AfterStringPtr, - uv_fs_readlink, *path); + AsyncCall(env, + req_wrap_async, + args, + "readlink", + encoding, + AfterStringPtr, + uv_fs_readlink, + *path); } else { // readlink(path, encoding) FSReqWrapSync req_wrap_sync("readlink", *path); FS_SYNC_TRACE_BEGIN(readlink); @@ -1499,9 +1553,17 @@ static void Rename(const FunctionCallbackInfo& args) { TRACE_STR_COPY(*old_path), "new_path", TRACE_STR_COPY(*new_path)) - AsyncDestCall(env, req_wrap_async, args, "rename", *new_path, - new_path.length(), UTF8, AfterNoArgs, uv_fs_rename, - *old_path, *new_path); + AsyncDestCall(env, + req_wrap_async, + args, + "rename", + *new_path, + new_path.length(), + UTF8, + AfterNoArgs, + uv_fs_rename, + *old_path, + *new_path); } else { // rename(old_path, new_path) THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemRead, view_old_path); @@ -1537,8 +1599,15 @@ static void FTruncate(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 2); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_FTRUNCATE, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "ftruncate", UTF8, AfterNoArgs, - uv_fs_ftruncate, fd, len); + AsyncCall(env, + req_wrap_async, + args, + "ftruncate", + UTF8, + AfterNoArgs, + uv_fs_ftruncate, + fd, + len); } else { // ftruncate(fd, len) FSReqWrapSync req_wrap_sync("ftruncate"); FS_SYNC_TRACE_BEGIN(ftruncate); @@ -1562,8 +1631,14 @@ static void Fdatasync(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 1); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_FDATASYNC, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "fdatasync", UTF8, AfterNoArgs, - uv_fs_fdatasync, fd); + AsyncCall(env, + req_wrap_async, + args, + "fdatasync", + UTF8, + AfterNoArgs, + uv_fs_fdatasync, + fd); } else { // fdatasync(fd) FSReqWrapSync req_wrap_sync("fdatasync"); FS_SYNC_TRACE_BEGIN(fdatasync); @@ -1587,8 +1662,8 @@ static void Fsync(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 1); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_FSYNC, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "fsync", UTF8, AfterNoArgs, - uv_fs_fsync, fd); + AsyncCall( + env, req_wrap_async, args, "fsync", UTF8, AfterNoArgs, uv_fs_fsync, fd); } else { FSReqWrapSync req_wrap_sync("fsync"); FS_SYNC_TRACE_BEGIN(fsync); @@ -1617,8 +1692,14 @@ static void Unlink(const FunctionCallbackInfo& args) { path.ToStringView()); FS_ASYNC_TRACE_BEGIN1( UV_FS_UNLINK, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "unlink", UTF8, AfterNoArgs, - uv_fs_unlink, *path); + AsyncCall(env, + req_wrap_async, + args, + "unlink", + UTF8, + AfterNoArgs, + uv_fs_unlink, + *path); } else { // unlink(path) THROW_IF_INSUFFICIENT_PERMISSIONS( env, @@ -1648,8 +1729,14 @@ static void RMDir(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_RMDIR, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "rmdir", UTF8, AfterNoArgs, - uv_fs_rmdir, *path); + AsyncCall(env, + req_wrap_async, + args, + "rmdir", + UTF8, + AfterNoArgs, + uv_fs_rmdir, + *path); } else { // rmdir(path) FSReqWrapSync req_wrap_sync("rmdir", *path); FS_SYNC_TRACE_BEGIN(rmdir); @@ -1809,7 +1896,7 @@ int MKDirpSync(uv_loop_t* loop, if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) { uv_fs_req_cleanup(req); if (orig_err == UV_EEXIST && - req_wrap->continuation_data()->paths().size() > 0) { + req_wrap->continuation_data()->paths().size() > 0) { return UV_ENOTDIR; } return UV_EEXIST; @@ -1825,11 +1912,8 @@ int MKDirpSync(uv_loop_t* loop, return 0; } -int MKDirpAsync(uv_loop_t* loop, - uv_fs_t* req, - const char* path, - int mode, - uv_fs_cb cb) { +int MKDirpAsync( + uv_loop_t* loop, uv_fs_t* req, const char* path, int mode, uv_fs_cb cb) { FSReqBase* req_wrap = FSReqBase::from_req(req); // on the first iteration of algorithm, stash state information. if (req_wrap->continuation_data() == nullptr) { @@ -1840,83 +1924,93 @@ int MKDirpAsync(uv_loop_t* loop, // on each iteration of algorithm, mkdir directory on top of stack. std::string next_path = req_wrap->continuation_data()->PopPath(); - int err = uv_fs_mkdir(loop, req, next_path.c_str(), mode, - uv_fs_callback_t{[](uv_fs_t* req) { - FSReqBase* req_wrap = FSReqBase::from_req(req); - Environment* env = req_wrap->env(); - uv_loop_t* loop = env->event_loop(); - std::string path = req->path; - int err = static_cast(req->result); - - while (true) { - switch (err) { - // Note: uv_fs_req_cleanup in terminal paths will be called by - // FSReqAfterScope::~FSReqAfterScope() - case 0: { - if (req_wrap->continuation_data()->paths().empty()) { - req_wrap->continuation_data()->MaybeSetFirstPath(path); - req_wrap->continuation_data()->Done(0); - } else { - req_wrap->continuation_data()->MaybeSetFirstPath(path); - uv_fs_req_cleanup(req); - MKDirpAsync(loop, req, path.c_str(), - req_wrap->continuation_data()->mode(), nullptr); - } - break; - } - case UV_EACCES: - case UV_ENOSPC: - case UV_ENOTDIR: - case UV_EPERM: { - req_wrap->continuation_data()->Done(err); - break; - } - case UV_ENOENT: { - std::string dirname = - path.substr(0, path.find_last_of(kPathSeparator)); - if (dirname != path) { - req_wrap->continuation_data()->PushPath(path); - req_wrap->continuation_data()->PushPath(std::move(dirname)); - } else if (req_wrap->continuation_data()->paths().empty()) { - err = UV_EEXIST; - continue; - } - uv_fs_req_cleanup(req); - MKDirpAsync(loop, req, path.c_str(), - req_wrap->continuation_data()->mode(), nullptr); - break; - } - default: - uv_fs_req_cleanup(req); - // Stash err for use in the callback. - req->data = reinterpret_cast(static_cast(err)); - int err = uv_fs_stat(loop, req, path.c_str(), - uv_fs_callback_t{[](uv_fs_t* req) { - FSReqBase* req_wrap = FSReqBase::from_req(req); - int err = static_cast(req->result); - if (reinterpret_cast(req->data) == UV_EEXIST && - req_wrap->continuation_data()->paths().size() > 0) { - if (err == 0 && S_ISDIR(req->statbuf.st_mode)) { - Environment* env = req_wrap->env(); - uv_loop_t* loop = env->event_loop(); - std::string path = req->path; + int err = uv_fs_mkdir( + loop, req, next_path.c_str(), mode, uv_fs_callback_t{[](uv_fs_t* req) { + FSReqBase* req_wrap = FSReqBase::from_req(req); + Environment* env = req_wrap->env(); + uv_loop_t* loop = env->event_loop(); + std::string path = req->path; + int err = static_cast(req->result); + + while (true) { + switch (err) { + // Note: uv_fs_req_cleanup in terminal paths will be called by + // FSReqAfterScope::~FSReqAfterScope() + case 0: { + if (req_wrap->continuation_data()->paths().empty()) { + req_wrap->continuation_data()->MaybeSetFirstPath(path); + req_wrap->continuation_data()->Done(0); + } else { + req_wrap->continuation_data()->MaybeSetFirstPath(path); uv_fs_req_cleanup(req); - MKDirpAsync(loop, req, path.c_str(), - req_wrap->continuation_data()->mode(), nullptr); - return; + MKDirpAsync(loop, + req, + path.c_str(), + req_wrap->continuation_data()->mode(), + nullptr); + } + break; + } + case UV_EACCES: + case UV_ENOSPC: + case UV_ENOTDIR: + case UV_EPERM: { + req_wrap->continuation_data()->Done(err); + break; + } + case UV_ENOENT: { + std::string dirname = + path.substr(0, path.find_last_of(kPathSeparator)); + if (dirname != path) { + req_wrap->continuation_data()->PushPath(path); + req_wrap->continuation_data()->PushPath(std::move(dirname)); + } else if (req_wrap->continuation_data()->paths().empty()) { + err = UV_EEXIST; + continue; } - err = UV_ENOTDIR; + uv_fs_req_cleanup(req); + MKDirpAsync(loop, + req, + path.c_str(), + req_wrap->continuation_data()->mode(), + nullptr); + break; } - // verify that the path pointed to is actually a directory. - if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) err = UV_EEXIST; - req_wrap->continuation_data()->Done(err); - }}); - if (err < 0) req_wrap->continuation_data()->Done(err); + default: + uv_fs_req_cleanup(req); + // Stash err for use in the callback. + req->data = reinterpret_cast(static_cast(err)); + int err = uv_fs_stat( + loop, req, path.c_str(), uv_fs_callback_t{[](uv_fs_t* req) { + FSReqBase* req_wrap = FSReqBase::from_req(req); + int err = static_cast(req->result); + if (reinterpret_cast(req->data) == UV_EEXIST && + req_wrap->continuation_data()->paths().size() > 0) { + if (err == 0 && S_ISDIR(req->statbuf.st_mode)) { + Environment* env = req_wrap->env(); + uv_loop_t* loop = env->event_loop(); + std::string path = req->path; + uv_fs_req_cleanup(req); + MKDirpAsync(loop, + req, + path.c_str(), + req_wrap->continuation_data()->mode(), + nullptr); + return; + } + err = UV_ENOTDIR; + } + // verify that the path pointed to is actually a directory. + if (err == 0 && !S_ISDIR(req->statbuf.st_mode)) + err = UV_EEXIST; + req_wrap->continuation_data()->Done(err); + }}); + if (err < 0) req_wrap->continuation_data()->Done(err); + break; + } break; - } - break; - } - }}); + } + }}); return err; } @@ -1945,9 +2039,15 @@ static void MKDir(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_UNLINK, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "mkdir", UTF8, + AsyncCall(env, + req_wrap_async, + args, + "mkdir", + UTF8, mkdirp ? AfterMkdirp : AfterNoArgs, - mkdirp ? MKDirpAsync : uv_fs_mkdir, *path, mode); + mkdirp ? MKDirpAsync : uv_fs_mkdir, + *path, + mode); } else { // mkdir(path, mode, recursive) FSReqWrapSync req_wrap_sync("mkdir", *path); FS_SYNC_TRACE_BEGIN(mkdir); @@ -1997,8 +2097,14 @@ static void RealPath(const FunctionCallbackInfo& args) { path.ToStringView()); FS_ASYNC_TRACE_BEGIN1( UV_FS_REALPATH, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "realpath", encoding, AfterStringPtr, - uv_fs_realpath, *path); + AsyncCall(env, + req_wrap_async, + args, + "realpath", + encoding, + AfterStringPtr, + uv_fs_realpath, + *path); } else { // realpath(path, encoding, undefined, ctx) THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); @@ -2096,8 +2202,7 @@ static void ReadDir(const FunctionCallbackInfo& args) { uv_dirent_t ent; r = uv_fs_scandir_next(&(req_wrap_sync.req), &ent); - if (r == UV_EOF) - break; + if (r == UV_EOF) break; if (is_uv_error(r)) { env->ThrowUVException(r, "scandir", nullptr, *path); return; @@ -2115,13 +2220,10 @@ static void ReadDir(const FunctionCallbackInfo& args) { } } - Local names = Array::New(isolate, name_v.data(), name_v.size()); if (with_types) { Local result[] = { - names, - Array::New(isolate, type_v.data(), type_v.size()) - }; + names, Array::New(isolate, type_v.data(), type_v.size())}; args.GetReturnValue().Set(Array::New(isolate, result, arraysize(result))); } else { args.GetReturnValue().Set(names); @@ -2216,8 +2318,16 @@ static void Open(const FunctionCallbackInfo& args) { req_wrap_async->set_is_plain_open(true); FS_ASYNC_TRACE_BEGIN1( UV_FS_OPEN, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterInteger, - uv_fs_open, *path, flags, mode); + AsyncCall(env, + req_wrap_async, + args, + "open", + UTF8, + AfterInteger, + uv_fs_open, + *path, + flags, + mode); } else { // open(path, flags, mode) if (CheckOpenPermissions(env, path, flags).IsNothing()) return; FSReqWrapSync req_wrap_sync("open", *path); @@ -2255,8 +2365,16 @@ static void OpenFileHandle(const FunctionCallbackInfo& args) { if (req_wrap_async != nullptr) { // openFileHandle(path, flags, mode, req) FS_ASYNC_TRACE_BEGIN1( UV_FS_OPEN, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterOpenFileHandle, - uv_fs_open, *path, flags, mode); + AsyncCall(env, + req_wrap_async, + args, + "open", + UTF8, + AfterOpenFileHandle, + uv_fs_open, + *path, + flags, + mode); } else { // openFileHandle(path, flags, mode, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; @@ -2323,9 +2441,18 @@ static void CopyFile(const FunctionCallbackInfo& args) { TRACE_STR_COPY(*src), "dest", TRACE_STR_COPY(*dest)) - AsyncDestCall(env, req_wrap_async, args, "copyfile", - *dest, dest.length(), UTF8, AfterNoArgs, - uv_fs_copyfile, *src, *dest, flags); + AsyncDestCall(env, + req_wrap_async, + args, + "copyfile", + *dest, + dest.length(), + UTF8, + AfterNoArgs, + uv_fs_copyfile, + *src, + *dest, + flags); } else { // copyFile(src, dest, flags) THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemRead, src.ToStringView()); @@ -2386,8 +2513,17 @@ static void WriteBuffer(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 5); if (req_wrap_async != nullptr) { // write(fd, buffer, off, len, pos, req) FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "write", UTF8, AfterInteger, - uv_fs_write, fd, &uvbuf, 1, pos); + AsyncCall(env, + req_wrap_async, + args, + "write", + UTF8, + AfterInteger, + uv_fs_write, + fd, + &uvbuf, + 1, + pos); } else { // write(fd, buffer, off, len, pos, undefined, ctx) CHECK_EQ(argc, 7); FSReqWrapSync req_wrap_sync; @@ -2411,7 +2547,6 @@ static void WriteBuffer(const FunctionCallbackInfo& args) { } } - // Wrapper for writev(2). // // bytesWritten = writev(fd, chunks, position, callback) @@ -2472,7 +2607,6 @@ static void WriteBuffers(const FunctionCallbackInfo& args) { } } - // Wrapper for write(2). // // bytesWritten = write(fd, string, position, enc, callback) @@ -2536,12 +2670,8 @@ static void WriteString(const FunctionCallbackInfo& args) { stack_buffer.SetLengthAndZeroTerminate(len); uv_buf_t uvbuf = uv_buf_init(*stack_buffer, len); FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async) - int err = req_wrap_async->Dispatch(uv_fs_write, - fd, - &uvbuf, - 1, - pos, - AfterInteger); + int err = + req_wrap_async->Dispatch(uv_fs_write, fd, &uvbuf, 1, pos, AfterInteger); if (err < 0) { uv_fs_t* uv_req = req_wrap_async->req(); uv_req->result = err; @@ -2553,13 +2683,11 @@ static void WriteString(const FunctionCallbackInfo& args) { CHECK_EQ(argc, 6); FSReqBase::FSReqBuffer stack_buffer; if (buf == nullptr) { - if (!StringBytes::StorageSize(isolate, value, enc).To(&len)) - return; + if (!StringBytes::StorageSize(isolate, value, enc).To(&len)) return; stack_buffer.AllocateSufficientStorage(len + 1); // StorageSize may return too large a char, so correct the actual length // by the write size - len = StringBytes::Write(isolate, *stack_buffer, - len, args[1], enc); + len = StringBytes::Write(isolate, *stack_buffer, len, args[1], enc); stack_buffer.SetLengthAndZeroTerminate(len); buf = *stack_buffer; } @@ -2703,9 +2831,8 @@ static void Read(const FunctionCallbackInfo& args) { CHECK(Buffer::IsWithinBounds(off, len, buffer_length)); CHECK(IsSafeJsInt(args[4]) || args[4]->IsBigInt()); - const int64_t pos = args[4]->IsNumber() ? - args[4].As()->Value() : - args[4].As()->Int64Value(); + const int64_t pos = args[4]->IsNumber() ? args[4].As()->Value() + : args[4].As()->Int64Value(); char* buf = buffer_data + off; uv_buf_t uvbuf = uv_buf_init(buf, len); @@ -2714,8 +2841,17 @@ static void Read(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 5); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "read", UTF8, AfterInteger, - uv_fs_read, fd, &uvbuf, 1, pos); + AsyncCall(env, + req_wrap_async, + args, + "read", + UTF8, + AfterInteger, + uv_fs_read, + fd, + &uvbuf, + 1, + pos); } else { // read(fd, buffer, offset, len, pos) FSReqWrapSync req_wrap_sync("read"); FS_SYNC_TRACE_BEGIN(read); @@ -2839,8 +2975,17 @@ static void ReadBuffers(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 3); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_READ, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "read", UTF8, AfterInteger, - uv_fs_read, fd, *iovs, iovs.length(), pos); + AsyncCall(env, + req_wrap_async, + args, + "read", + UTF8, + AfterInteger, + uv_fs_read, + fd, + *iovs, + iovs.length(), + pos); } else { // readBuffers(fd, buffers, undefined, ctx) FSReqWrapSync req_wrap_sync("read"); FS_SYNC_TRACE_BEGIN(read); @@ -2854,7 +2999,6 @@ static void ReadBuffers(const FunctionCallbackInfo& args) { } } - /* fs.chmod(path, mode); * Wrapper for chmod(1) / EIO_CHMOD */ @@ -2878,8 +3022,15 @@ static void Chmod(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_CHMOD, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "chmod", UTF8, AfterNoArgs, - uv_fs_chmod, *path, mode); + AsyncCall(env, + req_wrap_async, + args, + "chmod", + UTF8, + AfterNoArgs, + uv_fs_chmod, + *path, + mode); } else { // chmod(path, mode) FSReqWrapSync req_wrap_sync("chmod", *path); FS_SYNC_TRACE_BEGIN(chmod); @@ -2888,7 +3039,6 @@ static void Chmod(const FunctionCallbackInfo& args) { } } - /* fs.fchmod(fd, mode); * Wrapper for fchmod(1) / EIO_FCHMOD */ @@ -2910,8 +3060,15 @@ static void FChmod(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 2); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_FCHMOD, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "fchmod", UTF8, AfterNoArgs, - uv_fs_fchmod, fd, mode); + AsyncCall(env, + req_wrap_async, + args, + "fchmod", + UTF8, + AfterNoArgs, + uv_fs_fchmod, + fd, + mode); } else { // fchmod(fd, mode) FSReqWrapSync req_wrap_sync("fchmod"); FS_SYNC_TRACE_BEGIN(fchmod); @@ -2949,8 +3106,16 @@ static void Chown(const FunctionCallbackInfo& args) { path.ToStringView()); FS_ASYNC_TRACE_BEGIN1( UV_FS_CHOWN, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "chown", UTF8, AfterNoArgs, - uv_fs_chown, *path, uid, gid); + AsyncCall(env, + req_wrap_async, + args, + "chown", + UTF8, + AfterNoArgs, + uv_fs_chown, + *path, + uid, + gid); } else { // chown(path, uid, gid) THROW_IF_INSUFFICIENT_PERMISSIONS( env, @@ -2963,7 +3128,6 @@ static void Chown(const FunctionCallbackInfo& args) { } } - /* fs.fchown(fd, uid, gid); * Wrapper for fchown(1) / EIO_FCHOWN */ @@ -2988,8 +3152,16 @@ static void FChown(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 3); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_FCHOWN, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "fchown", UTF8, AfterNoArgs, - uv_fs_fchown, fd, uid, gid); + AsyncCall(env, + req_wrap_async, + args, + "fchown", + UTF8, + AfterNoArgs, + uv_fs_fchown, + fd, + uid, + gid); } else { // fchown(fd, uid, gid) FSReqWrapSync req_wrap_sync("fchown"); FS_SYNC_TRACE_BEGIN(fchown); @@ -2998,7 +3170,6 @@ static void FChown(const FunctionCallbackInfo& args) { } } - static void LChown(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -3025,8 +3196,16 @@ static void LChown(const FunctionCallbackInfo& args) { path.ToStringView()); FS_ASYNC_TRACE_BEGIN1( UV_FS_LCHOWN, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "lchown", UTF8, AfterNoArgs, - uv_fs_lchown, *path, uid, gid); + AsyncCall(env, + req_wrap_async, + args, + "lchown", + UTF8, + AfterNoArgs, + uv_fs_lchown, + *path, + uid, + gid); } else { // lchown(path, uid, gid) THROW_IF_INSUFFICIENT_PERMISSIONS( env, @@ -3039,7 +3218,6 @@ static void LChown(const FunctionCallbackInfo& args) { } } - static void UTimes(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -3063,8 +3241,16 @@ static void UTimes(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_UTIME, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "utime", UTF8, AfterNoArgs, - uv_fs_utime, *path, atime, mtime); + AsyncCall(env, + req_wrap_async, + args, + "utime", + UTF8, + AfterNoArgs, + uv_fs_utime, + *path, + atime, + mtime); } else { // utimes(path, atime, mtime) FSReqWrapSync req_wrap_sync("utime", *path); FS_SYNC_TRACE_BEGIN(utimes); @@ -3095,8 +3281,16 @@ static void FUTimes(const FunctionCallbackInfo& args) { FSReqBase* req_wrap_async = GetReqWrap(args, 3); CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN0(UV_FS_FUTIME, req_wrap_async) - AsyncCall(env, req_wrap_async, args, "futime", UTF8, AfterNoArgs, - uv_fs_futime, fd, atime, mtime); + AsyncCall(env, + req_wrap_async, + args, + "futime", + UTF8, + AfterNoArgs, + uv_fs_futime, + fd, + atime, + mtime); } else { // futimes(fd, atime, mtime) FSReqWrapSync req_wrap_sync("futime"); FS_SYNC_TRACE_BEGIN(futimes); @@ -3129,8 +3323,16 @@ static void LUTimes(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(req_wrap_async); FS_ASYNC_TRACE_BEGIN1( UV_FS_LUTIME, req_wrap_async, "path", TRACE_STR_COPY(*path)) - AsyncCall(env, req_wrap_async, args, "lutime", UTF8, AfterNoArgs, - uv_fs_lutime, *path, atime, mtime); + AsyncCall(env, + req_wrap_async, + args, + "lutime", + UTF8, + AfterNoArgs, + uv_fs_lutime, + *path, + atime, + mtime); } else { // lutimes(path, atime, mtime) FSReqWrapSync req_wrap_sync("lutime", *path); FS_SYNC_TRACE_BEGIN(lutimes); @@ -3167,8 +3369,14 @@ static void Mkdtemp(const FunctionCallbackInfo& args) { tmpl.ToStringView()); FS_ASYNC_TRACE_BEGIN1( UV_FS_MKDTEMP, req_wrap_async, "path", TRACE_STR_COPY(*tmpl)) - AsyncCall(env, req_wrap_async, args, "mkdtemp", encoding, AfterStringPath, - uv_fs_mkdtemp, *tmpl); + AsyncCall(env, + req_wrap_async, + args, + "mkdtemp", + encoding, + AfterStringPath, + uv_fs_mkdtemp, + *tmpl); } else { // mkdtemp(tmpl, encoding) THROW_IF_INSUFFICIENT_PERMISSIONS( env, @@ -3432,15 +3640,18 @@ static void CpSyncOverrideFile(const FunctionCallbackInfo& args) { THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView()); + auto src_path = src.ToPath(); + auto dest_path = dest.ToPath(); + std::error_code error; - if (!std::filesystem::remove(*dest, error)) { + if (!std::filesystem::remove(dest_path, error)) { return env->ThrowStdErrException(error, "unlink", *dest); } if (mode == 0) { // if no mode is specified use the faster std::filesystem API - if (!std::filesystem::copy_file(*src, *dest, error)) { + if (!std::filesystem::copy_file(src_path, dest_path, error)) { return env->ThrowStdErrException(error, "cp", *dest); } } else { @@ -3453,7 +3664,7 @@ static void CpSyncOverrideFile(const FunctionCallbackInfo& args) { } if (preserve_timestamps) { - CopyUtimes(*src, *dest, env); + CopyUtimes(src_path, dest_path, env); } } @@ -3496,8 +3707,11 @@ static void CpSyncCopyDir(const FunctionCallbackInfo& args) { bool verbatim_symlinks = args[5]->IsTrue(); bool preserve_timestamps = args[6]->IsTrue(); + auto src_path = src.ToPath(); + auto dest_path = dest.ToPath(); + std::error_code error; - std::filesystem::create_directories(*dest, error); + std::filesystem::create_directories(dest_path, error); if (error) { return env->ThrowStdErrException(error, "cp", *dest); } @@ -3639,7 +3853,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo& args) { return true; }; - copy_dir_contents(std::filesystem::path(*src), std::filesystem::path(*dest)); + copy_dir_contents(src_path, dest_path); } BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile( @@ -4009,8 +4223,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, // Create Function Template for FSReqPromise Local fpt = FunctionTemplate::New(isolate); fpt->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); - Local promiseString = - FIXED_ONE_BYTE_STRING(isolate, "FSReqPromise"); + Local promiseString = FIXED_ONE_BYTE_STRING(isolate, "FSReqPromise"); fpt->SetClassName(promiseString); Local fpo = fpt->InstanceTemplate(); fpo->SetInternalFieldCount(FSReqBase::kInternalFieldCount); @@ -4020,6 +4233,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, Local fd = NewFunctionTemplate(isolate, FileHandle::New); fd->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); SetProtoMethod(isolate, fd, "close", FileHandle::Close); + SetProtoMethod(isolate, fd, "closeSync", FileHandle::CloseSync); SetProtoMethod(isolate, fd, "releaseFD", FileHandle::ReleaseFD); Local fdt = fd->InstanceTemplate(); fdt->SetInternalFieldCount(FileHandle::kInternalFieldCount); @@ -4029,8 +4243,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, // Create FunctionTemplate for FileHandle::CloseReq Local fdclose = FunctionTemplate::New(isolate); - fdclose->SetClassName(FIXED_ONE_BYTE_STRING(isolate, - "FileHandleCloseReq")); + fdclose->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "FileHandleCloseReq")); fdclose->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data)); Local fdcloset = fdclose->InstanceTemplate(); fdcloset->SetInternalFieldCount(FSReqBase::kInternalFieldCount); @@ -4108,6 +4321,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(FileHandle::New); registry->Register(FileHandle::Close); + registry->Register(FileHandle::CloseSync); registry->Register(FileHandle::ReleaseFD); StreamBase::RegisterExternalReferences(registry); } diff --git a/src/node_file.h b/src/node_file.h index 2213d590659595..5d04a7d4dd6af3 100644 --- a/src/node_file.h +++ b/src/node_file.h @@ -80,8 +80,7 @@ class BindingData : public SnapshotableObject { AliasedFloat64Array statfs_field_array; AliasedBigInt64Array statfs_field_bigint_array; - std::vector> - file_handle_read_wrap_freelist; + std::vector> file_handle_read_wrap_freelist; SERIALIZABLE_OBJECT_METHODS() SET_BINDING_ID(fs_binding_data) @@ -146,7 +145,8 @@ class FSReqBase : public ReqWrap { const char* data, size_t len, enum encoding encoding); - inline FSReqBuffer& Init(const char* syscall, size_t len, + inline FSReqBuffer& Init(const char* syscall, + size_t len, enum encoding encoding); virtual void Reject(v8::Local reject) = 0; @@ -240,8 +240,7 @@ inline v8::Local FillGlobalStatFsArray(BindingData* binding_data, template class FSReqPromise final : public FSReqBase { public: - static inline FSReqPromise* New(BindingData* binding_data, - bool use_bigint); + static inline FSReqPromise* New(BindingData* binding_data, bool use_bigint); inline ~FSReqPromise() override; inline void Reject(v8::Local reject) override; @@ -345,6 +344,9 @@ class FileHandle final : public AsyncWrap, public StreamBase { // be resolved once closing is complete. static void Close(const v8::FunctionCallbackInfo& args); + // Synchronously closes the FD. Throws on error. + static void CloseSync(const v8::FunctionCallbackInfo& args); + // Releases ownership of the FD. static void ReleaseFD(const v8::FunctionCallbackInfo& args); @@ -499,19 +501,27 @@ inline FSReqBase* GetReqWrap(const v8::FunctionCallbackInfo& args, // Returns nullptr if the operation fails from the start. template -inline FSReqBase* AsyncDestCall(Environment* env, FSReqBase* req_wrap, +inline FSReqBase* AsyncDestCall(Environment* env, + FSReqBase* req_wrap, const v8::FunctionCallbackInfo& args, - const char* syscall, const char* dest, - size_t len, enum encoding enc, uv_fs_cb after, - Func fn, Args... fn_args); + const char* syscall, + const char* dest, + size_t len, + enum encoding enc, + uv_fs_cb after, + Func fn, + Args... fn_args); // Returns nullptr if the operation fails from the start. template inline FSReqBase* AsyncCall(Environment* env, FSReqBase* req_wrap, const v8::FunctionCallbackInfo& args, - const char* syscall, enum encoding enc, - uv_fs_cb after, Func fn, Args... fn_args); + const char* syscall, + enum encoding enc, + uv_fs_cb after, + Func fn, + Args... fn_args); // Template counterpart of SYNC_CALL, except that it only puts // the error number and the syscall in the context instead of diff --git a/src/node_options.cc b/src/node_options.cc index 60c08b95c2c798..fed7df90371658 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -599,6 +599,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { &EnvironmentOptions::experimental_sqlite, kAllowedInEnvvar, true); + AddOption("--experimental-stream-iter", + "experimental iterable streams API (node:stream/iter)", + &EnvironmentOptions::experimental_stream_iter, + kAllowedInEnvvar); AddOption("--experimental-quic", #ifndef OPENSSL_NO_QUIC "experimental QUIC support", @@ -1228,6 +1232,7 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( "help system profilers to translate JavaScript interpreted frames", V8Option{}, kAllowedInEnvvar); + AddOption("--max-heap-size", "", V8Option{}, kAllowedInEnvvar); AddOption("--max-old-space-size", "", V8Option{}, kAllowedInEnvvar); AddOption("--max-old-space-size-percentage", "set V8's max old space size as a percentage of available memory " diff --git a/src/node_options.h b/src/node_options.h index 662003120d61f1..9e02a0bdbe3942 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -127,10 +127,9 @@ class EnvironmentOptions : public Options { bool experimental_fetch = true; bool experimental_websocket = true; bool experimental_sqlite = true; + bool experimental_stream_iter = false; bool webstorage = HAVE_SQLITE; -#ifndef OPENSSL_NO_QUIC bool experimental_quic = false; -#endif std::string localstorage_file; bool experimental_global_navigator = true; bool experimental_global_web_crypto = true; @@ -287,7 +286,7 @@ class PerIsolateOptions : public Options { PerIsolateOptions() = default; PerIsolateOptions(PerIsolateOptions&&) = default; - std::shared_ptr per_env { new EnvironmentOptions() }; + std::shared_ptr per_env{new EnvironmentOptions()}; bool track_heap_objects = false; bool report_uncaught_exception = false; bool report_on_signal = false; @@ -320,7 +319,7 @@ class PerProcessOptions : public Options { // using the node::per_process::cli_options_mutex, typically: // // Mutex::ScopedLock lock(node::per_process::cli_options_mutex); - std::shared_ptr per_isolate { new PerIsolateOptions() }; + std::shared_ptr per_isolate{new PerIsolateOptions()}; std::string title; std::string trace_event_categories; @@ -530,8 +529,7 @@ class OptionsParser { // if the option has a non-option argument (not starting with -) following it. void AddAlias(const char* from, const char* to); void AddAlias(const char* from, const std::vector& to); - void AddAlias(const char* from, - const std::initializer_list& to); + void AddAlias(const char* from, const std::initializer_list& to); // Add implications from some arbitrary option to a boolean one, either // in a way that makes `from` set `to` to true or to false. @@ -543,7 +541,7 @@ class OptionsParser { // type. template void Insert(const OptionsParser& child_options_parser, - ChildOptions* (Options::* get_child)()); + ChildOptions* (Options::*get_child)()); // Parse a sequence of options into an options struct, a list of // arguments that were parsed as options, a list of unknown/JS engine options, @@ -632,17 +630,15 @@ class OptionsParser { // These are helpers that make `Insert()` support properties of other // options structs, if we know how to access them. template - static auto Convert( - std::shared_ptr original, - ChildOptions* (Options::* get_child)()); + static auto Convert(std::shared_ptr original, + ChildOptions* (Options::*get_child)()); template - static auto Convert( - typename OptionsParser::OptionInfo original, - ChildOptions* (Options::* get_child)()); + static auto Convert(typename OptionsParser::OptionInfo original, + ChildOptions* (Options::*get_child)()); template static auto Convert( typename OptionsParser::Implication original, - ChildOptions* (Options::* get_child)()); + ChildOptions* (Options::*get_child)()); std::unordered_map options_; std::unordered_map> aliases_; @@ -669,10 +665,12 @@ class OptionsParser { using StringVector = std::vector; template -void Parse( - StringVector* const args, StringVector* const exec_args, - StringVector* const v8_args, OptionsType* const options, - OptionEnvvarSettings required_env_settings, StringVector* const errors); +void Parse(StringVector* const args, + StringVector* const exec_args, + StringVector* const v8_args, + OptionsType* const options, + OptionEnvvarSettings required_env_settings, + StringVector* const errors); } // namespace options_parser diff --git a/src/node_root_certs.h b/src/node_root_certs.h index cd14f09fdc4620..e3c77a175f9ccf 100644 --- a/src/node_root_certs.h +++ b/src/node_root_certs.h @@ -3469,7 +3469,7 @@ "ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c=\n" "-----END CERTIFICATE-----", -/* OISTE Server Root RSA G1 */ +/* OISTE Server Root RSA G1 */ "-----BEGIN CERTIFICATE-----\n" "MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBLMQswCQYD\n" "VQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2Vy\n" @@ -3499,4 +3499,22 @@ "axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZyBiElxky8j3C7DORe\n" "IoMt0r7+hVu05L0=\n" "-----END CERTIFICATE-----", + +/* e-Szigno TLS Root CA 2023 */ +"-----BEGIN CERTIFICATE-----\n" +"MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYDVQQGEwJI\n" +"VTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEM\n" +"DlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBUTFMgUm9vdCBDQSAyMDIzMB4X\n" +"DTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFowdTELMAkGA1UEBhMCSFUxETAPBgNVBAcM\n" +"CEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNyb3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4\n" +"NDQ5NzEiMCAGA1UEAwwZZS1Temlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIB\n" +"BgUrgQQAIwOBhgAEAGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y5\n" +"3wLCMAFSAL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v\n" +"SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMBAf8EBTAD\n" +"AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8KECgj3NXW+0UwHwYD\n" +"VR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZIzj0EAwQDgYsAMIGHAkIBLdqu\n" +"9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpty7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3\n" +"hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZlC9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxH\n" +"x1YwlGhza9IuqbnZLBwpvQy6uWWL\n" +"-----END CERTIFICATE-----", #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/node_sea.cc b/src/node_sea.cc index 1f340cf56b21aa..1be41e6f14146e 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -24,6 +24,7 @@ using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; using v8::Context; +using v8::Data; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; @@ -31,11 +32,13 @@ using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::MaybeLocal; +using v8::Module; using v8::NewStringType; using v8::Object; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; +using v8::UnboundModuleScript; using v8::Value; namespace node { @@ -542,7 +545,7 @@ std::optional ParseSingleExecutableConfig( "\"useCodeCache\" is redundant when \"useSnapshot\" is true\n"); } - // TODO(joyeecheung): support ESM with useSnapshot and useCodeCache. + // TODO(joyeecheung): support ESM with useSnapshot. if (result.main_format == ModuleFormat::kModule && static_cast(result.flags & SeaFlags::kUseSnapshot)) { FPrintF(stderr, @@ -551,14 +554,6 @@ std::optional ParseSingleExecutableConfig( return std::nullopt; } - if (result.main_format == ModuleFormat::kModule && - static_cast(result.flags & SeaFlags::kUseCodeCache)) { - FPrintF(stderr, - "\"mainFormat\": \"module\" is not supported when " - "\"useCodeCache\" is true\n"); - return std::nullopt; - } - if (result.main_path.empty()) { FPrintF(stderr, "\"main\" field of %s is not a non-empty string\n", @@ -616,7 +611,8 @@ ExitCode GenerateSnapshotForSEA(const SeaConfig& config, } std::optional GenerateCodeCache(std::string_view main_path, - std::string_view main_script) { + std::string_view main_script, + ModuleFormat format) { RAIIIsolate raii_isolate(SnapshotBuilder::GetEmbeddedSnapshotData()); Isolate* isolate = raii_isolate.get(); @@ -647,34 +643,62 @@ std::optional GenerateCodeCache(std::string_view main_path, return std::nullopt; } - LocalVector parameters( - isolate, - { - FIXED_ONE_BYTE_STRING(isolate, "exports"), - FIXED_ONE_BYTE_STRING(isolate, "require"), - FIXED_ONE_BYTE_STRING(isolate, "module"), - FIXED_ONE_BYTE_STRING(isolate, "__filename"), - FIXED_ONE_BYTE_STRING(isolate, "__dirname"), - }); - ScriptOrigin script_origin(filename, 0, 0, true); - ScriptCompiler::Source script_source(content, script_origin); - MaybeLocal maybe_fn = - ScriptCompiler::CompileFunction(context, - &script_source, - parameters.size(), - parameters.data(), - 0, - nullptr); - Local fn; - if (!maybe_fn.ToLocal(&fn)) { - return std::nullopt; + std::unique_ptr cache; + + if (format == ModuleFormat::kModule) { + // Using empty host defined options is fine as it is not part of the cache + // key and will be reset after deserialization. + ScriptOrigin origin(filename, + 0, // line offset + 0, // column offset + true, // is cross origin + -1, // script id + Local(), // source map URL + false, // is opaque + false, // is WASM + true, // is ES Module + Local()); // host defined options + ScriptCompiler::Source source(content, origin); + Local module; + if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) { + return std::nullopt; + } + Local unbound = module->GetUnboundModuleScript(); + cache.reset(ScriptCompiler::CreateCodeCache(unbound)); + } else { + // TODO(RaisinTen): Using the V8 code cache prevents us from using + // `import()` in the SEA code. Support it. Refs: + // https://github.com/nodejs/node/pull/48191#discussion_r1213271430 + // TODO(joyeecheung): this likely has been fixed by + // https://chromium-review.googlesource.com/c/v8/v8/+/5401780 - add a test + // and update docs. + LocalVector parameters( + isolate, + { + FIXED_ONE_BYTE_STRING(isolate, "exports"), + FIXED_ONE_BYTE_STRING(isolate, "require"), + FIXED_ONE_BYTE_STRING(isolate, "module"), + FIXED_ONE_BYTE_STRING(isolate, "__filename"), + FIXED_ONE_BYTE_STRING(isolate, "__dirname"), + }); + ScriptOrigin script_origin(filename, 0, 0, true); + ScriptCompiler::Source script_source(content, script_origin); + Local fn; + if (!ScriptCompiler::CompileFunction(context, + &script_source, + parameters.size(), + parameters.data(), + 0, + nullptr) + .ToLocal(&fn)) { + return std::nullopt; + } + cache.reset(ScriptCompiler::CreateCodeCacheForFunction(fn)); } - // TODO(RaisinTen): Using the V8 code cache prevents us from using `import()` - // in the SEA code. Support it. - // Refs: https://github.com/nodejs/node/pull/48191#discussion_r1213271430 - std::unique_ptr cache{ - ScriptCompiler::CreateCodeCacheForFunction(fn)}; + if (!cache) { + return std::nullopt; + } std::string code_cache(cache->data, cache->data + cache->length); return code_cache; } @@ -728,7 +752,7 @@ ExitCode GenerateSingleExecutableBlob( std::string code_cache; if (static_cast(config.flags & SeaFlags::kUseCodeCache)) { std::optional optional_code_cache = - GenerateCodeCache(config.main_path, main_script); + GenerateCodeCache(config.main_path, main_script, config.main_format); if (!optional_code_cache.has_value()) { FPrintF(stderr, "Cannot generate V8 code cache\n"); return ExitCode::kGenericUserError; diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 9c3aa6e0b4dc5f..91b80b4fb44c26 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -2157,7 +2157,8 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo& args) { Local filterFunc = filterValue.As(); - context.filterCallback = [&](std::string_view item) -> bool { + context.filterCallback = + [env, db, filterFunc](std::string_view item) -> bool { // If there was an error in the previous call to the filter's // callback, we skip calling it again. if (db->ignore_next_sqlite_error_) { diff --git a/src/node_version.h b/src/node_version.h index 6376a47f8837a9..07e6470db645b4 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -23,13 +23,13 @@ #define SRC_NODE_VERSION_H_ #define NODE_MAJOR_VERSION 25 -#define NODE_MINOR_VERSION 8 -#define NODE_PATCH_VERSION 3 +#define NODE_MINOR_VERSION 9 +#define NODE_PATCH_VERSION 0 #define NODE_VERSION_IS_LTS 0 #define NODE_VERSION_LTS_CODENAME "" -#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_worker.cc b/src/node_worker.cc index dccba51d507ed0..d2dd7a9e0e9e4f 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -1121,7 +1121,8 @@ static bool serializeProfile(Isolate* isolate, std::ostringstream& out_stream) { if (!profile) { return false; } - JSONWriter writer(out_stream, false); + profiler->StopSamplingHeapProfiler(); + JSONWriter writer(out_stream, true); writer.json_start(); writer.json_arraystart("samples"); @@ -1139,7 +1140,6 @@ static bool serializeProfile(Isolate* isolate, std::ostringstream& out_stream) { writer.json_objectend(); writer.json_end(); - profiler->StopSamplingHeapProfiler(); return true; } diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 9d49f13d07c125..792d800847e105 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -644,6 +644,12 @@ class CompressionStream : public AsyncWrap, CompressionStream* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); + if (wrap->write_in_progress_) { + wrap->env()->ThrowError( + "Cannot reset zlib stream while a write is in progress"); + return; + } + AllocScope alloc_scope(wrap); const CompressionError err = wrap->context()->ResetStream(); if (err.IsError()) diff --git a/src/util-inl.h b/src/util-inl.h index f8cccfef6b65b3..d59e30a635b08b 100644 --- a/src/util-inl.h +++ b/src/util-inl.h @@ -591,7 +591,9 @@ void ArrayBufferViewContents::Read(v8::Local abv) { static_assert(sizeof(T) == 1, "Only supports one-byte data at the moment"); length_ = abv->ByteLength(); if (length_ > sizeof(stack_storage_) || abv->HasBuffer()) { - data_ = static_cast(abv->Buffer()->Data()) + abv->ByteOffset(); + auto buf_data = abv->Buffer()->Data(); + data_ = buf_data != nullptr ? static_cast(buf_data) + abv->ByteOffset() + : stack_storage_; } else { abv->CopyContents(stack_storage_, sizeof(stack_storage_)); data_ = stack_storage_; diff --git a/test/addons/addons.status b/test/addons/addons.status index 7f5fd8f0c2f51f..18b1c2b2157d81 100644 --- a/test/addons/addons.status +++ b/test/addons/addons.status @@ -12,6 +12,7 @@ openssl-binding/test: PASS,FLAKY [$system==ibmi] openssl-binding/test: SKIP +openssl-get-ssl-ctx/test: SKIP openssl-providers/test-default-only-config: SKIP openssl-providers/test-legacy-provider-config: SKIP openssl-providers/test-legacy-provider-inactive-config: SKIP diff --git a/test/addons/openssl-get-ssl-ctx/binding.cc b/test/addons/openssl-get-ssl-ctx/binding.cc new file mode 100644 index 00000000000000..3945ec870fb8b9 --- /dev/null +++ b/test/addons/openssl-get-ssl-ctx/binding.cc @@ -0,0 +1,52 @@ +#include +#include + +namespace { + +// Test: extract SSL_CTX* from a SecureContext object via +// node::crypto::GetSSLCtx. +void GetSSLCtx(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::Local context = isolate->GetCurrentContext(); + + SSL_CTX* ctx = node::crypto::GetSSLCtx(context, args[0]); + if (ctx == nullptr) { + isolate->ThrowException(v8::Exception::Error( + v8::String::NewFromUtf8( + isolate, "GetSSLCtx returned nullptr for a valid SecureContext") + .ToLocalChecked())); + return; + } + + // Verify the pointer is a valid SSL_CTX by calling an OpenSSL function. + const SSL_METHOD* method = SSL_CTX_get_ssl_method(ctx); + if (method == nullptr) { + isolate->ThrowException(v8::Exception::Error( + v8::String::NewFromUtf8(isolate, + "SSL_CTX_get_ssl_method returned nullptr") + .ToLocalChecked())); + return; + } + + args.GetReturnValue().Set(true); +} + +// Test: passing a non-SecureContext value returns nullptr. +void GetSSLCtxInvalid(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + v8::Local context = isolate->GetCurrentContext(); + + SSL_CTX* ctx = node::crypto::GetSSLCtx(context, args[0]); + args.GetReturnValue().Set(ctx == nullptr); +} + +void Initialize(v8::Local exports, + v8::Local module, + v8::Local context) { + NODE_SET_METHOD(exports, "getSSLCtx", GetSSLCtx); + NODE_SET_METHOD(exports, "getSSLCtxInvalid", GetSSLCtxInvalid); +} + +} // anonymous namespace + +NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize) diff --git a/test/addons/openssl-get-ssl-ctx/binding.gyp b/test/addons/openssl-get-ssl-ctx/binding.gyp new file mode 100644 index 00000000000000..46a558e1203036 --- /dev/null +++ b/test/addons/openssl-get-ssl-ctx/binding.gyp @@ -0,0 +1,29 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'includes': ['../common.gypi'], + 'conditions': [ + ['node_use_openssl=="true"', { + 'conditions': [ + ['OS in "aix os400"', { + 'variables': { + # Used to differentiate `AIX` and `OS400`(IBM i). + 'aix_variant_name': ' i)), // 0x00-0xC7 customization: Buffer.from('My Tagged Application'), - length: 256, + outputLength: 256, expected: Buffer.from([ 0x1f, 0x5b, 0x4e, 0x6c, 0xca, 0x02, 0x20, 0x9e, 0x0d, 0xcb, 0x5c, 0xa6, 0x35, 0xb8, 0x9a, 0x15, 0xe2, 0x71, 0xec, 0xc7, 0x60, 0x07, 0x1d, 0xfd, @@ -64,7 +64,7 @@ module.exports = function() { ]), data: Buffer.from([0x00, 0x01, 0x02, 0x03]), customization: Buffer.from('My Tagged Application'), - length: 512, + outputLength: 512, expected: Buffer.from([ 0x20, 0xc5, 0x70, 0xc3, 0x13, 0x46, 0xf7, 0x03, 0xc9, 0xac, 0x36, 0xc6, 0x1c, 0x03, 0xcb, 0x64, 0xc3, 0x97, 0x0d, 0x0c, 0xfc, 0x78, 0x7e, 0x9b, @@ -84,7 +84,7 @@ module.exports = function() { ]), data: Buffer.from(Array.from({ length: 200 }, (_, i) => i)), // 0x00-0xC7 customization: undefined, - length: 512, + outputLength: 512, expected: Buffer.from([ 0x75, 0x35, 0x8c, 0xf3, 0x9e, 0x41, 0x49, 0x4e, 0x94, 0x97, 0x07, 0x92, 0x7c, 0xee, 0x0a, 0xf2, 0x0a, 0x3f, 0xf5, 0x53, 0x90, 0x4c, 0x86, 0xb0, @@ -104,7 +104,7 @@ module.exports = function() { ]), data: Buffer.from(Array.from({ length: 200 }, (_, i) => i)), // 0x00-0xC7 customization: Buffer.from('My Tagged Application'), - length: 512, + outputLength: 512, expected: Buffer.from([ 0xb5, 0x86, 0x18, 0xf7, 0x1f, 0x92, 0xe1, 0xd5, 0x6c, 0x1b, 0x8c, 0x55, 0xdd, 0xd7, 0xcd, 0x18, 0x8b, 0x97, 0xb4, 0xca, 0x4d, 0x99, 0x83, 0x1e, diff --git a/test/fixtures/es-modules/js-string-builtins.wasm b/test/fixtures/es-modules/js-string-builtins.wasm index b4c08587dd08e7..fe520bab1fbbf5 100644 Binary files a/test/fixtures/es-modules/js-string-builtins.wasm and b/test/fixtures/es-modules/js-string-builtins.wasm differ diff --git a/test/fixtures/es-modules/js-string-builtins.wat b/test/fixtures/es-modules/js-string-builtins.wat index 9bc55a8fa750cc..08f4ade8c4151c 100644 --- a/test/fixtures/es-modules/js-string-builtins.wat +++ b/test/fixtures/es-modules/js-string-builtins.wat @@ -4,11 +4,15 @@ (import "wasm:js-string" "length" (func $string_length (param externref) (result i32))) (import "wasm:js-string" "concat" (func $string_concat (param externref externref) (result (ref extern)))) (import "wasm:js-string" "equals" (func $string_equals (param externref externref) (result i32))) + + ;; Import a string constant via importedStringConstants + (import "wasm:js/string-constants" "hello" (global $hello externref)) ;; Export functions that use the builtins (export "getLength" (func $get_length)) (export "concatStrings" (func $concat_strings)) (export "compareStrings" (func $compare_strings)) + (export "getHello" (func $get_hello)) (func $get_length (param $str externref) (result i32) local.get $str @@ -26,4 +30,8 @@ local.get $str2 call $string_equals ) + + (func $get_hello (result externref) + global.get $hello + ) ) \ No newline at end of file diff --git a/test/fixtures/es-modules/test-wasm-js-string-builtins.mjs b/test/fixtures/es-modules/test-wasm-js-string-builtins.mjs index 2364f246b2558d..c76dcc39894932 100644 --- a/test/fixtures/es-modules/test-wasm-js-string-builtins.mjs +++ b/test/fixtures/es-modules/test-wasm-js-string-builtins.mjs @@ -6,3 +6,4 @@ strictEqual(wasmExports.getLength('hello'), 5); strictEqual(wasmExports.concatStrings('hello', ' world'), 'hello world'); strictEqual(wasmExports.compareStrings('test', 'test'), 1); strictEqual(wasmExports.compareStrings('test', 'different'), 0); +strictEqual(wasmExports.getHello(), 'hello'); diff --git a/test/fixtures/es-modules/test-wasm-source-phase-identity-parent.mjs b/test/fixtures/es-modules/test-wasm-source-phase-identity-parent.mjs new file mode 100644 index 00000000000000..36d5765c17f0ad --- /dev/null +++ b/test/fixtures/es-modules/test-wasm-source-phase-identity-parent.mjs @@ -0,0 +1,6 @@ +import * as mod1 from './simple.wasm'; +import * as mod2 from './simple.wasm'; +import source mod3 from './simple.wasm'; +import source mod4 from './simple.wasm'; + +export { mod1, mod2, mod3, mod4 }; diff --git a/test/fixtures/es-modules/test-wasm-source-phase-identity.mjs b/test/fixtures/es-modules/test-wasm-source-phase-identity.mjs new file mode 100644 index 00000000000000..84cf6261139038 --- /dev/null +++ b/test/fixtures/es-modules/test-wasm-source-phase-identity.mjs @@ -0,0 +1,14 @@ +import { strictEqual } from 'node:assert'; + +// Pre-load simple.wasm at kSourcePhase to prime the loadCache. +const preloaded = await import.source('./simple.wasm'); +strictEqual(preloaded instanceof WebAssembly.Module, true); + +// Import a parent that has both eval-phase and source-phase imports of the +// same wasm file, which triggers ensurePhase(kEvaluationPhase) on the cached +// job and exposes the loadCache eviction bug. +const { mod1, mod2, mod3, mod4 } = + await import('./test-wasm-source-phase-identity-parent.mjs'); + +strictEqual(mod1, mod2, 'two eval-phase imports of the same wasm must be identical'); +strictEqual(mod3, mod4, 'two source-phase imports of the same wasm must be identical'); diff --git a/test/fixtures/repl-tab-completion-nested-repls.js b/test/fixtures/repl-tab-completion-nested-repls.js index 1d2b154f2b3341..79677491eca55f 100644 --- a/test/fixtures/repl-tab-completion-nested-repls.js +++ b/test/fixtures/repl-tab-completion-nested-repls.js @@ -1,6 +1,5 @@ // Tab completion sometimes uses a separate REPL instance under the hood. -// That REPL instance has its own domain. Make sure domain errors trickle back -// up to the main REPL. +// Make sure errors in completion callbacks are properly thrown. // // Ref: https://github.com/nodejs/node/issues/21586 @@ -31,11 +30,6 @@ const repl = require('repl'); const putIn = new ArrayStream(); const testMe = repl.start('', putIn); -// Some errors are passed to the domain, but do not callback. -testMe._domain.on('error', function(err) { - throw err; -}); - // Nesting of structures causes REPL to use a nested REPL for completion. putIn.run([ 'var top = function() {', diff --git a/test/fixtures/sea/esm-code-cache/sea-config.json b/test/fixtures/sea/esm-code-cache/sea-config.json new file mode 100644 index 00000000000000..53ba60cc157bde --- /dev/null +++ b/test/fixtures/sea/esm-code-cache/sea-config.json @@ -0,0 +1,7 @@ +{ + "main": "sea.mjs", + "output": "sea", + "mainFormat": "module", + "useCodeCache": true, + "disableExperimentalSEAWarning": true +} diff --git a/test/fixtures/sea/esm-code-cache/sea.mjs b/test/fixtures/sea/esm-code-cache/sea.mjs new file mode 100644 index 00000000000000..b2605a30ed0b63 --- /dev/null +++ b/test/fixtures/sea/esm-code-cache/sea.mjs @@ -0,0 +1,20 @@ +import assert from 'node:assert'; +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; +import { dirname } from 'node:path'; + +// Test createRequire with process.execPath. +const assert2 = createRequire(process.execPath)('node:assert'); +assert.strictEqual(assert2.strict, assert.strict); + +// Test import.meta properties. +assert.strictEqual(import.meta.url, pathToFileURL(process.execPath).href); +assert.strictEqual(import.meta.filename, process.execPath); +assert.strictEqual(import.meta.dirname, dirname(process.execPath)); +assert.strictEqual(import.meta.main, true); + +// Test import() with a built-in module. +const { strict } = await import('node:assert'); +assert.strictEqual(strict, assert.strict); + +console.log('ESM SEA with code cache executed successfully'); diff --git a/test/fixtures/test-runner/coverage-with-mock/dependency.cjs b/test/fixtures/test-runner/coverage-with-mock/dependency.cjs new file mode 100644 index 00000000000000..fa305d24a3f3c1 --- /dev/null +++ b/test/fixtures/test-runner/coverage-with-mock/dependency.cjs @@ -0,0 +1,9 @@ +throw new Error('This should never be called'); + +exports.dependency = function dependency() { + return 'foo'; +} + +exports.unused = function unused() { + return 'bar'; +} diff --git a/test/fixtures/test-runner/coverage-with-mock/subject.mjs b/test/fixtures/test-runner/coverage-with-mock/subject.mjs new file mode 100644 index 00000000000000..ae63dd386c4998 --- /dev/null +++ b/test/fixtures/test-runner/coverage-with-mock/subject.mjs @@ -0,0 +1,5 @@ +import { dependency } from './dependency.cjs'; + +export function subject() { + return dependency(); +} diff --git a/test/fixtures/test-runner/describe_error.js b/test/fixtures/test-runner/describe_error.js new file mode 100644 index 00000000000000..04e9d1faa042d1 --- /dev/null +++ b/test/fixtures/test-runner/describe_error.js @@ -0,0 +1,10 @@ +'use strict'; +const { describe, it } = require('node:test'); + +describe('should fail', () => { + throw new Error('error in describe'); +}); + +describe('should pass', () => { + it('ok', () => {}); +}); diff --git a/test/fixtures/test-runner/mock-timers-with-timeout.js b/test/fixtures/test-runner/mock-timers-with-timeout.js new file mode 100644 index 00000000000000..4eb94ec5d6d8e9 --- /dev/null +++ b/test/fixtures/test-runner/mock-timers-with-timeout.js @@ -0,0 +1,43 @@ +'use strict'; + +// Simulate @sinonjs/fake-timers: patch the timers module BEFORE +// the test runner is loaded, so the test runner captures the patched +// versions at import time. +const nodeTimers = require('node:timers'); +const originalSetTimeout = nodeTimers.setTimeout; +const originalClearTimeout = nodeTimers.clearTimeout; + +const fakeTimers = new Map(); +let nextId = 1; + +nodeTimers.setTimeout = (fn, delay, ...args) => { + const id = nextId++; + const timer = originalSetTimeout(fn, delay, ...args); + fakeTimers.set(id, timer); + // Sinon fake timers return an object with unref/ref but without + // Symbol.dispose, which would cause the test runner to throw. + return { id, unref() {}, ref() {} }; +}; + +nodeTimers.clearTimeout = (id) => { + if (id != null && typeof id === 'object') id = id.id; + const timer = fakeTimers.get(id); + if (timer) { + originalClearTimeout(timer); + fakeTimers.delete(id); + } +}; + +// Now load the test runner - it will capture our patched setTimeout/clearTimeout +const { test } = require('node:test'); + +test('test with fake timers and timeout', { timeout: 10_000 }, () => { + // This test verifies that the test runner works when setTimeout returns + // an object without Symbol.dispose (like sinon fake timers). + // Previously, the test runner called timer[Symbol.dispose]() which would + // throw TypeError on objects returned by fake timer implementations. +}); + +// Restore +nodeTimers.setTimeout = originalSetTimeout; +nodeTimers.clearTimeout = originalClearTimeout; diff --git a/test/fixtures/test-runner/output/coverage-with-mock-cjs.mjs b/test/fixtures/test-runner/output/coverage-with-mock-cjs.mjs new file mode 100644 index 00000000000000..9acf8019f789ea --- /dev/null +++ b/test/fixtures/test-runner/output/coverage-with-mock-cjs.mjs @@ -0,0 +1,11 @@ +import { mock, test } from 'node:test'; + +const dependency = mock.fn(() => 'mock-return-value'); +mock.module('../coverage-with-mock/dependency.cjs', { exports: { dependency } }); + +const { subject } = await import('../coverage-with-mock/subject.mjs'); + +test('subject calls dependency', (t) => { + t.assert.strictEqual(subject(), 'mock-return-value'); + t.assert.strictEqual(dependency.mock.callCount(), 1); +}); diff --git a/test/fixtures/test-runner/output/coverage-with-mock-cjs.snapshot b/test/fixtures/test-runner/output/coverage-with-mock-cjs.snapshot new file mode 100644 index 00000000000000..6d19904b02228a --- /dev/null +++ b/test/fixtures/test-runner/output/coverage-with-mock-cjs.snapshot @@ -0,0 +1,31 @@ +TAP version 13 +# Subtest: subject calls dependency +ok 1 - subject calls dependency + --- + duration_ms: * + type: 'test' + ... +1..1 +# tests 1 +# suites 0 +# pass 1 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms * +# start of coverage report +# ------------------------------------------------------------------------------- +# file | line % | branch % | funcs % | uncovered lines +# ------------------------------------------------------------------------------- +# test | | | | +# fixtures | | | | +# test-runner | | | | +# coverage-with-mock | | | | +# subject.mjs | 100.00 | 100.00 | 100.00 | +# output | | | | +# coverage-with-mock-cjs.mjs | 100.00 | 100.00 | 100.00 | +# ------------------------------------------------------------------------------- +# all files | 100.00 | 100.00 | 100.00 | +# ------------------------------------------------------------------------------- +# end of coverage report diff --git a/test/fixtures/test-runner/output/coverage-with-mock.mjs b/test/fixtures/test-runner/output/coverage-with-mock.mjs index 5d5b2b14f66a95..e3b8fcc473f450 100644 --- a/test/fixtures/test-runner/output/coverage-with-mock.mjs +++ b/test/fixtures/test-runner/output/coverage-with-mock.mjs @@ -2,7 +2,7 @@ import { describe, it, mock } from 'node:test'; describe('module test with mock', async () => { mock.module('../coverage-with-mock/sum.js', { - namedExports: { + exports: { sum: (a, b) => 1, getData: () => ({}), }, diff --git a/test/fixtures/test-runner/output/typescript-coverage.mts b/test/fixtures/test-runner/output/typescript-coverage.mts index c26ddcac9b33f9..5498d5f83831a1 100644 --- a/test/fixtures/test-runner/output/typescript-coverage.mts +++ b/test/fixtures/test-runner/output/typescript-coverage.mts @@ -10,8 +10,10 @@ describe('foo', { concurrency: true }, () => { .then(({ default: _, ...rest }) => rest); mock.module('../coverage/bar.mts', { - defaultExport: barMock, - namedExports: barNamedExports, + exports: { + ...barNamedExports, + default: barMock, + }, }); ({ foo } = await import('../coverage/foo.mts')); diff --git a/test/fixtures/test-runner/output/typescript-coverage.snapshot b/test/fixtures/test-runner/output/typescript-coverage.snapshot index 25546c9a5d0060..d31a59c5be61a3 100644 --- a/test/fixtures/test-runner/output/typescript-coverage.snapshot +++ b/test/fixtures/test-runner/output/typescript-coverage.snapshot @@ -34,6 +34,6 @@ ok 1 - foo # output | | | | # typescript-coverage.mts | 100.00 | 100.00 | 100.00 | # ---------------------------------------------------------------------------- -# all files | 93.55 | 100.00 | 85.71 | +# all files | 93.94 | 100.00 | 85.71 | # ---------------------------------------------------------------------------- # end of coverage report diff --git a/test/fixtures/tz-version.txt b/test/fixtures/tz-version.txt index cb3be9ab63e7fc..5d9126009e7f87 100644 --- a/test/fixtures/tz-version.txt +++ b/test/fixtures/tz-version.txt @@ -1 +1 @@ -2025c +2026a diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index 76d5e805cbc0e7..62b90daf7b0463 100644 --- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -17,17 +17,41 @@ const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveK export const vectors = { 'digest': [ [false, 'cSHAKE128'], - [shake128, { name: 'cSHAKE128', length: 128 }], - [shake128, { name: 'cSHAKE128', length: 128, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], - [false, { name: 'cSHAKE128', length: 128, functionName: Buffer.alloc(1) }], - [false, { name: 'cSHAKE128', length: 128, customization: Buffer.alloc(1) }], - [false, { name: 'cSHAKE128', length: 127 }], + [shake128, { name: 'cSHAKE128', outputLength: 128 }], + [shake128, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], + [false, { name: 'cSHAKE128', outputLength: 128, functionName: Buffer.alloc(1) }], + [false, { name: 'cSHAKE128', outputLength: 128, customization: Buffer.alloc(1) }], + [false, { name: 'cSHAKE128', outputLength: 127 }], [false, 'cSHAKE256'], - [shake256, { name: 'cSHAKE256', length: 256 }], - [shake256, { name: 'cSHAKE256', length: 256, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], - [false, { name: 'cSHAKE256', length: 256, functionName: Buffer.alloc(1) }], - [false, { name: 'cSHAKE256', length: 256, customization: Buffer.alloc(1) }], - [false, { name: 'cSHAKE256', length: 255 }], + [shake256, { name: 'cSHAKE256', outputLength: 256 }], + [shake256, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(0), customization: Buffer.alloc(0) }], + [false, { name: 'cSHAKE256', outputLength: 256, functionName: Buffer.alloc(1) }], + [false, { name: 'cSHAKE256', outputLength: 256, customization: Buffer.alloc(1) }], + [false, { name: 'cSHAKE256', outputLength: 255 }], + [false, 'TurboSHAKE128'], + [true, { name: 'TurboSHAKE128', outputLength: 128 }], + [true, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x07 }], + [false, { name: 'TurboSHAKE128', outputLength: 0 }], + [false, { name: 'TurboSHAKE128', outputLength: 127 }], + [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x00 }], + [false, { name: 'TurboSHAKE128', outputLength: 128, domainSeparation: 0x80 }], + [false, 'TurboSHAKE256'], + [true, { name: 'TurboSHAKE256', outputLength: 256 }], + [true, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x07 }], + [false, { name: 'TurboSHAKE256', outputLength: 0 }], + [false, { name: 'TurboSHAKE256', outputLength: 255 }], + [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x00 }], + [false, { name: 'TurboSHAKE256', outputLength: 256, domainSeparation: 0x80 }], + [false, 'KT128'], + [true, { name: 'KT128', outputLength: 128 }], + [true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(0) }], + [false, { name: 'KT128', outputLength: 0 }], + [false, { name: 'KT128', outputLength: 127 }], + [false, 'KT256'], + [true, { name: 'KT256', outputLength: 256 }], + [true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(0) }], + [false, { name: 'KT256', outputLength: 0 }], + [false, { name: 'KT256', outputLength: 255 }], ], 'sign': [ [pqc, 'ML-DSA-44'], @@ -44,8 +68,8 @@ export const vectors = { [false, 'Argon2id'], [false, 'KMAC128'], [false, 'KMAC256'], - [kmac, { name: 'KMAC128', length: 256 }], - [kmac, { name: 'KMAC256', length: 256 }], + [kmac, { name: 'KMAC128', outputLength: 256 }], + [kmac, { name: 'KMAC256', outputLength: 256 }], ], 'generateKey': [ [pqc, 'ML-DSA-44'], diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 34dadc7a702755..3699e51bcbea9d 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -28,13 +28,13 @@ Last update: - resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing - resources: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/resources - streams: https://github.com/web-platform-tests/wpt/tree/bc9dcbbf1a/streams -- url: https://github.com/web-platform-tests/wpt/tree/c928b19ab0/url +- url: https://github.com/web-platform-tests/wpt/tree/fc3e651593/url - urlpattern: https://github.com/web-platform-tests/wpt/tree/a2e15ad405/urlpattern - user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing - wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi - web-locks: https://github.com/web-platform-tests/wpt/tree/10a122a6bc/web-locks -- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/c9e955840a/WebCryptoAPI +- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/2cb332d710/WebCryptoAPI - webidl: https://github.com/web-platform-tests/wpt/tree/63ca529a02/webidl - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/2f96fa1996/webidl/ecmascript-binding/es-exceptions - webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/6495c91853/webmessaging/broadcastchannel diff --git a/test/fixtures/wpt/WebCryptoAPI/digest/cshake.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/digest/cshake.tentative.https.any.js index d5f790e42b4982..8be50b56097e89 100644 --- a/test/fixtures/wpt/WebCryptoAPI/digest/cshake.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/digest/cshake.tentative.https.any.js @@ -162,7 +162,7 @@ Object.keys(digestedData).forEach(function (alg) { Object.keys(sourceData).forEach(function (size) { promise_test(function (test) { return crypto.subtle - .digest({ name: alg, length: length }, sourceData[size]) + .digest({ name: alg, outputLength: length }, sourceData[size]) .then(function (result) { assert_true( equalBuffers(result, digestedData[alg][length][size]), @@ -183,7 +183,7 @@ Object.keys(digestedData).forEach(function (alg) { buffer[0] = sourceData[size][0]; return alg; }, - length + outputLength: length }, buffer) .then(function (result) { assert_true( @@ -195,17 +195,52 @@ Object.keys(digestedData).forEach(function (alg) { promise_test(function (test) { var buffer = new Uint8Array(sourceData[size]); - return crypto.subtle - .digest({ name: alg, length: length }, buffer) + var promise = crypto.subtle + .digest({ name: alg, outputLength: length }, buffer) .then(function (result) { - // Alter the buffer after calling digest - buffer[0] = ~buffer[0]; assert_true( equalBuffers(result, digestedData[alg][length][size]), 'digest matches expected' ); }); + // Alter the buffer after calling digest + buffer[0] = ~buffer[0]; + return promise; }, alg + ' with ' + length + ' bit output and ' + size + ' source data and altered buffer after call'); + + promise_test(function (test) { + var buffer = new Uint8Array(sourceData[size]); + return crypto.subtle + .digest({ + get name() { + // Transfer the buffer while calling digest + buffer.buffer.transfer(); + return alg; + }, + outputLength: length + }, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, digestedData[alg][length].empty), + 'digest on transferred buffer should match result for empty buffer' + ); + }); + }, alg + ' with ' + length + ' bit output and ' + size + ' source data and transferred buffer during call'); + + promise_test(function (test) { + var buffer = new Uint8Array(sourceData[size]); + var promise = crypto.subtle + .digest({ name: alg, outputLength: length }, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, digestedData[alg][length][size]), + 'digest matches expected' + ); + }); + // Transfer the buffer after calling digest + buffer.buffer.transfer(); + return promise; + }, alg + ' with ' + length + ' bit output and ' + size + ' source data and transferred buffer after call'); } }); }); diff --git a/test/fixtures/wpt/WebCryptoAPI/digest/digest.https.any.js b/test/fixtures/wpt/WebCryptoAPI/digest/digest.https.any.js index e0c85f8f6b30e4..47bc220d44fb70 100644 --- a/test/fixtures/wpt/WebCryptoAPI/digest/digest.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/digest/digest.https.any.js @@ -113,6 +113,30 @@ copiedBuffer[0] = 255 - copiedBuffer[0]; return promise; }, upCase + " with " + size + " source data and altered buffer after call"); + + promise_test(function(test) { + var copiedBuffer = copyBuffer(sourceData[size]); + copiedBuffer.buffer.transfer(); + return subtle.digest({name: upCase}, copiedBuffer) + .then(function(result) { + assert_true(equalBuffers(result, digestedData[alg].empty), "digest() on transferred buffer should yield result for empty buffer for " + alg + ":" + size); + }, function(err) { + assert_unreached("digest() threw an error for transferred buffer for " + alg + ":" + size + ": " + err.message); + }); + }, upCase + " with " + size + " source data and transferred buffer during call"); + + promise_test(function(test) { + var copiedBuffer = copyBuffer(sourceData[size]); + var promise = subtle.digest({name: upCase}, copiedBuffer) + .then(function(result) { + assert_true(equalBuffers(result, digestedData[alg][size]), "digest() yielded expected result for " + alg + ":" + size); + }, function(err) { + assert_unreached("digest() threw an error for " + alg + ":" + size + " - " + err.message); + }); + + copiedBuffer.buffer.transfer(); + return promise; + }, upCase + " with " + size + " source data and transferred buffer after call"); } }); }); diff --git a/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js index fc33608e07ab14..4ae99791b8c95f 100644 --- a/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/digest/sha3.tentative.https.any.js @@ -132,15 +132,47 @@ Object.keys(sourceData).forEach(function (size) { promise_test(function (test) { var buffer = new Uint8Array(sourceData[size]); - return crypto.subtle.digest(alg, buffer).then(function (result) { - // Alter the buffer after calling digest - buffer[0] = ~buffer[0]; + var promise = crypto.subtle.digest(alg, buffer).then(function (result) { assert_true( equalBuffers(result, digestedData[alg][size]), 'digest matches expected' ); }); + // Alter the buffer after calling digest + buffer[0] = ~buffer[0]; + return promise; }, alg + ' with ' + size + ' source data and altered buffer after call'); + + promise_test(function (test) { + var buffer = new Uint8Array(sourceData[size]); + return crypto.subtle + .digest({ + get name() { + // Transfer the buffer while calling digest + buffer.buffer.transfer(); + return alg; + } + }, buffer) + .then(function (result) { + assert_true( + equalBuffers(result, digestedData[alg].empty), + 'digest on transferred buffer should match result for empty buffer' + ); + }); + }, alg + ' with ' + size + ' source data and transferred buffer during call'); + + promise_test(function (test) { + var buffer = new Uint8Array(sourceData[size]); + var promise = crypto.subtle.digest(alg, buffer).then(function (result) { + assert_true( + equalBuffers(result, digestedData[alg][size]), + 'digest matches expected' + ); + }); + // Transfer the buffer after calling digest + buffer.buffer.transfer(); + return promise; + }, alg + ' with ' + size + ' source data and transferred buffer after call'); } }); }); diff --git a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js index b157a94a0dc4fb..456f66423419c8 100644 --- a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js +++ b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/aes.js @@ -93,6 +93,67 @@ function run_test() { all_promises.push(promise); }); + // Check for encryption of an empty value if the buffer is transferred while calling encrypt. + passingVectors.forEach(function(vector) { + var plaintext = copyBuffer(vector.plaintext); + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + var operation = subtle.encrypt({ + ...vector.algorithm, + get name() { + plaintext.buffer.transfer(); + return vector.algorithm.name; + } + }, vector.key, plaintext) + .then(function(result) { + var expectedLength = + ["AES-GCM", "AES-OCB"].includes(vector.algorithm.name) ? vector.algorithm.tagLength / 8 : + vector.algorithm.name === "AES-CBC" ? 16 : + 0; + assert_equals(result.byteLength, expectedLength, "Transferred plaintext yields an empty ciphertext"); + }, function(err) { + assert_unreached("encrypt error for test " + vector.name + ": " + err.message); + }); + return operation; + }, vector.name + " with transferred plaintext during call"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name + " with transferred plaintext during call"); + }); + + all_promises.push(promise); + }); + + // Check for successful encryption even if the buffer is transferred after calling encrypt. + passingVectors.forEach(function(vector) { + var plaintext = copyBuffer(vector.plaintext); + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + var operation = subtle.encrypt(vector.algorithm, vector.key, plaintext) + .then(function(result) { + assert_true(equalBuffers(result, vector.result), "Should return expected result"); + }, function(err) { + assert_unreached("encrypt error for test " + vector.name + ": " + err.message); + }); + plaintext.buffer.transfer(); + return operation; + }, vector.name + " with transferred plaintext after call"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step: " + vector.name + " with transferred plaintext after call"); + }); + + all_promises.push(promise); + }); + // Check for successful decryption. passingVectors.forEach(function(vector) { var promise = importVectorKey(vector, ["encrypt", "decrypt"]) @@ -174,6 +235,71 @@ function run_test() { all_promises.push(promise); }); + // Check for decryption when ciphertext is transferred while calling decrypt. + passingVectors.forEach(function(vector) { + var ciphertext = copyBuffer(vector.result); + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + var operation = subtle.decrypt({ + ...vector.algorithm, + get name() { + ciphertext.buffer.transfer(); + return vector.algorithm.name; + } + }, vector.key, ciphertext) + .then(function(result) { + if (vector.algorithm.name === "AES-CTR") { + assert_equals(result.byteLength, 0, "Transferred ciphertext yields empty plaintext"); + } else { + assert_unreached("decrypt should not have succeeded for " + vector.name); + } + }, function(err) { + if (vector.algorithm.name === "AES-CTR") { + assert_unreached("decrypt error for test " + vector.name + ": " + err.message); + } else { + assert_equals(err.name, "OperationError", "Should throw an OperationError instead of " + err.message); + } + }); + return operation; + }, vector.name + " decryption with transferred ciphertext during call"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step for decryption: " + vector.name + " with transferred ciphertext during call"); + }); + + all_promises.push(promise); + }); + + // Check for successful decryption even if ciphertext is transferred after calling encrypt. + passingVectors.forEach(function(vector) { + var ciphertext = copyBuffer(vector.result); + var promise = importVectorKey(vector, ["encrypt", "decrypt"]) + .then(function(vector) { + promise_test(function(test) { + var operation = subtle.decrypt(vector.algorithm, vector.key, ciphertext) + .then(function(result) { + assert_true(equalBuffers(result, vector.plaintext), "Should return expected result"); + }, function(err) { + assert_unreached("decrypt error for test " + vector.name + ": " + err.message); + }); + ciphertext.buffer.transfer(); + return operation; + }, vector.name + " decryption with transferred ciphertext after call"); + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importKey failed for " + vector.name); + }, "importKey step for decryption: " + vector.name + " with transferred ciphertext after call"); + }); + + all_promises.push(promise); + }); + // Everything that succeeded should fail if no "encrypt" usage. passingVectors.forEach(function(vector) { // Don't want to overwrite key being used for success tests! diff --git a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js index 76c90809783205..6f585cbe1ffcef 100644 --- a/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js +++ b/test/fixtures/wpt/WebCryptoAPI/encrypt_decrypt/rsa.js @@ -25,7 +25,7 @@ function run_test() { .then(function(plaintext) { assert_true(equalBuffers(plaintext, vector.plaintext, "Decryption works")); }, function(err) { - assert_unreached("Decryption should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Decryption should not throw error " + vector.name + ": '" + err.message + "'"); }); }, vector.name + " decryption"); @@ -62,7 +62,7 @@ function run_test() { .then(function(plaintext) { assert_true(equalBuffers(plaintext, vector.plaintext, "Decryption works")); }, function(err) { - assert_unreached("Decryption should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Decryption should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; }, vector.name + " decryption with altered ciphertext during call"); @@ -78,7 +78,7 @@ function run_test() { all_promises.push(promise); }); - // Test decryption with an altered buffer + // Test decryption with an altered buffer after call passingVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) .then(function(vectors) { @@ -93,7 +93,7 @@ function run_test() { .then(function(plaintext) { assert_true(equalBuffers(plaintext, vector.plaintext, "Decryption works")); }, function(err) { - assert_unreached("Decryption should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Decryption should not throw error " + vector.name + ": '" + err.message + "'"); }); ciphertext[0] = 255 - ciphertext[0]; return operation; @@ -110,6 +110,75 @@ function run_test() { all_promises.push(promise); }); + // Test decryption with a transferred buffer during call + passingVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) + .then(function(vectors) { + // Get a one byte longer plaintext to encrypt + if (!("ciphertext" in vector)) { + return; + } + + promise_test(function(test) { + var ciphertext = copyBuffer(vector.ciphertext); + var operation = subtle.decrypt({ + ...vector.algorithm, + get name() { + ciphertext.buffer.transfer(); + return vector.algorithm.name; + } + }, vector.privateKey, ciphertext) + .then(function(plaintext) { + assert_unreached("Decryption should not have succeeded for " + vector.name); + }, function(err) { + assert_equals(err.name, "OperationError", "Should throw OperationError instead of " + err.message); + }); + return operation; + }, vector.name + " decryption with transferred ciphertext during call"); + + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " decryption with transferred ciphertext during call"); + }); + + all_promises.push(promise); + }); + + // Test decryption with a transferred buffer after call + passingVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) + .then(function(vectors) { + // Get a one byte longer plaintext to encrypt + if (!("ciphertext" in vector)) { + return; + } + + promise_test(function(test) { + var ciphertext = copyBuffer(vector.ciphertext); + var operation = subtle.decrypt(vector.algorithm, vector.privateKey, ciphertext) + .then(function(plaintext) { + assert_true(equalBuffers(plaintext, vector.plaintext, "Decryption works")); + }, function(err) { + assert_unreached("Decryption should not throw error " + vector.name + ": '" + err.message + "'"); + }); + ciphertext.buffer.transfer(); + return operation; + }, vector.name + " decryption with transferred ciphertext after call"); + + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " decryption with transferred ciphertext after call"); + }); + + all_promises.push(promise); + }); + // Check for failures due to using publicKey to decrypt. passingVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) @@ -117,7 +186,7 @@ function run_test() { promise_test(function(test) { return subtle.decrypt(vector.algorithm, vector.publicKey, vector.ciphertext) .then(function(plaintext) { - assert_unreached("Should have thrown error for using publicKey to decrypt in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for using publicKey to decrypt in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); }); @@ -145,7 +214,7 @@ function run_test() { promise_test(function(test) { return subtle.decrypt(vector.algorithm, vector.publicKey, vector.ciphertext) .then(function(plaintext) { - assert_unreached("Should have thrown error for no decrypt usage in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for no decrypt usage in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); }); @@ -185,7 +254,7 @@ function run_test() { assert_true(equalBuffers(result, vector.plaintext), "Round trip returns original plaintext"); return ciphertext; }, function(err) { - assert_unreached("decrypt error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); }); }) .then(function(priorCiphertext) { @@ -229,7 +298,7 @@ function run_test() { assert_true(equalBuffers(result, vector.plaintext), "Round trip returns original plaintext"); return ciphertext; }, function(err) { - assert_unreached("decrypt error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); }); }) .then(function(priorCiphertext) { @@ -259,6 +328,92 @@ function run_test() { all_promises.push(promise); }); + // Check for encryption of an empty value if plaintext is transferred during call. + passingVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) + .then(function(vectors) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var operation = subtle.encrypt({ + ...vector.algorithm, + get name() { + plaintext.buffer.transfer(); + return vector.algorithm.name; + } + }, vector.publicKey, plaintext) + .then(function(ciphertext) { + assert_equals(ciphertext.byteLength * 8, vector.privateKey.algorithm.modulusLength, "Ciphertext length matches modulus length"); + // Do we get an empty plaintext back via decrypt? + return subtle.decrypt(vector.algorithm, vector.privateKey, ciphertext) + .then(function(result) { + assert_equals(result.byteLength, 0, "Decryption returns empty plaintext"); + return ciphertext; + }, function(err) { + assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); + }); + }, function(err) { + assert_unreached("encrypt error for test " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " with transferred plaintext during call"); + + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext during call"); + }); + + all_promises.push(promise); + }); + + // Check for successful encryption even if plaintext is transferred after call. + passingVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) + .then(function(vectors) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var operation = subtle.encrypt(vector.algorithm, vector.publicKey, plaintext) + .then(function(ciphertext) { + assert_equals(ciphertext.byteLength * 8, vector.privateKey.algorithm.modulusLength, "Ciphertext length matches modulus length"); + // Can we get the original plaintext back via decrypt? + return subtle.decrypt(vector.algorithm, vector.privateKey, ciphertext) + .then(function(result) { + assert_true(equalBuffers(result, vector.plaintext), "Round trip returns original plaintext"); + return ciphertext; + }, function(err) { + assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); + }); + }) + .then(function(priorCiphertext) { + // Will a second encrypt give us different ciphertext, as it should? + return subtle.encrypt(vector.algorithm, vector.publicKey, vector.plaintext) + .then(function(ciphertext) { + assert_false(equalBuffers(priorCiphertext, ciphertext), "Two encrypts give different results") + }, function(err) { + assert_unreached("second time encrypt error for test " + vector.name + ": '" + err.message + "'"); + }); + }, function(err) { + assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); + }); + + plaintext.buffer.transfer(); + return operation; + }, vector.name + " with transferred plaintext after call"); + + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested encryption + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext after call"); + }); + + all_promises.push(promise); + }); + // Check for successful encryption. passingVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) @@ -274,7 +429,7 @@ function run_test() { assert_true(equalBuffers(result, vector.plaintext), "Round trip returns original plaintext"); return ciphertext; }, function(err) { - assert_unreached("decrypt error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); }); }) .then(function(priorCiphertext) { @@ -312,7 +467,7 @@ function run_test() { promise_test(function(test) { return subtle.encrypt(vector.algorithm, vector.publicKey, plaintext) .then(function(ciphertext) { - assert_unreached("Should have thrown error for too long plaintext in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for too long plaintext in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "OperationError", "Should throw OperationError instead of " + err.message); }); @@ -337,7 +492,7 @@ function run_test() { promise_test(function(test) { return subtle.encrypt(vector.algorithm, vector.privateKey, vector.plaintext) .then(function(ciphertext) { - assert_unreached("Should have thrown error for using privateKey to encrypt in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for using privateKey to encrypt in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); }); @@ -365,7 +520,7 @@ function run_test() { promise_test(function(test) { return subtle.encrypt(vector.algorithm, vector.publicKey, vector.plaintext) .then(function(ciphertext) { - assert_unreached("Should have thrown error for no encrypt usage in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for no encrypt usage in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); }); diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/raw_format_aliases.tentative.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/raw_format_aliases.tentative.https.any.js new file mode 100644 index 00000000000000..646ae1b0de7428 --- /dev/null +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/raw_format_aliases.tentative.https.any.js @@ -0,0 +1,72 @@ +// META: title=WebCryptoAPI: raw-secret and raw-public importKey() format aliases +// META: timeout=long +// META: script=../util/helpers.js + +// For all existing symmetric algorithms in WebCrypto, "raw-secret" acts as an +// alias of "raw". For all existing asymmetric algorithms in WebCrypto, +// "raw-public" acts as an alias of "raw". + +"use strict"; + +const rawKeyData16 = crypto.getRandomValues(new Uint8Array(16)); +const rawKeyData32 = crypto.getRandomValues(new Uint8Array(32)); +const wrapAlgorithm = { name: "AES-GCM", iv: new Uint8Array(12) }; + +const symmetricAlgorithms = [ + { algorithm: { name: "AES-CTR", length: 128 }, keyData: rawKeyData16, usages: ["encrypt", "decrypt"] }, + { algorithm: { name: "AES-CBC", length: 128 }, keyData: rawKeyData16, usages: ["encrypt", "decrypt"] }, + { algorithm: { name: "AES-GCM", length: 128 }, keyData: rawKeyData16, usages: ["encrypt", "decrypt"] }, + { algorithm: { name: "AES-KW", length: 128 }, keyData: rawKeyData16, usages: ["wrapKey", "unwrapKey"] }, + { algorithm: { name: "HMAC", hash: "SHA-256", length: 256 }, keyData: rawKeyData32, usages: ["sign", "verify"] }, + { algorithm: { name: "HKDF" }, keyData: rawKeyData32, usages: ["deriveBits", "deriveKey"], extractable: false }, + { algorithm: { name: "PBKDF2" }, keyData: rawKeyData32, usages: ["deriveBits", "deriveKey"], extractable: false }, +]; + +for (const { algorithm, keyData, usages, extractable = true } of symmetricAlgorithms) { + promise_test(async () => { + const key = await crypto.subtle.importKey("raw-secret", keyData, algorithm, extractable, usages); + assert_goodCryptoKey(key, algorithm, extractable, usages, "secret"); + if (extractable) { + await crypto.subtle.exportKey("raw-secret", key); + } + }, `importKey/exportKey with raw-secret: ${algorithm.name}`); + + if (extractable) { + promise_test(async () => { + const wrappingKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["wrapKey", "unwrapKey"]); + const key = await crypto.subtle.importKey("raw-secret", keyData, algorithm, true, usages); + const wrapped = await crypto.subtle.wrapKey("raw-secret", key, wrappingKey, wrapAlgorithm); + const unwrapped = await crypto.subtle.unwrapKey("raw-secret", wrapped, wrappingKey, wrapAlgorithm, algorithm, true, usages); + assert_goodCryptoKey(unwrapped, algorithm, true, usages, "secret"); + }, `wrapKey/unwrapKey with raw-secret: ${algorithm.name}`); + } +} + +const asymmetricAlgorithms = [ + { algorithm: { name: "ECDSA", namedCurve: "P-256" }, usages: ["verify"] }, + { algorithm: { name: "ECDH", namedCurve: "P-256" }, usages: [] }, + { algorithm: { name: "Ed25519" }, usages: ["verify"] }, + { algorithm: { name: "X25519" }, usages: [] }, +]; + +for (const { algorithm, usages } of asymmetricAlgorithms) { + const generateKeyUsages = usages.length ? usages.concat("sign") : ["deriveBits"]; + + promise_test(async () => { + const keyPair = await crypto.subtle.generateKey(algorithm, true, generateKeyUsages); + const keyData = await crypto.subtle.exportKey("raw-public", keyPair.publicKey); + + const key = await crypto.subtle.importKey("raw-public", keyData, algorithm, true, usages); + assert_goodCryptoKey(key, algorithm, true, usages, "public"); + await crypto.subtle.exportKey("raw-public", key); + }, `importKey/exportKey with raw-public: ${algorithm.name}`); + + promise_test(async () => { + const keyPair = await crypto.subtle.generateKey(algorithm, true, generateKeyUsages); + + const wrappingKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["wrapKey", "unwrapKey"]); + const wrapped = await crypto.subtle.wrapKey("raw-public", keyPair.publicKey, wrappingKey, wrapAlgorithm); + const unwrapped = await crypto.subtle.unwrapKey("raw-public", wrapped, wrappingKey, wrapAlgorithm, algorithm, true, usages); + assert_goodCryptoKey(unwrapped, algorithm, true, usages, "public"); + }, `wrapKey/unwrapKey with raw-public: ${algorithm.name}`); +} diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/ecdsa.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/ecdsa.js index 9b47868fe32bfb..b2e0bf606b5fee 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/ecdsa.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/ecdsa.js @@ -22,7 +22,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -57,7 +57,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -82,7 +82,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); signature[0] = 255 - signature[0]; @@ -97,6 +97,63 @@ function run_test() { all_promises.push(promise); }); + // Test verification with a transferred buffer during call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var algorithm = { + get name() { + signature.buffer.transfer(); + return vector.algorithmName; + }, + hash: vector.hashName + }; + var operation = subtle.verify(algorithm, vector.publicKey, signature, vector.plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " verification with transferred signature during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature during call"); + }); + + all_promises.push(promise); + }); + + // Test verification with a transferred buffer after call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + var algorithm = {name: vector.algorithmName, hash: vector.hashName}; + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var operation = subtle.verify(algorithm, vector.publicKey, signature, vector.plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + signature.buffer.transfer(); + return operation; + }, vector.name + " verification with transferred signature after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature after call"); + }); + + all_promises.push(promise); + }); + // Check for successful verification even if plaintext is altered during call. testVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["verify"], ["sign"]) @@ -115,7 +172,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -140,7 +197,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); plaintext[0] = 255 - plaintext[0]; @@ -155,6 +212,63 @@ function run_test() { all_promises.push(promise); }); + // Check for failed verification if plaintext is transferred during call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var algorithm = { + get name() { + plaintext.buffer.transfer(); + return vector.algorithmName; + }, + hash: vector.hashName + }; + var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " with transferred plaintext during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext during call"); + }); + + all_promises.push(promise); + }); + + // Check for successful verification even if plaintext is transferred after call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + var algorithm = {name: vector.algorithmName, hash: vector.hashName}; + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + plaintext.buffer.transfer(); + return operation; + }, vector.name + " with transferred plaintext after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext after call"); + }); + + all_promises.push(promise); + }); + // Check for failures due to using privateKey to verify. testVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["verify"], ["sign"]) @@ -163,7 +277,7 @@ function run_test() { promise_test(function(test) { return subtle.verify(algorithm, vector.privateKey, vector.signature, vector.plaintext) .then(function(plaintext) { - assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -186,7 +300,7 @@ function run_test() { promise_test(function(test) { return subtle.sign(algorithm, vector.publicKey, vector.plaintext) .then(function(signature) { - assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -210,7 +324,7 @@ function run_test() { promise_test(function(test) { return subtle.verify(algorithm, vector.publicKey, vector.signature, vector.plaintext) .then(function(plaintext) { - assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -238,7 +352,7 @@ function run_test() { assert_true(is_verified, "Round trip verification works"); return signature; }, function(err) { - assert_unreached("verify error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("verify error for test " + vector.name + ": '" + err.message + "'"); }); }, function(err) { assert_unreached("sign error for test " + vector.name + ": '" + err.message + "'"); @@ -338,7 +452,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -369,7 +483,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -426,7 +540,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -455,7 +569,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -482,7 +596,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature unexpectedly verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js index 4bf1887b2ae9bb..5ff6f21150c272 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js @@ -18,7 +18,7 @@ function run_test(algorithmName) { isVerified = await subtle.verify(algorithm, key, vector.signature, vector.data) } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_true(isVerified, "Signature verified"); }, vector.name + " verification"); @@ -39,7 +39,7 @@ function run_test(algorithmName) { }, key, signature, vector.data); } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_true(isVerified, "Signature verified"); }, vector.name + " verification with altered signature during call"); @@ -57,11 +57,48 @@ function run_test(algorithmName) { ]); } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_true(isVerified, "Signature verified"); }, vector.name + " verification with altered signature after call"); + // Test verification with a transferred buffer during call + promise_test(async() => { + let isVerified = false; + let key; + try { + key = await subtle.importKey("spki", vector.publicKeyBuffer, algorithm, false, ["verify"]); + var signature = copyBuffer(vector.signature); + isVerified = await subtle.verify({ + get name() { + signature.buffer.transfer(); + return vector.algorithmName; + } + }, key, signature, vector.data); + } catch (err) { + assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }; + assert_false(isVerified, "Signature is NOT verified"); + }, vector.name + " verification with transferred signature during call"); + + // Test verification with a transferred buffer after call + promise_test(async() => { + let isVerified = false; + let key; + try { + key = await subtle.importKey("spki", vector.publicKeyBuffer, algorithm, false, ["verify"]); + var signature = copyBuffer(vector.signature); + var operation = subtle.verify(algorithm, key, signature, vector.data); + signature.buffer.transfer(); + isVerified = await operation; + } catch (err) { + assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }; + assert_true(isVerified, "Signature verified"); + }, vector.name + " verification with transferred signature after call"); + // Check for successful verification even if data is altered during call. promise_test(async() => { let isVerified = false; @@ -78,7 +115,7 @@ function run_test(algorithmName) { }, key, vector.signature, data); } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_true(isVerified, "Signature verified"); }, vector.name + " with altered data during call"); @@ -96,11 +133,48 @@ function run_test(algorithmName) { ]); } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_true(isVerified, "Signature verified"); }, vector.name + " with altered data after call"); + // Check for failed verification if data is transferred during call. + promise_test(async() => { + let isVerified = false; + let key; + try { + key = await subtle.importKey("spki", vector.publicKeyBuffer, algorithm, false, ["verify"]); + var data = copyBuffer(vector.data); + isVerified = await subtle.verify({ + get name() { + data.buffer.transfer(); + return vector.algorithmName; + } + }, key, vector.signature, data); + } catch (err) { + assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }; + assert_false(isVerified, "Signature is NOT verified"); + }, vector.name + " with transferred data during call"); + + // Check for successful verification even if data is transferred after call. + promise_test(async() => { + let isVerified = false; + let key; + try { + key = await subtle.importKey("spki", vector.publicKeyBuffer, algorithm, false, ["verify"]); + var data = copyBuffer(vector.data); + var operation = subtle.verify(algorithm, key, vector.signature, data); + data.buffer.transfer(); + isVerified = await operation; + } catch (err) { + assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }; + assert_true(isVerified, "Signature verified"); + }, vector.name + " with transferred data after call"); + // Check for failures due to using privateKey to verify. promise_test(async() => { let isVerified = false; @@ -165,7 +239,7 @@ function run_test(algorithmName) { } catch (err) { assert_false(publicKey === undefined || privateKey === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); assert_false(signature === undefined, "sign error for test " + vector.name + ": '" + err.message + "'"); - assert_unreached("verify error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("verify error for test " + vector.name + ": '" + err.message + "'"); }; assert_true(isVerified, "Round trip verification works"); }, vector.name + " round trip"); @@ -214,7 +288,7 @@ function run_test(algorithmName) { isVerified = await subtle.verify(algorithm, key, signature, vector.data) } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_false(isVerified, "Signature verified"); }, vector.name + " verification failure due to altered signature"); @@ -229,7 +303,7 @@ function run_test(algorithmName) { isVerified = await subtle.verify(algorithm, key, signature, vector.data) } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_false(isVerified, "Signature verified"); }, vector.name + " verification failure due to shortened signature"); @@ -245,7 +319,7 @@ function run_test(algorithmName) { isVerified = await subtle.verify(algorithm, key, vector.signature, data) } catch (err) { assert_false(key === undefined, "importKey failed for " + vector.name + ". Message: ''" + err.message + "''"); - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }; assert_false(isVerified, "Signature verified"); }, vector.name + " verification failure due to altered data"); diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/hmac.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/hmac.js index dff8c994d83de0..8a099f4ce9377a 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/hmac.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/hmac.js @@ -20,7 +20,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -45,16 +45,16 @@ function run_test() { var signature = copyBuffer(vector.signature); signature[0] = 255 - signature[0]; var operation = subtle.verify({ - hash: vector.hash, get name() { signature[0] = vector.signature[0]; return "HMAC"; - } + }, + hash: vector.hash }, vector.key, signature, vector.plaintext) .then(function(is_verified) { assert_true(is_verified, "Signature is not verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -78,7 +78,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature is not verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); signature[0] = 255 - signature[0]; @@ -93,6 +93,61 @@ function run_test() { all_promises.push(promise); }); + // Test verification with a transferred buffer during call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var operation = subtle.verify({ + get name() { + signature.buffer.transfer(); + return "HMAC"; + }, + hash: vector.hash + }, vector.key, signature, vector.plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " verification with transferred signature during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature during call"); + }); + + all_promises.push(promise); + }); + + // Test verification with a transferred buffer after call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, signature, vector.plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature is not verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + signature.buffer.transfer(); + return operation; + }, vector.name + " verification with transferred signature after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature after call"); + }); + + all_promises.push(promise); + }); + // Check for successful verification even if plaintext is altered during call. testVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["verify", "sign"]) @@ -110,7 +165,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -134,7 +189,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); plaintext[0] = 255 - plaintext[0]; @@ -149,6 +204,61 @@ function run_test() { all_promises.push(promise); }); + // Check for failed verification if plaintext is transferred during call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var operation = subtle.verify({ + get name() { + plaintext.buffer.transfer(); + return "HMAC"; + }, + hash: vector.hash + }, vector.key, vector.signature, plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " with transferred plaintext during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext during call"); + }); + + all_promises.push(promise); + }); + + // Check for successful verification even if plaintext is transferred after call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, vector.signature, plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + plaintext.buffer.transfer(); + return operation; + }, vector.name + " with transferred plaintext after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext after call"); + }); + + all_promises.push(promise); + }); + // Check for failures due to no "verify" usage. testVectors.forEach(function(originalVector) { var vector = Object.assign({}, originalVector); @@ -158,7 +268,7 @@ function run_test() { promise_test(function(test) { return subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, vector.signature, vector.plaintext) .then(function(plaintext) { - assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -186,7 +296,7 @@ function run_test() { assert_true(is_verified, "Round trip verifies"); return signature; }, function(err) { - assert_unreached("verify error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("verify error for test " + vector.name + ": '" + err.message + "'"); }); }); }, vector.name + " round trip"); @@ -281,7 +391,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature is NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -309,7 +419,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature is NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -336,7 +446,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature is NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac.js index 57c9dc6f34790a..b966c12b4ed07c 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac.js @@ -15,7 +15,7 @@ function run_test() { var promise = importVectorKeys(vector, ["verify", "sign"]) .then(function(vector) { promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -23,7 +23,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -48,7 +48,7 @@ function run_test() { var signature = copyBuffer(vector.signature); signature[0] = 255 - signature[0]; var algorithmParams = { - length: vector.length, + outputLength: vector.outputLength, get name() { signature[0] = vector.signature[0]; return vector.algorithm; @@ -61,7 +61,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature is not verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -81,7 +81,7 @@ function run_test() { .then(function(vector) { promise_test(function(test) { var signature = copyBuffer(vector.signature); - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -89,7 +89,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature is not verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); signature[0] = 255 - signature[0]; @@ -104,6 +104,69 @@ function run_test() { all_promises.push(promise); }); + // Test verification with a transferred buffer during call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var algorithmParams = { + get name() { + signature.buffer.transfer(); + return vector.algorithm; + }, + outputLength: vector.outputLength + }; + if (vector.customization !== undefined) { + algorithmParams.customization = vector.customization; + } + var operation = subtle.verify(algorithmParams, vector.key, signature, vector.plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " verification with transferred signature during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature during call"); + }); + + all_promises.push(promise); + }); + + // Test verification with a transferred buffer after call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; + if (vector.customization !== undefined) { + algorithmParams.customization = vector.customization; + } + var operation = subtle.verify(algorithmParams, vector.key, signature, vector.plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature is not verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + signature.buffer.transfer(); + return operation; + }, vector.name + " verification with transferred signature after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature after call"); + }); + + all_promises.push(promise); + }); + // Check for successful verification even if plaintext is altered during call. testVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["verify", "sign"]) @@ -112,7 +175,7 @@ function run_test() { var plaintext = copyBuffer(vector.plaintext); plaintext[0] = 255 - plaintext[0]; var algorithmParams = { - length: vector.length, + outputLength: vector.outputLength, get name() { plaintext[0] = vector.plaintext[0]; return vector.algorithm; @@ -125,7 +188,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -145,7 +208,7 @@ function run_test() { .then(function(vector) { promise_test(function(test) { var plaintext = copyBuffer(vector.plaintext); - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -153,7 +216,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); plaintext[0] = 255 - plaintext[0]; @@ -168,6 +231,69 @@ function run_test() { all_promises.push(promise); }); + // Check for failed verification if plaintext is transferred during call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var algorithmParams = { + get name() { + plaintext.buffer.transfer(); + return vector.algorithm; + }, + outputLength: vector.outputLength + }; + if (vector.customization !== undefined) { + algorithmParams.customization = vector.customization; + } + var operation = subtle.verify(algorithmParams, vector.key, vector.signature, plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " with transferred plaintext during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext during call"); + }); + + all_promises.push(promise); + }); + + // Check for successful verification even if plaintext is transferred after call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify", "sign"]) + .then(function(vector) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; + if (vector.customization !== undefined) { + algorithmParams.customization = vector.customization; + } + var operation = subtle.verify(algorithmParams, vector.key, vector.signature, plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + plaintext.buffer.transfer(); + return operation; + }, vector.name + " with transferred plaintext after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext after call"); + }); + + all_promises.push(promise); + }); + // Check for failures due to no "verify" usage. testVectors.forEach(function(originalVector) { var vector = Object.assign({}, originalVector); @@ -175,13 +301,13 @@ function run_test() { var promise = importVectorKeys(vector, ["sign"]) .then(function(vector) { promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } return subtle.verify(algorithmParams, vector.key, vector.signature, vector.plaintext) .then(function(plaintext) { - assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -200,7 +326,7 @@ function run_test() { var promise = importVectorKeys(vector, ["verify", "sign"]) .then(function(vectors) { promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -213,7 +339,7 @@ function run_test() { assert_true(is_verified, "Round trip verifies"); return signature; }, function(err) { - assert_unreached("verify error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("verify error for test " + vector.name + ": '" + err.message + "'"); }); }); }, vector.name + " round trip"); @@ -237,7 +363,7 @@ function run_test() { return importVectorKeys(vector, ["verify", "sign"]) .then(function(vectors) { promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -275,7 +401,7 @@ function run_test() { return importVectorKeys(vector, ["verify", "sign"]) .then(function(vector) { promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -312,7 +438,7 @@ function run_test() { var plaintext = copyBuffer(vector.plaintext); plaintext[0] = 255 - plaintext[0]; promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -320,7 +446,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature is NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -344,7 +470,7 @@ function run_test() { var signature = copyBuffer(vector.signature); signature[0] = 255 - signature[0]; promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -352,7 +478,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature is NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -375,7 +501,7 @@ function run_test() { .then(function(vector) { var signature = vector.signature.slice(1); // Drop first byte promise_test(function(test) { - var algorithmParams = {name: vector.algorithm, length: vector.length}; + var algorithmParams = {name: vector.algorithm, outputLength: vector.outputLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -383,7 +509,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature is NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -405,8 +531,8 @@ function run_test() { var promise = importVectorKeys(vector, ["verify", "sign"]) .then(function(vector) { promise_test(function(test) { - var differentLength = vector.length === 256 ? 512 : 256; - var algorithmParams = {name: vector.algorithm, length: differentLength}; + var differentLength = vector.outputLength === 256 ? 512 : 256; + var algorithmParams = {name: vector.algorithm, outputLength: differentLength}; if (vector.customization !== undefined) { algorithmParams.customization = vector.customization; } @@ -414,7 +540,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature is NOT verified with wrong length"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac_vectors.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac_vectors.js index 39d0efe71ba3e2..6b35cab168c9c6 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac_vectors.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/kmac_vectors.js @@ -6,7 +6,7 @@ function getTestVectors() { // Sample #1 - KMAC128, no customization name: "KMAC128 with no customization", algorithm: "KMAC128", - length: 256, + outputLength: 256, keyBuffer: new Uint8Array([ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, @@ -25,7 +25,7 @@ function getTestVectors() { // Sample #2 - KMAC128, with customization name: "KMAC128 with customization", algorithm: "KMAC128", - length: 256, + outputLength: 256, keyBuffer: new Uint8Array([ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, @@ -47,7 +47,7 @@ function getTestVectors() { // Sample #3 - KMAC128, large data, with customization name: "KMAC128 with large data and customization", algorithm: "KMAC128", - length: 256, + outputLength: 256, keyBuffer: new Uint8Array([ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, @@ -69,7 +69,7 @@ function getTestVectors() { // Sample #4 - KMAC256, with customization, 512-bit output name: "KMAC256 with customization and 512-bit output", algorithm: "KMAC256", - length: 512, + outputLength: 512, keyBuffer: new Uint8Array([ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, @@ -94,7 +94,7 @@ function getTestVectors() { // Sample #5 - KMAC256, large data, no customization, 512-bit output name: "KMAC256 with large data and no customization", algorithm: "KMAC256", - length: 512, + outputLength: 512, keyBuffer: new Uint8Array([ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, @@ -116,7 +116,7 @@ function getTestVectors() { // Sample #6 - KMAC256, large data, with customization, 512-bit output name: "KMAC256 with large data and customization", algorithm: "KMAC256", - length: 512, + outputLength: 512, keyBuffer: new Uint8Array([ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/mldsa.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/mldsa.js index 88143a33879ed7..9c654685c74f31 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/mldsa.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/mldsa.js @@ -156,6 +156,106 @@ function run_test() { all_promises.push(promise); }); + // Test verification with a transferred buffer during call + testVectors.forEach(function (vector) { + var promise = importVectorKeys(vector, ['verify'], ['sign']).then( + function (vectors) { + promise_test(function (test) { + var signature = copyBuffer(vector.signature); + var operation = subtle + .verify( + { + get name() { + signature.buffer.transfer(); + return vector.algorithmName; + }, + }, + vector.publicKey, + signature, + vector.data + ) + .then( + function (is_verified) { + assert_false(is_verified, 'Signature is NOT verified'); + }, + function (err) { + assert_unreached( + 'Verification should not throw error ' + + vector.name + + ': ' + + err.message + + "'" + ); + } + ); + + return operation; + }, vector.name + ' verification with transferred signature during call'); + }, + function (err) { + promise_test(function (test) { + assert_unreached( + 'importVectorKeys failed for ' + + vector.name + + ". Message: ''" + + err.message + + "''" + ); + }, 'importVectorKeys step: ' + + vector.name + + ' verification with transferred signature during call'); + } + ); + + all_promises.push(promise); + }); + + // Test verification with a transferred buffer after call + testVectors.forEach(function (vector) { + var promise = importVectorKeys(vector, ['verify'], ['sign']).then( + function (vectors) { + var algorithm = vector.algorithmName; + promise_test(function (test) { + var signature = copyBuffer(vector.signature); + var operation = subtle + .verify(algorithm, vector.publicKey, signature, vector.data) + .then( + function (is_verified) { + assert_true(is_verified, 'Signature verified'); + }, + function (err) { + assert_unreached( + 'Verification should not throw error ' + + vector.name + + ': ' + + err.message + + "'" + ); + } + ); + + signature.buffer.transfer(); + return operation; + }, vector.name + ' verification with transferred signature after call'); + }, + function (err) { + promise_test(function (test) { + assert_unreached( + 'importVectorKeys failed for ' + + vector.name + + ". Message: ''" + + err.message + + "''" + ); + }, 'importVectorKeys step: ' + + vector.name + + ' verification with transferred signature after call'); + } + ); + + all_promises.push(promise); + }); + // Check for successful verification even if plaintext is altered during call. testVectors.forEach(function (vector) { var promise = importVectorKeys(vector, ['verify'], ['sign']).then( @@ -257,6 +357,106 @@ function run_test() { all_promises.push(promise); }); + // Check for failed verification if plaintext is transferred during call. + testVectors.forEach(function (vector) { + var promise = importVectorKeys(vector, ['verify'], ['sign']).then( + function (vectors) { + promise_test(function (test) { + var plaintext = copyBuffer(vector.data); + var operation = subtle + .verify( + { + get name() { + plaintext.buffer.transfer(); + return vector.algorithmName; + }, + }, + vector.publicKey, + vector.signature, + plaintext + ) + .then( + function (is_verified) { + assert_false(is_verified, 'Signature is NOT verified'); + }, + function (err) { + assert_unreached( + 'Verification should not throw error ' + + vector.name + + ': ' + + err.message + + "'" + ); + } + ); + + return operation; + }, vector.name + ' with transferred plaintext during call'); + }, + function (err) { + promise_test(function (test) { + assert_unreached( + 'importVectorKeys failed for ' + + vector.name + + ". Message: ''" + + err.message + + "''" + ); + }, 'importVectorKeys step: ' + + vector.name + + ' with transferred plaintext during call'); + } + ); + + all_promises.push(promise); + }); + + // Check for successful verification even if plaintext is transferred after call. + testVectors.forEach(function (vector) { + var promise = importVectorKeys(vector, ['verify'], ['sign']).then( + function (vectors) { + var algorithm = vector.algorithmName; + promise_test(function (test) { + var plaintext = copyBuffer(vector.data); + var operation = subtle + .verify(algorithm, vector.publicKey, vector.signature, plaintext) + .then( + function (is_verified) { + assert_true(is_verified, 'Signature verified'); + }, + function (err) { + assert_unreached( + 'Verification should not throw error ' + + vector.name + + ': ' + + err.message + + "'" + ); + } + ); + + plaintext.buffer.transfer(); + return operation; + }, vector.name + ' with transferred plaintext after call'); + }, + function (err) { + promise_test(function (test) { + assert_unreached( + 'importVectorKeys failed for ' + + vector.name + + ". Message: ''" + + err.message + + "''" + ); + }, 'importVectorKeys step: ' + + vector.name + + ' with transferred plaintext after call'); + } + ); + + all_promises.push(promise); + }); + // Check for failures due to using privateKey to verify. testVectors.forEach(function (vector) { var promise = importVectorKeys(vector, ['verify'], ['sign']).then( diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js index f808714cfb11d4..09c7ceb7675107 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js @@ -20,7 +20,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -54,7 +54,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -78,7 +78,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); signature[0] = 255 - signature[0]; @@ -93,6 +93,61 @@ function run_test() { all_promises.push(promise); }); + // Test verification with a transferred buffer during call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var operation = subtle.verify({ + ...vector.algorithm, + get name() { + signature.buffer.transfer(); + return vector.algorithm.name; + } + }, vector.publicKey, signature, vector.plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " verification with transferred signature during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature during call"); + }); + + all_promises.push(promise); + }); + + // Test verification with a transferred buffer after call + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + promise_test(function(test) { + var signature = copyBuffer(vector.signature); + var operation = subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + signature.buffer.transfer(); + return operation; + }, vector.name + " verification with transferred signature after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification with transferred signature after call"); + }); + + all_promises.push(promise); + }); + // Check for successful verification even if plaintext is altered during call. testVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["verify"], ["sign"]) @@ -110,7 +165,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -134,7 +189,7 @@ function run_test() { .then(function(is_verified) { assert_true(is_verified, "Signature verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); plaintext[0] = 255 - plaintext[0]; @@ -149,6 +204,61 @@ function run_test() { all_promises.push(promise); }); + // Check for failed verification if plaintext is transferred during call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var operation = subtle.verify({ + ...vector.algorithm, + get name() { + plaintext.buffer.transfer(); + return vector.algorithm.name; + } + }, vector.publicKey, vector.signature, plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature is NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + return operation; + }, vector.name + " with transferred plaintext during call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext during call"); + }); + + all_promises.push(promise); + }); + + // Check for successful verification even if plaintext is transferred after call. + testVectors.forEach(function(vector) { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + promise_test(function(test) { + var plaintext = copyBuffer(vector.plaintext); + var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, plaintext) + .then(function(is_verified) { + assert_true(is_verified, "Signature verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); + }); + + plaintext.buffer.transfer(); + return operation; + }, vector.name + " with transferred plaintext after call"); + }, function(err) { + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " with transferred plaintext after call"); + }); + + all_promises.push(promise); + }); + // Check for failures due to using privateKey to verify. testVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["verify"], ["sign"]) @@ -156,7 +266,7 @@ function run_test() { promise_test(function(test) { return subtle.verify(vector.algorithm, vector.privateKey, vector.signature, vector.plaintext) .then(function(plaintext) { - assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -178,7 +288,7 @@ function run_test() { promise_test(function(test) { return subtle.sign(vector.algorithm, vector.publicKey, vector.plaintext) .then(function(signature) { - assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -201,7 +311,7 @@ function run_test() { promise_test(function(test) { return subtle.verify(vector.algorithm, vector.publicKey, vector.signature, vector.plaintext) .then(function(plaintext) { - assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'"); + assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": '" + err.message + "'"); }, function(err) { assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); }); @@ -234,7 +344,7 @@ function run_test() { assert_true(is_verified, "Round trip verifies"); return signature; }, function(err) { - assert_unreached("verify error for test " + vector.name + ": " + err.message + "'"); + assert_unreached("verify error for test " + vector.name + ": '" + err.message + "'"); }); }) .then(function(priorSignature) { @@ -351,7 +461,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -379,7 +489,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; @@ -408,7 +518,7 @@ function run_test() { .then(function(is_verified) { assert_false(is_verified, "Signature NOT verified"); }, function(err) { - assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + assert_unreached("Verification should not throw error " + vector.name + ": '" + err.message + "'"); }); return operation; diff --git a/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js index e40b4b6d35f794..880f7d650964ae 100644 --- a/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js @@ -75,7 +75,7 @@ var wrapper = wrappers[wrapperParam.name]; keysToWrapParameters.filter((param) => Object.keys(keys).includes(param.algorithm.name)).forEach(function(toWrapParam) { var keyData = keys[toWrapParam.algorithm.name]; - ["raw", "spki", "pkcs8"].filter((fmt) => Object.keys(keyData).includes(fmt)).forEach(function(keyDataFormat) { + ["raw", "raw-secret", "spki", "pkcs8"].filter((fmt) => Object.keys(keyData).includes(fmt)).forEach(function(keyDataFormat) { var toWrap = keyData[keyDataFormat]; [keyDataFormat, "jwk"].forEach(function(format) { if (wrappingIsPossible(toWrap.originalExport[format], wrapper.parameters.name)) { @@ -114,7 +114,7 @@ })); } else if (params.name === "ChaCha20-Poly1305") { var algorithm = {name: params.name}; - promises.push(subtle.importKey("raw", wrappingKeyData["SYMMETRIC256"].raw, algorithm, true, ["wrapKey", "unwrapKey"]) + promises.push(subtle.importKey("raw-secret", wrappingKeyData["SYMMETRIC256"].raw, algorithm, true, ["wrapKey", "unwrapKey"]) .then(function(key) { wrappers[params.name] = {wrappingKey: key, unwrappingKey: key, parameters: params}; })); @@ -165,7 +165,7 @@ promises.push(importAndExport("pkcs8", keyData.pkcs8, params.algorithm, params.privateUsages, "private key ")); } else if (params.algorithm.name === "ChaCha20-Poly1305") { keys[params.algorithm.name] = {}; - promises.push(importAndExport("raw", toWrapKeyData["SYMMETRIC256"].raw, params.algorithm, params.usages, "")); + promises.push(importAndExport("raw-secret", toWrapKeyData["SYMMETRIC256"].raw, params.algorithm, params.usages, "")); } else { keys[params.algorithm.name] = {}; promises.push(importAndExport("raw", toWrapKeyData["SYMMETRIC128"].raw, params.algorithm, params.usages, "")); diff --git a/test/fixtures/wpt/url/resources/urltestdata.json b/test/fixtures/wpt/url/resources/urltestdata.json index ec13a88cef0e8b..690a48870fd0d9 100644 --- a/test/fixtures/wpt/url/resources/urltestdata.json +++ b/test/fixtures/wpt/url/resources/urltestdata.json @@ -4268,6 +4268,36 @@ "search": "", "hash": "" }, + { + "input": "https://localhost?q=🔥", + "base": null, + "href": "https://localhost/?q=%F0%9F%94%A5", + "origin": "https://localhost", + "protocol": "https:", + "username": "", + "password": "", + "host": "localhost", + "hostname": "localhost", + "port": "", + "pathname": "/", + "search": "?q=%F0%9F%94%A5", + "hash": "" + }, + { + "input": "https://localhost#🔥", + "base": null, + "href": "https://localhost/#%F0%9F%94%A5", + "origin": "https://localhost", + "protocol": "https:", + "username": "", + "password": "", + "host": "localhost", + "hostname": "localhost", + "port": "", + "pathname": "/", + "search": "", + "hash": "#%F0%9F%94%A5" + }, "# resolving a fragment against any scheme succeeds", { "input": "#", @@ -6095,6 +6125,21 @@ "search": "", "hash": "" }, + { + "input": "https://0000000000000000000000000000000000000000177.0.0.1", + "base": null, + "href": "https://127.0.0.1/", + "origin": "https://127.0.0.1", + "protocol": "https:", + "username": "", + "password": "", + "host": "127.0.0.1", + "hostname": "127.0.0.1", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)", { "input": "https://0x100000000/test", diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index a3b6c290982f88..a5ffb33473f468 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -72,7 +72,7 @@ "path": "streams" }, "url": { - "commit": "c928b19ab04a4525807238e9299c23f3a1cca582", + "commit": "fc3e651593fab938433613eb5ad0b4dbd5a4e167", "path": "url" }, "urlpattern": { @@ -96,7 +96,7 @@ "path": "web-locks" }, "WebCryptoAPI": { - "commit": "c9e955840a21be6e492225a4a53fc4828d8933b9", + "commit": "2cb332d71030ba0200610d72b94bb1badf447418", "path": "WebCryptoAPI" }, "webidl": { diff --git a/test/parallel/test-async-local-storage-run-scope.js b/test/parallel/test-async-local-storage-run-scope.js new file mode 100644 index 00000000000000..1ae3e44aadf633 --- /dev/null +++ b/test/parallel/test-async-local-storage-run-scope.js @@ -0,0 +1,202 @@ +/* eslint-disable no-unused-vars */ +'use strict'; +require('../common'); +const assert = require('node:assert'); +const { AsyncLocalStorage } = require('node:async_hooks'); + +// Test basic RunScope with using +{ + const storage = new AsyncLocalStorage(); + + assert.strictEqual(storage.getStore(), undefined); + + { + using scope = storage.withScope('test'); + assert.strictEqual(storage.getStore(), 'test'); + } + + // Store should be restored to undefined + assert.strictEqual(storage.getStore(), undefined); +} + +// Test RunScope restores previous value +{ + const storage = new AsyncLocalStorage(); + + storage.enterWith('initial'); + assert.strictEqual(storage.getStore(), 'initial'); + + { + using scope = storage.withScope('scoped'); + assert.strictEqual(storage.getStore(), 'scoped'); + } + + // Should restore to previous value + assert.strictEqual(storage.getStore(), 'initial'); +} + +// Test nested RunScope +{ + const storage = new AsyncLocalStorage(); + const storeValues = []; + + { + using outer = storage.withScope('outer'); + storeValues.push(storage.getStore()); + + { + using inner = storage.withScope('inner'); + storeValues.push(storage.getStore()); + } + + // Should restore to outer + storeValues.push(storage.getStore()); + } + + // Should restore to undefined + storeValues.push(storage.getStore()); + + assert.deepStrictEqual(storeValues, ['outer', 'inner', 'outer', undefined]); +} + +// Test RunScope with error during usage +{ + const storage = new AsyncLocalStorage(); + + storage.enterWith('before'); + + const testError = new Error('test'); + + assert.throws(() => { + using scope = storage.withScope('during'); + assert.strictEqual(storage.getStore(), 'during'); + throw testError; + }, testError); + + // Store should be restored even after error + assert.strictEqual(storage.getStore(), 'before'); +} + +// Test idempotent disposal via named dispose() method +{ + const storage = new AsyncLocalStorage(); + + const scope = storage.withScope('test'); + assert.strictEqual(storage.getStore(), 'test'); + + // Dispose via named dispose() method + scope.dispose(); + assert.strictEqual(storage.getStore(), undefined); + + storage.enterWith('test2'); + assert.strictEqual(storage.getStore(), 'test2'); + + // Double dispose should be idempotent + scope.dispose(); + assert.strictEqual(storage.getStore(), 'test2'); +} + +// Test withScope without using keyword (scope leaks until manually disposed) +{ + const storage = new AsyncLocalStorage(); + + const scope = storage.withScope('leaked'); + assert.strictEqual(storage.getStore(), 'leaked'); + + // Without using, the scope persists + assert.strictEqual(storage.getStore(), 'leaked'); + + // Must manually dispose via named method + scope.dispose(); + assert.strictEqual(storage.getStore(), undefined); +} + +// Test that dispose undoes enterWith called inside scope +{ + const storage = new AsyncLocalStorage(); + + storage.enterWith('store1'); + assert.strictEqual(storage.getStore(), 'store1'); + + { + using _ = storage.withScope('store2'); + assert.strictEqual(storage.getStore(), 'store2'); + + storage.enterWith('store3'); + assert.strictEqual(storage.getStore(), 'store3'); + } + + // Restores to store1, undoing both withScope and the enterWith inside scope + assert.strictEqual(storage.getStore(), 'store1'); +} + +// Test RunScope with defaultValue +{ + const storage = new AsyncLocalStorage({ defaultValue: 'default' }); + + assert.strictEqual(storage.getStore(), 'default'); + + { + using scope = storage.withScope('custom'); + assert.strictEqual(storage.getStore(), 'custom'); + } + + // Should restore to default + assert.strictEqual(storage.getStore(), 'default'); +} + +// Test deeply nested RunScope +{ + const storage = new AsyncLocalStorage(); + + { + using s1 = storage.withScope(1); + assert.strictEqual(storage.getStore(), 1); + + { + using s2 = storage.withScope(2); + assert.strictEqual(storage.getStore(), 2); + + { + using s3 = storage.withScope(3); + assert.strictEqual(storage.getStore(), 3); + + { + using s4 = storage.withScope(4); + assert.strictEqual(storage.getStore(), 4); + } + + assert.strictEqual(storage.getStore(), 3); + } + + assert.strictEqual(storage.getStore(), 2); + } + + assert.strictEqual(storage.getStore(), 1); + } + + assert.strictEqual(storage.getStore(), undefined); +} + +// Test RunScope with multiple storages +{ + const storage1 = new AsyncLocalStorage(); + const storage2 = new AsyncLocalStorage(); + + { + using scope1 = storage1.withScope('A'); + + { + using scope2 = storage2.withScope('B'); + + assert.strictEqual(storage1.getStore(), 'A'); + assert.strictEqual(storage2.getStore(), 'B'); + } + + assert.strictEqual(storage1.getStore(), 'A'); + assert.strictEqual(storage2.getStore(), undefined); + } + + assert.strictEqual(storage1.getStore(), undefined); + assert.strictEqual(storage2.getStore(), undefined); +} diff --git a/test/parallel/test-async-local-storage-weak-asyncwrap-leak.js b/test/parallel/test-async-local-storage-weak-asyncwrap-leak.js new file mode 100644 index 00000000000000..baa13f610ed9aa --- /dev/null +++ b/test/parallel/test-async-local-storage-weak-asyncwrap-leak.js @@ -0,0 +1,49 @@ +// Flags: --expose-gc +'use strict'; +const common = require('../common'); +const assert = require('node:assert'); +const zlib = require('node:zlib'); +const v8 = require('node:v8'); +const { AsyncLocalStorage } = require('node:async_hooks'); + +// This test verifies that referencing an AsyncLocalStorage store from +// a weak AsyncWrap does not prevent the store from being garbage collected. +// We use zlib streams as examples of weak AsyncWraps here, but the +// issue is not specific to zlib. + +class Store {} + +let zlibDone = false; + +// Use immediates to ensure no accidental async context propagation occurs +setImmediate(common.mustCall(() => { + const asyncLocalStorage = new AsyncLocalStorage(); + const store = new Store(); + asyncLocalStorage.run(store, common.mustCall(() => { + (async () => { + const zlibStream = zlib.createGzip(); + // (Make sure this test does not break if _handle is renamed + // to something else) + assert.strictEqual(typeof zlibStream._handle, 'object'); + // Create backreference from AsyncWrap to store + store.zlibStream = zlibStream._handle; + // Let the zlib stream finish (not strictly necessary) + zlibStream.end('hello world'); + await zlibStream.toArray(); + zlibDone = true; + })().then(common.mustCall()); + })); +})); + +const finish = common.mustCall(() => { + global.gc(); + // Make sure the ALS instance has been garbage-collected + assert.strictEqual(v8.queryObjects(Store), 0); +}); + +const interval = setInterval(() => { + if (zlibDone) { + clearInterval(interval); + finish(); + } +}, 5); diff --git a/test/parallel/test-buffer-from.js b/test/parallel/test-buffer-from.js index 6c08b973e6ab41..b8f4a5d2a2a67b 100644 --- a/test/parallel/test-buffer-from.js +++ b/test/parallel/test-buffer-from.js @@ -140,5 +140,44 @@ assert.throws(() => { }) ); +// copyBytesFrom: length exceeds view (should clamp, not throw) +{ + const u8 = new Uint8Array([1, 2, 3, 4, 5]); + const b = Buffer.copyBytesFrom(u8, 2, 100); + assert.strictEqual(b.length, 3); + assert.deepStrictEqual([...b], [3, 4, 5]); +} + +// copyBytesFrom: length 0 returns empty buffer +{ + const u8 = new Uint8Array([1, 2, 3]); + const b = Buffer.copyBytesFrom(u8, 1, 0); + assert.strictEqual(b.length, 0); +} + +// copyBytesFrom: offset past end returns empty buffer +{ + const u8 = new Uint8Array([1, 2, 3]); + const b = Buffer.copyBytesFrom(u8, 10); + assert.strictEqual(b.length, 0); +} + +// copyBytesFrom: Float64Array with offset and length +{ + const f64 = new Float64Array([1.0, 2.0, 3.0, 4.0]); + const b = Buffer.copyBytesFrom(f64, 1, 2); + assert.strictEqual(b.length, 16); + const view = new Float64Array(b.buffer, b.byteOffset, 2); + assert.strictEqual(view[0], 2.0); + assert.strictEqual(view[1], 3.0); +} + +// copyBytesFrom: empty typed array returns empty buffer +{ + const empty = new Uint8Array(0); + const b = Buffer.copyBytesFrom(empty); + assert.strictEqual(b.length, 0); +} + // Invalid encoding is allowed Buffer.from('asd', 1); diff --git a/test/parallel/test-buffer-swap-fast.js b/test/parallel/test-buffer-swap-fast.js new file mode 100644 index 00000000000000..755edb6d598892 --- /dev/null +++ b/test/parallel/test-buffer-swap-fast.js @@ -0,0 +1,55 @@ +// Flags: --expose-internals --no-warnings --allow-natives-syntax +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +function testFastSwap16() { + const buf = Buffer.from([0x01, 0x02, 0x03, 0x04]); + const expected = Buffer.from([0x02, 0x01, 0x04, 0x03]); + const padded = Buffer.alloc(256); + buf.copy(padded); + padded.swap16(); + assert.deepStrictEqual(padded.subarray(0, 4), expected); +} + +function testFastSwap32() { + const buf = Buffer.from([0x01, 0x02, 0x03, 0x04]); + const expected = Buffer.from([0x04, 0x03, 0x02, 0x01]); + const padded = Buffer.alloc(256); + buf.copy(padded); + padded.swap32(); + assert.deepStrictEqual(padded.subarray(0, 4), expected); +} + +function testFastSwap64() { + const buf = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + const expected = Buffer.from([0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + const padded = Buffer.alloc(256); + buf.copy(padded); + padded.swap64(); + assert.deepStrictEqual(padded.subarray(0, 8), expected); +} + +eval('%PrepareFunctionForOptimization(Buffer.prototype.swap16)'); +testFastSwap16(); +eval('%OptimizeFunctionOnNextCall(Buffer.prototype.swap16)'); +testFastSwap16(); + +eval('%PrepareFunctionForOptimization(Buffer.prototype.swap32)'); +testFastSwap32(); +eval('%OptimizeFunctionOnNextCall(Buffer.prototype.swap32)'); +testFastSwap32(); + +eval('%PrepareFunctionForOptimization(Buffer.prototype.swap64)'); +testFastSwap64(); +eval('%OptimizeFunctionOnNextCall(Buffer.prototype.swap64)'); +testFastSwap64(); + +if (common.isDebug) { + const { internalBinding } = require('internal/test/binding'); + const { getV8FastApiCallCount } = internalBinding('debug'); + assert.strictEqual(getV8FastApiCallCount('buffer.swap16'), 1); + assert.strictEqual(getV8FastApiCallCount('buffer.swap32'), 1); + assert.strictEqual(getV8FastApiCallCount('buffer.swap64'), 1); +} diff --git a/test/parallel/test-buffer-tostring-range.js b/test/parallel/test-buffer-tostring-range.js index f4adf64c8d9129..00803bc35d7f4e 100644 --- a/test/parallel/test-buffer-tostring-range.js +++ b/test/parallel/test-buffer-tostring-range.js @@ -78,6 +78,46 @@ assert.strictEqual(rangeBuffer.toString('ascii', 0, '-1.99'), ''); assert.strictEqual(rangeBuffer.toString('ascii', 0, 1.99), 'a'); assert.strictEqual(rangeBuffer.toString('ascii', 0, true), 'a'); +// Test hex/base64/base64url partial range encoding (exercises V8 builtin path) +{ + const buf = Buffer.from([0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe]); + assert.strictEqual(buf.toString('hex'), 'deadbeefcafe'); + assert.strictEqual(buf.toString('hex', 0, 3), 'deadbe'); + assert.strictEqual(buf.toString('hex', 2, 5), 'beefca'); + assert.strictEqual(buf.toString('hex', 4), 'cafe'); + assert.strictEqual(buf.toString('hex', 6), ''); + assert.strictEqual(buf.toString('hex', 0, 0), ''); +} + +{ + const buf = Buffer.from('Hello, World!'); + assert.strictEqual(buf.toString('base64'), 'SGVsbG8sIFdvcmxkIQ=='); + assert.strictEqual(buf.toString('base64', 0, 5), 'SGVsbG8='); + assert.strictEqual(buf.toString('base64', 7), 'V29ybGQh'); + assert.strictEqual(buf.toString('base64', 0, 0), ''); +} + +{ + const buf = Buffer.from('Hello, World!'); + assert.strictEqual(buf.toString('base64url'), 'SGVsbG8sIFdvcmxkIQ'); + assert.strictEqual(buf.toString('base64url', 0, 5), 'SGVsbG8'); + assert.strictEqual(buf.toString('base64url', 7), 'V29ybGQh'); + assert.strictEqual(buf.toString('base64url', 0, 0), ''); +} + +// Test with pool-allocated buffer (has non-zero byteOffset) +{ + const poolBuf = Buffer.from('test data for hex encoding'); + assert.strictEqual( + poolBuf.toString('hex'), + Buffer.from('test data for hex encoding').toString('hex') + ); + assert.strictEqual( + poolBuf.toString('hex', 5, 9), + Buffer.from('data').toString('hex') + ); +} + // Try toString() with an object as an encoding assert.strictEqual(rangeBuffer.toString({ toString: function() { return 'ascii'; diff --git a/test/parallel/test-cli-eval.js b/test/parallel/test-cli-eval.js index 2e5b3dc5992abc..5b090279621f37 100644 --- a/test/parallel/test-cli-eval.js +++ b/test/parallel/test-cli-eval.js @@ -115,6 +115,26 @@ child.exec(...common.escapePOSIXShell`"${process.execPath}" -p "\\-42"`, common. assert.strictEqual(stderr, ''); })); +// Eval expressions that start with '-' can be passed with '='. +child.exec(...common.escapePOSIXShell`"${process.execPath}" --print --eval=-42`, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout, '-42\n'); + assert.strictEqual(stderr, ''); +})); + +// Edge case: negative zero should preserve its sign when printed. +child.exec(...common.escapePOSIXShell`"${process.execPath}" --print --eval=-0`, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout, '-0\n'); + assert.strictEqual(stderr, ''); +})); + +// Nearby-path safety: option-looking values should still be rejected with -e. +child.exec(...common.escapePOSIXShell`"${process.execPath}" -e -p`, common.mustCall((err, stdout, stderr) => { + assert.strictEqual(err.code, 9); + assert.strictEqual(stdout, ''); + assert.strictEqual(stderr.trim(), + `${process.execPath}: -e requires an argument`); +})); + // Long output should not be truncated. child.exec(...common.escapePOSIXShell`"${process.execPath}" -p "'1'.repeat(1e5)"`, common.mustSucceed((stdout, stderr) => { assert.strictEqual(stdout, `${'1'.repeat(1e5)}\n`); diff --git a/test/parallel/test-cluster-dgram-reuse.js b/test/parallel/test-cluster-dgram-reuse.js index d2790b5d99c012..b8eae5826fd349 100644 --- a/test/parallel/test-cluster-dgram-reuse.js +++ b/test/parallel/test-cluster-dgram-reuse.js @@ -1,7 +1,10 @@ 'use strict'; const common = require('../common'); +const os = require('os'); if (common.isWindows) common.skip('dgram clustering is currently not supported on windows.'); +if (common.isAIX && os.release() === '7.3') + common.skip('dgram clutering with reuse does not work if built on AIX 7.3.'); const assert = require('assert'); const cluster = require('cluster'); diff --git a/test/parallel/test-crypto-authenticated.js b/test/parallel/test-crypto-authenticated.js index e8fedf2d5d5072..9778ea548e81d7 100644 --- a/test/parallel/test-crypto-authenticated.js +++ b/test/parallel/test-crypto-authenticated.js @@ -772,3 +772,26 @@ for (const test of TEST_CASES) { decipher.final(); }, /Unsupported state or unable to authenticate data/); } + +// Refs: https://github.com/nodejs/node/issues/62342 +{ + const key = crypto.randomBytes(16); + const nonce = crypto.randomBytes(13); + + const cipher = crypto.createCipheriv('aes-128-ccm', key, nonce, { + authTagLength: 16, + }); + cipher.setAAD(Buffer.alloc(0), { plaintextLength: 0 }); + cipher.update(new DataView(new ArrayBuffer(0))); + cipher.final(); + const tag = cipher.getAuthTag(); + assert.strictEqual(tag.length, 16); + + const decipher = crypto.createDecipheriv('aes-128-ccm', key, nonce, { + authTagLength: 16, + }); + decipher.setAuthTag(tag); + decipher.setAAD(Buffer.alloc(0), { plaintextLength: 0 }); + decipher.update(new DataView(new ArrayBuffer(0))); + decipher.final(); +} diff --git a/test/parallel/test-crypto-webcrypto-aes-decrypt-tag-too-small.js b/test/parallel/test-crypto-webcrypto-aes-decrypt-tag-too-small.js index 589a2f91a17cc2..2f0612db2bc253 100644 --- a/test/parallel/test-crypto-webcrypto-aes-decrypt-tag-too-small.js +++ b/test/parallel/test-crypto-webcrypto-aes-decrypt-tag-too-small.js @@ -24,6 +24,5 @@ subtle.importKey( }, k, new Uint8Array(0)); }, { name: 'OperationError', - message: /The provided data is too small/, }) ).then(common.mustCall()); diff --git a/test/parallel/test-debugger-preserve-breaks.js b/test/parallel/test-debugger-preserve-breaks.js index 7730039aed71b8..fc9411290a9bc8 100644 --- a/test/parallel/test-debugger-preserve-breaks.js +++ b/test/parallel/test-debugger-preserve-breaks.js @@ -29,6 +29,8 @@ const script = path.relative(process.cwd(), scriptFullPath); await cli.stepCommand('c'); // hit line 3 assert.deepStrictEqual(cli.breakInfo, { filename: script, line: 3 }); await cli.command('restart'); + await cli.waitFor(/Debugger attached\./); + await cli.waitForPrompt(); await cli.waitForInitialBreak(); assert.deepStrictEqual(cli.breakInfo, { filename: script, line: 1 }); await cli.stepCommand('c'); diff --git a/test/parallel/test-debugger-run-after-quit-restart.js b/test/parallel/test-debugger-run-after-quit-restart.js index 8ea8b74035edf6..397c25cceaff65 100644 --- a/test/parallel/test-debugger-run-after-quit-restart.js +++ b/test/parallel/test-debugger-run-after-quit-restart.js @@ -41,7 +41,7 @@ const path = require('path'); .then(() => { assert.match(cli.output, /Use `run` to start the app again/); }) - .then(() => cli.stepCommand('run')) + .then(() => cli.command('run')) .then(() => cli.waitForInitialBreak()) .then(() => cli.waitForPrompt()) .then(() => { @@ -58,6 +58,8 @@ const path = require('path'); ); }) .then(() => cli.command('restart')) + .then(() => cli.waitFor(/Debugger attached\./)) + .then(() => cli.waitForPrompt()) .then(() => cli.waitForInitialBreak()) .then(() => { assert.deepStrictEqual( @@ -71,7 +73,7 @@ const path = require('path'); .then(() => { assert.match(cli.output, /Use `run` to start the app again/); }) - .then(() => cli.stepCommand('run')) + .then(() => cli.command('run')) .then(() => cli.waitForInitialBreak()) .then(() => cli.waitForPrompt()) .then(() => { diff --git a/test/parallel/test-diagnostics-channel-web-locks.js b/test/parallel/test-diagnostics-channel-web-locks.js new file mode 100644 index 00000000000000..dec0daef9868a0 --- /dev/null +++ b/test/parallel/test-diagnostics-channel-web-locks.js @@ -0,0 +1,167 @@ +'use strict'; + +const common = require('../common'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); +const dc = require('node:diagnostics_channel'); + +function subscribe({ start, grant, miss, end }) { + if (start) dc.subscribe('locks.request.start', start); + if (grant) dc.subscribe('locks.request.grant', grant); + if (miss) dc.subscribe('locks.request.miss', miss); + if (end) dc.subscribe('locks.request.end', end); + + return () => { + if (start) dc.unsubscribe('locks.request.start', start); + if (grant) dc.unsubscribe('locks.request.grant', grant); + if (miss) dc.unsubscribe('locks.request.miss', miss); + if (end) dc.unsubscribe('locks.request.end', end); + }; +} + +describe('Web Locks diagnostics channel', () => { + it('emits start, grant, and end on success', async () => { + let startEvent; + const unsubscribe = subscribe({ + start: common.mustCall((e) => startEvent = e), + grant: common.mustCall(), + miss: common.mustNotCall(), + end: common.mustCall(), + }); + + try { + const result = await navigator.locks.request('normal-lock', async () => 'done'); + assert.strictEqual(result, 'done'); + assert.strictEqual(startEvent.name, 'normal-lock'); + assert.strictEqual(startEvent.mode, 'exclusive'); + } finally { + unsubscribe(); + } + }); + + it('emits start, miss, and end when lock is unavailable', async () => { + await navigator.locks.request('ifavailable-true-lock', common.mustCall(async () => { + let startEvent; + const unsubscribe = subscribe({ + start: common.mustCall((e) => startEvent = e), + grant: common.mustNotCall(), + miss: common.mustCall(), + end: common.mustCall(), + }); + + try { + const result = await navigator.locks.request( + 'ifavailable-true-lock', + { ifAvailable: true }, + (lock) => lock, + ); + + assert.strictEqual(result, null); + assert.strictEqual(startEvent.name, 'ifavailable-true-lock'); + assert.strictEqual(startEvent.mode, 'exclusive'); + } finally { + unsubscribe(); + } + })); + }); + + it('queued lock request emits start, grant, and end without miss', async () => { + // Outer fires first, inner is queued — so events arrive in insertion order + let outerStartEvent, innerStartEvent; + const unsubscribe = subscribe({ + start: common.mustCall((e) => (outerStartEvent ? innerStartEvent = e : outerStartEvent = e), 2), + grant: common.mustCall(2), + miss: common.mustNotCall(), + end: common.mustCall(2), + }); + + try { + let innerDone; + + const outerResult = await navigator.locks.request('ifavailable-false-lock', common.mustCall(async () => { + innerDone = navigator.locks.request( + 'ifavailable-false-lock', + { ifAvailable: false }, + common.mustCall(async (lock) => { + assert.ok(lock); + return 'inner-done'; + }), + ); + await new Promise((resolve) => setTimeout(resolve, 20)); + return 'outer-done'; + })); + + assert.strictEqual(outerResult, 'outer-done'); + assert.strictEqual(await innerDone, 'inner-done'); + + assert.strictEqual(outerStartEvent.name, 'ifavailable-false-lock'); + assert.strictEqual(outerStartEvent.mode, 'exclusive'); + assert.strictEqual(innerStartEvent.name, 'ifavailable-false-lock'); + assert.strictEqual(innerStartEvent.mode, 'exclusive'); + } finally { + unsubscribe(); + } + }); + + it('reports callback error in end event', async () => { + const expectedError = new Error('Callback error'); + let endEvent; + const unsubscribe = subscribe({ + start: common.mustCall(), + grant: common.mustCall(), + miss: common.mustNotCall(), + end: common.mustCall((e) => endEvent = e), + }); + + try { + await assert.rejects( + navigator.locks.request('error-lock', async () => { throw expectedError; }), + (error) => error === expectedError, + ); + + assert.strictEqual(endEvent.name, 'error-lock'); + assert.strictEqual(endEvent.mode, 'exclusive'); + assert.strictEqual(endEvent.error, expectedError); + } finally { + unsubscribe(); + } + }); + + it('stolen lock ends original request with AbortError', async () => { + let stolenEndEvent, stealerEndEvent; + const unsubscribe = subscribe({ + start: common.mustCall(2), + grant: common.mustCall(2), + miss: common.mustNotCall(), + end: common.mustCall((e) => (e.steal ? stealerEndEvent = e : stolenEndEvent = e), 2), + }); + + try { + let resolveGranted; + const granted = new Promise((r) => { resolveGranted = r; }); + + const originalRejected = assert.rejects( + navigator.locks.request('steal-lock', async () => { + resolveGranted(); + await new Promise((r) => setTimeout(r, 200)); + }), + { name: 'AbortError' }, + ); + + await granted; + await navigator.locks.request('steal-lock', { steal: true }, async () => {}); + await originalRejected; + + assert.strictEqual(stolenEndEvent.name, 'steal-lock'); + assert.strictEqual(stolenEndEvent.mode, 'exclusive'); + assert.strictEqual(stolenEndEvent.steal, false); + assert.strictEqual(stealerEndEvent.name, 'steal-lock'); + assert.strictEqual(stealerEndEvent.mode, 'exclusive'); + assert.strictEqual(stealerEndEvent.steal, true); + assert.strictEqual(stolenEndEvent.error.name, 'AbortError'); + assert.strictEqual(stealerEndEvent.error, undefined); + } finally { + unsubscribe(); + } + }); +}); diff --git a/test/parallel/test-domain-load-after-set-uncaught-exception-capture.js b/test/parallel/test-domain-load-after-set-uncaught-exception-capture.js index 4018220711517f..73f5f989b8e776 100644 --- a/test/parallel/test-domain-load-after-set-uncaught-exception-capture.js +++ b/test/parallel/test-domain-load-after-set-uncaught-exception-capture.js @@ -1,17 +1,22 @@ 'use strict'; +// Tests that domain can be loaded after setUncaughtExceptionCaptureCallback +// has been called. This verifies that the mutual exclusivity has been removed. const common = require('../common'); -const assert = require('assert'); +// Set up a capture callback first process.setUncaughtExceptionCaptureCallback(common.mustNotCall()); -assert.throws( - () => require('domain'), - { - code: 'ERR_DOMAIN_CALLBACK_NOT_AVAILABLE', - name: 'Error', - message: /^A callback was registered.*with using the `domain` module/ - } -); +// Loading domain should not throw (coexistence is now supported) +const domain = require('domain'); + +// Verify domain module loaded successfully +const assert = require('assert'); +assert.ok(domain); +assert.ok(domain.create); +// Clean up process.setUncaughtExceptionCaptureCallback(null); -require('domain'); // Should not throw. + +// Domain should still be usable +const d = domain.create(); +assert.ok(d); diff --git a/test/parallel/test-domain-set-uncaught-exception-capture-after-load.js b/test/parallel/test-domain-set-uncaught-exception-capture-after-load.js index 4bf419d76eb453..64f129fd201781 100644 --- a/test/parallel/test-domain-set-uncaught-exception-capture-after-load.js +++ b/test/parallel/test-domain-set-uncaught-exception-capture-after-load.js @@ -1,30 +1,23 @@ 'use strict'; +// Tests that setUncaughtExceptionCaptureCallback can be called after domain +// is loaded. This verifies that the mutual exclusivity has been removed. const common = require('../common'); const assert = require('assert'); -Error.stackTraceLimit = Infinity; +// Load domain first +const domain = require('domain'); +assert.ok(domain); -(function foobar() { - require('domain'); -})(); +// Setting callback should not throw (coexistence is now supported) +process.setUncaughtExceptionCaptureCallback(common.mustNotCall()); -assert.throws( - () => process.setUncaughtExceptionCaptureCallback(common.mustNotCall()), - (err) => { - common.expectsError( - { - code: 'ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE', - name: 'Error', - message: /^The `domain` module is in use, which is mutually/ - } - )(err); +// Verify callback is registered +assert.ok(process.hasUncaughtExceptionCaptureCallback()); - assert(err.stack.includes('-'.repeat(40)), - `expected ${err.stack} to contain dashes`); +// Clean up +process.setUncaughtExceptionCaptureCallback(null); +assert.ok(!process.hasUncaughtExceptionCaptureCallback()); - const location = `at foobar (${__filename}:`; - assert(err.stack.includes(location), - `expected ${err.stack} to contain ${location}`); - return true; - } -); +// Domain should still be usable after callback operations +const d = domain.create(); +assert.ok(d); diff --git a/test/parallel/test-event-emitter-modify-in-emit.js b/test/parallel/test-event-emitter-modify-in-emit.js index 995fa01d11a1a8..fecabca9def3e8 100644 --- a/test/parallel/test-event-emitter-modify-in-emit.js +++ b/test/parallel/test-event-emitter-modify-in-emit.js @@ -78,3 +78,21 @@ assert.strictEqual(e.listeners('foo').length, 2); e.emit('foo'); assert.deepStrictEqual(['callback2', 'callback3'], callbacks_called); assert.strictEqual(e.listeners('foo').length, 0); + +// Verify that removing all callbacks while in emit allows the current emit to +// propagate to all listeners. +callbacks_called = []; + +function callback4() { + callbacks_called.push('callback4'); + e.removeAllListeners('foo'); +} + +e.on('foo', callback4); +e.on('foo', callback2); +e.on('foo', callback3); +assert.strictEqual(e.listeners('foo').length, 3); +e.emit('foo'); +assert.deepStrictEqual(['callback4', 'callback2', 'callback3'], + callbacks_called); +assert.strictEqual(e.listeners('foo').length, 0); diff --git a/test/parallel/test-fs-cp-sync-unicode-dest.mjs b/test/parallel/test-fs-cp-sync-unicode-dest.mjs new file mode 100644 index 00000000000000..0638b98180c5da --- /dev/null +++ b/test/parallel/test-fs-cp-sync-unicode-dest.mjs @@ -0,0 +1,23 @@ +// Regression test for https://github.com/nodejs/node/issues/61878 +// fs.cpSync should copy files when destination path has UTF characters. +import '../common/index.mjs'; +import { cpSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import assert from 'node:assert'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = join(tmpdir.path, 'src'); +mkdirSync(join(src, 'subdir'), { recursive: true }); +writeFileSync(join(src, 'file1.txt'), 'Hello World'); +writeFileSync(join(src, 'subdir', 'nested.txt'), 'Nested File'); + +const dest = join(tmpdir.path, 'Eyjafjallajökull-Pranckevičius'); +cpSync(src, dest, { recursive: true, force: true }); + +const destFiles = readdirSync(dest); +assert.ok(destFiles.includes('file1.txt')); +assert.strictEqual(readFileSync(join(dest, 'file1.txt'), 'utf8'), 'Hello World'); +assert.ok(destFiles.includes('subdir')); +assert.strictEqual(readFileSync(join(dest, 'subdir', 'nested.txt'), 'utf8'), 'Nested File'); diff --git a/test/parallel/test-fs-promises-file-handle-pull.js b/test/parallel/test-fs-promises-file-handle-pull.js new file mode 100644 index 00000000000000..3fc531baf7138b --- /dev/null +++ b/test/parallel/test-fs-promises-file-handle-pull.js @@ -0,0 +1,440 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const { open } = fs.promises; +const path = require('path'); +const tmpdir = require('../common/tmpdir'); +const { text, bytes } = require('stream/iter'); + +tmpdir.refresh(); + +const tmpDir = tmpdir.path; + +// ============================================================================= +// Basic pull() +// ============================================================================= + +async function testBasicPull() { + const filePath = path.join(tmpDir, 'pull-basic.txt'); + fs.writeFileSync(filePath, 'hello from file'); + + const fh = await open(filePath, 'r'); + try { + const readable = fh.pull(); + const data = await text(readable); + assert.strictEqual(data, 'hello from file'); + } finally { + await fh.close(); + } +} + +async function testPullBinary() { + const filePath = path.join(tmpDir, 'pull-binary.bin'); + const buf = Buffer.alloc(256); + for (let i = 0; i < 256; i++) buf[i] = i; + fs.writeFileSync(filePath, buf); + + const fh = await open(filePath, 'r'); + try { + const readable = fh.pull(); + const data = await bytes(readable); + assert.strictEqual(data.byteLength, 256); + for (let i = 0; i < 256; i++) { + assert.strictEqual(data[i], i); + } + } finally { + await fh.close(); + } +} + +async function testPullEmptyFile() { + const filePath = path.join(tmpDir, 'pull-empty.txt'); + fs.writeFileSync(filePath, ''); + + const fh = await open(filePath, 'r'); + try { + const readable = fh.pull(); + const data = await bytes(readable); + assert.strictEqual(data.byteLength, 0); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// Large file (multi-chunk) +// ============================================================================= + +async function testPullLargeFile() { + const filePath = path.join(tmpDir, 'pull-large.bin'); + // Write 64KB - enough for multiple 16KB read chunks + const size = 64 * 1024; + const buf = Buffer.alloc(size, 0x42); + fs.writeFileSync(filePath, buf); + + const fh = await open(filePath, 'r'); + try { + const readable = fh.pull(); + const data = await bytes(readable); + assert.strictEqual(data.byteLength, size); + // Verify content + for (let i = 0; i < data.byteLength; i++) { + assert.strictEqual(data[i], 0x42); + } + } finally { + await fh.close(); + } +} + +// ============================================================================= +// With transforms +// ============================================================================= + +async function testPullWithTransform() { + const filePath = path.join(tmpDir, 'pull-transform.txt'); + fs.writeFileSync(filePath, 'hello'); + + const fh = await open(filePath, 'r'); + try { + const upper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => { + const str = new TextDecoder().decode(c); + return new TextEncoder().encode(str.toUpperCase()); + }); + }; + + const readable = fh.pull(upper); + const data = await text(readable); + assert.strictEqual(data, 'HELLO'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// autoClose option +// ============================================================================= + +async function testPullAutoClose() { + const filePath = path.join(tmpDir, 'pull-autoclose.txt'); + fs.writeFileSync(filePath, 'auto close data'); + + const fh = await open(filePath, 'r'); + const readable = fh.pull({ autoClose: true }); + const data = await text(readable); + assert.strictEqual(data, 'auto close data'); + + // After consuming with autoClose, the file handle should be closed + // Trying to read again should throw + await assert.rejects( + async () => { + await fh.stat(); + }, + (err) => err.code === 'ERR_INVALID_STATE' || err.code === 'EBADF', + ); +} + +// ============================================================================= +// Locking +// ============================================================================= + +async function testPullLocking() { + const filePath = path.join(tmpDir, 'pull-lock.txt'); + fs.writeFileSync(filePath, 'lock data'); + + const fh = await open(filePath, 'r'); + try { + // First pull locks the handle + const readable = fh.pull(); + + // Second pull while locked should throw + assert.throws( + () => fh.pull(), + { code: 'ERR_INVALID_STATE' }, + ); + + // Consume the first stream to unlock + await text(readable); + + // Now it should be usable again + const readable2 = fh.pull(); + const data = await text(readable2); + assert.strictEqual(data, ''); // Already read to end + } finally { + await fh.close(); + } +} + +// ============================================================================= +// Closed handle +// ============================================================================= + +async function testPullClosedHandle() { + const filePath = path.join(tmpDir, 'pull-closed.txt'); + fs.writeFileSync(filePath, 'data'); + + const fh = await open(filePath, 'r'); + await fh.close(); + + assert.throws( + () => fh.pull(), + { code: 'ERR_INVALID_STATE' }, + ); +} + +// ============================================================================= +// AbortSignal +// ============================================================================= + +async function testPullAbortSignal() { + const filePath = path.join(tmpDir, 'pull-abort.txt'); + // Write enough data that we can abort mid-stream + fs.writeFileSync(filePath, 'a'.repeat(1024)); + + const ac = new AbortController(); + const fh = await open(filePath, 'r'); + try { + ac.abort(); + const readable = fh.pull({ signal: ac.signal }); + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { + assert.fail('Should not reach here'); + } + }, + (err) => err.name === 'AbortError', + ); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// Iterate batches directly +// ============================================================================= + +async function testPullIterateBatches() { + const filePath = path.join(tmpDir, 'pull-batches.txt'); + fs.writeFileSync(filePath, 'batch data'); + + const fh = await open(filePath, 'r'); + try { + const readable = fh.pull(); + const batches = []; + for await (const batch of readable) { + batches.push(batch); + // Each batch should be an array of Uint8Array + assert.ok(Array.isArray(batch)); + for (const chunk of batch) { + assert.ok(chunk instanceof Uint8Array); + } + } + assert.ok(batches.length > 0); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pull() with start option - read from specific position +// ============================================================================= + +async function testPullStart() { + const filePath = path.join(tmpDir, 'pull-start.txt'); + fs.writeFileSync(filePath, 'AAABBBCCC'); + + const fh = await open(filePath, 'r'); + try { + // Read from offset 3 + const data = await text(fh.pull({ start: 3 })); + assert.strictEqual(data, 'BBBCCC'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pull() with limit option - read at most N bytes +// ============================================================================= + +async function testPullLimit() { + const filePath = path.join(tmpDir, 'pull-limit.txt'); + fs.writeFileSync(filePath, 'Hello, World! Extra data here.'); + + const fh = await open(filePath, 'r'); + try { + const data = await text(fh.pull({ limit: 13 })); + assert.strictEqual(data, 'Hello, World!'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pull() with start + limit - read a slice +// ============================================================================= + +async function testPullStartAndLimit() { + const filePath = path.join(tmpDir, 'pull-start-limit.txt'); + fs.writeFileSync(filePath, 'AAABBBCCCDDD'); + + const fh = await open(filePath, 'r'); + try { + // Read 3 bytes starting at offset 3 + const data = await text(fh.pull({ start: 3, limit: 3 })); + assert.strictEqual(data, 'BBB'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pull() with limit larger than file - reads whole file +// ============================================================================= + +async function testPullLimitLargerThanFile() { + const filePath = path.join(tmpDir, 'pull-limit-large.txt'); + fs.writeFileSync(filePath, 'short'); + + const fh = await open(filePath, 'r'); + try { + const data = await text(fh.pull({ limit: 1000000 })); + assert.strictEqual(data, 'short'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pull() with limit spanning multiple chunks +// ============================================================================= + +async function testPullLimitMultiChunk() { + const filePath = path.join(tmpDir, 'pull-limit-multi.bin'); + // 300KB file - spans multiple 128KB reads + const input = Buffer.alloc(300 * 1024, 'x'); + fs.writeFileSync(filePath, input); + + const fh = await open(filePath, 'r'); + try { + // Read exactly 200KB from offset 50KB + const data = await bytes(fh.pull({ start: 50 * 1024, limit: 200 * 1024 })); + assert.strictEqual(data.byteLength, 200 * 1024); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pull() with start + limit + transforms +// ============================================================================= + +async function testPullStartLimitWithTransforms() { + const filePath = path.join(tmpDir, 'pull-start-limit-transform.txt'); + fs.writeFileSync(filePath, 'aaabbbcccddd'); + + const fh = await open(filePath, 'r'); + try { + const { compressGzip, decompressGzip } = require('zlib/iter'); + const compressed = fh.pull(compressGzip(), { start: 3, limit: 6 }); + const decompressed = await text( + require('stream/iter').pull(compressed, decompressGzip())); + assert.strictEqual(decompressed, 'bbbccc'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pull() with chunkSize option +// ============================================================================= + +async function testPullChunkSize() { + const filePath = path.join(tmpDir, 'pull-chunksize.bin'); + // Write 64KB of data + const input = Buffer.alloc(64 * 1024, 'z'); + fs.writeFileSync(filePath, input); + + const fh = await open(filePath, 'r'); + try { + // Use 16KB chunks - should produce 4 batches + let batchCount = 0; + for await (const batch of fh.pull({ chunkSize: 16 * 1024 })) { + batchCount++; + for (const chunk of batch) { + assert.ok(chunk.byteLength <= 16 * 1024, + `Chunk ${chunk.byteLength} should be <= 16384`); + } + } + assert.strictEqual(batchCount, 4); + } finally { + await fh.close(); + } +} + +async function testPullChunkSizeSmall() { + const filePath = path.join(tmpDir, 'pull-chunksize-small.txt'); + fs.writeFileSync(filePath, 'hello'); + + const fh = await open(filePath, 'r'); + try { + // 1-byte chunks + let totalBytes = 0; + let batchCount = 0; + for await (const batch of fh.pull({ chunkSize: 1 })) { + batchCount++; + for (const chunk of batch) totalBytes += chunk.byteLength; + } + assert.strictEqual(totalBytes, 5); + assert.strictEqual(batchCount, 5); + } finally { + await fh.close(); + } +} + +async function testPullSyncArgumentValidation() { + const filePath = path.join(tmpDir, 'pull-arg-validation.txt'); + fs.writeFileSync(filePath, 'data'); + + const fh = await open(filePath, 'r'); + try { + assert.throws(() => fh.pull({ autoClose: 'no' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pull({ start: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pull({ limit: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pull({ chunkSize: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pull({ signal: {} }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pull({ start: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => fh.pull({ limit: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => fh.pull({ chunkSize: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + } finally { + await fh.close(); + } +} + +Promise.all([ + testBasicPull(), + testPullBinary(), + testPullEmptyFile(), + testPullLargeFile(), + testPullWithTransform(), + testPullAutoClose(), + testPullLocking(), + testPullClosedHandle(), + testPullAbortSignal(), + testPullIterateBatches(), + testPullStart(), + testPullLimit(), + testPullStartAndLimit(), + testPullLimitLargerThanFile(), + testPullLimitMultiChunk(), + testPullStartLimitWithTransforms(), + testPullChunkSize(), + testPullChunkSizeSmall(), + testPullSyncArgumentValidation(), +]).then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-file-handle-pullsync.js b/test/parallel/test-fs-promises-file-handle-pullsync.js new file mode 100644 index 00000000000000..20c429972573d6 --- /dev/null +++ b/test/parallel/test-fs-promises-file-handle-pullsync.js @@ -0,0 +1,498 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const { open } = fs.promises; +const path = require('path'); +const tmpdir = require('../common/tmpdir'); +const { + textSync, + bytesSync, + pipeToSync, + pullSync, +} = require('stream/iter'); +const { + compressGzipSync, + decompressGzipSync, +} = require('zlib/iter'); + +tmpdir.refresh(); + +const tmpDir = tmpdir.path; + +// ============================================================================= +// Basic pullSync() +// ============================================================================= + +async function testBasicPullSync() { + const filePath = path.join(tmpDir, 'pullsync-basic.txt'); + fs.writeFileSync(filePath, 'hello from sync file read'); + + const fh = await open(filePath, 'r'); + try { + const data = textSync(fh.pullSync()); + assert.strictEqual(data, 'hello from sync file read'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// Large file (multi-chunk) +// ============================================================================= + +async function testLargeFile() { + const filePath = path.join(tmpDir, 'pullsync-large.txt'); + const input = 'sync large data test. '.repeat(10000); + fs.writeFileSync(filePath, input); + + const fh = await open(filePath, 'r'); + try { + const data = textSync(fh.pullSync()); + assert.strictEqual(data, input); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// Binary data round-trip +// ============================================================================= + +async function testBinaryData() { + const filePath = path.join(tmpDir, 'pullsync-binary.bin'); + const input = Buffer.alloc(200000); + for (let i = 0; i < input.length; i++) input[i] = i & 0xff; + fs.writeFileSync(filePath, input); + + const fh = await open(filePath, 'r'); + try { + const data = bytesSync(fh.pullSync()); + assert.deepStrictEqual(Buffer.from(data), input); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pullSync with sync compression transform round-trip +// ============================================================================= + +async function testPullSyncWithCompression() { + const filePath = path.join(tmpDir, 'pullsync-compress-src.txt'); + const dstPath = path.join(tmpDir, 'pullsync-compress-dst.gz'); + const input = 'compress via sync pullSync. '.repeat(1000); + fs.writeFileSync(filePath, input); + + // Compress: pullSync -> compressGzipSync -> write to file + const srcFh = await open(filePath, 'r'); + const dstFh = await open(dstPath, 'w'); + try { + const w = dstFh.writer(); + pipeToSync(srcFh.pullSync(compressGzipSync()), w); + } finally { + await srcFh.close(); + await dstFh.close(); + } + + // Verify compressed file is smaller + const compressedSize = fs.statSync(dstPath).size; + assert.ok(compressedSize < Buffer.byteLength(input), + `Compressed ${compressedSize} should be < original ` + + `${Buffer.byteLength(input)}`); + + // Decompress and verify + const readFh = await open(dstPath, 'r'); + try { + const result = textSync(readFh.pullSync(decompressGzipSync())); + assert.strictEqual(result, input); + } finally { + await readFh.close(); + } +} + +// ============================================================================= +// pullSync with stateless transform +// ============================================================================= + +async function testPullSyncWithStatelessTransform() { + const filePath = path.join(tmpDir, 'pullsync-upper.txt'); + fs.writeFileSync(filePath, 'hello world'); + + const upper = (chunks) => { + if (chunks === null) return null; + const out = new Array(chunks.length); + for (let j = 0; j < chunks.length; j++) { + const src = chunks[j]; + const buf = Buffer.allocUnsafe(src.length); + for (let i = 0; i < src.length; i++) { + const b = src[i]; + buf[i] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b; + } + out[j] = buf; + } + return out; + }; + + const fh = await open(filePath, 'r'); + try { + const data = textSync(fh.pullSync(upper)); + assert.strictEqual(data, 'HELLO WORLD'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pullSync with mixed stateless + stateful transforms +// ============================================================================= + +async function testPullSyncMixedTransforms() { + const filePath = path.join(tmpDir, 'pullsync-mixed.txt'); + const input = 'mixed transform test '.repeat(500); + fs.writeFileSync(filePath, input); + + const upper = (chunks) => { + if (chunks === null) return null; + const out = new Array(chunks.length); + for (let j = 0; j < chunks.length; j++) { + const src = chunks[j]; + const buf = Buffer.allocUnsafe(src.length); + for (let i = 0; i < src.length; i++) { + const b = src[i]; + buf[i] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b; + } + out[j] = buf; + } + return out; + }; + + const fh = await open(filePath, 'r'); + try { + // Upper + compress + decompress + const data = textSync( + pullSync(fh.pullSync(upper, compressGzipSync()), decompressGzipSync()), + ); + assert.strictEqual(data, input.toUpperCase()); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// autoClose: true - handle closed after iteration completes +// ============================================================================= + +async function testAutoClose() { + const filePath = path.join(tmpDir, 'pullsync-autoclose.txt'); + fs.writeFileSync(filePath, 'auto close test'); + + const fh = await open(filePath, 'r'); + const data = textSync(fh.pullSync({ autoClose: true })); + assert.strictEqual(data, 'auto close test'); + + // Handle should be closed + await assert.rejects(fh.stat(), { code: 'EBADF' }); +} + +// ============================================================================= +// autoClose: true with early break +// ============================================================================= + +async function testAutoCloseEarlyBreak() { + const filePath = path.join(tmpDir, 'pullsync-autoclose-break.txt'); + fs.writeFileSync(filePath, 'x'.repeat(1000000)); + + const fh = await open(filePath, 'r'); + // eslint-disable-next-line no-unused-vars + for (const batch of fh.pullSync({ autoClose: true })) { + break; // Early exit + } + + // Handle should be closed by autoClose + await assert.rejects(fh.stat(), { code: 'EBADF' }); +} + +// ============================================================================= +// autoClose: false (default) - handle stays open +// ============================================================================= + +async function testNoAutoClose() { + const filePath = path.join(tmpDir, 'pullsync-no-autoclose.txt'); + fs.writeFileSync(filePath, 'still open'); + + const fh = await open(filePath, 'r'); + const data = textSync(fh.pullSync()); + assert.strictEqual(data, 'still open'); + + // Handle should still be open and reusable + const stat = await fh.stat(); + assert.ok(stat.size > 0); + await fh.close(); +} + +// ============================================================================= +// Lock semantics - pullSync locks the handle +// ============================================================================= + +async function testLocked() { + const filePath = path.join(tmpDir, 'pullsync-locked.txt'); + fs.writeFileSync(filePath, 'lock test'); + + const fh = await open(filePath, 'r'); + const iter = fh.pullSync()[Symbol.iterator](); + iter.next(); // Start iteration, handle is locked + + assert.throws(() => fh.pullSync(), { + code: 'ERR_INVALID_STATE', + }); + + assert.throws(() => fh.pull(), { + code: 'ERR_INVALID_STATE', + }); + + // Finish iteration to unlock + while (!iter.next().done) { /* drain */ } + await fh.close(); +} + +// ============================================================================= +// Empty file +// ============================================================================= + +async function testEmptyFile() { + const filePath = path.join(tmpDir, 'pullsync-empty.txt'); + fs.writeFileSync(filePath, ''); + + const fh = await open(filePath, 'r'); + try { + const data = textSync(fh.pullSync()); + assert.strictEqual(data, ''); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pipeToSync: file-to-file sync pipeline +// ============================================================================= + +async function testPipeToSync() { + const srcPath = path.join(tmpDir, 'pullsync-pipeto-src.txt'); + const dstPath = path.join(tmpDir, 'pullsync-pipeto-dst.txt'); + const input = 'pipeToSync test data '.repeat(200); + fs.writeFileSync(srcPath, input); + + const srcFh = await open(srcPath, 'r'); + const dstFh = await open(dstPath, 'w'); + try { + const w = dstFh.writer(); + pipeToSync(srcFh.pullSync(), w); + } finally { + await srcFh.close(); + await dstFh.close(); + } + + assert.strictEqual(fs.readFileSync(dstPath, 'utf8'), input); +} + +// ============================================================================= +// pullSync() with start option +// ============================================================================= + +async function testPullSyncStart() { + const filePath = path.join(tmpDir, 'pullsync-start.txt'); + fs.writeFileSync(filePath, 'AAABBBCCC'); + + const fh = await open(filePath, 'r'); + try { + const data = textSync(fh.pullSync({ start: 3 })); + assert.strictEqual(data, 'BBBCCC'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pullSync() with limit option +// ============================================================================= + +async function testPullSyncLimit() { + const filePath = path.join(tmpDir, 'pullsync-limit.txt'); + fs.writeFileSync(filePath, 'Hello, World! Extra data here.'); + + const fh = await open(filePath, 'r'); + try { + const data = textSync(fh.pullSync({ limit: 13 })); + assert.strictEqual(data, 'Hello, World!'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pullSync() with start + limit +// ============================================================================= + +async function testPullSyncStartAndLimit() { + const filePath = path.join(tmpDir, 'pullsync-start-limit.txt'); + fs.writeFileSync(filePath, 'AAABBBCCCDDD'); + + const fh = await open(filePath, 'r'); + try { + const data = textSync(fh.pullSync({ start: 3, limit: 3 })); + assert.strictEqual(data, 'BBB'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pullSync() with limit spanning multiple chunks +// ============================================================================= + +async function testPullSyncLimitMultiChunk() { + const filePath = path.join(tmpDir, 'pullsync-limit-multi.bin'); + const input = Buffer.alloc(300 * 1024, 'x'); + fs.writeFileSync(filePath, input); + + const fh = await open(filePath, 'r'); + try { + const data = bytesSync(fh.pullSync({ start: 50 * 1024, limit: 200 * 1024 })); + assert.strictEqual(data.byteLength, 200 * 1024); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pullSync() with start + limit + compression transform +// ============================================================================= + +async function testPullSyncStartLimitWithTransforms() { + const filePath = path.join(tmpDir, 'pullsync-start-limit-transform.txt'); + fs.writeFileSync(filePath, 'aaabbbcccddd'); + + const fh = await open(filePath, 'r'); + try { + const compressed = fh.pullSync(compressGzipSync(), + { start: 3, limit: 6 }); + const decompressed = textSync(pullSync(compressed, decompressGzipSync())); + assert.strictEqual(decompressed, 'bbbccc'); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// pullSync() with start + autoClose +// ============================================================================= + +async function testPullSyncStartAutoClose() { + const filePath = path.join(tmpDir, 'pullsync-start-autoclose.txt'); + fs.writeFileSync(filePath, 'AAABBBCCC'); + + const fh = await open(filePath, 'r'); + const data = textSync(fh.pullSync({ start: 3, autoClose: true })); + assert.strictEqual(data, 'BBBCCC'); + + // Handle should be closed + await assert.rejects(fh.stat(), { code: 'EBADF' }); +} + +// ============================================================================= +// pullSync() with chunkSize option +// ============================================================================= + +async function testPullSyncChunkSize() { + const filePath = path.join(tmpDir, 'pullsync-chunksize.bin'); + const input = Buffer.alloc(64 * 1024, 'z'); + fs.writeFileSync(filePath, input); + + const fh = await open(filePath, 'r'); + try { + let batchCount = 0; + for (const batch of fh.pullSync({ chunkSize: 16 * 1024 })) { + batchCount++; + for (const chunk of batch) { + assert.ok(chunk.byteLength <= 16 * 1024, + `Chunk ${chunk.byteLength} should be <= 16384`); + } + } + assert.strictEqual(batchCount, 4); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// writer() with chunkSize option (sync write threshold) +// ============================================================================= + +async function testWriterChunkSize() { + const filePath = path.join(tmpDir, 'pullsync-writer-chunksize.txt'); + const fh = await open(filePath, 'w'); + // Set chunkSize to 1024 - writes larger than this should fall back to async + const w = fh.writer({ chunkSize: 1024 }); + + // Small write should succeed sync + assert.strictEqual(w.writeSync(Buffer.alloc(512, 'a')), true); + + // Write larger than chunkSize should return false + assert.strictEqual(w.writeSync(Buffer.alloc(2048, 'b')), false); + + await w.end(); + await fh.close(); +} + +// ============================================================================= +// Argument validation +// ============================================================================= + +async function testPullArgumentValidation() { + const filePath = path.join(tmpDir, 'pull-arg-validation.txt'); + fs.writeFileSync(filePath, 'data'); + + const fh = await open(filePath, 'r'); + try { + assert.throws(() => fh.pullSync({ autoClose: 'no' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pullSync({ start: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pullSync({ limit: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pullSync({ chunkSize: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.pullSync({ start: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => fh.pullSync({ limit: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => fh.pullSync({ chunkSize: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// Run all tests +// ============================================================================= + +Promise.all([ + testBasicPullSync(), + testLargeFile(), + testBinaryData(), + testPullSyncWithCompression(), + testPullSyncWithStatelessTransform(), + testPullSyncMixedTransforms(), + testAutoClose(), + testAutoCloseEarlyBreak(), + testNoAutoClose(), + testLocked(), + testEmptyFile(), + testPipeToSync(), + testPullSyncStart(), + testPullSyncLimit(), + testPullSyncStartAndLimit(), + testPullSyncLimitMultiChunk(), + testPullSyncStartLimitWithTransforms(), + testPullSyncStartAutoClose(), + testPullSyncChunkSize(), + testWriterChunkSize(), + testPullArgumentValidation(), +]).then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-file-handle-writer.js b/test/parallel/test-fs-promises-file-handle-writer.js new file mode 100644 index 00000000000000..95ba8756fbe3cd --- /dev/null +++ b/test/parallel/test-fs-promises-file-handle-writer.js @@ -0,0 +1,1126 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const { open } = fs.promises; +const path = require('path'); +const tmpdir = require('../common/tmpdir'); +const { + pipeTo, text, +} = require('stream/iter'); +const { + compressGzip, decompressGzip, +} = require('zlib/iter'); + +tmpdir.refresh(); + +const tmpDir = tmpdir.path; + +// ============================================================================= +// Basic write() +// ============================================================================= + +async function testBasicWrite() { + const filePath = path.join(tmpDir, 'writer-basic.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + await w.write(Buffer.from('Hello ')); + await w.write(Buffer.from('World!')); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 12); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'Hello World!'); +} + +// ============================================================================= +// Basic writev() +// ============================================================================= + +async function testBasicWritev() { + const filePath = path.join(tmpDir, 'writer-writev.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + await w.writev([ + Buffer.from('aaa'), + Buffer.from('bbb'), + Buffer.from('ccc'), + ]); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 9); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'aaabbbccc'); +} + +// ============================================================================= +// Mixed write() and writev() +// ============================================================================= + +async function testMixedWriteAndWritev() { + const filePath = path.join(tmpDir, 'writer-mixed.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + await w.write(Buffer.from('head-')); + await w.writev([Buffer.from('mid1-'), Buffer.from('mid2-')]); + await w.write(Buffer.from('tail')); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 19); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'head-mid1-mid2-tail'); +} + +// ============================================================================= +// end() returns totalBytesWritten +// ============================================================================= + +async function testEndReturnsTotalBytes() { + const filePath = path.join(tmpDir, 'writer-totalbytes.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + // Write some data in various sizes + const sizes = [100, 200, 300, 400, 500]; + let expected = 0; + for (const size of sizes) { + await w.write(Buffer.alloc(size, 0x41)); + expected += size; + } + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, expected); + assert.strictEqual(totalBytes, 1500); + assert.strictEqual(fs.statSync(filePath).size, 1500); +} + +// ============================================================================= +// autoClose: true - handle closed after end() +// ============================================================================= + +async function testAutoCloseOnEnd() { + const filePath = path.join(tmpDir, 'writer-autoclose-end.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ autoClose: true }); + await w.write(Buffer.from('auto close test')); + await w.end(); + + // Handle should be closed + await assert.rejects(fh.stat(), { code: 'EBADF' }); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'auto close test'); +} + +// ============================================================================= +// autoClose: true - handle closed after fail() +// ============================================================================= + +async function testAutoCloseOnFail() { + const filePath = path.join(tmpDir, 'writer-autoclose-fail.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ autoClose: true }); + await w.write(Buffer.from('partial')); + w.fail(new Error('test fail')); + + // Handle should be closed + await assert.rejects(fh.stat(), { code: 'EBADF' }); + // Partial data should still be on disk (fail doesn't truncate) + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'partial'); +} + +// ============================================================================= +// start option - write at specified offset +// ============================================================================= + +async function testStartOption() { + const filePath = path.join(tmpDir, 'writer-start.txt'); + // Pre-fill with 10 A's + fs.writeFileSync(filePath, 'AAAAAAAAAA'); + + const fh = await open(filePath, 'r+'); + const w = fh.writer({ start: 3 }); + await w.write(Buffer.from('BBB')); + await w.end(); + await fh.close(); + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'AAABBBAAAA'); +} + +// ============================================================================= +// start option - sequential writes advance position +// ============================================================================= + +async function testStartSequentialPosition() { + const filePath = path.join(tmpDir, 'writer-start-seq.txt'); + fs.writeFileSync(filePath, 'XXXXXXXXXX'); + + const fh = await open(filePath, 'r+'); + const w = fh.writer({ start: 2 }); + await w.write(Buffer.from('AA')); + await w.write(Buffer.from('BB')); + await w.writev([Buffer.from('C'), Buffer.from('D')]); + await w.end(); + await fh.close(); + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'XXAABBCDXX'); +} + +// ============================================================================= +// Locked state - can't create second writer while active +// ============================================================================= + +async function testLockedState() { + const filePath = path.join(tmpDir, 'writer-locked.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + assert.throws(() => fh.writer(), { + name: 'Error', + message: /locked/, + }); + + // Also can't pull while writer is active + assert.throws(() => fh.pull(), { + name: 'Error', + message: /locked/, + }); + + await w.end(); + await fh.close(); +} + +// ============================================================================= +// Unlock after end - handle reusable +// ============================================================================= + +async function testUnlockAfterEnd() { + const filePath = path.join(tmpDir, 'writer-unlock.txt'); + const fh = await open(filePath, 'w'); + + const w1 = fh.writer(); + await w1.write(Buffer.from('first')); + await w1.end(); + + // Should work - handle is unlocked + const w2 = fh.writer(); + await w2.write(Buffer.from(' second')); + await w2.end(); + await fh.close(); + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'first second'); +} + +// ============================================================================= +// Unlock after fail - handle reusable +// ============================================================================= + +async function testUnlockAfterFail() { + const filePath = path.join(tmpDir, 'writer-unlock-fail.txt'); + const fh = await open(filePath, 'w'); + + const w1 = fh.writer(); + await w1.write(Buffer.from('failed')); + await w1.fail(new Error('test')); + + // Should work - handle is unlocked + const w2 = fh.writer(); + await w2.write(Buffer.from('recovered')); + await w2.end(); + await fh.close(); + + // 'recovered' is appended after 'failed' at current file offset + const content = fs.readFileSync(filePath, 'utf8'); + assert.ok(content.startsWith('failed')); + assert.ok(content.includes('recovered')); +} + +// ============================================================================= +// Write after end/fail rejects +// ============================================================================= + +async function testWriteAfterEndRejects() { + const filePath = path.join(tmpDir, 'writer-closed.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + await w.write(Buffer.from('data')); + await w.end(); + + await assert.rejects(w.write(Buffer.from('more')), { + name: 'TypeError', + message: /closed/, + }); + await assert.rejects(w.writev([Buffer.from('more')]), { + name: 'TypeError', + message: /closed/, + }); + + await fh.close(); +} + +// ============================================================================= +// Closed handle - writer() throws +// ============================================================================= + +async function testClosedHandle() { + const filePath = path.join(tmpDir, 'writer-closed-handle.txt'); + const fh = await open(filePath, 'w'); + await fh.close(); + + assert.throws(() => fh.writer(), { + name: 'Error', + message: /closed/, + }); +} + +// ============================================================================= +// pipeTo() integration - pipe source through writer +// ============================================================================= + +async function testPipeToIntegration() { + const srcPath = path.join(tmpDir, 'writer-pipeto-src.txt'); + const dstPath = path.join(tmpDir, 'writer-pipeto-dst.txt'); + const data = 'The quick brown fox jumps over the lazy dog.\n'.repeat(500); + fs.writeFileSync(srcPath, data); + + const rfh = await open(srcPath, 'r'); + const wfh = await open(dstPath, 'w'); + const w = wfh.writer(); + + const totalBytes = await pipeTo(rfh.pull(), w); + + await rfh.close(); + await wfh.close(); + + assert.strictEqual(totalBytes, Buffer.byteLength(data)); + assert.strictEqual(fs.readFileSync(dstPath, 'utf8'), data); +} + +// ============================================================================= +// pipeTo() with transforms - uppercase through writer +// ============================================================================= + +async function testPipeToWithTransform() { + const srcPath = path.join(tmpDir, 'writer-transform-src.txt'); + const dstPath = path.join(tmpDir, 'writer-transform-dst.txt'); + const data = 'hello world from transforms test\n'.repeat(200); + fs.writeFileSync(srcPath, data); + + function uppercase(chunks) { + if (chunks === null) return null; + const out = new Array(chunks.length); + for (let i = 0; i < chunks.length; i++) { + const src = chunks[i]; + const buf = Buffer.allocUnsafe(src.length); + for (let j = 0; j < src.length; j++) { + const b = src[j]; + buf[j] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b; + } + out[i] = buf; + } + return out; + } + + const rfh = await open(srcPath, 'r'); + const wfh = await open(dstPath, 'w'); + const w = wfh.writer(); + + await pipeTo(rfh.pull(), uppercase, w); + + await rfh.close(); + await wfh.close(); + + assert.strictEqual(fs.readFileSync(dstPath, 'utf8'), data.toUpperCase()); +} + +// ============================================================================= +// Round-trip: pull → compress → writer, pull → decompress → verify +// ============================================================================= + +async function testCompressRoundTrip() { + const srcPath = path.join(tmpDir, 'writer-rt-src.txt'); + const gzPath = path.join(tmpDir, 'writer-rt.gz'); + const original = 'Round trip compression test data. '.repeat(2000); + fs.writeFileSync(srcPath, original); + + // Compress: pull → gzip → writer + { + const rfh = await open(srcPath, 'r'); + const wfh = await open(gzPath, 'w'); + const w = wfh.writer({ autoClose: true }); + await pipeTo(rfh.pull(), compressGzip(), w); + await rfh.close(); + } + + // Verify compressed file is smaller + const compressedSize = fs.statSync(gzPath).size; + assert.ok(compressedSize < Buffer.byteLength(original), + `Compressed ${compressedSize} should be < original ${Buffer.byteLength(original)}`); + + // Decompress: pull → gunzip → text → verify + { + const rfh = await open(gzPath, 'r'); + const result = await text(rfh.pull(decompressGzip())); + await rfh.close(); + assert.strictEqual(result, original); + } +} + +// ============================================================================= +// Large file write - write 1MB in 64KB chunks +// ============================================================================= + +async function testLargeFileWrite() { + const filePath = path.join(tmpDir, 'writer-large.bin'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + const chunkSize = 65536; + const totalSize = 1024 * 1024; // 1MB + const chunk = Buffer.alloc(chunkSize, 0x42); + let written = 0; + + while (written < totalSize) { + await w.write(chunk); + written += chunkSize; + } + + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, totalSize); + assert.strictEqual(fs.statSync(filePath).size, totalSize); + + // Verify content + const data = fs.readFileSync(filePath); + for (let i = 0; i < data.length; i++) { + if (data[i] !== 0x42) { + assert.fail(`Byte at offset ${i} is ${data[i]}, expected 0x42`); + } + } +} + +// ============================================================================= +// Symbol.asyncDispose - await using +// ============================================================================= + +async function testAsyncDispose() { + const filePath = path.join(tmpDir, 'writer-async-dispose.txt'); + { + await using fh = await open(filePath, 'w'); + await using w = fh.writer({ autoClose: true }); + await w.write(Buffer.from('async dispose')); + } + // Both writer and file handle should be cleaned up + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'async dispose'); + + // Verify the handle is actually closed by trying to open a new one + // (if the old one were still open with a write lock on some OSes, + // this could fail - but it should succeed). + const fh2 = await open(filePath, 'r'); + await fh2.close(); +} + +// ============================================================================= +// Symbol.asyncDispose - cleanup on error (await using unwinds) +// ============================================================================= + +async function testAsyncDisposeOnError() { + const filePath = path.join(tmpDir, 'writer-dispose-error.txt'); + const fh = await open(filePath, 'w'); + + try { + await using w = fh.writer(); + await w.write(Buffer.from('before error')); + throw new Error('intentional'); + } catch (e) { + assert.strictEqual(e.message, 'intentional'); + } + + // If asyncDispose ran, the handle should be unlocked and reusable + const w2 = fh.writer(); + await w2.write(Buffer.from('after error')); + await w2.end(); + await fh.close(); + + const content = fs.readFileSync(filePath, 'utf8'); + assert.ok(content.includes('after error'), + `Expected 'after error' in ${JSON.stringify(content)}`); +} + +// ============================================================================= +// Pre-aborted signal rejects write/writev/end +// ============================================================================= + +async function testWriteWithAbortedSignalRejects() { + const filePath = path.join(tmpDir, 'writer-signal-write.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + const ac = new AbortController(); + ac.abort(); + + await assert.rejects( + w.write(Buffer.from('data'), { signal: ac.signal }), + { name: 'AbortError' }, + ); + + // Writer should still be usable after a signal rejection + await w.write(Buffer.from('ok')); + await w.end(); + await fh.close(); + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'ok'); +} + +async function testWritevWithAbortedSignalRejects() { + const filePath = path.join(tmpDir, 'writer-signal-writev.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + const ac = new AbortController(); + ac.abort(); + + await assert.rejects( + w.writev([Buffer.from('a'), Buffer.from('b')], { signal: ac.signal }), + { name: 'AbortError' }, + ); + + await w.writev([Buffer.from('ok')]); + await w.end(); + await fh.close(); + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'ok'); +} + +async function testEndWithAbortedSignalRejects() { + const filePath = path.join(tmpDir, 'writer-signal-end.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + await w.write(Buffer.from('data')); + + const ac = new AbortController(); + ac.abort(); + + await assert.rejects( + w.end({ signal: ac.signal }), + { name: 'AbortError' }, + ); + + // end() was rejected so writer is still open - end it cleanly + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 4); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'data'); +} + +// ============================================================================= +// write() with string input (UTF-8 encoding) +// ============================================================================= + +async function testWriteString() { + const filePath = path.join(tmpDir, 'writer-string.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + await w.write('Hello '); + await w.write('World!'); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 12); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'Hello World!'); +} + +// ============================================================================= +// write() with string containing multi-byte UTF-8 characters +// ============================================================================= + +async function testWriteStringMultibyte() { + const filePath = path.join(tmpDir, 'writer-string-multibyte.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + const input = 'café ☕ 日本語'; + await w.write(input); + const totalBytes = await w.end(); + await fh.close(); + + const expected = Buffer.from(input, 'utf8'); + assert.strictEqual(totalBytes, expected.byteLength); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), input); +} + +// ============================================================================= +// writev() with string chunks (UTF-8 encoding) +// ============================================================================= + +async function testWritevStrings() { + const filePath = path.join(tmpDir, 'writer-writev-strings.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + await w.writev(['aaa', 'bbb', 'ccc']); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 9); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'aaabbbccc'); +} + +// ============================================================================= +// writev() with mixed string and Uint8Array chunks +// ============================================================================= + +async function testWritevMixed() { + const filePath = path.join(tmpDir, 'writer-writev-mixed.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + await w.writev(['hello', Buffer.from(' '), 'world']); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 11); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'hello world'); +} + +// ============================================================================= +// Symbol.dispose calls fail() +// ============================================================================= + +async function testSyncDispose() { + const filePath = path.join(tmpDir, 'writer-sync-dispose.txt'); + const fh = await open(filePath, 'w'); + + { + using w = fh.writer(); + await w.write(Buffer.from('before dispose')); + } + // Symbol.dispose calls fail(), which unlocks the handle. + // The handle should be reusable. + const w2 = fh.writer(); + await w2.write(Buffer.from('after dispose')); + await w2.end(); + await fh.close(); + + const content = fs.readFileSync(filePath, 'utf8'); + assert.ok(content.includes('after dispose'), + `Expected 'after dispose' in ${JSON.stringify(content)}`); +} + +// ============================================================================= +// Symbol.dispose on error unwind +// ============================================================================= + +async function testSyncDisposeOnError() { + const filePath = path.join(tmpDir, 'writer-sync-dispose-error.txt'); + const fh = await open(filePath, 'w'); + + try { + using w = fh.writer(); + await w.write(Buffer.from('data')); + throw new Error('intentional'); + } catch (e) { + assert.strictEqual(e.message, 'intentional'); + } + + // Handle should be unlocked and reusable after sync dispose + const w2 = fh.writer(); + await w2.write(Buffer.from('recovered')); + await w2.end(); + await fh.close(); + + const content = fs.readFileSync(filePath, 'utf8'); + assert.ok(content.includes('recovered'), + `Expected 'recovered' in ${JSON.stringify(content)}`); +} + +// ============================================================================= +// writeSync() basic +// ============================================================================= + +async function testWriteSyncBasic() { + const filePath = path.join(tmpDir, 'writer-writesync-basic.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + assert.strictEqual(w.writeSync('Hello '), true); + assert.strictEqual(w.writeSync(Buffer.from('World!')), true); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 12); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'Hello World!'); +} + +// ============================================================================= +// writevSync() basic +// ============================================================================= + +async function testWritevSyncBasic() { + const filePath = path.join(tmpDir, 'writer-writevsync-basic.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + assert.strictEqual(w.writevSync(['aaa', Buffer.from('bbb'), 'ccc']), true); + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 9); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'aaabbbccc'); +} + +// ============================================================================= +// writeSync() returns false for large chunks +// ============================================================================= + +async function testWriteSyncLargeChunk() { + const filePath = path.join(tmpDir, 'writer-writesync-large.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + // Chunk larger than 131072 should return false + const bigChunk = Buffer.alloc(131073, 'x'); + assert.strictEqual(w.writeSync(bigChunk), false); + + // Chunk at exactly 131072 should succeed + const exactChunk = Buffer.alloc(131072, 'y'); + assert.strictEqual(w.writeSync(exactChunk), true); + + await w.end(); + await fh.close(); + + // Only the exact chunk should have been written + const content = fs.readFileSync(filePath); + assert.strictEqual(content.length, 131072); +} + +// ============================================================================= +// writeSync() returns false when async op is in flight +// ============================================================================= + +async function testWriteSyncReturnsFalseDuringAsync() { + const filePath = path.join(tmpDir, 'writer-writesync-async.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + // Start an async write but don't await yet + const p = w.write(Buffer.from('async')); + + // Sync write should return false because async is in flight + assert.strictEqual(w.writeSync(Buffer.from('sync')), false); + + await p; + + // After async completes, sync should work again + assert.strictEqual(w.writeSync(Buffer.from(' then sync')), true); + + await w.end(); + await fh.close(); + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'async then sync'); +} + +// ============================================================================= +// writeSync() returns false on closed/errored writer +// ============================================================================= + +async function testWriteSyncClosedErrored() { + const filePath = path.join(tmpDir, 'writer-writesync-closed.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + await w.end(); + + // Should return false after end() + assert.strictEqual(w.writeSync(Buffer.from('data')), false); + await fh.close(); + + // Test errored state + const fh2 = await open(filePath, 'w'); + const w2 = fh2.writer(); + w2.fail(new Error('test')); + assert.strictEqual(w2.writeSync(Buffer.from('data')), false); + await fh2.close(); +} + +// ============================================================================= +// endSync() basic +// ============================================================================= + +async function testEndSyncBasic() { + const filePath = path.join(tmpDir, 'writer-endsync-basic.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + w.writeSync(Buffer.from('hello')); + const totalBytes = w.endSync(); + await fh.close(); + + assert.strictEqual(totalBytes, 5); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'hello'); +} + +// ============================================================================= +// endSync() returns -1 when async op is in flight +// ============================================================================= + +async function testEndSyncReturnsFalseDuringAsync() { + const filePath = path.join(tmpDir, 'writer-endsync-async.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + const p = w.write(Buffer.from('data')); + assert.strictEqual(w.endSync(), -1); + + await p; + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 4); +} + +// ============================================================================= +// endSync() idempotent on closed writer +// ============================================================================= + +async function testEndSyncIdempotent() { + const filePath = path.join(tmpDir, 'writer-endsync-idempotent.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + w.writeSync(Buffer.from('data')); + const first = w.endSync(); + const second = w.endSync(); + + assert.strictEqual(first, 4); + assert.strictEqual(second, 4); // Idempotent + await fh.close(); +} + +// ============================================================================= +// endSync() with autoClose fires handle.close() +// ============================================================================= + +async function testEndSyncAutoClose() { + const filePath = path.join(tmpDir, 'writer-endsync-autoclose.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ autoClose: true }); + + w.writeSync(Buffer.from('auto')); + const totalBytes = w.endSync(); + + assert.strictEqual(totalBytes, 4); + + // Handle should be closed synchronously + await assert.rejects(fh.stat(), { code: 'EBADF' }); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'auto'); +} + +// ============================================================================= +// Full sync pipeline: writeSync + endSync (no async at all) +// ============================================================================= + +async function testFullSyncPipeline() { + const filePath = path.join(tmpDir, 'writer-full-sync.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + // Entirely synchronous write pipeline + w.writeSync('line 1\n'); + w.writeSync('line 2\n'); + w.writevSync(['line 3\n', 'line 4\n']); + const totalBytes = w.endSync(); + await fh.close(); + + assert.strictEqual(totalBytes, 28); + assert.strictEqual( + fs.readFileSync(filePath, 'utf8'), + 'line 1\nline 2\nline 3\nline 4\n', + ); +} + +// ============================================================================= +// end() rejects on errored writer +// ============================================================================= + +async function testEndRejectsOnErrored() { + const filePath = path.join(tmpDir, 'writer-end-errored.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + await w.write(Buffer.from('data')); + w.fail(new Error('test error')); + + await assert.rejects( + w.end(), + { message: 'test error' }, + ); + await fh.close(); +} + +// ============================================================================= +// end() is idempotent when closing/closed +// ============================================================================= + +async function testEndIdempotent() { + const filePath = path.join(tmpDir, 'writer-end-idempotent.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + await w.write(Buffer.from('data')); + + // Call end() twice concurrently - second should return same promise + const p1 = w.end(); + const p2 = w.end(); + const [bytes1, bytes2] = await Promise.all([p1, p2]); + + assert.strictEqual(bytes1, 4); + assert.strictEqual(bytes2, 4); + + // After closed, calling end() again returns totalBytesWritten + const bytes3 = await w.end(); + assert.strictEqual(bytes3, 4); + + await fh.close(); +} + +// ============================================================================= +// asyncDispose waits for pending end() when closing +// ============================================================================= + +async function testAsyncDisposeWhileClosing() { + const filePath = path.join(tmpDir, 'writer-dispose-closing.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ autoClose: true }); + + await w.write(Buffer.from('closing test')); + + // Start end() but don't await - writer is now "closing" + const endPromise = w.end(); + + // asyncDispose should wait for the pending end, not call fail() + await w[Symbol.asyncDispose](); + await endPromise; + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), 'closing test'); +} + +// ============================================================================= +// asyncDispose calls fail() on open writer (not graceful cleanup) +// ============================================================================= + +async function testAsyncDisposeCallsFail() { + const filePath = path.join(tmpDir, 'writer-dispose-fails.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer(); + + await w.write(Buffer.from('some data')); + + // Dispose without end() - should call fail(), not graceful cleanup + await w[Symbol.asyncDispose](); + + // Writer should be in errored state - write should reject + await assert.rejects( + w.write(Buffer.from('more')), + (err) => err instanceof Error, + ); + + // Handle should be unlocked and reusable + const w2 = fh.writer(); + await w2.end(); + await fh.close(); +} + +// ============================================================================= +// writer() with limit - async write within limit succeeds +// ============================================================================= + +async function testWriterLimit() { + const filePath = path.join(tmpDir, 'writer-limit.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ limit: 10 }); + + await w.write(Buffer.from('12345')); // 5 bytes, 5 remaining + await w.write(Buffer.from('67890')); // 5 bytes, 0 remaining + const totalBytes = await w.end(); + await fh.close(); + + assert.strictEqual(totalBytes, 10); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), '1234567890'); +} + +// ============================================================================= +// writer() with limit - async write exceeding limit rejects +// ============================================================================= + +async function testWriterLimitExceeded() { + const filePath = path.join(tmpDir, 'writer-limit-exceeded.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ limit: 5 }); + + await w.write(Buffer.from('123')); // 3 bytes, 2 remaining + + await assert.rejects( + w.write(Buffer.from('45678')), // 5 bytes > 2 remaining + { code: 'ERR_OUT_OF_RANGE' }, + ); + + await w.end(); + await fh.close(); +} + +// ============================================================================= +// writer() with limit - writev exceeding limit rejects +// ============================================================================= + +async function testWriterLimitWritev() { + const filePath = path.join(tmpDir, 'writer-limit-writev.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ limit: 6 }); + + await w.writev([Buffer.from('ab'), Buffer.from('cd')]); // 4 bytes + + await assert.rejects( + w.writev([Buffer.from('ef'), Buffer.from('gh')]), // 4 bytes > 2 remaining + { code: 'ERR_OUT_OF_RANGE' }, + ); + + await w.end(); + await fh.close(); +} + +// ============================================================================= +// writer() with limit - writeSync returns false when exceeding limit +// ============================================================================= + +async function testWriterLimitWriteSync() { + const filePath = path.join(tmpDir, 'writer-limit-writesync.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ limit: 10 }); + + assert.strictEqual(w.writeSync(Buffer.from('12345')), true); // 5 ok + assert.strictEqual(w.writeSync(Buffer.from('678')), true); // 3 ok + assert.strictEqual(w.writeSync(Buffer.from('901')), false); // 3 > 2 remaining + + const totalBytes = w.endSync(); + await fh.close(); + + assert.strictEqual(totalBytes, 8); + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), '12345678'); +} + +// ============================================================================= +// writer() with limit - writevSync returns false when exceeding limit +// ============================================================================= + +async function testWriterLimitWritevSync() { + const filePath = path.join(tmpDir, 'writer-limit-writevsync.txt'); + const fh = await open(filePath, 'w'); + const w = fh.writer({ limit: 5 }); + + assert.strictEqual(w.writevSync([Buffer.from('ab')]), true); + // 4 bytes > 3 remaining + assert.strictEqual( + w.writevSync([Buffer.from('cd'), Buffer.from('ef')]), false); + + w.endSync(); + await fh.close(); +} + +// ============================================================================= +// writer() with limit + start +// ============================================================================= + +async function testWriterLimitAndStart() { + const filePath = path.join(tmpDir, 'writer-limit-start.txt'); + // Pre-fill file with dots + fs.writeFileSync(filePath, '...........'); // 11 dots + + const fh = await open(filePath, 'r+'); + const w = fh.writer({ start: 3, limit: 5 }); + + await w.write(Buffer.from('HELLO')); // Write at offset 3 + await w.end(); + await fh.close(); + + assert.strictEqual(fs.readFileSync(filePath, 'utf8'), '...HELLO...'); +} + +// ============================================================================= +// Argument validation +// ============================================================================= + +async function testWriterArgumentValidation() { + const filePath = path.join(tmpDir, 'pull-arg-validation.txt'); + fs.writeFileSync(filePath, 'data'); + + const fh = await open(filePath, 'r'); + try { + assert.throws(() => fh.writer({ autoClose: 'no' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.writer({ start: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.writer({ limit: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.writer({ chunkSize: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => fh.writer({ start: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => fh.writer({ limit: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + assert.throws(() => fh.writer({ chunkSize: 1.1 }), { code: 'ERR_OUT_OF_RANGE' }); + } finally { + await fh.close(); + } +} + +// ============================================================================= +// Run all tests +// ============================================================================= + +Promise.all([ + testBasicWrite(), + testBasicWritev(), + testMixedWriteAndWritev(), + testEndReturnsTotalBytes(), + testAutoCloseOnEnd(), + testAutoCloseOnFail(), + testStartOption(), + testStartSequentialPosition(), + testLockedState(), + testUnlockAfterEnd(), + testUnlockAfterFail(), + testWriteAfterEndRejects(), + testClosedHandle(), + testPipeToIntegration(), + testPipeToWithTransform(), + testCompressRoundTrip(), + testLargeFileWrite(), + testAsyncDispose(), + testAsyncDisposeOnError(), + testWriteWithAbortedSignalRejects(), + testWritevWithAbortedSignalRejects(), + testEndWithAbortedSignalRejects(), + testWriteString(), + testWriteStringMultibyte(), + testWritevStrings(), + testWritevMixed(), + testSyncDispose(), + testSyncDisposeOnError(), + testWriteSyncBasic(), + testWritevSyncBasic(), + testWriteSyncLargeChunk(), + testWriteSyncReturnsFalseDuringAsync(), + testWriteSyncClosedErrored(), + testEndSyncBasic(), + testEndSyncReturnsFalseDuringAsync(), + testEndSyncIdempotent(), + testEndSyncAutoClose(), + testFullSyncPipeline(), + testEndRejectsOnErrored(), + testEndIdempotent(), + testAsyncDisposeWhileClosing(), + testAsyncDisposeCallsFail(), + testWriterLimit(), + testWriterLimitExceeded(), + testWriterLimitWritev(), + testWriterLimitWriteSync(), + testWriterLimitWritevSync(), + testWriterLimitAndStart(), + testWriterArgumentValidation(), +]).then(common.mustCall()); diff --git a/test/parallel/test-inspector-worker-target.js b/test/parallel/test-inspector-worker-target.js index e5d2a0c2039712..6bfe8bf9304d5c 100644 --- a/test/parallel/test-inspector-worker-target.js +++ b/test/parallel/test-inspector-worker-target.js @@ -3,6 +3,8 @@ const common = require('../common'); const fixtures = require('../common/fixtures'); +const assert = require('assert'); + common.skipIfInspectorDisabled(); const { NodeInstance } = require('../common/inspector-helper.js'); @@ -21,6 +23,15 @@ async function setupInspector(session, sessionId = undefined) { }); } +async function assertTargetAttachedState(session, targetId, attached) { + const { targetInfos } = await session.send({ method: 'Target.getTargets' }); + const targetInfo = targetInfos.find((target) => { + return target.targetId === targetId; + }); + assert.notStrictEqual(targetInfo, undefined); + assert.strictEqual(targetInfo.attached, attached); +} + async function test(isSetAutoAttachBeforeExecution) { const child = new NodeInstance(['--inspect-brk=0', '--experimental-worker-inspection'], '', @@ -38,7 +49,10 @@ async function test(isSetAutoAttachBeforeExecution) { await session.send({ method: 'Debugger.resume' }); const sessionId = '1'; - await session.waitForNotification('Target.targetCreated'); + const targetCreated = await session.waitForNotification('Target.targetCreated'); + const targetId = targetCreated.params.targetInfo.targetId; + + await assertTargetAttachedState(session, targetId, isSetAutoAttachBeforeExecution); if (!isSetAutoAttachBeforeExecution) { await session.send({ method: 'Target.setAutoAttach', params: { autoAttach: true, waitForDebuggerOnStart: true } }); @@ -47,6 +61,7 @@ async function test(isSetAutoAttachBeforeExecution) { return notification.method === 'Target.attachedToTarget' && notification.params.sessionId === sessionId; }); + await assertTargetAttachedState(session, targetId, true); await setupInspector(session, sessionId); await session.waitForNotification('Debugger.paused'); await session.send({ method: 'Debugger.resume', sessionId }); diff --git a/test/parallel/test-repl-eval-error-after-close.js b/test/parallel/test-repl-eval-error-after-close.js index 8c30a533efba15..0b4683fda4bfda 100644 --- a/test/parallel/test-repl-eval-error-after-close.js +++ b/test/parallel/test-repl-eval-error-after-close.js @@ -20,8 +20,6 @@ const assert = require('node:assert'); eval$.resolve(); }); }, - }, { - disableDomainErrorAssert: true, }); replServer.write('\n'); diff --git a/test/parallel/test-repl-let-process.js b/test/parallel/test-repl-let-process.js index eb6cbc6a472c72..22b57ab5bb977e 100644 --- a/test/parallel/test-repl-let-process.js +++ b/test/parallel/test-repl-let-process.js @@ -3,5 +3,5 @@ require('../common'); const { startNewREPLServer } = require('../common/repl'); // Regression test for https://github.com/nodejs/node/issues/6802 -const { input } = startNewREPLServer({ useGlobal: true }, { disableDomainErrorAssert: true }); +const { input } = startNewREPLServer({ useGlobal: true }); input.run(['let process']); diff --git a/test/parallel/test-repl-mode.js b/test/parallel/test-repl-mode.js index 3faa0a96d58799..4d614af1a16392 100644 --- a/test/parallel/test-repl-mode.js +++ b/test/parallel/test-repl-mode.js @@ -30,8 +30,8 @@ function testSloppyMode() { } function testStrictMode() { - const { input, output } = startNewREPLServer({ replMode: repl.REPL_MODE_STRICT, terminal: false, prompt: '> ' }, { - disableDomainErrorAssert: true, + const { input, output } = startNewREPLServer({ + replMode: repl.REPL_MODE_STRICT, terminal: false, prompt: '> ' }); input.emit('data', 'x = 3\n'); diff --git a/test/parallel/test-repl-multiple-instances-async-error.js b/test/parallel/test-repl-multiple-instances-async-error.js new file mode 100644 index 00000000000000..ddc8a5eaccdcca --- /dev/null +++ b/test/parallel/test-repl-multiple-instances-async-error.js @@ -0,0 +1,69 @@ +'use strict'; + +// This test verifies that when multiple REPL instances exist concurrently, +// async errors are correctly routed to the REPL instance that created them. + +const common = require('../common'); +const assert = require('assert'); +const repl = require('repl'); +const { Writable, PassThrough } = require('stream'); + +// Create two REPLs with separate inputs and outputs +let output1 = ''; +let output2 = ''; + +const input1 = new PassThrough(); +const input2 = new PassThrough(); + +const writable1 = new Writable({ + write(chunk, encoding, callback) { + output1 += chunk.toString(); + callback(); + } +}); + +const writable2 = new Writable({ + write(chunk, encoding, callback) { + output2 += chunk.toString(); + callback(); + } +}); + +const r1 = repl.start({ + input: input1, + output: writable1, + terminal: false, + prompt: 'R1> ', +}); + +const r2 = repl.start({ + input: input2, + output: writable2, + terminal: false, + prompt: 'R2> ', +}); + +// Create async error in REPL 1 +input1.write('setTimeout(() => { throw new Error("error from repl1") }, 10)\n'); + +// Create async error in REPL 2 +input2.write('setTimeout(() => { throw new Error("error from repl2") }, 20)\n'); + +setTimeout(common.mustCall(() => { + r1.close(); + r2.close(); + + // Verify error from REPL 1 went to REPL 1's output + assert.match(output1, /error from repl1/, + 'REPL 1 should have received its own async error'); + + // Verify error from REPL 2 went to REPL 2's output + assert.match(output2, /error from repl2/, + 'REPL 2 should have received its own async error'); + + // Verify errors did not cross over to wrong REPL + assert.doesNotMatch(output1, /error from repl2/, + 'REPL 1 should not have received REPL 2\'s error'); + assert.doesNotMatch(output2, /error from repl1/, + 'REPL 2 should not have received REPL 1\'s error'); +}), 100); diff --git a/test/parallel/test-repl-pretty-custom-stack.js b/test/parallel/test-repl-pretty-custom-stack.js index 82df8ff4fc6335..0efb814f38d2b5 100644 --- a/test/parallel/test-repl-pretty-custom-stack.js +++ b/test/parallel/test-repl-pretty-custom-stack.js @@ -10,8 +10,6 @@ function run({ command, expected }) { const { replServer, output } = startNewREPLServer({ terminal: false, useColors: false - }, { - disableDomainErrorAssert: true, }); replServer.write(`${command}\n`); diff --git a/test/parallel/test-repl-pretty-stack-custom-writer.js b/test/parallel/test-repl-pretty-stack-custom-writer.js index 2d39633030d775..e31460dbc93efb 100644 --- a/test/parallel/test-repl-pretty-stack-custom-writer.js +++ b/test/parallel/test-repl-pretty-stack-custom-writer.js @@ -5,10 +5,7 @@ const { startNewREPLServer } = require('../common/repl'); const testingReplPrompt = '_REPL_TESTING_PROMPT_>'; -const { replServer, output } = startNewREPLServer( - { prompt: testingReplPrompt }, - { disableDomainErrorAssert: true } -); +const { replServer, output } = startNewREPLServer({ prompt: testingReplPrompt }); replServer.write('throw new Error("foo[a]")\n'); diff --git a/test/parallel/test-repl-pretty-stack.js b/test/parallel/test-repl-pretty-stack.js index a7f13dea75aaea..b2f9cc82c08df0 100644 --- a/test/parallel/test-repl-pretty-stack.js +++ b/test/parallel/test-repl-pretty-stack.js @@ -7,14 +7,11 @@ const { startNewREPLServer } = require('../common/repl'); const stackRegExp = /(at .*REPL\d+:)[0-9]+:[0-9]+/g; function run({ command, expected, ...extraREPLOptions }, i) { - const { replServer, output } = startNewREPLServer( - { - terminal: false, - useColors: false, - ...extraREPLOptions - }, - { disableDomainErrorAssert: true } - ); + const { replServer, output } = startNewREPLServer({ + terminal: false, + useColors: false, + ...extraREPLOptions + }); replServer.write(`${command}\n`); if (typeof expected === 'string') { diff --git a/test/parallel/test-repl-preview-newlines.js b/test/parallel/test-repl-preview-newlines.js index 22ffe0db108590..34a944beb538d7 100644 --- a/test/parallel/test-repl-preview-newlines.js +++ b/test/parallel/test-repl-preview-newlines.js @@ -6,9 +6,7 @@ const { startNewREPLServer } = require('../common/repl'); common.skipIfInspectorDisabled(); -const { input, output } = startNewREPLServer( - { useColors: true }, { disableDomainErrorAssert: true } -); +const { input, output } = startNewREPLServer({ useColors: true }); output.accumulator = ''; diff --git a/test/parallel/test-repl-syntax-error-stack.js b/test/parallel/test-repl-syntax-error-stack.js index 1b6e3fb6e879f2..16bf27d045bc77 100644 --- a/test/parallel/test-repl-syntax-error-stack.js +++ b/test/parallel/test-repl-syntax-error-stack.js @@ -11,7 +11,7 @@ process.on('exit', () => { assert.strictEqual(found, true); }); -const { input, output } = startNewREPLServer({}, { disableDomainErrorAssert: true }); +const { input, output } = startNewREPLServer(); output.write = (data) => { // Matching only on a minimal piece of the stack because the string will vary diff --git a/test/parallel/test-repl-tab-complete-crash.js b/test/parallel/test-repl-tab-complete-crash.js index 58628eb85b2a38..29f75028bdac94 100644 --- a/test/parallel/test-repl-tab-complete-crash.js +++ b/test/parallel/test-repl-tab-complete-crash.js @@ -4,7 +4,7 @@ const common = require('../common'); const assert = require('assert'); const { startNewREPLServer } = require('../common/repl'); -const { replServer, input } = startNewREPLServer({}, { disableDomainErrorAssert: true }); +const { replServer, input } = startNewREPLServer(); // https://github.com/nodejs/node/issues/3346 // Tab-completion should be empty diff --git a/test/parallel/test-repl-tab.js b/test/parallel/test-repl-tab.js index e99f667c4a38f5..710fca9fae2d1e 100644 --- a/test/parallel/test-repl-tab.js +++ b/test/parallel/test-repl-tab.js @@ -10,6 +10,4 @@ const testMe = repl.start('', putIn, function(cmd, context, filename, callback(null, cmd); }); -testMe._domain.on('error', common.mustNotCall()); - testMe.complete('', common.mustSucceed()); diff --git a/test/parallel/test-repl-top-level-await.js b/test/parallel/test-repl-top-level-await.js index 0b35443dbdce14..a94ff8e48984a3 100644 --- a/test/parallel/test-repl-top-level-await.js +++ b/test/parallel/test-repl-top-level-await.js @@ -180,7 +180,7 @@ async function ordinaryTests() { ['k', '234'], ['const k = await Promise.resolve(345)', "Uncaught SyntaxError: Identifier 'k' has already been declared"], // Regression test for https://github.com/nodejs/node/issues/43777. - ['await Promise.resolve(123), Promise.resolve(456)', 'Promise {', { line: 0 }], + ['await Promise.resolve(123), Promise.resolve(456)', 'Promise { 456 }'], ['await Promise.resolve(123), await Promise.resolve(456)', '456'], ['await (Promise.resolve(123), Promise.resolve(456))', '456'], ]; diff --git a/test/parallel/test-repl-uncaught-exception-after-input-ended.js b/test/parallel/test-repl-uncaught-exception-after-input-ended.js new file mode 100644 index 00000000000000..1e2ca86a9f079c --- /dev/null +++ b/test/parallel/test-repl-uncaught-exception-after-input-ended.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common'); +const { start } = require('node:repl'); +const { PassThrough } = require('node:stream'); +const assert = require('node:assert'); + +// This test verifies that uncaught exceptions in the REPL +// do not bring down the process, even if stdin may already +// have been ended at that point (and the REPL closed as +// a result of that). +const input = new PassThrough(); +const output = new PassThrough().setEncoding('utf8'); +start({ + input, + output, + terminal: false, +}); + +input.end('setImmediate(() => { throw new Error("test"); });\n'); + +setImmediate(common.mustCall(() => { + assert.match(output.read(), /Uncaught Error: test/); +})); diff --git a/test/parallel/test-repl-uncaught-exception-async.js b/test/parallel/test-repl-uncaught-exception-async.js index f4180080f496f7..e5373cdaca4d8d 100644 --- a/test/parallel/test-repl-uncaught-exception-async.js +++ b/test/parallel/test-repl-uncaught-exception-async.js @@ -8,17 +8,12 @@ const common = require('../common'); const assert = require('assert'); const { startNewREPLServer } = require('../common/repl'); -const { replServer, output } = startNewREPLServer( - { - prompt: '', - terminal: false, - useColors: false, - global: false, - }, - { - disableDomainErrorAssert: true - }, -); +const { replServer, output } = startNewREPLServer({ + prompt: '', + terminal: false, + useColors: false, + global: false, +}); replServer.write( 'process.nextTick(() => {\n' + diff --git a/test/parallel/test-repl-uncaught-exception-evalcallback.js b/test/parallel/test-repl-uncaught-exception-evalcallback.js index 77d03320ee9375..844fce6995aaa6 100644 --- a/test/parallel/test-repl-uncaught-exception-evalcallback.js +++ b/test/parallel/test-repl-uncaught-exception-evalcallback.js @@ -3,21 +3,16 @@ const common = require('../common'); const assert = require('assert'); const { startNewREPLServer } = require('../common/repl'); -const { replServer, output } = startNewREPLServer( - { - prompt: '', - terminal: false, - useColors: false, - global: false, - eval: common.mustCall((code, context, filename, cb) => { - replServer.setPrompt('prompt! '); - cb(new Error('err')); - }) - }, - { - disableDomainErrorAssert: true - }, -); +const { replServer, output } = startNewREPLServer({ + prompt: '', + terminal: false, + useColors: false, + global: false, + eval: common.mustCall((code, context, filename, cb) => { + replServer.setPrompt('prompt! '); + cb(new Error('err')); + }) +}); replServer.write('foo\n'); diff --git a/test/parallel/test-repl-uncaught-exception.js b/test/parallel/test-repl-uncaught-exception.js index 7753fe180b07fd..012c7f59ebc8a8 100644 --- a/test/parallel/test-repl-uncaught-exception.js +++ b/test/parallel/test-repl-uncaught-exception.js @@ -6,16 +6,11 @@ const { startNewREPLServer } = require('../common/repl'); let count = 0; function run({ command, expected, useColors = false }) { - const { replServer, output } = startNewREPLServer( - { - prompt: '', - terminal: false, - useColors, - }, - { - disableDomainErrorAssert: true - }, - ); + const { replServer, output } = startNewREPLServer({ + prompt: '', + terminal: false, + useColors, + }); replServer.write(`${command}\n`); diff --git a/test/parallel/test-repl-underscore.js b/test/parallel/test-repl-underscore.js index 4c091a268fb701..c9ae7ca0e7ca0c 100644 --- a/test/parallel/test-repl-underscore.js +++ b/test/parallel/test-repl-underscore.js @@ -138,8 +138,6 @@ function testError() { prompt: testingReplPrompt, replMode: repl.REPL_MODE_STRICT, preview: false, - }, { - disableDomainErrorAssert: true }); replServer.write(`_error; // initial value undefined diff --git a/test/parallel/test-repl-user-error-handler.js b/test/parallel/test-repl-user-error-handler.js new file mode 100644 index 00000000000000..31bd46b13d36d1 --- /dev/null +++ b/test/parallel/test-repl-user-error-handler.js @@ -0,0 +1,84 @@ +'use strict'; +const common = require('../common'); +const { start } = require('node:repl'); +const assert = require('node:assert'); +const { PassThrough } = require('node:stream'); +const { once } = require('node:events'); +const test = require('node:test'); +const { spawn } = require('node:child_process'); + +function* generateCases() { + for (const async of [false, true]) { + for (const handleErrorReturn of ['ignore', 'print', 'unhandled', 'badvalue']) { + if (handleErrorReturn === 'badvalue' && async) { + // Handled through a separate test using a child process + continue; + } + yield { async, handleErrorReturn }; + } + } +} + +for (const { async, handleErrorReturn } of generateCases()) { + test(`async: ${async}, handleErrorReturn: ${handleErrorReturn}`, async () => { + let err; + const options = { + input: new PassThrough(), + output: new PassThrough().setEncoding('utf8'), + handleError: common.mustCall((e) => { + err = e; + queueMicrotask(() => repl.emit('handled-error')); + return handleErrorReturn; + }) + }; + + let uncaughtExceptionEvent; + if (handleErrorReturn === 'unhandled' && async) { + process.removeAllListeners('uncaughtException'); // Remove the test runner's handler + uncaughtExceptionEvent = once(process, 'uncaughtException'); + } + + const repl = start(options); + const inputString = async ? + 'setImmediate(() => { throw new Error("testerror") })\n42\n' : + 'throw new Error("testerror")\n42\n'; + if (handleErrorReturn === 'badvalue') { + assert.throws(() => options.input.end(inputString), /ERR_INVALID_STATE/); + return; + } + options.input.end(inputString); + + await once(repl, 'handled-error'); + assert.strictEqual(err.message, 'testerror'); + const outputString = options.output.read(); + assert.match(outputString, /42/); + + if (handleErrorReturn === 'print') { + assert.match(outputString, /testerror/); + } else { + assert.doesNotMatch(outputString, /testerror/); + } + + if (uncaughtExceptionEvent) { + const [uncaughtErr] = await uncaughtExceptionEvent; + assert.strictEqual(uncaughtErr, err); + } + }); +} + +test('async: true, handleErrorReturn: badvalue', async () => { + // Can't test this the same way as the other combinations + // since this will take the process down in a way that + // cannot be caught. + const proc = spawn(process.execPath, ['-e', ` + require('node:repl').start({ + handleError: () => 'badvalue' + }) + `], { encoding: 'utf8', stdio: 'pipe' }); + proc.stdin.end('throw new Error("foo");'); + let stderr = ''; + proc.stderr.setEncoding('utf8').on('data', (data) => stderr += data); + const [exit] = await once(proc, 'close'); + assert.strictEqual(exit, 1); + assert.match(stderr, /ERR_INVALID_STATE.+badvalue/); +}); diff --git a/test/parallel/test-runner-exit-code.js b/test/parallel/test-runner-exit-code.js index 4024a52841bb28..c25becee3f708f 100644 --- a/test/parallel/test-runner-exit-code.js +++ b/test/parallel/test-runner-exit-code.js @@ -70,6 +70,14 @@ if (process.argv[2] === 'child') { assert.strictEqual(child.status, 1); assert.strictEqual(child.signal, null); + // An error thrown inside describe() should cause a non-zero exit code. + child = spawnSync(process.execPath, [ + '--test', + fixtures.path('test-runner', 'describe_error.js'), + ]); + assert.strictEqual(child.status, 1); + assert.strictEqual(child.signal, null); + // With process isolation (default), the test name shown is the file path // because the parent runner only knows about file-level tests const neverEndingSync = fixtures.path('test-runner', 'never_ending_sync.js'); diff --git a/test/parallel/test-runner-mock-timers-with-timeout.js b/test/parallel/test-runner-mock-timers-with-timeout.js new file mode 100644 index 00000000000000..67f266851fe1ed --- /dev/null +++ b/test/parallel/test-runner-mock-timers-with-timeout.js @@ -0,0 +1,14 @@ +'use strict'; +require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('node:assert'); +const { spawnSync } = require('node:child_process'); +const { test } = require('node:test'); + +test('mock timers do not break test timeout cleanup', async () => { + const fixture = fixtures.path('test-runner', 'mock-timers-with-timeout.js'); + const cp = spawnSync(process.execPath, ['--test', fixture], { + timeout: 30_000, + }); + assert.strictEqual(cp.status, 0, `Test failed:\nstdout: ${cp.stdout}\nstderr: ${cp.stderr}`); +}); diff --git a/test/parallel/test-runner-module-mocking.js b/test/parallel/test-runner-module-mocking.js index dcb6f84597fe71..7f2c76663ba29c 100644 --- a/test/parallel/test-runner-module-mocking.js +++ b/test/parallel/test-runner-module-mocking.js @@ -39,6 +39,42 @@ test('input validation', async (t) => { }); }, { code: 'ERR_INVALID_ARG_TYPE' }); }); + + await t.test('throws if exports is not an object', async (t) => { + assert.throws(() => { + t.mock.module(__filename, { + exports: null, + }); + }, { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + await t.test('throws if exports is used with namedExports', async (t) => { + assert.throws(() => { + t.mock.module(__filename, { + exports: {}, + namedExports: {}, + }); + }, { code: 'ERR_INVALID_ARG_VALUE' }); + }); + + await t.test('throws if exports is used with defaultExport', async (t) => { + assert.throws(() => { + t.mock.module(__filename, { + exports: {}, + defaultExport: {}, + }); + }, { code: 'ERR_INVALID_ARG_VALUE' }); + }); + + await t.test('throws if exports is used with both legacy options', async (t) => { + assert.throws(() => { + t.mock.module(__filename, { + exports: {}, + namedExports: {}, + defaultExport: {}, + }); + }, { code: 'ERR_INVALID_ARG_VALUE' }); + }); }); test('core module mocking with namedExports option', async (t) => { @@ -517,42 +553,33 @@ test('mocks can be restored independently', async (t) => { assert.strictEqual(esmImpl.fn, undefined); }); -test('core module mocks can be used by both module systems', async (t) => { - const coreMock = t.mock.module('readline', { - namedExports: { fn() { return 42; } }, - }); +async function assertCoreModuleMockWorksInBothModuleSystems(t, specifier, options) { + const coreMock = t.mock.module(specifier, options); - let esmImpl = await import('readline'); - let cjsImpl = require('readline'); + let esmImpl = await import(specifier); + let cjsImpl = require(specifier); assert.strictEqual(esmImpl.fn(), 42); assert.strictEqual(cjsImpl.fn(), 42); coreMock.restore(); - esmImpl = await import('readline'); - cjsImpl = require('readline'); + esmImpl = await import(specifier); + cjsImpl = require(specifier); assert.strictEqual(typeof esmImpl.cursorTo, 'function'); assert.strictEqual(typeof cjsImpl.cursorTo, 'function'); +} + +test('core module mocks can be used by both module systems', async (t) => { + await assertCoreModuleMockWorksInBothModuleSystems(t, 'readline', { + namedExports: { fn() { return 42; } }, + }); }); test('node:- core module mocks can be used by both module systems', async (t) => { - const coreMock = t.mock.module('node:readline', { + await assertCoreModuleMockWorksInBothModuleSystems(t, 'node:readline', { namedExports: { fn() { return 42; } }, }); - - let esmImpl = await import('node:readline'); - let cjsImpl = require('node:readline'); - - assert.strictEqual(esmImpl.fn(), 42); - assert.strictEqual(cjsImpl.fn(), 42); - - coreMock.restore(); - esmImpl = await import('node:readline'); - cjsImpl = require('node:readline'); - - assert.strictEqual(typeof esmImpl.cursorTo, 'function'); - assert.strictEqual(typeof cjsImpl.cursorTo, 'function'); }); test('CJS mocks can be used by both module systems', async (t) => { @@ -666,6 +693,92 @@ test('defaultExports work with ESM mocks in both module systems', async (t) => { assert.strictEqual(require(fixturePath), defaultExport); }); +test('exports option works with core module mocks in both module systems', async (t) => { + await assertCoreModuleMockWorksInBothModuleSystems(t, 'readline', { + exports: { fn() { return 42; } }, + }); +}); + +async function assertGetterMockWorksInBothSystems(t, mockOptionsFactory) { + const fixturePath = fixtures.path('module-mocking', 'basic-esm.mjs'); + const fixture = pathToFileURL(fixturePath); + const original = await import(fixture); + let getterCalls = 0; + + assert.strictEqual(original.string, 'original esm string'); + + const options = mockOptionsFactory(() => { + getterCalls++; + return { mocked: true }; + }); + + t.mock.module(`${fixture}`, options); + + assert.deepStrictEqual((await import(fixture)).default, { mocked: true }); + assert.deepStrictEqual(require(fixturePath), { mocked: true }); + assert.strictEqual(getterCalls, 2); +} + +test('defaultExports getter works in both module systems', async (t) => { + await assertGetterMockWorksInBothSystems(t, (getter) => ({ + get defaultExport() { + return getter(); + }, + })); +}); + +test('exports.default getter works in both module systems', async (t) => { + await assertGetterMockWorksInBothSystems(t, (getter) => ({ + exports: { + get default() { + return getter(); + }, + }, + })); +}); +test('exports option supports default for CJS mocks in both module systems', async (t) => { + const fixturePath = fixtures.path('module-mocking', 'basic-cjs.js'); + const fixture = pathToFileURL(fixturePath); + const defaultExport = { val1: 5, val2: 3 }; + + t.mock.module(fixture, { + exports: { + default: defaultExport, + val1: 'mock value', + }, + }); + + const cjsMock = require(fixturePath); + const esmMock = await import(fixture); + + assert.strictEqual(cjsMock, defaultExport); + assert.strictEqual(esmMock.default, defaultExport); + assert.strictEqual(cjsMock.val1, 'mock value'); + assert.strictEqual(esmMock.val1, 'mock value'); + assert.strictEqual(cjsMock.val2, 3); +}); + +test('exports option supports default for ESM mocks in both module systems', async (t) => { + const fixturePath = fixtures.path('module-mocking', 'basic-esm.mjs'); + const fixture = pathToFileURL(fixturePath); + const defaultExport = { mocked: true }; + + t.mock.module(fixture, { + exports: { + default: defaultExport, + val1: 'mock value', + }, + }); + + const esmMock = await import(fixture); + const cjsMock = require(fixturePath); + + assert.strictEqual(esmMock.default, defaultExport); + assert.strictEqual(esmMock.val1, 'mock value'); + assert.strictEqual(cjsMock, defaultExport); + assert.strictEqual(cjsMock.val1, 'mock value'); +}); + test('wrong import syntax should throw error after module mocking', async () => { const { stdout, stderr, code } = await common.spawnPromisified( process.execPath, diff --git a/test/parallel/test-stream-iter-broadcast-backpressure.js b/test/parallel/test-stream-iter-broadcast-backpressure.js new file mode 100644 index 00000000000000..d1e466c3ac44cf --- /dev/null +++ b/test/parallel/test-stream-iter-broadcast-backpressure.js @@ -0,0 +1,138 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { broadcast, text } = require('stream/iter'); + +// ============================================================================= +// Backpressure policies +// ============================================================================= + +async function testDropOldest() { + const { writer, broadcast: bc } = broadcast({ + highWaterMark: 2, + backpressure: 'drop-oldest', + }); + const consumer = bc.push(); + + writer.writeSync('first'); + writer.writeSync('second'); + // This should drop 'first' + writer.writeSync('third'); + writer.endSync(); + + const data = await text(consumer); + assert.strictEqual(data, 'secondthird'); +} + +async function testDropNewest() { + const { writer, broadcast: bc } = broadcast({ + highWaterMark: 1, + backpressure: 'drop-newest', + }); + const consumer = bc.push(); + + writer.writeSync('kept'); + // This should be silently dropped + writer.writeSync('dropped'); + writer.endSync(); + + const data = await text(consumer); + assert.strictEqual(data, 'kept'); +} + +// ============================================================================= +// Block backpressure +// ============================================================================= + +async function testBlockBackpressure() { + const { writer, broadcast: bc } = broadcast({ + highWaterMark: 1, + backpressure: 'block', + }); + const consumer = bc.push(); + writer.writeSync('a'); + + // Next write should block + let writeResolved = false; + const writePromise = writer.write('b').then(() => { writeResolved = true; }); + await new Promise(setImmediate); + assert.strictEqual(writeResolved, false); + + // Drain consumer to unblock the pending write + const iter = consumer[Symbol.asyncIterator](); + const first = await iter.next(); + assert.strictEqual(first.done, false); + await new Promise(setImmediate); + assert.strictEqual(writeResolved, true); + + writer.endSync(); + // Drain remaining data and verify completion + const second = await iter.next(); + assert.strictEqual(second.done, false); + await writePromise; +} + +// Verify block backpressure data flows correctly end-to-end +async function testBlockBackpressureContent() { + const { writer, broadcast: bc } = broadcast({ + highWaterMark: 1, + backpressure: 'block', + }); + const consumer = bc.push(); + + writer.writeSync('a'); + const writePromise = writer.write('b'); + await new Promise(setImmediate); + + // Read all and verify content + const iter = consumer[Symbol.asyncIterator](); + const first = await iter.next(); + assert.strictEqual(first.done, false); + const firstStr = new TextDecoder().decode(first.value[0]); + assert.strictEqual(firstStr, 'a'); + + await writePromise; + writer.endSync(); + + const second = await iter.next(); + assert.strictEqual(second.done, false); + const secondStr = new TextDecoder().decode(second.value[0]); + assert.strictEqual(secondStr, 'b'); + + const done = await iter.next(); + assert.strictEqual(done.done, true); +} + +// Writev async path +async function testWritevAsync() { + const { writer, broadcast: bc } = broadcast({ highWaterMark: 10 }); + const consumer = bc.push(); + + await writer.writev(['hello', ' ', 'world']); + await writer.end(); + + const data = await text(consumer); + assert.strictEqual(data, 'hello world'); +} + +// endSync returns the total byte count +async function testEndSyncReturnValue() { + const { writer, broadcast: bc } = broadcast({ highWaterMark: 10 }); + bc.push(); // Need a consumer to write to + + writer.writeSync('hello'); // 5 bytes + writer.writeSync(' world'); // 6 bytes + const total = writer.endSync(); + assert.strictEqual(total, 11); +} + +Promise.all([ + testDropOldest(), + testDropNewest(), + testBlockBackpressure(), + testBlockBackpressureContent(), + testWritevAsync(), + testEndSyncReturnValue(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-broadcast-basic.js b/test/parallel/test-stream-iter-broadcast-basic.js new file mode 100644 index 00000000000000..ab2c81304ec2ac --- /dev/null +++ b/test/parallel/test-stream-iter-broadcast-basic.js @@ -0,0 +1,260 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { broadcast, text } = require('stream/iter'); + +// ============================================================================= +// Basic broadcast +// ============================================================================= + +async function testBasicBroadcast() { + const { writer, broadcast: bc } = broadcast(); + + // Create two consumers + const consumer1 = bc.push(); + const consumer2 = bc.push(); + + assert.strictEqual(bc.consumerCount, 2); + + await writer.write('hello'); + await writer.end(); + + const [data1, data2] = await Promise.all([ + text(consumer1), + text(consumer2), + ]); + + assert.strictEqual(data1, 'hello'); + assert.strictEqual(data2, 'hello'); +} + +async function testMultipleWrites() { + const { writer, broadcast: bc } = broadcast({ highWaterMark: 10 }); + + const consumer = bc.push(); + + await writer.write('a'); + await writer.write('b'); + await writer.write('c'); + await writer.end(); + + const data = await text(consumer); + assert.strictEqual(data, 'abc'); +} + +async function testConsumerCount() { + const { broadcast: bc } = broadcast(); + + assert.strictEqual(bc.consumerCount, 0); + + const c1 = bc.push(); + assert.strictEqual(bc.consumerCount, 1); + + bc.push(); + assert.strictEqual(bc.consumerCount, 2); + + bc.cancel(); + + // After cancel, consumer count drops to 0 + assert.strictEqual(bc.consumerCount, 0); + + // Consumers are detached and yield nothing + const batches = []; + for await (const batch of c1) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +// ============================================================================= +// Writer methods +// ============================================================================= + +async function testWriteSync() { + const { writer, broadcast: bc } = broadcast({ highWaterMark: 2 }); + const consumer = bc.push(); + + assert.strictEqual(writer.writeSync('a'), true); + assert.strictEqual(writer.writeSync('b'), true); + // Buffer full (highWaterMark=2, strict policy) + assert.strictEqual(writer.writeSync('c'), false); + + writer.endSync(); + + const data = await text(consumer); + assert.strictEqual(data, 'ab'); +} + +async function testWritevSync() { + const { writer, broadcast: bc } = broadcast({ highWaterMark: 10 }); + const consumer = bc.push(); + + assert.strictEqual(writer.writevSync(['hello', ' ', 'world']), true); + writer.endSync(); + + const data = await text(consumer); + assert.strictEqual(data, 'hello world'); +} + +async function testWriterEnd() { + const { writer, broadcast: bc } = broadcast(); + const consumer = bc.push(); + + await writer.write('data'); + const totalBytes = await writer.end(); + assert.strictEqual(totalBytes, 4); // 'data' = 4 UTF-8 bytes + + const data = await text(consumer); + assert.strictEqual(data, 'data'); +} + +async function testWriterFail() { + const { writer, broadcast: bc } = broadcast(); + const consumer = bc.push(); + + writer.fail(new Error('test error')); + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of consumer) { + assert.fail('Should not reach here'); + } + }, + { message: 'test error' }, + ); +} + +// ============================================================================= +// Cancel +// ============================================================================= + +async function testCancelWithoutReason() { + const { broadcast: bc } = broadcast(); + const consumer = bc.push(); + + bc.cancel(); + + const batches = []; + for await (const batch of consumer) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +async function testCancelWithReason() { + const { broadcast: bc } = broadcast(); + + // Start a consumer that is waiting for data (promise pending) + const consumer = bc.push(); + const resultPromise = text(consumer).catch((err) => err); + + // Give the consumer time to enter the waiting state + await new Promise((resolve) => setImmediate(resolve)); + + bc.cancel(new Error('cancelled')); + + const result = await resultPromise; + assert.ok(result instanceof Error); + assert.strictEqual(result.message, 'cancelled'); +} + +// ============================================================================= +// Writer fail detaches consumers +// ============================================================================= + +async function testFailDetachesConsumers() { + const { writer, broadcast: bc } = broadcast(); + const consumer1 = bc.push(); + const consumer2 = bc.push(); + + assert.strictEqual(bc.consumerCount, 2); + + // Write some data, then fail the writer + await writer.write('data'); + await writer.fail(new Error('writer failed')); + + // After fail, consumers are detached + assert.strictEqual(bc.consumerCount, 0); + + // Both consumers should see the error + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of consumer1) { + assert.fail('Should not reach here'); + } + }, + { message: 'writer failed' }, + ); + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of consumer2) { + assert.fail('Should not reach here'); + } + }, + { message: 'writer failed' }, + ); +} + +// ============================================================================= +// Writer fail idempotent +// ============================================================================= + +async function testWriterFailIdempotent() { + const { writer, broadcast: bc } = broadcast(); + const consumer = bc.push(); + writer.writeSync('hello'); + writer.fail(new Error('fail!')); + // Second call is a no-op (already errored) + writer.fail(new Error('fail2')); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of consumer) { /* consume */ } + }, { message: 'fail!' }); +} + +// cancel() with falsy reason (0, "", false) should still treat as error +async function testCancelWithFalsyReason() { + const { broadcast: bc } = broadcast(); + const consumer = bc.push(); + const resultPromise = text(consumer).catch((err) => err); + await new Promise((resolve) => setImmediate(resolve)); + bc.cancel(0); + const result = await resultPromise; + assert.strictEqual(result, 0); +} + +// Late-joining consumer should read from oldest buffered entry +async function testLateJoinerSeesBufferedData() { + const { writer, broadcast: bc } = broadcast({ highWaterMark: 16 }); + + // Write data before any consumer joins + writer.writeSync('before-join'); + writer.endSync(); + + // Consumer joins after data is written + const consumer = bc.push(); + const result = await text(consumer); + assert.strictEqual(result, 'before-join'); +} + +Promise.all([ + testBasicBroadcast(), + testMultipleWrites(), + testConsumerCount(), + testWriteSync(), + testWritevSync(), + testWriterEnd(), + testWriterFail(), + testCancelWithoutReason(), + testCancelWithReason(), + testCancelWithFalsyReason(), + testFailDetachesConsumers(), + testWriterFailIdempotent(), + testLateJoinerSeesBufferedData(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-broadcast-coverage.js b/test/parallel/test-stream-iter-broadcast-coverage.js new file mode 100644 index 00000000000000..bc86f44867dcc5 --- /dev/null +++ b/test/parallel/test-stream-iter-broadcast-coverage.js @@ -0,0 +1,115 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Coverage tests for broadcast.js: signal abort on pending write, +// sync iterable from, ringbuffer grow. + +const common = require('../common'); +const assert = require('assert'); +const { + broadcast, + Broadcast, + text, +} = require('stream/iter'); + +// Signal abort on pending write (covers wireBroadcastWriteSignal + removeAt) +async function testBroadcastWriteAbort() { + const { writer, broadcast: bc } = broadcast({ + highWaterMark: 1, + backpressure: 'block', + }); + const consumer = bc.push(); + + // Fill the buffer to capacity + writer.writeSync(new Uint8Array([1])); + + // Next write will block — pass a signal + const ac = new AbortController(); + const writePromise = writer.write(new Uint8Array([2]), + { signal: ac.signal }); + + // Abort the signal + ac.abort(); + + await assert.rejects(writePromise, { name: 'AbortError' }); + + // Clean up + writer.endSync(); + // Drain the consumer + const result = []; + for await (const batch of consumer) { + result.push(...batch); + } + assert.ok(result.length >= 1); +} + +// Broadcast.from with sync iterable (generator) +async function testBroadcastFromSyncIterable() { + function* source() { + yield [new Uint8Array([10, 20])]; + yield [new Uint8Array([30, 40])]; + } + + const { broadcast: bc } = Broadcast.from(source()); + const consumer = bc.push(); + // Just verify it completes without error and produces data + let count = 0; + for await (const batch of consumer) { + count += batch.length; + } + assert.ok(count > 0); +} + +// Broadcast.from with sync iterable — string chunks +async function testBroadcastFromSyncIterableStrings() { + function* source() { + yield 'hello'; + yield ' world'; + } + const { broadcast: bc } = Broadcast.from(source()); + const consumer = bc.push(); + const result = await text(consumer); + assert.strictEqual(result, 'hello world'); +} + +// Ringbuffer grow — push > 16 items without consumer draining +async function testRingbufferGrow() { + const { writer, broadcast: bc } = broadcast({ highWaterMark: 32 }); + const consumer = bc.push(); + + // Push 20 items (exceeds default ringbuffer capacity of 16) + for (let i = 0; i < 20; i++) { + writer.writeSync(new Uint8Array([i])); + } + writer.endSync(); + + // Read all items back and verify order + const items = []; + for await (const batch of consumer) { + for (const chunk of batch) { + items.push(chunk[0]); + } + } + assert.strictEqual(items.length, 20); + for (let i = 0; i < 20; i++) { + assert.strictEqual(items[i], i); + } +} + +// Broadcast drainableProtocol after close returns null +async function testDrainableAfterClose() { + const { drainableProtocol } = require('stream/iter'); + const { writer } = broadcast(); + writer.endSync(); + const result = writer[drainableProtocol](); + // After close, desired should be null + assert.strictEqual(result, null); +} + +Promise.all([ + testBroadcastWriteAbort(), + testBroadcastFromSyncIterable(), + testBroadcastFromSyncIterableStrings(), + testRingbufferGrow(), + testDrainableAfterClose(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-broadcast-from.js b/test/parallel/test-stream-iter-broadcast-from.js new file mode 100644 index 00000000000000..2f17b1a7de92fa --- /dev/null +++ b/test/parallel/test-stream-iter-broadcast-from.js @@ -0,0 +1,192 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { broadcast, Broadcast, from, text } = require('stream/iter'); + +// ============================================================================= +// Broadcast.from +// ============================================================================= + +async function testBroadcastFromAsyncIterable() { + const source = from('broadcast-from'); + const { broadcast: bc } = Broadcast.from(source); + const consumer = bc.push(); + + const data = await text(consumer); + assert.strictEqual(data, 'broadcast-from'); +} + +async function testBroadcastFromNonArrayChunks() { + // Source that yields single Uint8Array chunks (not arrays) + const enc = new TextEncoder(); + async function* singleChunkSource() { + yield enc.encode('hello'); + yield enc.encode(' world'); + } + const { broadcast: bc } = Broadcast.from(singleChunkSource()); + const consumer = bc.push(); + const data = await text(consumer); + assert.strictEqual(data, 'hello world'); +} + +async function testBroadcastFromStringChunks() { + // Source that yields bare strings (not arrays) + async function* stringSource() { + yield 'foo'; + yield 'bar'; + } + const { broadcast: bc } = Broadcast.from(stringSource()); + const consumer = bc.push(); + const data = await text(consumer); + assert.strictEqual(data, 'foobar'); +} + +async function testBroadcastFromMultipleConsumers() { + const source = from('shared-data'); + const { broadcast: bc } = Broadcast.from(source); + + const c1 = bc.push(); + const c2 = bc.push(); + + const [data1, data2] = await Promise.all([ + text(c1), + text(c2), + ]); + + assert.strictEqual(data1, 'shared-data'); + assert.strictEqual(data2, 'shared-data'); +} + +// ============================================================================= +// AbortSignal +// ============================================================================= + +async function testAbortSignal() { + const ac = new AbortController(); + const { broadcast: bc } = broadcast({ signal: ac.signal }); + const consumer = bc.push(); + + ac.abort(); + + const batches = []; + for await (const batch of consumer) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +async function testAlreadyAbortedSignal() { + const ac = new AbortController(); + ac.abort(); + + const { broadcast: bc } = broadcast({ signal: ac.signal }); + const consumer = bc.push(); + + const batches = []; + for await (const batch of consumer) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +// ============================================================================= +// Broadcast.from() hang fix - cancel while write blocked on backpressure +// ============================================================================= + +async function testBroadcastFromCancelWhileBlocked() { + // Create a slow async source that blocks between yields + let sourceFinished = false; + async function* slowSource() { + const enc = new TextEncoder(); + yield [enc.encode('chunk1')]; + // Simulate a long delay - the cancel should unblock this + await new Promise((resolve) => setTimeout(resolve, 10000)); + yield [enc.encode('chunk2')]; + sourceFinished = true; + } + + const { broadcast: bc } = Broadcast.from(slowSource()); + const consumer = bc.push(); + + // Read the first chunk + const iter = consumer[Symbol.asyncIterator](); + const first = await iter.next(); + assert.strictEqual(first.done, false); + + // Cancel while the source is blocked waiting to yield the next chunk + bc.cancel(); + + // The iteration should complete (not hang) + const next = await iter.next(); + assert.strictEqual(next.done, true); + + // Source should NOT have finished (we cancelled before chunk2) + assert.strictEqual(sourceFinished, false); +} + +// ============================================================================= +// Source error propagation via Broadcast.from() +// ============================================================================= + +async function testBroadcastFromSourceError() { + async function* failingSource() { + yield [new TextEncoder().encode('a')]; + throw new Error('broadcast source boom'); + } + const { broadcast: bc } = Broadcast.from(failingSource()); + const consumer = bc.push(); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of consumer) { /* consume */ } + }, { message: 'broadcast source boom' }); +} + +// ============================================================================= +// Protocol validation +// ============================================================================= + +function testBroadcastProtocolReturnsNull() { + const obj = { + [Symbol.for('Stream.broadcastProtocol')]() { return null; }, + }; + assert.throws( + () => Broadcast.from(obj), + { code: 'ERR_INVALID_RETURN_VALUE' }, + ); +} + +function testBroadcastProtocolReturnsString() { + const obj = { + [Symbol.for('Stream.broadcastProtocol')]() { return 'bad'; }, + }; + assert.throws( + () => Broadcast.from(obj), + { code: 'ERR_INVALID_RETURN_VALUE' }, + ); +} + +function testBroadcastProtocolReturnsUndefined() { + const obj = { + [Symbol.for('Stream.broadcastProtocol')]() { }, + }; + assert.throws( + () => Broadcast.from(obj), + { code: 'ERR_INVALID_RETURN_VALUE' }, + ); +} + +Promise.all([ + testBroadcastFromAsyncIterable(), + testBroadcastFromNonArrayChunks(), + testBroadcastFromStringChunks(), + testBroadcastFromMultipleConsumers(), + testAbortSignal(), + testAlreadyAbortedSignal(), + testBroadcastFromCancelWhileBlocked(), + testBroadcastFromSourceError(), + testBroadcastProtocolReturnsNull(), + testBroadcastProtocolReturnsString(), + testBroadcastProtocolReturnsUndefined(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-consumers-bytes.js b/test/parallel/test-stream-iter-consumers-bytes.js new file mode 100644 index 00000000000000..ebb5dae0ac636e --- /dev/null +++ b/test/parallel/test-stream-iter-consumers-bytes.js @@ -0,0 +1,220 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + fromSync, + bytes, + bytesSync, + text, + textSync, + arrayBuffer, + arrayBufferSync, + array, + arraySync, +} = require('stream/iter'); + +// ============================================================================= +// bytesSync / bytes +// ============================================================================= + +async function testBytesSyncBasic() { + const data = bytesSync(fromSync('hello')); + assert.deepStrictEqual(data, new TextEncoder().encode('hello')); +} + +async function testBytesSyncLimit() { + assert.throws( + () => bytesSync(fromSync('hello world'), { limit: 3 }), + { name: 'RangeError' }, + ); +} + +async function testBytesAsync() { + const data = await bytes(from('hello-async')); + assert.deepStrictEqual(data, new TextEncoder().encode('hello-async')); +} + +async function testBytesAsyncLimit() { + await assert.rejects( + () => bytes(from('hello world'), { limit: 3 }), + { name: 'RangeError' }, + ); +} + +async function testBytesAsyncAbort() { + const ac = new AbortController(); + ac.abort(); + await assert.rejects( + () => bytes(from('data'), { signal: ac.signal }), + { name: 'AbortError' }, + ); +} + +async function testBytesEmpty() { + const data = await bytes(from([])); + assert.ok(data instanceof Uint8Array); + assert.strictEqual(data.byteLength, 0); +} + +// ============================================================================= +// arrayBufferSync / arrayBuffer +// ============================================================================= + +async function testArrayBufferSyncBasic() { + const ab = arrayBufferSync(fromSync(new Uint8Array([1, 2, 3]))); + assert.ok(ab instanceof ArrayBuffer); + assert.strictEqual(ab.byteLength, 3); + const view = new Uint8Array(ab); + assert.deepStrictEqual(view, new Uint8Array([1, 2, 3])); +} + +async function testArrayBufferAsync() { + const ab = await arrayBuffer(from(new Uint8Array([10, 20, 30]))); + assert.ok(ab instanceof ArrayBuffer); + assert.strictEqual(ab.byteLength, 3); + const view = new Uint8Array(ab); + assert.deepStrictEqual(view, new Uint8Array([10, 20, 30])); +} + +// ============================================================================= +// arraySync / array +// ============================================================================= + +async function testArraySyncBasic() { + function* gen() { + yield new Uint8Array([1]); + yield new Uint8Array([2]); + yield new Uint8Array([3]); + } + const chunks = arraySync(fromSync(gen())); + assert.strictEqual(chunks.length, 3); + assert.deepStrictEqual(chunks[0], new Uint8Array([1])); + assert.deepStrictEqual(chunks[1], new Uint8Array([2])); + assert.deepStrictEqual(chunks[2], new Uint8Array([3])); +} + +async function testArraySyncLimit() { + function* gen() { + yield new Uint8Array(100); + yield new Uint8Array(100); + } + const source = fromSync(gen()); + assert.throws( + () => arraySync(source, { limit: 50 }), + { name: 'RangeError' }, + ); +} + +async function testArrayAsync() { + async function* gen() { + yield [new Uint8Array([1])]; + yield [new Uint8Array([2])]; + } + const chunks = await array(gen()); + assert.strictEqual(chunks.length, 2); + assert.deepStrictEqual(chunks[0], new Uint8Array([1])); + assert.deepStrictEqual(chunks[1], new Uint8Array([2])); +} + +async function testArrayAsyncLimit() { + async function* gen() { + yield [new Uint8Array(100)]; + yield [new Uint8Array(100)]; + } + await assert.rejects( + () => array(gen(), { limit: 50 }), + { name: 'RangeError' }, + ); +} + +// ============================================================================= +// Non-array batch tolerance +// ============================================================================= + +// Regression test: consumers should tolerate sources that yield raw +// Uint8Array or string values instead of Uint8Array[] batches. +async function testConsumersNonArrayBatch() { + const encoder = new TextEncoder(); + + // Source yields raw Uint8Array, not wrapped in an array + async function* rawSource() { + yield encoder.encode('hello'); + yield encoder.encode(' world'); + } + const result = await text(rawSource()); + assert.strictEqual(result, 'hello world'); + + // bytes() with raw chunks + async function* rawSource2() { + yield encoder.encode('ab'); + } + const data = await bytes(rawSource2()); + assert.strictEqual(data.length, 2); + assert.strictEqual(data[0], 97); // 'a' + assert.strictEqual(data[1], 98); // 'b' + + // array() with raw chunks + async function* rawSource3() { + yield encoder.encode('x'); + yield encoder.encode('y'); + } + const arr = await array(rawSource3()); + assert.strictEqual(arr.length, 2); +} + +async function testConsumersNonArrayBatchSync() { + const encoder = new TextEncoder(); + + function* rawSyncSource() { + yield encoder.encode('sync'); + yield encoder.encode('data'); + } + const result = textSync(rawSyncSource()); + assert.strictEqual(result, 'syncdata'); + + const data = bytesSync(rawSyncSource()); + assert.strictEqual(data.length, 8); + + const arr = arraySync(rawSyncSource()); + assert.strictEqual(arr.length, 2); +} + +// Consumers accept string sources directly (normalized via from/fromSync) +async function testBytesStringSource() { + const result = await bytes('hello-bytes'); + assert.strictEqual(new TextDecoder().decode(result), 'hello-bytes'); +} + +function testBytesSyncStringSource() { + const result = bytesSync('hello-sync'); + assert.strictEqual(new TextDecoder().decode(result), 'hello-sync'); +} + +async function testTextStringSource() { + const { text } = require('stream/iter'); + const result = await text('direct-string'); + assert.strictEqual(result, 'direct-string'); +} + +Promise.all([ + testBytesSyncBasic(), + testBytesSyncLimit(), + testBytesAsync(), + testBytesAsyncLimit(), + testBytesAsyncAbort(), + testBytesEmpty(), + testArrayBufferSyncBasic(), + testArrayBufferAsync(), + testArraySyncBasic(), + testArraySyncLimit(), + testArrayAsync(), + testArrayAsyncLimit(), + testConsumersNonArrayBatch(), + testConsumersNonArrayBatchSync(), + testBytesStringSource(), + testBytesSyncStringSource(), + testTextStringSource(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-consumers-merge.js b/test/parallel/test-stream-iter-consumers-merge.js new file mode 100644 index 00000000000000..ad047c0ffd7ed4 --- /dev/null +++ b/test/parallel/test-stream-iter-consumers-merge.js @@ -0,0 +1,175 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + fromSync, + push, + merge, + text, +} = require('stream/iter'); + +// ============================================================================= +// merge +// ============================================================================= + +async function testMergeTwoSources() { + const { writer: w1, readable: r1 } = push(); + const { writer: w2, readable: r2 } = push(); + + w1.write('from-a'); + w1.end(); + w2.write('from-b'); + w2.end(); + + const merged = merge(r1, r2); + const chunks = []; + for await (const batch of merged) { + for (const chunk of batch) { + chunks.push(new TextDecoder().decode(chunk)); + } + } + + // Both sources should be present (order is temporal, not guaranteed) + assert.strictEqual(chunks.length, 2); + assert.ok(chunks.includes('from-a')); + assert.ok(chunks.includes('from-b')); +} + +async function testMergeSingleSource() { + const data = await text(merge(from('only-one'))); + assert.strictEqual(data, 'only-one'); +} + +async function testMergeEmpty() { + const merged = merge(); + const batches = []; + for await (const batch of merged) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +async function testMergeWithAbortSignal() { + const ac = new AbortController(); + ac.abort(); + + const merged = merge(from('data'), { signal: ac.signal }); + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of merged) { + assert.fail('Should not reach here'); + } + }, + { name: 'AbortError' }, + ); +} + +// Regression test: merge() with sync iterable sources +async function testMergeSyncSources() { + const s1 = fromSync('abc'); + const s2 = fromSync('def'); + const result = await text(merge(s1, s2)); + // Both sources should be fully consumed; order may vary + assert.strictEqual(result.length, 6); + for (const ch of 'abcdef') { + assert.ok(result.includes(ch), `missing '${ch}' in '${result}'`); + } +} + +// ============================================================================= +// Merge error propagation +// ============================================================================= + +async function testMergeSourceError() { + async function* goodSource() { + const enc = new TextEncoder(); + yield [enc.encode('a')]; + // Slow so the bad source errors first + await new Promise((r) => setTimeout(r, 50)); + yield [enc.encode('b')]; + } + + async function* badSource() { + yield [new TextEncoder().encode('x')]; + throw new Error('merge source boom'); + } + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of merge(goodSource(), badSource())) { /* consume */ } + }, { message: 'merge source boom' }); +} + +async function testMergeConsumerBreak() { + let source1Return = false; + let source2Return = false; + async function* source1() { + try { + while (true) yield [new TextEncoder().encode('a')]; + } finally { + source1Return = true; + } + } + + async function* source2() { + try { + while (true) yield [new TextEncoder().encode('b')]; + } finally { + source2Return = true; + } + } + // eslint-disable-next-line no-unused-vars + for await (const _ of merge(source1(), source2())) { + break; // Break after first batch + } + // Give async cleanup a tick to complete + await new Promise(setImmediate); + // Both sources should be cleaned up + assert.strictEqual(source1Return && source2Return, true); +} + +async function testMergeSignalMidIteration() { + const ac = new AbortController(); + async function* slowSource() { + const enc = new TextEncoder(); + yield [enc.encode('a')]; + await new Promise((r) => setTimeout(r, 100)); + yield [enc.encode('b')]; + } + const merged = merge(slowSource(), { signal: ac.signal }); + const iter = merged[Symbol.asyncIterator](); + await iter.next(); // First batch + ac.abort(); + await assert.rejects(() => iter.next(), { name: 'AbortError' }); +} + +// merge() accepts string sources (normalized via from()) +async function testMergeStringSources() { + const batches = []; + for await (const batch of merge('hello', 'world')) { + batches.push(batch); + } + // Each string becomes a single-batch source + assert.strictEqual(batches.length >= 2, true); + const combined = new TextDecoder().decode( + Buffer.concat(batches.flat())); + // Both strings should appear (order may vary) + assert.ok(combined.includes('hello')); + assert.ok(combined.includes('world')); +} + +Promise.all([ + testMergeTwoSources(), + testMergeSingleSource(), + testMergeEmpty(), + testMergeWithAbortSignal(), + testMergeSyncSources(), + testMergeSourceError(), + testMergeConsumerBreak(), + testMergeSignalMidIteration(), + testMergeStringSources(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-consumers-tap.js b/test/parallel/test-stream-iter-consumers-tap.js new file mode 100644 index 00000000000000..b93f93eb242b2c --- /dev/null +++ b/test/parallel/test-stream-iter-consumers-tap.js @@ -0,0 +1,130 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + fromSync, + pull, + pullSync, + push, + tap, + tapSync, + text, + textSync, +} = require('stream/iter'); + +// ============================================================================= +// tap / tapSync +// ============================================================================= + +async function testTapSync() { + const observed = []; + const observer = tapSync((chunks) => { + if (chunks !== null) { + observed.push(chunks.length); + } + }); + + // tapSync returns a function transform + assert.strictEqual(typeof observer, 'function'); + + // Test that it passes data through unchanged + const input = [new Uint8Array([1]), new Uint8Array([2])]; + const result = observer(input); + assert.deepStrictEqual(result, input); + assert.deepStrictEqual(observed, [2]); + + // null (flush) passes through + const flushResult = observer(null); + assert.strictEqual(flushResult, null); +} + +async function testTapAsync() { + const observed = []; + const observer = tap(async (chunks) => { + if (chunks !== null) { + observed.push(chunks.length); + } + }); + + assert.strictEqual(typeof observer, 'function'); + + const input = [new Uint8Array([1])]; + const result = await observer(input); + assert.deepStrictEqual(result, input); + assert.deepStrictEqual(observed, [1]); +} + +async function testTapInPipeline() { + const { writer, readable } = push(); + const seen = []; + + const observer = tap(async (chunks) => { + if (chunks !== null) { + for (const chunk of chunks) { + seen.push(new TextDecoder().decode(chunk)); + } + } + }); + + writer.write('hello'); + writer.end(); + + // Use pull with tap as a transform + const result = pull(readable, observer); + const data = await text(result); + + assert.strictEqual(data, 'hello'); + assert.strictEqual(seen.length, 1); + assert.strictEqual(seen[0], 'hello'); +} + +// Tap callback error propagates through async pipeline +async function testTapAsyncErrorPropagation() { + const badTap = tap(() => { throw new Error('tap error'); }); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(from('hello'), badTap)) { /* consume */ } + }, { message: 'tap error' }); +} + +// TapSync callback error propagates through sync pipeline +function testTapSyncErrorPropagation() { + const badTap = tapSync(() => { throw new Error('tapSync error'); }); + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of pullSync(fromSync('hello'), badTap)) { /* consume */ } + }, { message: 'tapSync error' }); +} + +// TapSync in a pullSync pipeline passes through data and flush +function testTapSyncInPipeline() { + const seen = []; + let sawFlush = false; + const observer = tapSync((chunks) => { + if (chunks === null) { + sawFlush = true; + } else { + for (const chunk of chunks) { + seen.push(new TextDecoder().decode(chunk)); + } + } + }); + + const data = textSync(pullSync(fromSync('hello'), observer)); + assert.strictEqual(data, 'hello'); + assert.strictEqual(seen.length, 1); + assert.strictEqual(seen[0], 'hello'); + assert.strictEqual(sawFlush, true); +} + +Promise.all([ + testTapSync(), + testTapAsync(), + testTapInPipeline(), + testTapAsyncErrorPropagation(), + testTapSyncErrorPropagation(), + testTapSyncInPipeline(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-consumers-text.js b/test/parallel/test-stream-iter-consumers-text.js new file mode 100644 index 00000000000000..8bfa7c3320981c --- /dev/null +++ b/test/parallel/test-stream-iter-consumers-text.js @@ -0,0 +1,164 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + fromSync, + text, + textSync, +} = require('stream/iter'); + +// ============================================================================= +// textSync / text +// ============================================================================= + +async function testTextSyncBasic() { + const data = textSync(fromSync('hello text')); + assert.strictEqual(data, 'hello text'); +} + +async function testTextAsync() { + const data = await text(from('hello async text')); + assert.strictEqual(data, 'hello async text'); +} + +async function testTextEncoding() { + // Default encoding is utf-8 + const data = await text(from('café')); + assert.strictEqual(data, 'café'); +} + +// ============================================================================= +// Text encoding tests +// ============================================================================= + +async function testTextNonUtf8Encoding() { + // Latin-1 encoding + const latin1Bytes = new Uint8Array([0xE9, 0xE8, 0xEA]); // é, è, ê in latin1 + const result = await text(from(latin1Bytes), { encoding: 'iso-8859-1' }); + assert.strictEqual(result, 'éèê'); +} + +async function testTextSyncNonUtf8Encoding() { + const latin1Bytes = new Uint8Array([0xE9, 0xE8, 0xEA]); + const result = textSync(fromSync(latin1Bytes), { encoding: 'iso-8859-1' }); + assert.strictEqual(result, 'éèê'); +} + +async function testTextInvalidUtf8() { + // Invalid UTF-8 sequence with fatal: true should throw + const invalid = new Uint8Array([0xFF, 0xFE]); + await assert.rejects( + () => text(from(invalid)), + { name: 'TypeError' }, // TextDecoder fatal throws TypeError + ); +} + +async function testTextWithLimit() { + // Limit caps total bytes; exceeding throws ERR_OUT_OF_RANGE + await assert.rejects( + () => text(from('hello world'), { limit: 5 }), + { code: 'ERR_OUT_OF_RANGE' }, + ); + // Within limit should succeed + const result = await text(from('hello'), { limit: 10 }); + assert.strictEqual(result, 'hello'); + + // Exact boundary: 'hello' is 5 UTF-8 bytes, limit: 5 should succeed + // (source uses > not >=) + const exact = await text(from('hello'), { limit: 5 }); + assert.strictEqual(exact, 'hello'); +} + +async function testTextSyncWithLimit() { + // Sync version of limit testing + assert.throws( + () => textSync(fromSync('hello world'), { limit: 5 }), + { code: 'ERR_OUT_OF_RANGE' }, + ); + const result = textSync(fromSync('hello'), { limit: 10 }); + assert.strictEqual(result, 'hello'); + + // Exact boundary + const exact = textSync(fromSync('hello'), { limit: 5 }); + assert.strictEqual(exact, 'hello'); +} + +async function testTextEmpty() { + const result = await text(from('')); + assert.strictEqual(result, ''); + + const syncResult = textSync(fromSync('')); + assert.strictEqual(syncResult, ''); +} + +// text() with abort signal +async function testTextWithSignal() { + const ac = new AbortController(); + ac.abort(); + await assert.rejects( + () => text(from('data'), { signal: ac.signal }), + { name: 'AbortError' }, + ); +} + +// Multi-chunk source with a multi-byte UTF-8 character split across chunks +async function testTextMultiChunkSplitCodepoint() { + // '€' is U+20AC, encoded as 3 UTF-8 bytes: 0xE2, 0x82, 0xAC + // Split these bytes across two chunks to test proper re-assembly + async function* splitSource() { + yield [new Uint8Array([0xE2, 0x82])]; // First 2 bytes of '€' + yield [new Uint8Array([0xAC])]; // Last byte of '€' + } + const result = await text(splitSource()); + assert.strictEqual(result, '€'); +} + +// BOM should be stripped (ignoreBOM defaults to false per spec) +async function testTextBOMStripped() { + // UTF-8 BOM: 0xEF, 0xBB, 0xBF followed by 'hi' + const withBOM = new Uint8Array([0xEF, 0xBB, 0xBF, 0x68, 0x69]); + const result = await text(from(withBOM)); + assert.strictEqual(result, 'hi'); +} + +async function testTextSyncBOMStripped() { + const withBOM = new Uint8Array([0xEF, 0xBB, 0xBF, 0x68, 0x69]); + const result = textSync(fromSync(withBOM)); + assert.strictEqual(result, 'hi'); +} + +// Unsupported encoding throws RangeError +async function testTextUnsupportedEncodingThrowsRangeError() { + await assert.rejects( + () => text(from('hello'), { encoding: 'not-a-real-encoding' }), + { name: 'RangeError' }, + ); +} + +function testTextSyncUnsupportedEncodingThrowsRangeError() { + assert.throws( + () => textSync(fromSync('hello'), { encoding: 'not-a-real-encoding' }), + { name: 'RangeError' }, + ); +} + +Promise.all([ + testTextSyncBasic(), + testTextAsync(), + testTextEncoding(), + testTextNonUtf8Encoding(), + testTextSyncNonUtf8Encoding(), + testTextInvalidUtf8(), + testTextWithLimit(), + testTextSyncWithLimit(), + testTextEmpty(), + testTextWithSignal(), + testTextMultiChunkSplitCodepoint(), + testTextBOMStripped(), + testTextSyncBOMStripped(), + testTextUnsupportedEncodingThrowsRangeError(), + testTextSyncUnsupportedEncodingThrowsRangeError(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-cross-realm.js b/test/parallel/test-stream-iter-cross-realm.js new file mode 100644 index 00000000000000..6a6e92179253ec --- /dev/null +++ b/test/parallel/test-stream-iter-cross-realm.js @@ -0,0 +1,132 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const { from, fromSync, pull, text, bytesSync } = require('stream/iter'); + +// Cross-realm objects are created in a different VM context. +// They have different prototypes, so `instanceof` checks fail. +// These tests verify that stream/iter correctly handles cross-realm types. + +// Helper: compare Uint8Array content regardless of realm. +function assertBytes(actual, expected) { + assert.strictEqual(actual.length, expected.length, + `length mismatch: ${actual.length} !== ${expected.length}`); + for (let i = 0; i < expected.length; i++) { + assert.strictEqual(actual[i], expected[i], `byte mismatch at index ${i}`); + } +} + +// ============================================================================= +// from() / fromSync() with cross-realm Uint8Array +// ============================================================================= + +async function testFromSyncCrossRealmUint8Array() { + const crossRealm = vm.runInNewContext('new Uint8Array([1, 2, 3])'); + const data = bytesSync(fromSync(crossRealm)); + assertBytes(data, new Uint8Array([1, 2, 3])); +} + +async function testFromCrossRealmUint8Array() { + const crossRealm = vm.runInNewContext('new Uint8Array([4, 5, 6])'); + const result = await text(from(crossRealm)); + assert.strictEqual(result, '\x04\x05\x06'); +} + +// ============================================================================= +// from() / fromSync() with cross-realm ArrayBuffer +// ============================================================================= + +async function testFromSyncCrossRealmArrayBuffer() { + const crossRealm = vm.runInNewContext( + 'new Uint8Array([7, 8, 9]).buffer', + ); + const data = bytesSync(fromSync(crossRealm)); + assertBytes(data, new Uint8Array([7, 8, 9])); +} + +async function testFromCrossRealmArrayBuffer() { + const crossRealm = vm.runInNewContext( + 'new Uint8Array([10, 11, 12]).buffer', + ); + const result = await text(from(crossRealm)); + assert.strictEqual(result, '\x0a\x0b\x0c'); +} + +// ============================================================================= +// from() / fromSync() with cross-realm Uint8Array[] +// ============================================================================= + +async function testFromSyncCrossRealmUint8ArrayArray() { + const crossRealm = vm.runInNewContext( + '[new Uint8Array([1, 2]), new Uint8Array([3, 4])]', + ); + const data = bytesSync(fromSync(crossRealm)); + assertBytes(data, new Uint8Array([1, 2, 3, 4])); +} + +async function testFromCrossRealmUint8ArrayArray() { + const crossRealm = vm.runInNewContext( + '[new Uint8Array([5, 6]), new Uint8Array([7, 8])]', + ); + const result = await text(from(crossRealm)); + assert.strictEqual(result, '\x05\x06\x07\x08'); +} + +// ============================================================================= +// pull() with cross-realm Uint8Array from transforms +// ============================================================================= + +async function testPullCrossRealmTransformOutput() { + // Transform that returns cross-realm Uint8Array[] batches + const crossRealmTransform = (chunks) => { + if (chunks === null) return null; + // Re-encode each chunk as cross-realm Uint8Array + return vm.runInNewContext( + `[new Uint8Array([${[...chunks[0]]}])]`, + ); + }; + const output = await text(pull(from('hello'), crossRealmTransform)); + assert.strictEqual(output, 'hello'); +} + +// ============================================================================= +// from() with cross-realm Promise +// ============================================================================= + +async function testFromCrossRealmPromise() { + const crossRealmPromise = vm.runInNewContext( + 'Promise.resolve("promised-data")', + ); + async function* gen() { + yield crossRealmPromise; + } + const result = await text(from(gen())); + assert.strictEqual(result, 'promised-data'); +} + +// ============================================================================= +// from() with cross-realm typed arrays (non-Uint8Array views) +// ============================================================================= + +async function testFromSyncCrossRealmInt32Array() { + const crossRealm = vm.runInNewContext('new Int32Array([1])'); + const data = bytesSync(fromSync(crossRealm)); + // Int32Array([1]) = 4 bytes, endianness varies by platform + assert.strictEqual(data.length, 4); + assert.strictEqual(new Int32Array(data.buffer, data.byteOffset, 1)[0], 1); +} + +Promise.all([ + testFromSyncCrossRealmUint8Array(), + testFromCrossRealmUint8Array(), + testFromSyncCrossRealmArrayBuffer(), + testFromCrossRealmArrayBuffer(), + testFromSyncCrossRealmUint8ArrayArray(), + testFromCrossRealmUint8ArrayArray(), + testPullCrossRealmTransformOutput(), + testFromCrossRealmPromise(), + testFromSyncCrossRealmInt32Array(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-disabled.js b/test/parallel/test-stream-iter-disabled.js new file mode 100644 index 00000000000000..8c8538ffae9268 --- /dev/null +++ b/test/parallel/test-stream-iter-disabled.js @@ -0,0 +1,34 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { spawnPromisified } = common; + +async function testRequireNodeStreamIterWithoutFlag() { + const { stderr, code } = await spawnPromisified(process.execPath, [ + '-e', 'require("node:stream/iter")', + ]); + assert.match(stderr, /No such built-in module: node:stream\/iter/); + assert.notStrictEqual(code, 0); +} + +async function testRequireStreamIterWithoutFlag() { + const { stderr, code } = await spawnPromisified(process.execPath, [ + '-e', 'require("stream/iter")', + ]); + assert.match(stderr, /Cannot find module/); + assert.notStrictEqual(code, 0); +} + +async function testRequireWithFlag() { + const { code } = await spawnPromisified(process.execPath, [ + '--experimental-stream-iter', + '-e', 'require("node:stream/iter")', + ]); + assert.strictEqual(code, 0); +} + +Promise.all([ + testRequireNodeStreamIterWithoutFlag(), + testRequireStreamIterWithoutFlag(), + testRequireWithFlag(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-duplex.js b/test/parallel/test-stream-iter-duplex.js new file mode 100644 index 00000000000000..83c85d7be00816 --- /dev/null +++ b/test/parallel/test-stream-iter-duplex.js @@ -0,0 +1,188 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { duplex, text, bytes } = require('stream/iter'); + +// ============================================================================= +// Basic duplex +// ============================================================================= + +async function testBasicDuplex() { + const [channelA, channelB] = duplex(); + + // A writes, B reads + await channelA.writer.write('hello from A'); + await channelA.close(); + + const dataAtB = await text(channelB.readable); + assert.strictEqual(dataAtB, 'hello from A'); +} + +async function testBidirectional() { + const [channelA, channelB] = duplex(); + + // A writes to B, B writes to A concurrently + const writeA = (async () => { + await channelA.writer.write('A to B'); + await channelA.close(); + })(); + + const writeB = (async () => { + await channelB.writer.write('B to A'); + await channelB.close(); + })(); + + const readAtB = text(channelB.readable); + const readAtA = text(channelA.readable); + + await Promise.all([writeA, writeB]); + + const [dataAtA, dataAtB] = await Promise.all([readAtA, readAtB]); + + assert.strictEqual(dataAtB, 'A to B'); + assert.strictEqual(dataAtA, 'B to A'); +} + +async function testMultipleWrites() { + const [channelA, channelB] = duplex({ highWaterMark: 10 }); + + await channelA.writer.write('one'); + await channelA.writer.write('two'); + await channelA.writer.write('three'); + await channelA.close(); + + const data = await text(channelB.readable); + assert.strictEqual(data, 'onetwothree'); +} + +async function testChannelClose() { + const [channelA, channelB] = duplex(); + + await channelA.close(); + + // Should be able to close twice without error + await channelA.close(); + + // B's readable should end (A -> B direction is closed) + const batches = []; + for await (const batch of channelB.readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +async function testWithOptions() { + const [channelA, channelB] = duplex({ + highWaterMark: 2, + backpressure: 'strict', + }); + + await channelA.writer.write('msg'); + await channelA.close(); + + const data = await text(channelB.readable); + assert.strictEqual(data, 'msg'); +} + +async function testPerChannelOptions() { + const [channelA, channelB] = duplex({ + a: { highWaterMark: 1 }, + b: { highWaterMark: 4 }, + }); + + // Channel A -> B direction uses A's options + // Channel B -> A direction uses B's options + await channelA.writer.write('from-a'); + await channelA.close(); + + await channelB.writer.write('from-b'); + await channelB.close(); + + const [dataAtA, dataAtB] = await Promise.all([ + text(channelA.readable), + text(channelB.readable), + ]); + + assert.strictEqual(dataAtB, 'from-a'); + assert.strictEqual(dataAtA, 'from-b'); +} + +async function testAbortSignal() { + const ac = new AbortController(); + const [channelA] = duplex({ signal: ac.signal }); + + ac.abort(); + + // Both directions should error + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of channelA.readable) { + assert.fail('Should not reach here'); + } + }, + (err) => err.name === 'AbortError', + ); +} + +async function testEmptyDuplex() { + const [channelA, channelB] = duplex(); + + // Close without writing + await channelA.close(); + await channelB.close(); + + const dataAtA = await bytes(channelA.readable); + const dataAtB = await bytes(channelB.readable); + + assert.strictEqual(dataAtA.byteLength, 0); + assert.strictEqual(dataAtB.byteLength, 0); +} + +// Channel fail propagation +async function testChannelFail() { + const [a, b] = duplex(); + a.writer.fail(new Error('channel failed')); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of b.readable) { /* consume */ } + }, { message: 'channel failed' }); + await b.close(); +} + +// Abort signal affects both channels +async function testAbortSignalBothChannels() { + const ac = new AbortController(); + const [channelA, channelB] = duplex({ signal: ac.signal }); + + ac.abort(); + + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of channelA.readable) { + assert.fail('Should not reach here'); + } + }, (err) => err.name === 'AbortError'); + + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of channelB.readable) { + assert.fail('Should not reach here'); + } + }, (err) => err.name === 'AbortError'); +} + +Promise.all([ + testBasicDuplex(), + testBidirectional(), + testMultipleWrites(), + testChannelClose(), + testWithOptions(), + testPerChannelOptions(), + testAbortSignal(), + testEmptyDuplex(), + testChannelFail(), + testAbortSignalBothChannels(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-from-async.js b/test/parallel/test-stream-iter-from-async.js new file mode 100644 index 00000000000000..8080b7a5cd86ca --- /dev/null +++ b/test/parallel/test-stream-iter-from-async.js @@ -0,0 +1,251 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { from, text, Stream } = require('stream/iter'); + +async function testFromString() { + const readable = from('hello-async'); + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], + new TextEncoder().encode('hello-async')); +} + +async function testFromAsyncGenerator() { + async function* gen() { + yield new Uint8Array([10, 20]); + yield new Uint8Array([30, 40]); + } + const readable = from(gen()); + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 2); + assert.deepStrictEqual(batches[0][0], new Uint8Array([10, 20])); + assert.deepStrictEqual(batches[1][0], new Uint8Array([30, 40])); +} + +async function testFromSyncIterableAsAsync() { + // Sync iterable passed to from() should work + function* gen() { + yield new Uint8Array([1]); + yield new Uint8Array([2]); + } + const readable = from(gen()); + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + // Sync iterables get batched together into a single batch + assert.strictEqual(batches.length, 1); + assert.strictEqual(batches[0].length, 2); + assert.deepStrictEqual(batches[0][0], new Uint8Array([1])); + assert.deepStrictEqual(batches[0][1], new Uint8Array([2])); +} + +async function testFromToAsyncStreamableProtocol() { + const sym = Symbol.for('Stream.toAsyncStreamable'); + const obj = { + [sym]() { + return 'async-protocol-data'; + }, + }; + async function* gen() { + yield obj; + } + const readable = from(gen()); + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], + new TextEncoder().encode('async-protocol-data')); +} + +function testFromRejectsNonStreamable() { + assert.throws( + () => from(12345), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); + assert.throws( + () => from(null), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +async function testFromEmptyArray() { + const readable = from([]); + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +// Also accessible via Stream namespace +async function testStreamNamespace() { + const readable = Stream.from('via-namespace'); + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new TextEncoder().encode('via-namespace')); +} + +async function testCustomToStringInStreamRejects() { + // Objects with custom toString but no toStreamable protocol are rejected. + // Use toStreamable protocol instead. + const obj = { toString() { return 'from toString'; } }; + async function* source() { + yield obj; + } + await assert.rejects( + () => text(from(source())), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +async function testCustomToPrimitiveInStreamRejects() { + // Objects with Symbol.toPrimitive but no toStreamable protocol are rejected. + const obj = { + [Symbol.toPrimitive](hint) { + if (hint === 'string') return 'from toPrimitive'; + return 42; + }, + }; + async function* source() { + yield obj; + } + await assert.rejects( + () => text(from(source())), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +async function testToStreamableProtocolInStream() { + // Objects should use toStreamable protocol instead of toString + const obj = { + [Symbol.for('Stream.toStreamable')]() { return 'from protocol'; }, + }; + async function* source() { + yield obj; + } + const result = await text(from(source())); + assert.strictEqual(result, 'from protocol'); +} + +// Both toAsyncStreamable and toStreamable: async takes precedence +async function testFromAsyncStreamablePrecedence() { + const obj = { + [Symbol.for('Stream.toStreamable')]() { return 'sync version'; }, + [Symbol.for('Stream.toAsyncStreamable')]() { return 'async version'; }, + }; + async function* gen() { yield obj; } + const result = await text(from(gen())); + assert.strictEqual(result, 'async version'); +} + +// Top-level toAsyncStreamable protocol on input to from() +async function testFromTopLevelToAsyncStreamable() { + const obj = { + [Symbol.for('Stream.toAsyncStreamable')]() { + return 'top-level-async'; + }, + }; + const result = await text(from(obj)); + assert.strictEqual(result, 'top-level-async'); +} + +// Top-level toAsyncStreamable returning a Promise +async function testFromTopLevelToAsyncStreamablePromise() { + const obj = { + [Symbol.for('Stream.toAsyncStreamable')]() { + return Promise.resolve('async-promise'); + }, + }; + const result = await text(from(obj)); + assert.strictEqual(result, 'async-promise'); +} + +// Top-level toStreamable protocol on input to from() +async function testFromTopLevelToStreamable() { + const obj = { + [Symbol.for('Stream.toStreamable')]() { + return 'top-level-sync'; + }, + }; + const result = await text(from(obj)); + assert.strictEqual(result, 'top-level-sync'); +} + +// Top-level: toAsyncStreamable takes precedence over toStreamable +async function testFromTopLevelAsyncPrecedence() { + const obj = { + [Symbol.for('Stream.toStreamable')]() { return 'sync'; }, + [Symbol.for('Stream.toAsyncStreamable')]() { return 'async'; }, + }; + const result = await text(from(obj)); + assert.strictEqual(result, 'async'); +} + +// Top-level: toAsyncStreamable takes precedence over Symbol.asyncIterator +async function testFromTopLevelProtocolOverIterator() { + const obj = { + [Symbol.for('Stream.toAsyncStreamable')]() { return 'from-protocol'; }, + async *[Symbol.asyncIterator]() { yield [new TextEncoder().encode('from-iterator')]; }, + }; + const result = await text(from(obj)); + assert.strictEqual(result, 'from-protocol'); +} + +// DataView input should be converted to Uint8Array (zero-copy) +async function testFromDataView() { + const buf = new ArrayBuffer(5); + const view = new DataView(buf); + // Write "hello" into the DataView + view.setUint8(0, 0x68); // h + view.setUint8(1, 0x65); // e + view.setUint8(2, 0x6c); // l + view.setUint8(3, 0x6c); // l + view.setUint8(4, 0x6f); // o + const result = await text(from(view)); + assert.strictEqual(result, 'hello'); +} + +function testFromNullThrows() { + assert.throws(() => from(null), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +function testFromUndefinedThrows() { + assert.throws(() => from(undefined), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +Promise.all([ + testFromString(), + testFromAsyncGenerator(), + testFromSyncIterableAsAsync(), + testFromToAsyncStreamableProtocol(), + testFromRejectsNonStreamable(), + testFromEmptyArray(), + testStreamNamespace(), + testCustomToStringInStreamRejects(), + testCustomToPrimitiveInStreamRejects(), + testToStreamableProtocolInStream(), + testFromAsyncStreamablePrecedence(), + testFromNullThrows(), + testFromUndefinedThrows(), + testFromTopLevelToAsyncStreamable(), + testFromTopLevelToAsyncStreamablePromise(), + testFromTopLevelToStreamable(), + testFromTopLevelAsyncPrecedence(), + testFromTopLevelProtocolOverIterator(), + testFromDataView(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-from-coverage.js b/test/parallel/test-stream-iter-from-coverage.js new file mode 100644 index 00000000000000..c4f622a56bd7fa --- /dev/null +++ b/test/parallel/test-stream-iter-from-coverage.js @@ -0,0 +1,144 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Coverage tests for from.js: sub-batching >128, DataView in generator, +// non-Uint8Array TypedArray normalization. + +const common = require('../common'); +const assert = require('assert'); +const { + from, + fromSync, + bytes, + bytesSync, +} = require('stream/iter'); + +// fromSync: Uint8Array[] with > 128 elements triggers sub-batching +async function testFromSyncSubBatching() { + const bigBatch = Array.from({ length: 200 }, + (_, i) => new Uint8Array([i & 0xFF])); + const batches = []; + for (const batch of fromSync(bigBatch)) { + batches.push(batch); + } + // Should be split into sub-batches: 128 + 72 + assert.strictEqual(batches.length, 2); + assert.strictEqual(batches[0].length, 128); + assert.strictEqual(batches[1].length, 72); + // Verify no data loss + let totalChunks = 0; + for (const batch of batches) totalChunks += batch.length; + assert.strictEqual(totalChunks, 200); +} + +// from: Uint8Array[] with > 128 elements triggers sub-batching (async) +async function testFromAsyncSubBatching() { + const bigBatch = Array.from({ length: 200 }, + (_, i) => new Uint8Array([i & 0xFF])); + const batches = []; + for await (const batch of from(bigBatch)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 2); + assert.strictEqual(batches[0].length, 128); + assert.strictEqual(batches[1].length, 72); +} + +// Exact boundary: 128 elements → single batch (no split) +async function testFromSubBatchingBoundary() { + const exactBatch = Array.from({ length: 128 }, + (_, i) => new Uint8Array([i])); + const batches = []; + for (const batch of fromSync(exactBatch)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.strictEqual(batches[0].length, 128); +} + +// 129 elements → 2 batches (128 + 1) +async function testFromSubBatchingBoundaryPlus1() { + const batch129 = Array.from({ length: 129 }, + (_, i) => new Uint8Array([i & 0xFF])); + const batches = []; + for await (const batch of from(batch129)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 2); + assert.strictEqual(batches[0].length, 128); + assert.strictEqual(batches[1].length, 1); +} + +// DataView yielded from a sync generator → normalizeSyncValue path +async function testFromSyncDataViewInGenerator() { + function* gen() { + const buf = new ArrayBuffer(3); + const dv = new DataView(buf); + dv.setUint8(0, 65); + dv.setUint8(1, 66); + dv.setUint8(2, 67); + yield dv; + } + const data = bytesSync(fromSync(gen())); + assert.deepStrictEqual(data, new Uint8Array([65, 66, 67])); +} + +// DataView yielded from an async generator → normalizeAsyncValue path +async function testFromAsyncDataViewInGenerator() { + async function* gen() { + const buf = new ArrayBuffer(3); + const dv = new DataView(buf); + dv.setUint8(0, 68); + dv.setUint8(1, 69); + dv.setUint8(2, 70); + yield dv; + } + const data = await bytes(from(gen())); + assert.deepStrictEqual(data, new Uint8Array([68, 69, 70])); +} + +// Int16Array yielded from generator → primitiveToUint8Array fallback +async function testFromSyncInt16ArrayInGenerator() { + function* gen() { + yield new Int16Array([0x0102, 0x0304]); + } + const data = bytesSync(fromSync(gen())); + assert.strictEqual(data.byteLength, 4); // 2 int16 = 4 bytes +} + +// Float64Array as top-level input to from() +async function testFromFloat64Array() { + const f64 = new Float64Array([1.0]); + const batches = []; + for await (const batch of from(f64)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.strictEqual(batches[0][0].byteLength, 8); // 1 float64 = 8 bytes +} + +// Sync generator yielding invalid type → ERR_INVALID_ARG_TYPE +async function testFromSyncInvalidYield() { + function* gen() { + yield 42; // Not a valid stream value + } + assert.throws( + () => { + // eslint-disable-next-line no-unused-vars + for (const batch of fromSync(gen())) { /* consume */ } + }, + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +Promise.all([ + testFromSyncSubBatching(), + testFromAsyncSubBatching(), + testFromSubBatchingBoundary(), + testFromSubBatchingBoundaryPlus1(), + testFromSyncDataViewInGenerator(), + testFromAsyncDataViewInGenerator(), + testFromSyncInt16ArrayInGenerator(), + testFromFloat64Array(), + testFromSyncInvalidYield(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-from-sync.js b/test/parallel/test-stream-iter-from-sync.js new file mode 100644 index 00000000000000..a9ce0bd575abdc --- /dev/null +++ b/test/parallel/test-stream-iter-from-sync.js @@ -0,0 +1,236 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { fromSync } = require('stream/iter'); + +function testFromSyncString() { + // String input should be UTF-8 encoded + const readable = fromSync('hello'); + const batches = []; + for (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.strictEqual(batches[0].length, 1); + assert.deepStrictEqual(batches[0][0], + new TextEncoder().encode('hello')); +} + +function testFromSyncUint8Array() { + const input = new Uint8Array([1, 2, 3]); + const readable = fromSync(input); + const batches = []; + for (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.strictEqual(batches[0].length, 1); + assert.deepStrictEqual(batches[0][0], input); +} + +function testFromSyncArrayBuffer() { + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([10, 20, 30, 40]); + const readable = fromSync(ab); + const batches = []; + for (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new Uint8Array([10, 20, 30, 40])); +} + +function testFromSyncUint8ArrayArray() { + // Array of Uint8Array should yield as a single batch + const chunks = [new Uint8Array([1]), new Uint8Array([2])]; + const readable = fromSync(chunks); + const batches = []; + for (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.strictEqual(batches[0].length, 2); + assert.deepStrictEqual(batches[0][0], new Uint8Array([1])); + assert.deepStrictEqual(batches[0][1], new Uint8Array([2])); +} + +function testFromSyncGenerator() { + function* gen() { + yield new Uint8Array([1, 2]); + yield new Uint8Array([3, 4]); + } + const readable = fromSync(gen()); + const batches = []; + for (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 2); + assert.deepStrictEqual(batches[0][0], new Uint8Array([1, 2])); + assert.deepStrictEqual(batches[1][0], new Uint8Array([3, 4])); +} + +function testFromSyncNestedIterables() { + // Nested arrays and strings should be flattened + function* gen() { + yield ['hello', ' ', 'world']; + } + const readable = fromSync(gen()); + const batches = []; + for (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.strictEqual(batches[0].length, 3); + assert.deepStrictEqual(batches[0][0], new TextEncoder().encode('hello')); + assert.deepStrictEqual(batches[0][1], new TextEncoder().encode(' ')); + assert.deepStrictEqual(batches[0][2], new TextEncoder().encode('world')); +} + +function testFromSyncToStreamableProtocol() { + const sym = Symbol.for('Stream.toStreamable'); + const obj = { + [sym]() { + return 'protocol-data'; + }, + }; + function* gen() { + yield obj; + } + const readable = fromSync(gen()); + const batches = []; + for (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], + new TextEncoder().encode('protocol-data')); +} + +function testFromSyncGeneratorError() { + function* gen() { + yield new Uint8Array([1]); + throw new Error('generator boom'); + } + const readable = fromSync(gen()); + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of readable) { /* consume */ } + }, { message: 'generator boom' }); +} + +function testFromSyncRejectsNonStreamable() { + assert.throws( + () => fromSync(12345), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); + assert.throws( + () => fromSync(null), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +function testFromSyncEmptyGenerator() { + function* empty() {} + let count = 0; + // eslint-disable-next-line no-unused-vars + for (const _ of fromSync(empty())) { count++; } + assert.strictEqual(count, 0); +} + +// Top-level toStreamable protocol on input to fromSync() +function testFromSyncTopLevelToStreamable() { + const obj = { + [Symbol.for('Stream.toStreamable')]() { + return 'top-level-sync'; + }, + }; + const batches = []; + for (const batch of fromSync(obj)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], + new TextEncoder().encode('top-level-sync')); +} + +// Top-level: toStreamable takes precedence over Symbol.iterator +function testFromSyncTopLevelProtocolOverIterator() { + const obj = { + [Symbol.for('Stream.toStreamable')]() { return 'from-protocol'; }, + *[Symbol.iterator]() { yield [new TextEncoder().encode('from-iterator')]; }, + }; + const batches = []; + for (const batch of fromSync(obj)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], + new TextEncoder().encode('from-protocol')); +} + +// Top-level: toAsyncStreamable is ignored by fromSync +function testFromSyncIgnoresAsyncStreamable() { + const obj = { + [Symbol.for('Stream.toAsyncStreamable')]() { return 'async'; }, + }; + // Has no toStreamable and no Symbol.iterator, should throw + assert.throws(() => fromSync(obj), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +// Explicit async iterable rejected +function testFromSyncRejectsAsyncIterable() { + async function* gen() { yield [new TextEncoder().encode('a')]; } + assert.throws(() => fromSync(gen()), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +// Promise rejected +function testFromSyncRejectsPromise() { + assert.throws(() => fromSync(Promise.resolve('hello')), + { code: 'ERR_INVALID_ARG_TYPE' }); +} + +// DataView input should be converted to Uint8Array (zero-copy) +function testFromSyncDataView() { + const buf = new ArrayBuffer(3); + const view = new DataView(buf); + view.setUint8(0, 0x48); // H + view.setUint8(1, 0x49); // I + view.setUint8(2, 0x21); // ! + const batches = []; + for (const batch of fromSync(view)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new Uint8Array([0x48, 0x49, 0x21])); +} + +function testFromSyncNullThrows() { + assert.throws(() => fromSync(null), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +function testFromSyncUndefinedThrows() { + assert.throws(() => fromSync(undefined), { code: 'ERR_INVALID_ARG_TYPE' }); +} + +Promise.all([ + testFromSyncString(), + testFromSyncUint8Array(), + testFromSyncArrayBuffer(), + testFromSyncUint8ArrayArray(), + testFromSyncGenerator(), + testFromSyncNestedIterables(), + testFromSyncToStreamableProtocol(), + testFromSyncGeneratorError(), + testFromSyncRejectsNonStreamable(), + testFromSyncEmptyGenerator(), + testFromSyncNullThrows(), + testFromSyncUndefinedThrows(), + testFromSyncTopLevelToStreamable(), + testFromSyncTopLevelProtocolOverIterator(), + testFromSyncIgnoresAsyncStreamable(), + testFromSyncRejectsAsyncIterable(), + testFromSyncRejectsPromise(), + testFromSyncDataView(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-namespace.js b/test/parallel/test-stream-iter-namespace.js new file mode 100644 index 00000000000000..ce197e85846dfe --- /dev/null +++ b/test/parallel/test-stream-iter-namespace.js @@ -0,0 +1,210 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const streamNew = require('stream/iter'); + +// ============================================================================= +// Stream namespace object +// ============================================================================= + +async function testStreamNamespaceExists() { + assert.ok(streamNew.Stream); + assert.strictEqual(typeof streamNew.Stream, 'object'); +} + +async function testStreamNamespaceFrozen() { + assert.ok(Object.isFrozen(streamNew.Stream)); +} + +async function testStreamNamespaceFactories() { + const { Stream } = streamNew; + + assert.strictEqual(typeof Stream.push, 'function'); + assert.strictEqual(typeof Stream.duplex, 'function'); + assert.strictEqual(typeof Stream.from, 'function'); + assert.strictEqual(typeof Stream.fromSync, 'function'); +} + +async function testStreamNamespacePipelines() { + const { Stream } = streamNew; + + assert.strictEqual(typeof Stream.pull, 'function'); + assert.strictEqual(typeof Stream.pullSync, 'function'); + assert.strictEqual(typeof Stream.pipeTo, 'function'); + assert.strictEqual(typeof Stream.pipeToSync, 'function'); +} + +async function testStreamNamespaceAsyncConsumers() { + const { Stream } = streamNew; + + assert.strictEqual(typeof Stream.bytes, 'function'); + assert.strictEqual(typeof Stream.text, 'function'); + assert.strictEqual(typeof Stream.arrayBuffer, 'function'); + assert.strictEqual(typeof Stream.array, 'function'); +} + +async function testStreamNamespaceSyncConsumers() { + const { Stream } = streamNew; + + assert.strictEqual(typeof Stream.bytesSync, 'function'); + assert.strictEqual(typeof Stream.textSync, 'function'); + assert.strictEqual(typeof Stream.arrayBufferSync, 'function'); + assert.strictEqual(typeof Stream.arraySync, 'function'); +} + +async function testStreamNamespaceCombining() { + const { Stream } = streamNew; + + assert.strictEqual(typeof Stream.merge, 'function'); + assert.strictEqual(typeof Stream.broadcast, 'function'); + assert.strictEqual(typeof Stream.share, 'function'); + assert.strictEqual(typeof Stream.shareSync, 'function'); +} + +async function testStreamNamespaceUtilities() { + const { Stream } = streamNew; + + assert.strictEqual(typeof Stream.tap, 'function'); + assert.strictEqual(typeof Stream.tapSync, 'function'); + assert.strictEqual(typeof Stream.ondrain, 'function'); +} + +async function testStreamNamespaceProtocols() { + const { Stream } = streamNew; + + assert.strictEqual(typeof Stream.toStreamable, 'symbol'); + assert.strictEqual(typeof Stream.toAsyncStreamable, 'symbol'); + assert.strictEqual(typeof Stream.broadcastProtocol, 'symbol'); + assert.strictEqual(typeof Stream.shareProtocol, 'symbol'); + assert.strictEqual(typeof Stream.shareSyncProtocol, 'symbol'); + assert.strictEqual(typeof Stream.drainableProtocol, 'symbol'); +} + +// ============================================================================= +// Individual exports (destructured imports) +// ============================================================================= + +async function testIndividualExports() { + // Factories + assert.strictEqual(typeof streamNew.push, 'function'); + assert.strictEqual(typeof streamNew.duplex, 'function'); + assert.strictEqual(typeof streamNew.from, 'function'); + assert.strictEqual(typeof streamNew.fromSync, 'function'); + + // Pipelines + assert.strictEqual(typeof streamNew.pull, 'function'); + assert.strictEqual(typeof streamNew.pullSync, 'function'); + assert.strictEqual(typeof streamNew.pipeTo, 'function'); + assert.strictEqual(typeof streamNew.pipeToSync, 'function'); + + // Consumers + assert.strictEqual(typeof streamNew.bytes, 'function'); + assert.strictEqual(typeof streamNew.bytesSync, 'function'); + assert.strictEqual(typeof streamNew.text, 'function'); + assert.strictEqual(typeof streamNew.textSync, 'function'); + assert.strictEqual(typeof streamNew.arrayBuffer, 'function'); + assert.strictEqual(typeof streamNew.arrayBufferSync, 'function'); + assert.strictEqual(typeof streamNew.array, 'function'); + assert.strictEqual(typeof streamNew.arraySync, 'function'); + + // Combining + assert.strictEqual(typeof streamNew.merge, 'function'); + assert.strictEqual(typeof streamNew.broadcast, 'function'); + assert.strictEqual(typeof streamNew.share, 'function'); + assert.strictEqual(typeof streamNew.shareSync, 'function'); + + // Utilities + assert.strictEqual(typeof streamNew.tap, 'function'); + assert.strictEqual(typeof streamNew.tapSync, 'function'); + assert.strictEqual(typeof streamNew.ondrain, 'function'); + + // Protocol symbols + assert.strictEqual(typeof streamNew.toStreamable, 'symbol'); + assert.strictEqual(typeof streamNew.toAsyncStreamable, 'symbol'); + assert.strictEqual(typeof streamNew.broadcastProtocol, 'symbol'); + assert.strictEqual(typeof streamNew.shareProtocol, 'symbol'); + assert.strictEqual(typeof streamNew.shareSyncProtocol, 'symbol'); + assert.strictEqual(typeof streamNew.drainableProtocol, 'symbol'); +} + +async function testMultiConsumerExports() { + // Broadcast and Share constructors/factories + assert.ok(streamNew.Broadcast); + assert.strictEqual(typeof streamNew.Broadcast.from, 'function'); + assert.ok(streamNew.Share); + assert.strictEqual(typeof streamNew.Share.from, 'function'); + assert.ok(streamNew.SyncShare); + assert.strictEqual(typeof streamNew.SyncShare.fromSync, 'function'); +} + +// ============================================================================= +// Cross-check: namespace matches individual exports +// ============================================================================= + +async function testNamespaceMatchesExports() { + const { Stream } = streamNew; + + // Every function on Stream should also be available as a direct export + assert.strictEqual(Stream.push, streamNew.push); + assert.strictEqual(Stream.duplex, streamNew.duplex); + assert.strictEqual(Stream.from, streamNew.from); + assert.strictEqual(Stream.fromSync, streamNew.fromSync); + assert.strictEqual(Stream.pull, streamNew.pull); + assert.strictEqual(Stream.pullSync, streamNew.pullSync); + assert.strictEqual(Stream.pipeTo, streamNew.pipeTo); + assert.strictEqual(Stream.pipeToSync, streamNew.pipeToSync); + assert.strictEqual(Stream.bytes, streamNew.bytes); + assert.strictEqual(Stream.text, streamNew.text); + assert.strictEqual(Stream.arrayBuffer, streamNew.arrayBuffer); + assert.strictEqual(Stream.array, streamNew.array); + assert.strictEqual(Stream.bytesSync, streamNew.bytesSync); + assert.strictEqual(Stream.textSync, streamNew.textSync); + assert.strictEqual(Stream.arrayBufferSync, streamNew.arrayBufferSync); + assert.strictEqual(Stream.arraySync, streamNew.arraySync); + assert.strictEqual(Stream.merge, streamNew.merge); + assert.strictEqual(Stream.broadcast, streamNew.broadcast); + assert.strictEqual(Stream.share, streamNew.share); + assert.strictEqual(Stream.shareSync, streamNew.shareSync); + assert.strictEqual(Stream.tap, streamNew.tap); + assert.strictEqual(Stream.tapSync, streamNew.tapSync); + assert.strictEqual(Stream.ondrain, streamNew.ondrain); + + // Protocol symbols + assert.strictEqual(Stream.toStreamable, streamNew.toStreamable); + assert.strictEqual(Stream.toAsyncStreamable, streamNew.toAsyncStreamable); + assert.strictEqual(Stream.broadcastProtocol, streamNew.broadcastProtocol); + assert.strictEqual(Stream.shareProtocol, streamNew.shareProtocol); + assert.strictEqual(Stream.shareSyncProtocol, streamNew.shareSyncProtocol); + assert.strictEqual(Stream.drainableProtocol, streamNew.drainableProtocol); +} + +// ============================================================================= +// Require paths +// ============================================================================= + +async function testRequirePaths() { + // Both require('stream/iter') and require('node:stream/iter') should work + const fromPlain = require('stream/iter'); + const fromNode = require('node:stream/iter'); + + assert.strictEqual(fromPlain.Stream, fromNode.Stream); + assert.strictEqual(fromPlain.push, fromNode.push); +} + +Promise.all([ + testStreamNamespaceExists(), + testStreamNamespaceFrozen(), + testStreamNamespaceFactories(), + testStreamNamespacePipelines(), + testStreamNamespaceAsyncConsumers(), + testStreamNamespaceSyncConsumers(), + testStreamNamespaceCombining(), + testStreamNamespaceUtilities(), + testStreamNamespaceProtocols(), + testIndividualExports(), + testMultiConsumerExports(), + testNamespaceMatchesExports(), + testRequirePaths(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-pipeto-edge.js b/test/parallel/test-stream-iter-pipeto-edge.js new file mode 100644 index 00000000000000..3f09c4dfd42d00 --- /dev/null +++ b/test/parallel/test-stream-iter-pipeto-edge.js @@ -0,0 +1,68 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Edge case tests for pipeToSync: endSync fallback, preventFail. + +const common = require('../common'); +const assert = require('assert'); +const { pipeToSync, fromSync } = require('stream/iter'); + +// pipeToSync endSync returns negative → falls back to end() +async function testPipeToSyncEndSyncFallback() { + let endCalled = false; + const writer = { + writeSync() { return true; }, + endSync() { return -1; }, // Negative → triggers end() fallback + end() { endCalled = true; }, + }; + pipeToSync(fromSync('data'), writer); + assert.strictEqual(endCalled, true); +} + +// pipeToSync endSync missing → falls back to end() +async function testPipeToSyncNoEndSync() { + let endCalled = false; + const writer = { + writeSync() { return true; }, + end() { endCalled = true; }, + }; + pipeToSync(fromSync('data'), writer); + assert.strictEqual(endCalled, true); +} + +// pipeToSync with preventFail: true — source error does NOT call fail() +async function testPipeToSyncPreventFail() { + let failCalled = false; + const writer = { + writeSync() { return true; }, + endSync() { return 0; }, + fail() { failCalled = true; }, + }; + function* badSource() { + yield [new Uint8Array([1])]; + throw new Error('source error'); + } + assert.throws( + () => pipeToSync(badSource(), writer, { preventFail: true }), + { message: 'source error' }, + ); + assert.strictEqual(failCalled, false); +} + +// pipeToSync with preventClose: true — end/endSync not called +async function testPipeToSyncPreventClose() { + let endCalled = false; + const writer = { + writeSync() { return true; }, + endSync() { endCalled = true; return 0; }, + }; + pipeToSync(fromSync('data'), writer, { preventClose: true }); + assert.strictEqual(endCalled, false); +} + +Promise.all([ + testPipeToSyncEndSyncFallback(), + testPipeToSyncNoEndSync(), + testPipeToSyncPreventFail(), + testPipeToSyncPreventClose(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-pipeto-signal.js b/test/parallel/test-stream-iter-pipeto-signal.js new file mode 100644 index 00000000000000..153ee1a80e6176 --- /dev/null +++ b/test/parallel/test-stream-iter-pipeto-signal.js @@ -0,0 +1,90 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Tests for pipeTo with live (non-pre-aborted) AbortSignal, +// both with and without transforms. + +const common = require('../common'); +const assert = require('assert'); +const { pipeTo, from } = require('stream/iter'); + +// pipeTo with live signal, no transforms — abort mid-stream +async function testPipeToLiveSignalNoTransforms() { + const ac = new AbortController(); + const written = []; + const writer = { + async write(chunk) { written.push(chunk); }, + async end() {}, + }; + async function* source() { + yield [new Uint8Array([1])]; + yield [new Uint8Array([2])]; + ac.abort(); + yield [new Uint8Array([3])]; + } + await assert.rejects( + () => pipeTo(source(), writer, { signal: ac.signal }), + { name: 'AbortError' }, + ); + // Should have written at least the first two chunks before abort + assert.ok(written.length >= 1); +} + +// pipeTo with live signal + transforms — abort mid-stream +async function testPipeToLiveSignalWithTransforms() { + const ac = new AbortController(); + const written = []; + const writer = { + async write(chunk) { written.push(chunk); }, + async end() {}, + }; + const identity = (chunks) => chunks; + async function* source() { + yield [new Uint8Array([10])]; + yield [new Uint8Array([20])]; + ac.abort(); + yield [new Uint8Array([30])]; + } + await assert.rejects( + () => pipeTo(source(), identity, writer, { signal: ac.signal }), + { name: 'AbortError' }, + ); + assert.ok(written.length >= 1); +} + +// pipeTo with live signal, no abort — runs to completion +async function testPipeToLiveSignalCompletes() { + const ac = new AbortController(); + const written = []; + const writer = { + write(chunk) { written.push(chunk); }, + writeSync(chunk) { written.push(chunk); return true; }, + async end() {}, + endSync() { return written.length; }, + }; + await pipeTo(from('signal-ok'), writer, { signal: ac.signal }); + assert.ok(written.length > 0); +} + +// pipeTo with live signal + transforms, no abort — runs to completion +async function testPipeToLiveSignalWithTransformsCompletes() { + const ac = new AbortController(); + const written = []; + const writer = { + write(chunk) { written.push(chunk); }, + writeSync(chunk) { written.push(chunk); return true; }, + async end() {}, + endSync() { return written.length; }, + }; + const identity = (chunks) => chunks; + await pipeTo(from('signal-tx-ok'), identity, writer, + { signal: ac.signal }); + assert.ok(written.length > 0); +} + +Promise.all([ + testPipeToLiveSignalNoTransforms(), + testPipeToLiveSignalWithTransforms(), + testPipeToLiveSignalCompletes(), + testPipeToLiveSignalWithTransformsCompletes(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-pipeto-writev.js b/test/parallel/test-stream-iter-pipeto-writev.js new file mode 100644 index 00000000000000..505bdd6d2b2ced --- /dev/null +++ b/test/parallel/test-stream-iter-pipeto-writev.js @@ -0,0 +1,147 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Tests for pipeTo writev/writevSync paths and writeBatchAsyncFallback. + +const common = require('../common'); +const assert = require('assert'); +const { pipeTo, pipeToSync } = require('stream/iter'); + +// Multi-chunk batch with writevSync (sync success path) +async function testWritevSyncSuccess() { + const batches = []; + const writer = { + write(chunk) {}, + writevSync(chunks) { batches.push(chunks); return true; }, + writev(chunks) { batches.push(chunks); }, + writeSync(chunk) { return true; }, + endSync() { return 0; }, + }; + // Source that yields multi-chunk batches + async function* source() { + yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])]; + yield [new Uint8Array([4]), new Uint8Array([5])]; + } + const total = await pipeTo(source(), writer); + assert.ok(batches.length > 0); + // writevSync was used for multi-chunk batches + assert.ok(batches.some((b) => b.length > 1)); + assert.strictEqual(total, 5); +} + +// Multi-chunk batch with writev async (no writevSync) +async function testWritevAsyncFallback() { + const batches = []; + const writer = { + async writev(chunks) { batches.push(chunks); }, + async write(chunk) { batches.push([chunk]); }, + async end() {}, + }; + async function* source() { + yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])]; + } + await pipeTo(source(), writer); + assert.ok(batches.length > 0); + assert.ok(batches.some((b) => b.length > 1)); +} + +// writevSync returns false — falls through to async writev +async function testWritevSyncFails() { + const asyncCalls = []; + const writer = { + write() {}, + writevSync() { return false; }, + async writev(chunks) { asyncCalls.push(chunks); }, + writeSync() { return true; }, + endSync() { return 0; }, + }; + async function* source() { + yield [new Uint8Array([1]), new Uint8Array([2])]; + } + await pipeTo(source(), writer); + assert.strictEqual(asyncCalls.length, 1); + assert.strictEqual(asyncCalls[0].length, 2); +} + +// writeSync fails mid-batch — triggers writeBatchAsyncFallback +async function testWriteSyncFailsMidBatch() { + const asyncWrites = []; + const writer = { + writeSync(chunk) { + // Fail for chunk value 2 — always, including retries + if (chunk[0] === 2) return false; + return true; + }, + async write(chunk) { asyncWrites.push(chunk); }, + async end() {}, + }; + // Single batch with 3 chunks + async function* source() { + yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])]; + } + const total = await pipeTo(source(), writer); + // Chunk 1: writeSync succeeds + // Chunk 2: writeSync fails → writeBatchAsyncFallback → write() called + // Chunk 3: writeBatchAsyncFallback retries writeSync → succeeds + assert.ok(asyncWrites.length >= 1); + assert.deepStrictEqual(asyncWrites[0], new Uint8Array([2])); + assert.strictEqual(total, 3); +} + +// writeSync always fails — all chunks go through async +async function testWriteSyncAlwaysFails() { + const asyncWrites = []; + const writer = { + writeSync() { return false; }, + async write(chunk) { asyncWrites.push(chunk); }, + async end() {}, + }; + async function* source() { + yield [new Uint8Array([10]), new Uint8Array([20])]; + } + const total = await pipeTo(source(), writer); + assert.strictEqual(asyncWrites.length, 2); + assert.strictEqual(total, 2); +} + +// pipeToSync with writevSync +async function testPipeToSyncWritev() { + const batches = []; + const writer = { + writevSync(chunks) { batches.push(chunks); }, + writeSync(chunk) { return true; }, + endSync() { return 0; }, + }; + function* source() { + yield [new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])]; + yield [new Uint8Array([4])]; + } + pipeToSync(source(), writer); + // Multi-chunk batch should have used writevSync + assert.ok(batches.some((b) => b.length > 1)); +} + +// pipeToSync with writer that has write() and writeSync() — writeSync preferred +async function testPipeToSyncWriteFallback() { + const syncWrites = []; + const writer = { + writeSync(chunk) { syncWrites.push(chunk); return true; }, + write(chunk) { /* should not be called */ }, + endSync() { return 0; }, + }; + function* source() { + yield [new Uint8Array([1]), new Uint8Array([2])]; + } + pipeToSync(source(), writer); + assert.strictEqual(syncWrites.length, 2); +} + +Promise.all([ + testWritevSyncSuccess(), + testWritevAsyncFallback(), + testWritevSyncFails(), + testWriteSyncFailsMidBatch(), + testWriteSyncAlwaysFails(), + testPipeToSyncWritev(), + testPipeToSyncWriteFallback(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-pipeto.js b/test/parallel/test-stream-iter-pipeto.js new file mode 100644 index 00000000000000..9845a5ba254efb --- /dev/null +++ b/test/parallel/test-stream-iter-pipeto.js @@ -0,0 +1,237 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { pipeTo, pipeToSync, from, fromSync } = require('stream/iter'); + +async function testPipeToSync() { + const written = []; + const writer = { + writeSync(chunk) { written.push(chunk); return true; }, + endSync() { return written.length; }, + fail() {}, + }; + + const totalBytes = pipeToSync(fromSync('pipe-data'), writer); + assert.strictEqual(totalBytes, 9); // 'pipe-data' = 9 UTF-8 bytes + assert.ok(written.length > 0); + const result = new TextDecoder().decode( + new Uint8Array(written.reduce((acc, c) => [...acc, ...c], []))); + assert.strictEqual(result, 'pipe-data'); +} + +async function testPipeTo() { + const written = []; + const writer = { + async write(chunk) { written.push(chunk); }, + async end() { return written.length; }, + async fail() {}, + }; + + const totalBytes = await pipeTo(from('async-pipe-data'), writer); + assert.strictEqual(totalBytes, 15); // 'async-pipe-data' = 15 UTF-8 bytes + assert.ok(written.length > 0); +} + +async function testPipeToPreventClose() { + let endCalled = false; + const writer = { + async write() {}, + async end() { endCalled = true; }, + async fail() {}, + }; + + await pipeTo(from('data'), writer, { preventClose: true }); + assert.strictEqual(endCalled, false); +} + +// PipeTo source error calls writer.fail() +async function testPipeToSourceError() { + let failCalled = false; + let failReason; + const writer = { + write() {}, + fail(reason) { failCalled = true; failReason = reason; }, + }; + async function* failingSource() { + yield [new TextEncoder().encode('a')]; + throw new Error('pipe source boom'); + } + await assert.rejects( + () => pipeTo(failingSource(), writer), + { message: 'pipe source boom' }, + ); + assert.strictEqual(failCalled, true); + assert.strictEqual(failReason.message, 'pipe source boom'); +} + +// PipeToSync source error calls writer.fail() +async function testPipeToSyncSourceError() { + let failCalled = false; + const writer = { + writeSync() { return true; }, + fail(reason) { failCalled = true; }, + }; + function* failingSource() { + yield [new TextEncoder().encode('a')]; + throw new Error('sync pipe boom'); + } + assert.throws( + () => pipeToSync(failingSource(), writer), + { message: 'sync pipe boom' }, + ); + assert.strictEqual(failCalled, true); +} + +// PipeTo with AbortSignal +async function testPipeToWithSignal() { + const ac = new AbortController(); + const chunks = []; + const writer = { + write(chunk) { chunks.push(chunk); }, + }; + async function* slowSource() { + yield [new TextEncoder().encode('a')]; + await new Promise((r) => setTimeout(r, 50)); + yield [new TextEncoder().encode('b')]; + } + ac.abort(); + await assert.rejects( + () => pipeTo(slowSource(), writer, { signal: ac.signal }), + { name: 'AbortError' }, + ); +} + +// PipeTo with transforms +async function testPipeToWithTransforms() { + const chunks = []; + const writer = { + write(chunk) { chunks.push(new TextDecoder().decode(chunk)); }, + }; + const upper = (batch) => { + if (batch === null) return null; + return batch.map((c) => { + const out = new Uint8Array(c); + for (let i = 0; i < out.length; i++) + out[i] -= (out[i] >= 97 && out[i] <= 122) * 32; + return out; + }); + }; + await pipeTo(from('hello'), upper, writer); + assert.strictEqual(chunks.join(''), 'HELLO'); +} + +// PipeToSync with transforms +async function testPipeToSyncWithTransforms() { + const chunks = []; + const writer = { + writeSync(chunk) { chunks.push(new TextDecoder().decode(chunk)); return true; }, + }; + const upper = (batch) => { + if (batch === null) return null; + return batch.map((c) => { + const out = new Uint8Array(c); + for (let i = 0; i < out.length; i++) + out[i] -= (out[i] >= 97 && out[i] <= 122) * 32; + return out; + }); + }; + pipeToSync(fromSync('hello'), upper, writer); + assert.strictEqual(chunks.join(''), 'HELLO'); +} + +// PipeTo with writev writer +async function testPipeToWithWritevWriter() { + const allChunks = []; + const writer = { + write(chunk) { allChunks.push(chunk); }, + writev(chunks) { allChunks.push(...chunks); }, + }; + await pipeTo(from('hello world'), writer); + assert.strictEqual(allChunks.length > 0, true); +} + +// PipeTo with writeSync/writevSync fallback +async function testPipeToSyncFallback() { + const chunks = []; + const writer = { + writeSync(chunk) { chunks.push(chunk); return true; }, + write(chunk) { chunks.push(chunk); }, + }; + await pipeTo(from('hello'), writer); + assert.strictEqual(chunks.length > 0, true); +} + +// PipeTo preventFail option +async function testPipeToPreventFail() { + let failCalled = false; + const writer = { + write() {}, + fail() { failCalled = true; }, + }; + // eslint-disable-next-line require-yield + async function* failingSource() { + throw new Error('boom'); + } + await assert.rejects( + () => pipeTo(failingSource(), writer, { preventFail: true }), + { message: 'boom' }, + ); + assert.strictEqual(failCalled, false); +} + +// PipeToSync preventClose option +async function testPipeToSyncPreventClose() { + let endCalled = false; + const writer = { + writeSync() { return true; }, + endSync() { endCalled = true; return 0; }, + }; + pipeToSync(fromSync('hello'), writer, { preventClose: true }); + assert.strictEqual(endCalled, false); +} + +// Regression test: pipeTo should work with a minimal writer that only +// implements write(). end(), fail(), and all *Sync methods are optional. +async function testPipeToMinimalWriter() { + const chunks = []; + const minimalWriter = { + write(chunk) { + chunks.push(chunk); + }, + }; + + await pipeTo(from('minimal'), minimalWriter); + assert.strictEqual(chunks.length > 0, true); +} + +async function testPipeToSyncMinimalWriter() { + const chunks = []; + const minimalWriter = { + writeSync(chunk) { + chunks.push(chunk); + return true; + }, + }; + + pipeToSync(fromSync('minimal-sync'), minimalWriter); + assert.strictEqual(chunks.length > 0, true); +} + +Promise.all([ + testPipeToSync(), + testPipeTo(), + testPipeToPreventClose(), + testPipeToSourceError(), + testPipeToSyncSourceError(), + testPipeToWithSignal(), + testPipeToWithTransforms(), + testPipeToSyncWithTransforms(), + testPipeToWithWritevWriter(), + testPipeToSyncFallback(), + testPipeToPreventFail(), + testPipeToSyncPreventClose(), + testPipeToMinimalWriter(), + testPipeToSyncMinimalWriter(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-pull-async.js b/test/parallel/test-stream-iter-pull-async.js new file mode 100644 index 00000000000000..157cc5e265ea34 --- /dev/null +++ b/test/parallel/test-stream-iter-pull-async.js @@ -0,0 +1,371 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { pull, from, text, tap } = require('stream/iter'); + +async function testPullIdentity() { + const data = await text(pull(from('hello-async'))); + assert.strictEqual(data, 'hello-async'); +} + +async function testPullStatelessTransform() { + const upper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => { + const str = new TextDecoder().decode(c); + return new TextEncoder().encode(str.toUpperCase()); + }); + }; + const data = await text(pull(from('abc'), upper)); + assert.strictEqual(data, 'ABC'); +} + +async function testPullStatefulTransform() { + const stateful = { + transform: async function*(source) { + for await (const chunks of source) { + if (chunks === null) { + yield new TextEncoder().encode('-ASYNC-END'); + continue; + } + for (const chunk of chunks) { + yield chunk; + } + } + }, + }; + const data = await text(pull(from('data'), stateful)); + assert.strictEqual(data, 'data-ASYNC-END'); +} + +async function testPullWithAbortSignal() { + const ac = new AbortController(); + ac.abort(); + + async function* gen() { + yield [new Uint8Array([1])]; + } + + const result = pull(gen(), { signal: ac.signal }); + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of result) { + assert.fail('Should not reach here'); + } + }, + { name: 'AbortError' }, + ); +} + +async function testPullChainedTransforms() { + const enc = new TextEncoder(); + const transforms = [ + (chunks) => { + if (chunks === null) return null; + return [...chunks, enc.encode('!')]; + }, + (chunks) => { + if (chunks === null) return null; + return [...chunks, enc.encode('?')]; + }, + ]; + const data = await text(pull(from('hello'), ...transforms)); + assert.strictEqual(data, 'hello!?'); +} + +// Source error → controller.abort() → transform listener throws → +// source error propagates to consumer; listener error becomes uncaught +// exception (per EventTarget spec behavior). +async function testTransformSignalListenerErrorOnSourceError() { + // Listener errors from dispatchEvent are rethrown via process.nextTick, + // so we must catch them as uncaught exceptions. + const uncaughtErrors = []; + const handler = (err) => uncaughtErrors.push(err); + process.on('uncaughtException', handler); + + const throwingTransform = { + transform(source, options) { + options.signal.addEventListener('abort', () => { + throw new Error('listener boom'); + }); + return source; + }, + }; + + async function* failingSource() { + yield [new TextEncoder().encode('a')]; + throw new Error('source error'); + } + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(failingSource(), throwingTransform)) { + // Consume + } + }, + { message: 'source error' }, + ); + + // Give the nextTick rethrow a chance to fire + await new Promise(setImmediate); + process.removeListener('uncaughtException', handler); + + assert.strictEqual(uncaughtErrors.length, 1); + assert.strictEqual(uncaughtErrors[0].message, 'listener boom'); +} + +// Pull source error propagates to consumer +async function testPullSourceError() { + async function* failingSource() { + yield [new TextEncoder().encode('a')]; + throw new Error('source boom'); + } + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(failingSource())) { /* consume */ } + }, { message: 'source boom' }); +} + +// Tap callback error propagates through pipeline +async function testTapCallbackError() { + const badTap = tap(() => { throw new Error('tap boom'); }); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(from('hello'), badTap)) { /* consume */ } + }, { message: 'tap boom' }); +} + +// Pull signal aborted mid-iteration (not pre-aborted) +async function testPullSignalAbortMidIteration() { + const ac = new AbortController(); + const enc = new TextEncoder(); + async function* slowSource() { + yield [enc.encode('a')]; + yield [enc.encode('b')]; + yield [enc.encode('c')]; + } + const result = pull(slowSource(), { signal: ac.signal }); + const iter = result[Symbol.asyncIterator](); + const first = await iter.next(); // Read first batch + assert.strictEqual(first.done, false); + ac.abort(); + await assert.rejects(() => iter.next(), { name: 'AbortError' }); +} + +// Pull consumer break (return()) cleans up transform signal +async function testPullConsumerBreakCleanup() { + let signalAborted = false; + const trackingTransform = { + transform(source, options) { + options.signal.addEventListener('abort', () => { + signalAborted = true; + }); + return source; + }, + }; + async function* infiniteSource() { + let i = 0; + while (true) { + yield [new TextEncoder().encode(`chunk${i++}`)]; + } + } + // Consumer breaks after first chunk + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(infiniteSource(), trackingTransform)) { + break; + } + // Give the abort handler a tick to fire + await new Promise(setImmediate); + assert.strictEqual(signalAborted, true); +} + +// Pull transform returning a Promise +async function testPullTransformReturnsPromise() { + const asyncTransform = async (chunks) => { + if (chunks === null) return null; + return chunks; + }; + const result = await text(pull(from('hello'), asyncTransform)); + assert.strictEqual(result, 'hello'); +} + +// Stateless transform error propagates +async function testPullStatelessTransformError() { + const badTransform = (chunks) => { + if (chunks === null) return null; + throw new Error('async stateless boom'); + }; + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(from('hello'), badTransform)) { /* consume */ } + }, { message: 'async stateless boom' }); +} + +// Stateful transform error propagates +async function testPullStatefulTransformError() { + const badStateful = { + transform: async function*(source) { // eslint-disable-line require-yield + for await (const chunks of source) { + if (chunks === null) continue; + throw new Error('async stateful boom'); + } + }, + }; + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(from('hello'), badStateful)) { /* consume */ } + }, { message: 'async stateful boom' }); +} + +// Stateless transform flush emitting data +async function testPullStatelessTransformFlush() { + const withTrailer = (chunks) => { + if (chunks === null) { + return [new TextEncoder().encode('-TRAILER')]; + } + return chunks; + }; + const data = await text(pull(from('data'), withTrailer)); + assert.strictEqual(data, 'data-TRAILER'); +} + +// Stateless transform flush error propagates +async function testPullStatelessTransformFlushError() { + const badFlush = (chunks) => { + if (chunks === null) { + throw new Error('async flush boom'); + } + return chunks; + }; + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of pull(from('hello'), badFlush)) { /* consume */ } + }, { message: 'async flush boom' }); +} + +// Pull with a sync iterable source (not async) +async function testPullWithSyncSource() { + function* gen() { + yield new TextEncoder().encode('sync-source'); + } + const data = await text(pull(gen())); + assert.strictEqual(data, 'sync-source'); +} + +// Pull transform yielding strings +async function testPullTransformYieldsStrings() { + const stringTransform = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => new TextDecoder().decode(c)); + }; + const result = await text(pull(from('hello'), stringTransform)); + assert.strictEqual(result, 'hello'); +} + +// pull() accepts a string source directly (normalized via from()) +async function testPullStringSource() { + const data = await text(pull('hello-direct')); + assert.strictEqual(data, 'hello-direct'); +} + +// Transform returning a single Uint8Array should be wrapped as a batch, +// not iterated byte-by-byte +async function testTransformReturnsSingleUint8Array() { + const transform = (chunks) => { + if (chunks === null) return null; + // Return a single Uint8Array, not an array + const enc = new TextEncoder(); + return enc.encode('transformed'); + }; + const data = await text(pull(from('input'), transform)); + assert.strictEqual(data, 'transformed'); +} + +// Transform returning a single string should be UTF-8 encoded, +// not iterated character-by-character +async function testTransformReturnsSingleString() { + const transform = (chunks) => { + if (chunks === null) return null; + return 'hello-string'; + }; + const data = await text(pull(from('input'), transform)); + assert.strictEqual(data, 'hello-string'); +} + +// Transform returning an ArrayBuffer should be converted to Uint8Array +async function testTransformReturnsArrayBuffer() { + const transform = (chunks) => { + if (chunks === null) return null; + const enc = new TextEncoder(); + return enc.encode('arraybuf').buffer; + }; + const data = await text(pull(from('input'), transform)); + assert.strictEqual(data, 'arraybuf'); +} + +// pipeTo() accepts a string source directly (normalized via from()) +async function testPipeToStringSource() { + const { pipeTo, push: pushFn, text: textFn } = require('stream/iter'); + const { writer, readable } = pushFn({ highWaterMark: 10 }); + const consume = (async () => textFn(readable))(); + await pipeTo('hello-pipe', writer); + const data = await consume; + assert.strictEqual(data, 'hello-pipe'); +} + +// INVARIANT: Each transform invocation receives its own options object. +// A transform that mutates options must not affect subsequent transforms. +async function testTransformOptionsNotShared() { + const seen = []; + const transform1 = (chunks, options) => { + // Mutate the options object + options.mutated = true; + seen.push({ id: 1, mutated: options.mutated }); + return chunks; + }; + const transform2 = (chunks, options) => { + // Should NOT see mutation from transform1 + seen.push({ id: 2, mutated: options.mutated }); + return chunks; + }; + await text(pull(from('test'), transform1, transform2)); + // transform1 sees its own mutation + assert.strictEqual(seen[0].mutated, true); + // transform2 gets a fresh options object - no mutation visible + assert.strictEqual(seen[1].mutated, undefined); +} + +// Run the uncaughtException test sequentially (it installs a global handler +// that would interfere with concurrent tests). +(async () => { + await Promise.all([ + testPullIdentity(), + testPullStatelessTransform(), + testPullStatefulTransform(), + testPullWithAbortSignal(), + testPullChainedTransforms(), + testPullSourceError(), + testTapCallbackError(), + testPullSignalAbortMidIteration(), + testPullConsumerBreakCleanup(), + testPullTransformReturnsPromise(), + testPullTransformYieldsStrings(), + testPullStatelessTransformError(), + testPullStatefulTransformError(), + testPullStatelessTransformFlush(), + testPullStatelessTransformFlushError(), + testPullWithSyncSource(), + testPullStringSource(), + testTransformReturnsSingleUint8Array(), + testTransformReturnsSingleString(), + testTransformReturnsArrayBuffer(), + testPipeToStringSource(), + testTransformOptionsNotShared(), + ]); + // Run after all concurrent tests complete to avoid global handler races + await testTransformSignalListenerErrorOnSourceError(); +})().then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-pull-sync.js b/test/parallel/test-stream-iter-pull-sync.js new file mode 100644 index 00000000000000..35679ac102d512 --- /dev/null +++ b/test/parallel/test-stream-iter-pull-sync.js @@ -0,0 +1,178 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { pullSync, fromSync, bytesSync, tapSync } = require('stream/iter'); + +function testPullSyncIdentity() { + // No transforms - just pass through + const data = bytesSync(pullSync(fromSync('hello'))); + assert.deepStrictEqual(data, new TextEncoder().encode('hello')); +} + +function testPullSyncStatelessTransform() { + const upper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => { + const str = new TextDecoder().decode(c); + return new TextEncoder().encode(str.toUpperCase()); + }); + }; + const data = bytesSync(pullSync(fromSync('abc'), upper)); + assert.deepStrictEqual(data, new TextEncoder().encode('ABC')); +} + +function testPullSyncStatefulTransform() { + const source = fromSync('data'); + const stateful = { + transform: function*(source) { + for (const chunks of source) { + if (chunks === null) { + // Flush: emit trailer + yield new TextEncoder().encode('-END'); + continue; + } + for (const chunk of chunks) { + yield chunk; + } + } + }, + }; + const result = pullSync(source, stateful); + const data = new TextDecoder().decode(bytesSync(result)); + assert.strictEqual(data, 'data-END'); +} + +function testPullSyncChainedTransforms() { + const addExcl = (chunks) => { + if (chunks === null) return null; + return [...chunks, new TextEncoder().encode('!')]; + }; + const addQ = (chunks) => { + if (chunks === null) return null; + return [...chunks, new TextEncoder().encode('?')]; + }; + const result = pullSync(fromSync('hello'), addExcl, addQ); + const data = new TextDecoder().decode(bytesSync(result)); + assert.strictEqual(data, 'hello!?'); +} + +// PullSync source error propagates +function testPullSyncSourceError() { + function* failingSource() { + yield [new TextEncoder().encode('a')]; + throw new Error('sync source boom'); + } + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of pullSync(failingSource())) { /* consume */ } + }, { message: 'sync source boom' }); +} + +// PullSync with empty source +function testPullSyncEmptySource() { + function* empty() {} + const result = bytesSync(pullSync(empty())); + assert.strictEqual(result.length, 0); +} + +// TapSync callback error propagates +function testTapSyncCallbackError() { + const badTap = tapSync(() => { throw new Error('tapSync boom'); }); + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of pullSync(fromSync('hello'), badTap)) { /* consume */ } + }, { message: 'tapSync boom' }); +} + +// Stateless transform error propagates +function testPullSyncStatelessTransformError() { + const badTransform = (chunks) => { + if (chunks === null) return null; + throw new Error('stateless transform boom'); + }; + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of pullSync(fromSync('hello'), badTransform)) { /* consume */ } + }, { message: 'stateless transform boom' }); +} + +// Stateful transform error propagates +function testPullSyncStatefulTransformError() { + const badStateful = { + transform: function*(source) { // eslint-disable-line require-yield + for (const chunks of source) { + if (chunks === null) continue; + throw new Error('stateful transform boom'); + } + }, + }; + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of pullSync(fromSync('hello'), badStateful)) { /* consume */ } + }, { message: 'stateful transform boom' }); +} + +// Stateless transform flush emitting data +function testPullSyncStatelessTransformFlush() { + const withTrailer = (chunks) => { + if (chunks === null) { + // Flush: emit trailing data + return [new TextEncoder().encode('-TRAILER')]; + } + return chunks; + }; + const data = new TextDecoder().decode(bytesSync(pullSync(fromSync('data'), withTrailer))); + assert.strictEqual(data, 'data-TRAILER'); +} + +// Stateless transform flush error propagates +function testPullSyncStatelessTransformFlushError() { + const badFlush = (chunks) => { + if (chunks === null) { + throw new Error('flush boom'); + } + return chunks; + }; + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of pullSync(fromSync('hello'), badFlush)) { /* consume */ } + }, { message: 'flush boom' }); +} + +// Empty source result is a Uint8Array +function testPullSyncEmptySourceType() { + function* empty() {} + const result = bytesSync(pullSync(empty())); + assert.ok(result instanceof Uint8Array); + assert.strictEqual(result.byteLength, 0); +} + +// Invalid transform argument +function testPullSyncInvalidTransform() { + assert.throws( + () => { for (const _ of pullSync(fromSync('x'), 42)) { /* consume */ } }, // eslint-disable-line no-unused-vars + { code: 'ERR_INVALID_ARG_TYPE' }, + ); + assert.throws( + () => { for (const _ of pullSync(fromSync('x'), null)) { /* consume */ } }, // eslint-disable-line no-unused-vars + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +Promise.all([ + testPullSyncIdentity(), + testPullSyncStatelessTransform(), + testPullSyncStatefulTransform(), + testPullSyncChainedTransforms(), + testPullSyncSourceError(), + testPullSyncEmptySource(), + testPullSyncEmptySourceType(), + testTapSyncCallbackError(), + testPullSyncStatelessTransformError(), + testPullSyncStatefulTransformError(), + testPullSyncStatelessTransformFlush(), + testPullSyncStatelessTransformFlushError(), + testPullSyncInvalidTransform(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-push-backpressure.js b/test/parallel/test-stream-iter-push-backpressure.js new file mode 100644 index 00000000000000..a62a24ee6b623c --- /dev/null +++ b/test/parallel/test-stream-iter-push-backpressure.js @@ -0,0 +1,156 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { push, text } = require('stream/iter'); + +async function testStrictBackpressure() { + const { writer, readable } = push({ + highWaterMark: 1, + backpressure: 'strict', + }); + + // First write should succeed synchronously + assert.strictEqual(writer.writeSync('a'), true); + // Second write should fail synchronously (buffer full) + assert.strictEqual(writer.writeSync('b'), false); + + // Consume to free space, then end + const resultPromise = text(readable); + writer.end(); + const data = await resultPromise; + assert.strictEqual(data, 'a'); +} + +async function testDropOldest() { + const { writer, readable } = push({ + highWaterMark: 2, + backpressure: 'drop-oldest', + }); + + assert.strictEqual(writer.writeSync('first'), true); + assert.strictEqual(writer.writeSync('second'), true); + // This should drop 'first' — return value is true (write accepted via drop) + assert.strictEqual(writer.writeSync('third'), true); + writer.end(); + + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + // Should have 'second' and 'third' + const allBytes = []; + for (const batch of batches) { + for (const chunk of batch) { + allBytes.push(...chunk); + } + } + const result = new TextDecoder().decode(new Uint8Array(allBytes)); + assert.strictEqual(result, 'secondthird'); +} + +async function testDropNewest() { + const { writer, readable } = push({ + highWaterMark: 1, + backpressure: 'drop-newest', + }); + + assert.strictEqual(writer.writeSync('kept'), true); + // This is silently dropped — return value is true (accepted but discarded) + assert.strictEqual(writer.writeSync('dropped'), true); + writer.end(); + + const data = await text(readable); + assert.strictEqual(data, 'kept'); +} + +async function testBlockBackpressure() { + const { writer, readable } = push({ highWaterMark: 1, backpressure: 'block' }); + + // Fill the buffer + writer.writeSync('a'); + + // Next write should block (not throw, not drop) + let writeState = 'pending'; + const writePromise = writer.write('b').then(() => { writeState = 'resolved'; }); + + // The write cannot resolve until the buffer is drained, so a microtask + // tick is sufficient to confirm it is still blocked. + await new Promise(setImmediate); + assert.strictEqual(writeState, 'pending'); // Still blocked + + // Read from the consumer to drain + const iter = readable[Symbol.asyncIterator](); + const first = await iter.next(); // Drains 'a' + assert.strictEqual(first.done, false); + + // After draining, the pending write resolves as a microtask + await new Promise(setImmediate); + assert.strictEqual(writeState, 'resolved'); // Now unblocked + + writer.endSync(); + const second = await iter.next(); // Read 'b' + assert.strictEqual(second.done, false); + await writePromise; +} + +async function testBlockWriteSyncEnqueues() { + // With block policy, writeSync should enqueue the data even when the buffer + // is full, returning false as a backpressure signal. The data IS accepted. + const { writer, readable } = push({ highWaterMark: 1, backpressure: 'block' }); + + // Fill the buffer + assert.strictEqual(writer.writeSync('a'), true); + + // Buffer full: writeSync should enqueue and return false (data accepted) + assert.strictEqual(writer.writeSync('b'), false); + + writer.endSync(); + + // Both chunks should be delivered (drain flushes all slots into one batch) + const result = await text(readable); + assert.strictEqual(result, 'ab'); +} + +async function testStrictPendingQueueOverflow() { + // With highWaterMark: 1 and strict, the pending writes queue is also limited to 1. + // Filling the buffer (1 sync write) + filling the pending queue (1 async write) + // should leave no room. A third write must reject with a RangeError. + const { writer, readable } = push({ + highWaterMark: 1, + backpressure: 'strict', + }); + + // Fill the buffer + assert.strictEqual(writer.writeSync('a'), true); + + // This async write goes into the pending queue (buffer full, queue has room) + const pendingWrite = writer.write('b'); + + // This write should reject: buffer full AND pending queue at capacity + await assert.rejects( + () => writer.write('c'), + { + code: 'ERR_INVALID_STATE', + name: 'RangeError', + }, + ); + + // Clean up: drain the readable + const iter = readable[Symbol.asyncIterator](); + await iter.next(); + await iter.next(); + await pendingWrite; + writer.endSync(); + await iter.return(); +} + +Promise.all([ + testStrictBackpressure(), + testDropOldest(), + testDropNewest(), + testBlockBackpressure(), + testBlockWriteSyncEnqueues(), + testStrictPendingQueueOverflow(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-push-basic.js b/test/parallel/test-stream-iter-push-basic.js new file mode 100644 index 00000000000000..22d5b26c830a47 --- /dev/null +++ b/test/parallel/test-stream-iter-push-basic.js @@ -0,0 +1,182 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { push, text } = require('stream/iter'); + +async function testBasicWriteRead() { + const { writer, readable } = push(); + + writer.write('hello'); + writer.end(); + + const data = await text(readable); + assert.strictEqual(data, 'hello'); +} + +async function testMultipleWrites() { + const { writer, readable } = push({ highWaterMark: 10 }); + + writer.write('a'); + writer.write('b'); + writer.write('c'); + writer.end(); + + const data = await text(readable); + assert.strictEqual(data, 'abc'); +} + +async function testDesiredSize() { + const { writer } = push({ highWaterMark: 3 }); + + assert.strictEqual(writer.desiredSize, 3); + writer.writeSync('a'); + assert.strictEqual(writer.desiredSize, 2); + writer.writeSync('b'); + assert.strictEqual(writer.desiredSize, 1); + writer.writeSync('c'); + assert.strictEqual(writer.desiredSize, 0); + + writer.end(); + assert.strictEqual(writer.desiredSize, null); +} + +async function testWriterEnd() { + const { writer, readable } = push(); + + const totalBytes = writer.endSync(); + assert.strictEqual(totalBytes, 0); + + // Calling endSync again returns byte count (idempotent when closed) + assert.strictEqual(writer.endSync(), 0); + + const batches = []; + for await (const batch of readable) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +async function testWriterFail() { + const { writer, readable } = push(); + + writer.fail(new Error('test fail')); + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { + assert.fail('Should not reach here'); + } + }, + { message: 'test fail' }, + ); +} + +async function testConsumerBreak() { + const { writer, readable } = push({ highWaterMark: 10 }); + + writer.writeSync('a'); + writer.writeSync('b'); + writer.writeSync('c'); + + // Break after first batch + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { + break; + } + + // Writer should now see null desiredSize + assert.strictEqual(writer.desiredSize, null); +} + +async function testAbortSignal() { + const ac = new AbortController(); + const { readable } = push({ signal: ac.signal }); + + ac.abort(); + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { + assert.fail('Should not reach here'); + } + }, + { name: 'AbortError' }, + ); +} + +async function testPreAbortedSignal() { + const ac = new AbortController(); + ac.abort(); + const { readable } = push({ signal: ac.signal }); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { + assert.fail('Should not reach here'); + } + }, { name: 'AbortError' }); +} + +async function testConsumerBreakWriteSyncReturnsFalse() { + const { writer, readable } = push({ highWaterMark: 10 }); + writer.writeSync('a'); + + // Break after first batch + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { + break; + } + + // After consumer break, writeSync should return false + assert.strictEqual(writer.writeSync('b'), false); + assert.strictEqual(writer.desiredSize, null); +} + +async function testPushWithTransforms() { + const upper = (chunks) => { + if (chunks === null) return null; + return chunks.map((c) => { + const str = new TextDecoder().decode(c); + return new TextEncoder().encode(str.toUpperCase()); + }); + }; + + const { writer, readable } = push(upper); + + writer.write('hello'); + writer.end(); + + const data = await text(readable); + assert.strictEqual(data, 'HELLO'); +} + +async function testInvalidBackpressure() { + assert.throws(() => push({ backpressure: 'banana' }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + assert.throws(() => push({ backpressure: '' }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + + // Valid values should not throw + for (const bp of ['strict', 'block', 'drop-oldest', 'drop-newest']) { + push({ backpressure: bp }); + } +} + +Promise.all([ + testBasicWriteRead(), + testMultipleWrites(), + testDesiredSize(), + testWriterEnd(), + testWriterFail(), + testConsumerBreak(), + testAbortSignal(), + testPreAbortedSignal(), + testConsumerBreakWriteSyncReturnsFalse(), + testPushWithTransforms(), + testInvalidBackpressure(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-push-writer.js b/test/parallel/test-stream-iter-push-writer.js new file mode 100644 index 00000000000000..e7e783d7b74a9c --- /dev/null +++ b/test/parallel/test-stream-iter-push-writer.js @@ -0,0 +1,426 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { push, ondrain, text } = require('stream/iter'); + +async function testOndrain() { + const { writer } = push({ highWaterMark: 1 }); + + // With space available, ondrain resolves immediately + const drainResult = ondrain(writer); + assert.ok(drainResult instanceof Promise); + const result = await drainResult; + assert.strictEqual(result, true); + + // After close, ondrain returns null + writer.end(); + assert.strictEqual(ondrain(writer), null); +} + +async function testOndrainNonDrainable() { + // Non-drainable objects return null + assert.strictEqual(ondrain(null), null); + assert.strictEqual(ondrain({}), null); + assert.strictEqual(ondrain('string'), null); +} + +async function testOndrainProtocolErrorPropagates() { + const badDrainable = { + [Symbol.for('Stream.drainableProtocol')]() { + throw new Error('protocol error'); + }, + }; + assert.throws( + () => ondrain(badDrainable), + { message: 'protocol error' }, + ); +} + +async function testWriteWithSignalRejects() { + const { writer, readable } = push({ highWaterMark: 1 }); + + // Fill the buffer so write will block + writer.writeSync('a'); + + const ac = new AbortController(); + const writePromise = writer.write('b', { signal: ac.signal }); + + // Signal fires while write is pending + ac.abort(); + + await assert.rejects(writePromise, { name: 'AbortError' }); + + // Clean up + writer.end(); + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { break; } +} + +async function testWriteWithPreAbortedSignal() { + const { writer, readable } = push({ highWaterMark: 1 }); + + const ac = new AbortController(); + ac.abort(); + + // Pre-aborted signal should reject immediately + await assert.rejects( + writer.write('data', { signal: ac.signal }), + { name: 'AbortError' }, + ); + + // Writer should still be usable for other writes + writer.write('ok'); + writer.end(); + const data = await text(readable); + assert.strictEqual(data, 'ok'); +} + +async function testCancelledWriteRemovedFromQueue() { + const { writer, readable } = push({ highWaterMark: 1 }); + + // Fill the buffer + writer.writeSync('first'); + + const ac = new AbortController(); + // This write should be queued since buffer is full + const cancelledWrite = writer.write('cancelled', { signal: ac.signal }); + + // Cancel it + ac.abort(); + await cancelledWrite.catch(() => {}); + + // Drain 'first' to make room for the replacement write + const iter = readable[Symbol.asyncIterator](); + await iter.next(); + + // The cancelled write should NOT occupy a pending slot. + // A new write should succeed now that the buffer has room. + await writer.write('second'); + writer.end(); + + const result = await iter.next(); + // 'second' should be the next (and only remaining) chunk + const decoder = new TextDecoder(); + let data = ''; + for (const chunk of result.value) { + data += decoder.decode(chunk, { stream: true }); + } + assert.strictEqual(data, 'second'); + await iter.return(); +} + +async function testOndrainResolvesFalseOnConsumerBreak() { + const { writer, readable } = push({ highWaterMark: 1 }); + + // Fill the buffer so desiredSize = 0 + writer.writeSync('a'); + + // Also queue a pending write so that reading one chunk + // doesn't clear backpressure (the pending write refills the slot) + const pendingWrite = writer.write('b'); + + // Start a drain wait - still at capacity + const drainPromise = ondrain(writer); + + // Consumer returns without draining enough to clear backpressure + const iter = readable[Symbol.asyncIterator](); + await iter.return(); + + // Ondrain should resolve false since the consumer terminated + const result = await drainPromise; + assert.strictEqual(result, false); + await pendingWrite.catch(() => {}); // Ignore write rejection +} + +async function testOndrainRejectsOnConsumerThrow() { + const { writer, readable } = push({ highWaterMark: 1 }); + + // Fill the buffer so desiredSize = 0 + writer.writeSync('a'); + + // Also queue a pending write so that reading one chunk + // doesn't clear backpressure (the pending write refills the slot) + const pendingWrite = writer.write('b'); + + // Start a drain wait - still at capacity + const drainPromise = ondrain(writer); + + // Consumer throws via iterator.throw() before draining enough + // to clear backpressure. The drain should reject. + const iter = readable[Symbol.asyncIterator](); + await iter.throw(new Error('consumer error')); + + await assert.rejects(drainPromise, /consumer error/); + await pendingWrite.catch(() => {}); // Ignore write rejection +} + +async function testWritev() { + const { writer, readable } = push({ highWaterMark: 10 }); + const enc = new TextEncoder(); + writer.writev([enc.encode('hel'), enc.encode('lo')]); + writer.endSync(); + const result = await text(readable); + assert.strictEqual(result, 'hello'); +} + +async function testWritevSync() { + const { writer, readable } = push({ highWaterMark: 10 }); + const enc = new TextEncoder(); + assert.strictEqual(writer.writevSync([enc.encode('hel'), enc.encode('lo')]), true); + writer.endSync(); + const result = await text(readable); + assert.strictEqual(result, 'hello'); +} + +async function testWritevMixedTypes() { + const { writer, readable } = push({ highWaterMark: 10 }); + // Mix strings and Uint8Arrays + writer.writev(['hel', new TextEncoder().encode('lo')]); + writer.endSync(); + const result = await text(readable); + assert.strictEqual(result, 'hello'); +} + +async function testWriteAfterEnd() { + const { writer } = push(); + writer.endSync(); + // Sync write after end returns false + assert.strictEqual(writer.writeSync('fail'), false); + // Async write after end rejects + await assert.rejects( + () => writer.write('fail'), + { code: 'ERR_INVALID_STATE' }, + ); +} + +async function testWriteAfterFail() { + const { writer } = push(); + writer.fail(new Error('failed')); + // Sync write after fail returns false + assert.strictEqual(writer.writeSync('fail'), false); + // Async write after fail rejects with the stored error + await assert.rejects( + () => writer.write('fail'), + { message: 'failed' }, + ); +} + +async function testFail() { + const { writer, readable } = push(); + writer.writeSync('hello'); + writer.fail(new Error('boom')); + // Second fail is a no-op (already errored) + writer.fail(new Error('boom2')); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { /* consume */ } + }, { message: 'boom' }); +} + +async function testEndAsyncReturnValue() { + const { writer, readable } = push(); + writer.writeSync('hello'); + // Start consuming concurrently (end() waits for drain) + const consume = (async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { /* drain */ } + })(); + const total = await writer.end(); + assert.strictEqual(total, 5); + await consume; +} + +async function testWriteUint8Array() { + const { writer, readable } = push(); + writer.write(new Uint8Array([72, 73])); // 'HI' + writer.endSync(); + const result = await text(readable); + assert.strictEqual(result, 'HI'); +} + +async function testOndrainWaitsForDrain() { + const { writer, readable } = push({ highWaterMark: 1 }); + writer.writeSync('a'); // Fills buffer + + let drainState = 'pending'; + const drainPromise = ondrain(writer).then((v) => { drainState = v; }); + + await new Promise(setImmediate); + assert.strictEqual(drainState, 'pending'); // Still waiting + + // Read to drain + const iter = readable[Symbol.asyncIterator](); + await iter.next(); + + await drainPromise; + assert.strictEqual(drainState, true); + writer.endSync(); +} + +// Consumer throw causes subsequent writes to reject with consumer's error +async function testConsumerThrowRejectsWrites() { + const { writer, readable } = push({ highWaterMark: 1 }); + writer.writeSync('a'); + + const iter = readable[Symbol.asyncIterator](); + await iter.throw(new Error('consumer boom')); + + // Subsequent async writes should reject with the consumer's error + await assert.rejects( + () => writer.write('x'), + { message: 'consumer boom' }, + ); +} + +// end() resolves a pending read as done:true +async function testEndResolvesPendingRead() { + const { writer, readable } = push(); + + // Consumer starts reading — blocks because buffer is empty + const iter = readable[Symbol.asyncIterator](); + const readPromise = iter.next(); + + // Give the read a tick to enter the pending state + await new Promise(setImmediate); + + // End the writer — should resolve the pending read with done:true + writer.endSync(); + const result = await readPromise; + assert.strictEqual(result.done, true); +} + +// fail() rejects a pending read with the error +async function testFailRejectsPendingRead() { + const { writer, readable } = push(); + + const iter = readable[Symbol.asyncIterator](); + const readPromise = iter.next(); + + await new Promise(setImmediate); + + writer.fail(new Error('fail during read')); + await assert.rejects( + () => readPromise, + { message: 'fail during read' }, + ); +} + +// end() while writes are pending rejects those writes +async function testEndRejectsPendingWrites() { + const { writer, readable } = push({ highWaterMark: 1, backpressure: 'block' }); + writer.writeSync('a'); // fill buffer + + // This write blocks on backpressure + const writePromise = writer.write('b'); + + await new Promise(setImmediate); + + // Ending should reject the pending write + writer.endSync(); + + await assert.rejects( + () => writePromise, + { code: 'ERR_INVALID_STATE' }, + ); + + // Clean up: drain the readable + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { break; } +} + +async function testEndIdempotentWhenClosed() { + const { writer, readable } = push({ highWaterMark: 10 }); + await writer.write('hello'); + // Start consuming concurrently (end() waits for drain) + const consume = (async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { /* drain */ } + })(); + const first = await writer.end(); + assert.strictEqual(first, 5); + // Second end() should resolve with same byte count (idempotent) + const second = await writer.end(); + assert.strictEqual(second, 5); + await consume; +} + +async function testAsyncDispose() { + const { writer, readable } = push({ highWaterMark: 10 }); + writer.writeSync('hello'); + // Symbol.asyncDispose calls fail() with no argument + await writer[Symbol.asyncDispose](); + // Writer is now errored, writes should fail + assert.strictEqual(writer.writeSync('fail'), false); + // Drain readable + try { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { /* consume */ } + } catch { + // Expected - reader sees the error + } +} + +async function testSyncDispose() { + const { writer, readable } = push({ highWaterMark: 10 }); + writer.writeSync('hello'); + // Symbol.dispose calls fail() with no argument + writer[Symbol.dispose](); + // Writer is now errored, writes should fail + assert.strictEqual(writer.writeSync('fail'), false); + // Drain readable + try { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { /* consume */ } + } catch { + // Expected + } +} + +async function testEndRejectsWhenErrored() { + const { writer, readable } = push({ highWaterMark: 10 }); + await writer.write('hello'); + const err = new Error('boom'); + await writer.fail(err); + // end() after fail should reject with the stored error + await assert.rejects( + () => writer.end(), + (e) => e === err, + ); + // Drain readable + try { + // eslint-disable-next-line no-unused-vars + for await (const _ of readable) { break; } + } catch { + // Expected - reader may see the error + } +} + +Promise.all([ + testOndrain(), + testOndrainNonDrainable(), + testWriteWithSignalRejects(), + testWriteWithPreAbortedSignal(), + testCancelledWriteRemovedFromQueue(), + testOndrainResolvesFalseOnConsumerBreak(), + testOndrainRejectsOnConsumerThrow(), + testWritev(), + testWritevSync(), + testWritevMixedTypes(), + testWriteAfterEnd(), + testWriteAfterFail(), + testOndrainProtocolErrorPropagates(), + testFail(), + testEndAsyncReturnValue(), + testWriteUint8Array(), + testOndrainWaitsForDrain(), + testConsumerThrowRejectsWrites(), + testEndResolvesPendingRead(), + testFailRejectsPendingRead(), + testEndRejectsPendingWrites(), + testEndIdempotentWhenClosed(), + testEndRejectsWhenErrored(), + testAsyncDispose(), + testSyncDispose(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-share-async.js b/test/parallel/test-stream-iter-share-async.js new file mode 100644 index 00000000000000..86b0eb9b273a34 --- /dev/null +++ b/test/parallel/test-stream-iter-share-async.js @@ -0,0 +1,282 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + share, + text, +} = require('stream/iter'); + +const { setTimeout } = require('timers/promises'); + +// ============================================================================= +// Async share() +// ============================================================================= + +async function testBasicShare() { + const shared = share(from('hello shared')); + + const consumer = shared.pull(); + const data = await text(consumer); + assert.strictEqual(data, 'hello shared'); +} + +async function testShareMultipleConsumers() { + async function* gen() { + yield [new TextEncoder().encode('chunk1')]; + yield [new TextEncoder().encode('chunk2')]; + yield [new TextEncoder().encode('chunk3')]; + } + + const shared = share(gen(), { highWaterMark: 16 }); + + const c1 = shared.pull(); + const c2 = shared.pull(); + + assert.strictEqual(shared.consumerCount, 2); + + const [data1, data2] = await Promise.all([ + text(c1), + text(c2), + ]); + + assert.strictEqual(data1, 'chunk1chunk2chunk3'); + assert.strictEqual(data2, 'chunk1chunk2chunk3'); +} + +async function testShareConsumerCount() { + const shared = share(from('data')); + + assert.strictEqual(shared.consumerCount, 0); + + const c1 = shared.pull(); + assert.strictEqual(shared.consumerCount, 1); + + const c2 = shared.pull(); + assert.strictEqual(shared.consumerCount, 2); + + // Cancel detaches all consumers + shared.cancel(); + assert.strictEqual(shared.consumerCount, 0); + + // Both should complete immediately + const [data1, data2] = await Promise.all([ + text(c1), + text(c2), + ]); + assert.strictEqual(data1, ''); + assert.strictEqual(data2, ''); +} + +async function testShareCancel() { + const shared = share(from('data')); + const consumer = shared.pull(); + + shared.cancel(); + assert.strictEqual(shared.consumerCount, 0); + + const batches = []; + for await (const batch of consumer) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +async function testShareCancelMidIteration() { + // Verify that cancel during iteration stops data flow + let sourceReturnCalled = false; + const enc = new TextEncoder(); + async function* gen() { + try { + yield [enc.encode('a')]; + yield [enc.encode('b')]; + yield [enc.encode('c')]; + } finally { + sourceReturnCalled = true; + } + } + const shared = share(gen(), { highWaterMark: 16 }); + const consumer = shared.pull(); + + const items = []; + for await (const batch of consumer) { + for (const chunk of batch) { + items.push(new TextDecoder().decode(chunk)); + } + // Cancel after first batch + shared.cancel(); + } + assert.strictEqual(items.length, 1); + assert.strictEqual(items[0], 'a'); + + await new Promise(setImmediate); + assert.strictEqual(sourceReturnCalled, true); +} + +async function testShareCancelWithReason() { + const shared = share(from('data')); + const consumer = shared.pull(); + + shared.cancel(new Error('share cancelled')); + + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of consumer) { + assert.fail('Should not reach here'); + } + }, + { message: 'share cancelled' }, + ); +} + +async function testShareAbortSignal() { + const ac = new AbortController(); + const shared = share(from('data'), { signal: ac.signal }); + const consumer = shared.pull(); + + ac.abort(); + + const batches = []; + for await (const batch of consumer) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +async function testShareAlreadyAborted() { + const ac = new AbortController(); + ac.abort(); + + const shared = share(from('data'), { signal: ac.signal }); + const consumer = shared.pull(); + + const batches = []; + for await (const batch of consumer) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +// ============================================================================= +// Source error propagation +// ============================================================================= + +async function testShareSourceError() { + async function* failingSource() { + yield [new TextEncoder().encode('a')]; + throw new Error('share source boom'); + } + const shared = share(failingSource()); + const c1 = shared.pull(); + const c2 = shared.pull(); + + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of c1) { /* consume */ } + }, { message: 'share source boom' }); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of c2) { /* consume */ } + }, { message: 'share source boom' }); +} + +async function testShareLateJoiningConsumer() { + // A consumer that joins after some data has been consumed should only + // see data remaining in the buffer (not items already trimmed). + const enc = new TextEncoder(); + async function* gen() { + yield [enc.encode('a')]; + yield [enc.encode('b')]; + yield [enc.encode('c')]; + } + const shared = share(gen(), { highWaterMark: 16 }); + + // First consumer reads all data + const c1 = shared.pull(); + const data1 = await text(c1); + assert.strictEqual(data1, 'abc'); + + // Late-joining consumer: source is exhausted, buffer has been trimmed + // past all data by c1's reads, so c2 gets nothing. + const c2 = shared.pull(); + const data2 = await text(c2); + assert.strictEqual(data2, ''); +} + +async function testShareConsumerBreak() { + // Verify that a consumer breaking mid-iteration detaches properly + const enc = new TextEncoder(); + async function* gen() { + yield [enc.encode('a')]; + yield [enc.encode('b')]; + yield [enc.encode('c')]; + } + const shared = share(gen(), { highWaterMark: 16 }); + const c1 = shared.pull(); + const c2 = shared.pull(); + + assert.strictEqual(shared.consumerCount, 2); + + // c1 breaks after first batch + // eslint-disable-next-line no-unused-vars + for await (const _ of c1) { + break; + } + // c1 should be detached + assert.strictEqual(shared.consumerCount, 1); + + // c2 should still get all data + const data2 = await text(c2); + assert.strictEqual(data2, 'abc'); +} + +async function testShareMultipleConsumersConcurrentPull() { + // Multiple consumers pulling concurrently should each receive all items + // even when only one item is pulled from source at a time. + async function* slowSource() { + const enc = new TextEncoder(); + for (let i = 0; i < 5; i++) { + await setTimeout(1); + yield [enc.encode(`item-${i}`)]; + } + } + const shared = share(slowSource()); + const c1 = shared.pull(); + const c2 = shared.pull(); + const c3 = shared.pull(); + + const [t1, t2, t3] = await Promise.all([ + text(c1), text(c2), text(c3), + ]); + + const expected = 'item-0item-1item-2item-3item-4'; + assert.strictEqual(t1, expected); + assert.strictEqual(t2, expected); + assert.strictEqual(t3, expected); +} + +// share() accepts string source directly (normalized via from()) +async function testShareStringSource() { + const shared = share('hello-share'); + const result = await text(shared.pull()); + assert.strictEqual(result, 'hello-share'); +} + +Promise.all([ + testBasicShare(), + testShareMultipleConsumers(), + testShareConsumerCount(), + testShareCancel(), + testShareCancelMidIteration(), + testShareCancelWithReason(), + testShareAbortSignal(), + testShareAlreadyAborted(), + testShareSourceError(), + testShareLateJoiningConsumer(), + testShareConsumerBreak(), + testShareMultipleConsumersConcurrentPull(), + testShareStringSource(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-share-coverage.js b/test/parallel/test-stream-iter-share-coverage.js new file mode 100644 index 00000000000000..48866e61deab9b --- /dev/null +++ b/test/parallel/test-stream-iter-share-coverage.js @@ -0,0 +1,128 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Coverage tests for share.js: protocol happy path, dispose, throw, +// non-Error source throws. + +const common = require('../common'); +const assert = require('assert'); +const { + Share, + SyncShare, + share, + shareSync, + shareProtocol, + shareSyncProtocol, + from, + fromSync, + text, + textSync, +} = require('stream/iter'); + +// Share.from — protocol symbol happy path (returns valid share object) +async function testShareProtocolHappyPath() { + const obj = { + [shareProtocol](options) { + return share(from('protocol-data'), options); + }, + }; + const shared = Share.from(obj); + const result = await text(shared.pull()); + assert.strictEqual(result, 'protocol-data'); +} + +// SyncShare.fromSync — protocol symbol happy path +async function testSyncShareProtocolHappyPath() { + const obj = { + [shareSyncProtocol](options) { + return shareSync(fromSync('sync-protocol'), options); + }, + }; + const shared = SyncShare.fromSync(obj); + const result = textSync(shared.pull()); + assert.strictEqual(result, 'sync-protocol'); +} + +// Async share — Symbol.dispose cancels +async function testShareDispose() { + const shared = share(from('dispose-test')); + const consumer = shared.pull(); + shared[Symbol.dispose](); + assert.strictEqual(shared.consumerCount, 0); + // Consumer should yield nothing (cancelled before read) + const result = await text(consumer); + assert.strictEqual(result, ''); +} + +// Sync share — Symbol.dispose cancels +async function testSyncShareDispose() { + const shared = shareSync(fromSync('sync-dispose')); + const consumer = shared.pull(); + shared[Symbol.dispose](); + assert.strictEqual(shared.consumerCount, 0); + const result = textSync(consumer); + assert.strictEqual(result, ''); +} + +// Async consumer iterator throw() +async function testAsyncIteratorThrow() { + const shared = share(from('throw-test')); + const consumer = shared.pull(); + const iter = consumer[Symbol.asyncIterator](); + const first = await iter.next(); + assert.strictEqual(first.done, false); + const result = await iter.throw(new Error('test-throw')); + assert.strictEqual(result.done, true); + assert.strictEqual(shared.consumerCount, 0); +} + +// Sync consumer iterator throw() +async function testSyncIteratorThrow() { + const shared = shareSync(fromSync('throw-sync')); + const consumer = shared.pull(); + const iter = consumer[Symbol.iterator](); + const first = iter.next(); + assert.strictEqual(first.done, false); + const result = iter.throw(new Error('test-throw')); + assert.strictEqual(result.done, true); + assert.strictEqual(shared.consumerCount, 0); +} + +// Async source throws non-Error value → wrapError +async function testShareSourceThrowsNonError() { + async function* source() { + yield [new TextEncoder().encode('ok')]; + throw 'not an error'; // eslint-disable-line no-throw-literal + } + const shared = share(source()); + const consumer = shared.pull(); + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const batch of consumer) { /* consume */ } + }, { code: 'ERR_OPERATION_FAILED' }); +} + +// Sync source throws non-Error value → wrapError +async function testSyncShareSourceThrowsNonError() { + function* source() { + yield [new TextEncoder().encode('ok')]; + throw 42; // eslint-disable-line no-throw-literal + } + const shared = shareSync(source()); + const consumer = shared.pull(); + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const batch of consumer) { /* consume */ } + }, { code: 'ERR_OPERATION_FAILED' }); +} + +Promise.all([ + testShareProtocolHappyPath(), + testSyncShareProtocolHappyPath(), + testShareDispose(), + testSyncShareDispose(), + testAsyncIteratorThrow(), + testSyncIteratorThrow(), + testShareSourceThrowsNonError(), + testSyncShareSourceThrowsNonError(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-share-from.js b/test/parallel/test-stream-iter-share-from.js new file mode 100644 index 00000000000000..362bfef78f1a2e --- /dev/null +++ b/test/parallel/test-stream-iter-share-from.js @@ -0,0 +1,242 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + fromSync, + share, + Share, + SyncShare, + text, + textSync, + +} = require('stream/iter'); + +// ============================================================================= +// Share.from +// ============================================================================= + +async function testShareFrom() { + const shared = Share.from(from('share-from')); + const consumer = shared.pull(); + + const data = await text(consumer); + assert.strictEqual(data, 'share-from'); +} + +function testShareFromRejectsNonStreamable() { + assert.throws( + () => Share.from(12345), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +// ============================================================================= +// SyncShare.fromSync +// ============================================================================= + +async function testSyncShareFromSync() { + const shared = SyncShare.fromSync(fromSync('sync-share-from')); + const consumer = shared.pull(); + + const data = textSync(consumer); + assert.strictEqual(data, 'sync-share-from'); +} + +function testSyncShareFromRejectsNonStreamable() { + assert.throws( + () => SyncShare.fromSync(12345), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +// ============================================================================= +// Protocol validation +// ============================================================================= + +function testShareProtocolReturnsNull() { + const obj = { + [Symbol.for('Stream.shareProtocol')]() { return null; }, + }; + assert.throws( + () => Share.from(obj), + { code: 'ERR_INVALID_RETURN_VALUE' }, + ); +} + +function testShareProtocolReturnsNonObject() { + const obj = { + [Symbol.for('Stream.shareProtocol')]() { return 42; }, + }; + assert.throws( + () => Share.from(obj), + { code: 'ERR_INVALID_RETURN_VALUE' }, + ); +} + +function testSyncShareProtocolReturnsNull() { + const obj = { + [Symbol.for('Stream.shareSyncProtocol')]() { return null; }, + }; + assert.throws( + () => SyncShare.fromSync(obj), + { code: 'ERR_INVALID_RETURN_VALUE' }, + ); +} + +function testSyncShareProtocolReturnsNonObject() { + const obj = { + [Symbol.for('Stream.shareSyncProtocol')]() { return 'bad'; }, + }; + assert.throws( + () => SyncShare.fromSync(obj), + { code: 'ERR_INVALID_RETURN_VALUE' }, + ); +} + +// ============================================================================= +// Block backpressure: two consumers, slow consumer blocks the source +// ============================================================================= + +async function testShareBlockBackpressure() { + // A source that yields 5 items. With two consumers and highWaterMark: 2, + // the fast consumer drives the source forward. The slow consumer holds back + // trimming, causing the buffer to fill. 'block' mode should stall the + // source pull until the slow consumer catches up. + const enc = new TextEncoder(); + async function* source() { + for (let i = 0; i < 5; i++) { + yield [enc.encode(`item${i}`)]; + } + } + const shared = share(source(), { highWaterMark: 2, backpressure: 'block' }); + const fast = shared.pull(); + const slow = shared.pull(); + + // Both consumers should ultimately receive all 5 items + const [fastData, slowData] = await Promise.all([ + text(fast), + text(slow), + ]); + + assert.strictEqual(fastData, 'item0item1item2item3item4'); + assert.strictEqual(slowData, 'item0item1item2item3item4'); +} + +// ============================================================================= +// Drop backpressure modes: use a fast + stalled consumer to trigger drops +// ============================================================================= + +async function testShareDropOldest() { + // Two consumers, fast reads eagerly then slow reads. With drop-oldest, + // the slow consumer's cursor is advanced past dropped items, so it + // misses old data and only sees recent items. + async function* source() { + for (let i = 0; i < 4; i++) { + yield [new TextEncoder().encode(`${i}`)]; + } + } + const shared = share(source(), { highWaterMark: 2, backpressure: 'drop-oldest' }); + const fast = shared.pull(); + const slow = shared.pull(); + + // Fast consumer reads all items + const fastItems = []; + for await (const batch of fast) { + for (const chunk of batch) { + fastItems.push(new TextDecoder().decode(chunk)); + } + } + assert.strictEqual(fastItems.length, 4); + + // Slow consumer reads after fast is done — old items were dropped + const slowItems = []; + for await (const batch of slow) { + for (const chunk of batch) { + slowItems.push(new TextDecoder().decode(chunk)); + } + } + // The slow consumer should see fewer items than were produced + assert.ok(slowItems.length < 4, + `Expected < 4 items after drop-oldest, got ${slowItems.length}`); + assert.ok(slowItems.length > 0, + 'Expected at least some items after drop-oldest'); + // The last item should always be present (most recent items kept) + assert.strictEqual(slowItems[slowItems.length - 1], '3'); +} + +async function testShareDropNewest() { + // With drop-newest and a stalled consumer, the async path allows the + // buffer to grow beyond highWaterMark (the "drop" applies to the + // backpressure signal, not the buffer contents). Both consumers + // ultimately see all items. + async function* source() { + for (let i = 0; i < 4; i++) { + yield [new TextEncoder().encode(`${i}`)]; + } + } + const shared = share(source(), { highWaterMark: 2, backpressure: 'drop-newest' }); + const fast = shared.pull(); + const slow = shared.pull(); + + // Fast consumer reads all items + const fastItems = []; + for await (const batch of fast) { + for (const chunk of batch) { + fastItems.push(new TextDecoder().decode(chunk)); + } + } + assert.strictEqual(fastItems.length, 4); + + // Slow consumer also sees all items (buffer grew past hwm) + const slowItems = []; + for await (const batch of slow) { + for (const chunk of batch) { + slowItems.push(new TextDecoder().decode(chunk)); + } + } + assert.strictEqual(slowItems.length, 4); + assert.strictEqual(slowItems[0], '0'); + assert.strictEqual(slowItems[3], '3'); +} + +// ============================================================================= +// Strict backpressure: should throw when buffer overflows +// ============================================================================= + +async function testShareStrictBackpressure() { + async function* source() { + for (let i = 0; i < 10; i++) { + yield [new TextEncoder().encode(`${i}`)]; + } + } + const shared = share(source(), { highWaterMark: 2, backpressure: 'strict' }); + const fast = shared.pull(); + // Create a second consumer that never reads — this prevents buffer trimming + shared.pull(); + + // The fast consumer's pulls will eventually cause the buffer to exceed + // the highWaterMark (since the slow consumer prevents trimming), + // triggering an ERR_OUT_OF_RANGE error. + await assert.rejects(async () => { + // eslint-disable-next-line no-unused-vars + for await (const _ of fast) { /* consume */ } + }, { code: 'ERR_OUT_OF_RANGE' }); +} + +Promise.all([ + testShareFrom(), + testShareFromRejectsNonStreamable(), + testSyncShareFromSync(), + testSyncShareFromRejectsNonStreamable(), + testShareProtocolReturnsNull(), + testShareProtocolReturnsNonObject(), + testSyncShareProtocolReturnsNull(), + testSyncShareProtocolReturnsNonObject(), + testShareBlockBackpressure(), + testShareDropOldest(), + testShareDropNewest(), + testShareStrictBackpressure(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-share-sync.js b/test/parallel/test-stream-iter-share-sync.js new file mode 100644 index 00000000000000..98ad74fe4f1c72 --- /dev/null +++ b/test/parallel/test-stream-iter-share-sync.js @@ -0,0 +1,162 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + shareSync, + fromSync, + textSync, + +} = require('stream/iter'); + +// ============================================================================= +// Sync share +// ============================================================================= + +async function testShareSyncBasic() { + const shared = shareSync(fromSync('sync shared')); + + const consumer = shared.pull(); + const data = textSync(consumer); + assert.strictEqual(data, 'sync shared'); +} + +async function testShareSyncMultipleConsumers() { + const enc = new TextEncoder(); + function* gen() { + yield [enc.encode('a')]; + yield [enc.encode('b')]; + yield [enc.encode('c')]; + } + + const shared = shareSync(gen(), { highWaterMark: 16 }); + + const c1 = shared.pull(); + const c2 = shared.pull(); + + const data1 = textSync(c1); + const data2 = textSync(c2); + + assert.strictEqual(data1, 'abc'); + assert.strictEqual(data2, 'abc'); +} + +function testShareSyncCancel() { + // Verify that cancel() on a pre-iteration share yields nothing + const shared = shareSync(fromSync('data')); + const consumer = shared.pull(); + + shared.cancel(); + assert.strictEqual(shared.consumerCount, 0); + + const batches = []; + for (const batch of consumer) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +function testShareSyncCancelMidIteration() { + // Verify cancel during iteration stops data flow and cleans up + const enc = new TextEncoder(); + let sourceReturnCalled = false; + function* gen() { + try { + yield [enc.encode('a')]; + yield [enc.encode('b')]; + yield [enc.encode('c')]; + } finally { + sourceReturnCalled = true; + } + } + const shared = shareSync(gen(), { highWaterMark: 16 }); + const consumer = shared.pull(); + + const items = []; + for (const batch of consumer) { + for (const chunk of batch) { + items.push(new TextDecoder().decode(chunk)); + } + // Cancel after first batch + shared.cancel(); + } + assert.strictEqual(items.length, 1); + assert.strictEqual(items[0], 'a'); + assert.strictEqual(sourceReturnCalled, true); +} + +function testShareSyncCancelWithReason() { + // When cancel(reason) is called, a consumer that hasn't started + // iterating is already detached, so it sees done:true (not the error). + // But a consumer that is mid-iteration when another consumer cancels + // with a reason will see the error on the next pull after cancel. + const enc = new TextEncoder(); + function* gen() { + yield [enc.encode('a')]; + yield [enc.encode('b')]; + yield [enc.encode('c')]; + } + const shared = shareSync(gen(), { highWaterMark: 16 }); + const c1 = shared.pull(); + const c2 = shared.pull(); + + // c1 reads one item, then c2 cancels with a reason + const iter1 = c1[Symbol.iterator](); + const first = iter1.next(); + assert.strictEqual(first.done, false); + + shared.cancel(new Error('sync cancel reason')); + + // c1 was already iterating, it's now detached → done + const next = iter1.next(); + assert.strictEqual(next.done, true); + + // c2 never started, also detached → done (not error) + const batches = []; + for (const batch of c2) { + batches.push(batch); + } + assert.strictEqual(batches.length, 0); +} + +// ============================================================================= +// Source error propagation +// ============================================================================= + +function testShareSyncSourceError() { + function* failingSource() { + yield [new TextEncoder().encode('ok')]; + throw new Error('sync share boom'); + } + const shared = shareSync(failingSource()); + const c1 = shared.pull(); + const c2 = shared.pull(); + + // Both consumers should see the error + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of c1) { /* consume */ } + }, { message: 'sync share boom' }); + assert.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const _ of c2) { /* consume */ } + }, { message: 'sync share boom' }); +} + +// shareSync() accepts string source directly (normalized via fromSync()) +function testShareSyncStringSource() { + const shared = shareSync('hello-sync-share'); + const result = textSync(shared.pull()); + assert.strictEqual(result, 'hello-sync-share'); +} + +Promise.all([ + testShareSyncBasic(), + testShareSyncMultipleConsumers(), + testShareSyncCancel(), + testShareSyncCancelMidIteration(), + testShareSyncCancelWithReason(), + testShareSyncSourceError(), + testShareSyncStringSource(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-sharedarraybuffer.js b/test/parallel/test-stream-iter-sharedarraybuffer.js new file mode 100644 index 00000000000000..7aff205bf7664a --- /dev/null +++ b/test/parallel/test-stream-iter-sharedarraybuffer.js @@ -0,0 +1,195 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + fromSync, + bytes, + bytesSync, + text, + textSync, + arrayBuffer, + arrayBufferSync, + pipeTo, + pipeToSync, + pull, + pullSync, +} = require('stream/iter'); + +// ============================================================================= +// from() / fromSync() with SharedArrayBuffer +// ============================================================================= + +function testFromSyncSAB() { + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([10, 20, 30, 40]); + const batches = []; + for (const batch of fromSync(sab)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new Uint8Array([10, 20, 30, 40])); +} + +async function testFromAsyncSAB() { + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([10, 20, 30, 40]); + const batches = []; + for await (const batch of from(sab)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new Uint8Array([10, 20, 30, 40])); +} + +// ============================================================================= +// Consumers with SAB-backed source +// ============================================================================= + +function testBytesSyncSAB() { + const sab = new SharedArrayBuffer(5); + new Uint8Array(sab).set([104, 101, 108, 108, 111]); // 'hello' + const data = bytesSync(fromSync(sab)); + assert.deepStrictEqual(data, new Uint8Array([104, 101, 108, 108, 111])); +} + +async function testBytesAsyncSAB() { + const sab = new SharedArrayBuffer(5); + new Uint8Array(sab).set([104, 101, 108, 108, 111]); // 'hello' + const data = await bytes(from(sab)); + assert.deepStrictEqual(data, new Uint8Array([104, 101, 108, 108, 111])); +} + +function testTextSyncSAB() { + const sab = new SharedArrayBuffer(5); + new Uint8Array(sab).set([104, 101, 108, 108, 111]); // 'hello' + const result = textSync(fromSync(sab)); + assert.strictEqual(result, 'hello'); +} + +async function testTextAsyncSAB() { + const sab = new SharedArrayBuffer(5); + new Uint8Array(sab).set([104, 101, 108, 108, 111]); // 'hello' + const result = await text(from(sab)); + assert.strictEqual(result, 'hello'); +} + +function testArrayBufferSyncSAB() { + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const result = arrayBufferSync(fromSync(sab)); + assert.ok(result instanceof SharedArrayBuffer || result instanceof ArrayBuffer); + assert.deepStrictEqual(new Uint8Array(result), new Uint8Array([1, 2, 3, 4])); +} + +async function testArrayBufferAsyncSAB() { + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const result = await arrayBuffer(from(sab)); + assert.ok(result instanceof SharedArrayBuffer || result instanceof ArrayBuffer); + assert.deepStrictEqual(new Uint8Array(result), new Uint8Array([1, 2, 3, 4])); +} + +// ============================================================================= +// pipeTo / pipeToSync with SAB source +// ============================================================================= + +function testPipeToSyncSAB() { + const sab = new SharedArrayBuffer(3); + new Uint8Array(sab).set([65, 66, 67]); // 'ABC' + const written = []; + const writer = { + writeSync(chunk) { written.push(chunk); return true; }, + endSync() { return written.length; }, + }; + const totalBytes = pipeToSync(fromSync(sab), writer); + assert.strictEqual(totalBytes, 3); + assert.deepStrictEqual(written[0], new Uint8Array([65, 66, 67])); +} + +async function testPipeToAsyncSAB() { + const sab = new SharedArrayBuffer(3); + new Uint8Array(sab).set([65, 66, 67]); // 'ABC' + const written = []; + const writer = { + async write(chunk) { written.push(chunk); }, + async end() { return written.length; }, + }; + await pipeTo(from(sab), writer); + assert.deepStrictEqual(written[0], new Uint8Array([65, 66, 67])); +} + +// ============================================================================= +// pull / pullSync with SAB-yielding generator +// ============================================================================= + +function testPullSyncSABChunks() { + function* gen() { + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([1, 2]); + yield [new Uint8Array(sab)]; + } + const batches = []; + for (const batch of pullSync(gen())) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new Uint8Array([1, 2])); +} + +async function testPullAsyncSABChunks() { + async function* gen() { + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([3, 4]); + yield [new Uint8Array(sab)]; + } + const batches = []; + for await (const batch of pull(gen())) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new Uint8Array([3, 4])); +} + +// ============================================================================= +// Transform returning SAB +// ============================================================================= + +async function testTransformReturningSAB() { + function* source() { + yield [new Uint8Array([1, 2, 3])]; + } + const transform = (chunks) => { + if (chunks === null) return null; + // Transform returns a Uint8Array backed by a SharedArrayBuffer + const sab = new SharedArrayBuffer(chunks[0].length); + new Uint8Array(sab).set(chunks[0]); + return new Uint8Array(sab); + }; + const batches = []; + for await (const batch of pull(source(), transform)) { + batches.push(batch); + } + assert.strictEqual(batches.length, 1); + assert.deepStrictEqual(batches[0][0], new Uint8Array([1, 2, 3])); +} + +// ============================================================================= + +Promise.all([ + testFromSyncSAB(), + testFromAsyncSAB(), + testBytesSyncSAB(), + testBytesAsyncSAB(), + testTextSyncSAB(), + testTextAsyncSAB(), + testArrayBufferSyncSAB(), + testArrayBufferAsyncSAB(), + testPipeToSyncSAB(), + testPipeToAsyncSAB(), + testPullSyncSABChunks(), + testPullAsyncSABChunks(), + testTransformReturningSAB(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-transform-compat.js b/test/parallel/test-stream-iter-transform-compat.js new file mode 100644 index 00000000000000..b3180f41d98fa1 --- /dev/null +++ b/test/parallel/test-stream-iter-transform-compat.js @@ -0,0 +1,127 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); +const { promisify } = require('util'); +const { + from, + pull, + bytes, + text, +} = require('stream/iter'); +const { + compressGzip, + compressDeflate, + compressBrotli, + compressZstd, + decompressGzip, + decompressDeflate, + decompressBrotli, + decompressZstd, +} = require('zlib/iter'); + +// ============================================================================= +// Cross-compatibility: verify gzip/deflate output is compatible with zlib +// ============================================================================= + +async function testGzipCompatWithZlib() { + const gunzip = promisify(zlib.gunzip); + + const input = 'Cross-compat test with node:zlib. '.repeat(100); + const compressed = await bytes(pull(from(input), compressGzip())); + + // Decompress with standard zlib + const decompressed = await gunzip(compressed); + assert.strictEqual(decompressed.toString(), input); +} + +async function testDeflateCompatWithZlib() { + const inflate = promisify(zlib.inflate); + + const input = 'Cross-compat deflate test. '.repeat(100); + const compressed = await bytes(pull(from(input), compressDeflate())); + + // Decompress with standard zlib + const decompressed = await inflate(compressed); + assert.strictEqual(decompressed.toString(), input); +} + +async function testBrotliCompatWithZlib() { + const brotliDecompress = promisify(zlib.brotliDecompress); + + const input = 'Cross-compat brotli test. '.repeat(100); + const compressed = await bytes(pull(from(input), compressBrotli())); + + const decompressed = await brotliDecompress(compressed); + assert.strictEqual(decompressed.toString(), input); +} + +async function testZstdCompatWithZlib() { + const zstdDecompress = promisify(zlib.zstdDecompress); + + const input = 'Cross-compat zstd test. '.repeat(100); + const compressed = await bytes(pull(from(input), compressZstd())); + + const decompressed = await zstdDecompress(compressed); + assert.strictEqual(decompressed.toString(), input); +} + +// ============================================================================= +// Reverse compat: compress with zlib, decompress with new streams +// ============================================================================= + +async function testZlibGzipToNewStreams() { + const gzip = promisify(zlib.gzip); + + const input = 'Reverse compat gzip test. '.repeat(100); + const compressed = await gzip(input); + const result = await text(pull(from(compressed), decompressGzip())); + assert.strictEqual(result, input); +} + +async function testZlibDeflateToNewStreams() { + const deflate = promisify(zlib.deflate); + + const input = 'Reverse compat deflate test. '.repeat(100); + const compressed = await deflate(input); + const result = await text(pull(from(compressed), decompressDeflate())); + assert.strictEqual(result, input); +} + +async function testZlibBrotliToNewStreams() { + const brotliCompress = promisify(zlib.brotliCompress); + + const input = 'Reverse compat brotli test. '.repeat(100); + const compressed = await brotliCompress(input); + const result = await text(pull(from(compressed), decompressBrotli())); + assert.strictEqual(result, input); +} + +async function testZlibZstdToNewStreams() { + const zstdCompress = promisify(zlib.zstdCompress); + + const input = 'Reverse compat zstd test. '.repeat(100); + const compressed = await zstdCompress(input); + const result = await text(pull(from(compressed), decompressZstd())); + assert.strictEqual(result, input); +} + +// ============================================================================= +// Run all tests +// ============================================================================= + +(async () => { + // Cross-compat: new streams compress → zlib decompress + await testGzipCompatWithZlib(); + await testDeflateCompatWithZlib(); + await testBrotliCompatWithZlib(); + await testZstdCompatWithZlib(); + + // Reverse compat: zlib compress → new streams decompress + await testZlibGzipToNewStreams(); + await testZlibDeflateToNewStreams(); + await testZlibBrotliToNewStreams(); + await testZlibZstdToNewStreams(); +})().then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-transform-coverage.js b/test/parallel/test-stream-iter-transform-coverage.js new file mode 100644 index 00000000000000..d10766badd4dc6 --- /dev/null +++ b/test/parallel/test-stream-iter-transform-coverage.js @@ -0,0 +1,119 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Coverage tests for transform.js: abort cleanup, strategy/params options, +// early consumer exit triggering finally block. + +const common = require('../common'); +const assert = require('assert'); +const { + from, + pull, + bytes, + text, +} = require('stream/iter'); +const { + compressGzip, + decompressGzip, + compressDeflate, + decompressDeflate, + compressBrotli, + decompressBrotli, + compressZstd, + decompressZstd, +} = require('zlib/iter'); +const { constants } = require('zlib'); + +async function roundTrip(input, compress, decompress) { + return text(pull(from(input), compress, decompress)); +} + +// Abort mid-compression triggers finally cleanup +async function testAbortMidCompression() { + const ac = new AbortController(); + const largeInput = 'x'.repeat(100_000); + const compressed = pull(from(largeInput), compressGzip(), + { signal: ac.signal }); + const iter = compressed[Symbol.asyncIterator](); + + // Read one batch then abort + const first = await iter.next(); + assert.strictEqual(first.done, false); + ac.abort(); + await assert.rejects(iter.next(), { name: 'AbortError' }); +} + +// Early consumer exit (break from for-await) triggers finally +async function testEarlyConsumerExit() { + const largeInput = 'y'.repeat(100_000); + const compressed = pull(from(largeInput), compressGzip()); + + // eslint-disable-next-line no-unused-vars + for await (const batch of compressed) { + break; // Early exit — should trigger finally block cleanup + } + // If we get here without hanging or crashing, cleanup worked +} + +// Gzip with explicit strategy option +async function testGzipWithStrategy() { + const input = 'strategy test data '.repeat(100); + const c = compressGzip({ strategy: constants.Z_DEFAULT_STRATEGY }); + const result = await roundTrip(input, c, decompressGzip()); + assert.strictEqual(result, input); +} + +// Deflate with Z_FIXED strategy +async function testDeflateWithFixedStrategy() { + const input = 'fixed strategy '.repeat(100); + const c = compressDeflate({ strategy: constants.Z_FIXED }); + const result = await roundTrip(input, c, decompressDeflate()); + assert.strictEqual(result, input); +} + +// Brotli with custom quality param +async function testBrotliWithParams() { + const input = 'brotli params test '.repeat(100); + const params = { [constants.BROTLI_PARAM_QUALITY]: 5 }; + const result = await roundTrip(input, compressBrotli({ params }), + decompressBrotli()); + assert.strictEqual(result, input); +} + +// Zstd with custom compression level param +async function testZstdWithParams() { + const input = 'zstd params test '.repeat(100); + const params = { [constants.ZSTD_c_compressionLevel]: 10 }; + const result = await roundTrip(input, compressZstd({ params }), + decompressZstd()); + assert.strictEqual(result, input); +} + +// Gzip with custom chunkSize +async function testGzipWithChunkSize() { + const input = 'chunk size test'; + const c = compressGzip({ chunkSize: 256 }); + const d = decompressGzip({ chunkSize: 256 }); + const result = await roundTrip(input, c, d); + assert.strictEqual(result, input); +} + +// Invalid chunkSize throws when transform is invoked +async function testInvalidChunkSize() { + const tx = compressGzip({ chunkSize: 8 }); + await assert.rejects( + async () => await bytes(pull(from('data'), tx)), + { code: 'ERR_OUT_OF_RANGE' }, + ); +} + +Promise.all([ + testAbortMidCompression(), + testEarlyConsumerExit(), + testGzipWithStrategy(), + testDeflateWithFixedStrategy(), + testBrotliWithParams(), + testZstdWithParams(), + testGzipWithChunkSize(), + testInvalidChunkSize(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-transform-errors.js b/test/parallel/test-stream-iter-transform-errors.js new file mode 100644 index 00000000000000..62a3c178ac5f58 --- /dev/null +++ b/test/parallel/test-stream-iter-transform-errors.js @@ -0,0 +1,71 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + pull, + bytes, +} = require('stream/iter'); +const { + decompressGzip, + decompressDeflate, + decompressBrotli, + decompressZstd, +} = require('zlib/iter'); + +// ============================================================================= +// Decompression of corrupt data +// ============================================================================= + +async function testCorruptGzipData() { + const corrupt = new Uint8Array([0x1F, 0x8B, 0xFF, 0xFF, 0xFF]); + + await assert.rejects( + async () => await bytes(pull(from(corrupt), decompressGzip())), { + name: 'Error', + code: 'Z_DATA_ERROR', + }); +} + +async function testCorruptDeflateData() { + const corrupt = new Uint8Array([0x78, 0xFF, 0xFF, 0xFF]); + + await assert.rejects( + async () => await bytes(pull(from(corrupt), decompressDeflate())), { + name: 'Error', + code: 'Z_DATA_ERROR', + }); +} + +async function testCorruptBrotliData() { + const corrupt = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]); + + await assert.rejects( + async () => await bytes(pull(from(corrupt), decompressBrotli())), { + name: 'Error', + code: 'ERR__ERROR_FORMAT_PADDING_2', + }); +} + +async function testCorruptZstdData() { + // Completely invalid data (not even valid magic bytes) + const corrupt = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); + await assert.rejects( + async () => await bytes(pull(from(corrupt), decompressZstd())), { + name: 'Error', + code: 'ZSTD_error_prefix_unknown', + }); +} + +// ============================================================================= +// Run all tests +// ============================================================================= + +(async () => { + await testCorruptGzipData(); + await testCorruptDeflateData(); + await testCorruptBrotliData(); + await testCorruptZstdData(); +})().then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-transform-output.js b/test/parallel/test-stream-iter-transform-output.js new file mode 100644 index 00000000000000..a37cf08dc9d53f --- /dev/null +++ b/test/parallel/test-stream-iter-transform-output.js @@ -0,0 +1,225 @@ +// Flags: --experimental-stream-iter +'use strict'; + +// Tests for transform output normalization edge cases: +// ArrayBuffer, ArrayBufferView, iterables, strings, invalid types. + +const common = require('../common'); +const assert = require('assert'); +const { + pull, + pullSync, + bytes, + bytesSync, + from, + fromSync, +} = require('stream/iter'); + +// Stateless transform returns ArrayBuffer (async) +async function testTransformReturnsArrayBuffer() { + const tx = (chunks) => { + if (chunks === null) return null; + const ab = new ArrayBuffer(chunks[0].length); + new Uint8Array(ab).set(chunks[0]); + return ab; + }; + const data = await bytes(pull(from('AB'), tx)); + assert.deepStrictEqual(data, new TextEncoder().encode('AB')); +} + +// Stateless transform returns ArrayBuffer (sync) +async function testSyncTransformReturnsArrayBuffer() { + const tx = (chunks) => { + if (chunks === null) return null; + const ab = new ArrayBuffer(chunks[0].length); + new Uint8Array(ab).set(chunks[0]); + return ab; + }; + const data = bytesSync(pullSync(fromSync('AB'), tx)); + assert.deepStrictEqual(data, new TextEncoder().encode('AB')); +} + +// Stateless transform returns Float32Array (non-Uint8Array ArrayBufferView) +async function testTransformReturnsFloat32Array() { + const tx = (chunks) => { + if (chunks === null) return null; + return new Float32Array([1.0]); + }; + const data = await bytes(pull(from('x'), tx)); + assert.strictEqual(data.byteLength, 4); // 1 float32 = 4 bytes +} + +// Stateless sync transform returns Float32Array +async function testSyncTransformReturnsFloat32Array() { + const tx = (chunks) => { + if (chunks === null) return null; + return new Float32Array([1.0]); + }; + const data = bytesSync(pullSync(fromSync('x'), tx)); + assert.strictEqual(data.byteLength, 4); +} + +// Stateless transform returns a sync generator (iterable) +async function testTransformReturnsGenerator() { + const tx = (chunks) => { + if (chunks === null) return null; + return (function*() { + yield new Uint8Array([65]); + yield new Uint8Array([66]); + })(); + }; + const data = await bytes(pull(from('x'), tx)); + assert.deepStrictEqual(data, new Uint8Array([65, 66])); +} + +// Stateless sync transform returns a generator +async function testSyncTransformReturnsGenerator() { + const tx = (chunks) => { + if (chunks === null) return null; + return (function*() { + yield new Uint8Array([67]); + yield new Uint8Array([68]); + })(); + }; + const data = bytesSync(pullSync(fromSync('x'), tx)); + assert.deepStrictEqual(data, new Uint8Array([67, 68])); +} + +// Stateless async transform returns an async generator +async function testTransformReturnsAsyncGenerator() { + const tx = (chunks) => { + if (chunks === null) return null; + return (async function*() { + yield new Uint8Array([69]); + yield new Uint8Array([70]); + })(); + }; + const data = await bytes(pull(from('x'), tx)); + assert.deepStrictEqual(data, new Uint8Array([69, 70])); +} + +// Stateful async transform yields string +async function testStatefulTransformYieldsString() { + const tx = { + async *transform(source) { + for await (const chunks of source) { + if (chunks === null) return; + yield 'hello'; + } + }, + }; + const data = await bytes(pull(from('x'), tx)); + assert.deepStrictEqual(data, new TextEncoder().encode('hello')); +} + +// Stateful async transform yields ArrayBuffer +async function testStatefulTransformYieldsArrayBuffer() { + const tx = { + async *transform(source) { + for await (const chunks of source) { + if (chunks === null) return; + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([71, 72]); + yield ab; + } + }, + }; + const data = await bytes(pull(from('x'), tx)); + assert.deepStrictEqual(data, new Uint8Array([71, 72])); +} + +// Stateful sync transform yields string +async function testStatefulSyncTransformYieldsString() { + const tx = { + *transform(source) { + for (const chunks of source) { + if (chunks === null) return; + yield 'world'; + } + }, + }; + const data = bytesSync(pullSync(fromSync('x'), tx)); + assert.deepStrictEqual(data, new TextEncoder().encode('world')); +} + +// Flush returns single Uint8Array (not batch) +async function testFlushReturnsSingleUint8Array() { + const tx = (chunks) => { + if (chunks === null) return new Uint8Array([99]); // Flush returns single + return chunks; + }; + const data = await bytes(pull(from('x'), tx)); + // Should contain both the original data and the flush byte + assert.ok(data.includes(99)); +} + +// Flush returns string +async function testFlushReturnsString() { + const tx = (chunks) => { + if (chunks === null) return 'trailer'; + return chunks; + }; + const data = await bytes(pull(from('x'), tx)); + const trailer = new TextEncoder().encode('trailer'); + // Last bytes should be the trailer + const tail = data.slice(data.length - trailer.length); + assert.deepStrictEqual(tail, trailer); +} + +// Sync flush returns single Uint8Array +async function testSyncFlushReturnsSingleUint8Array() { + const tx = (chunks) => { + if (chunks === null) return new Uint8Array([88]); + return chunks; + }; + const data = bytesSync(pullSync(fromSync('x'), tx)); + assert.ok(data.includes(88)); +} + +// Transform returns invalid type → ERR_INVALID_ARG_TYPE +async function testTransformReturnsInvalidType() { + const tx = (chunks) => { + if (chunks === null) return null; + return 42; // Invalid + }; + await assert.rejects( + async () => { + // eslint-disable-next-line no-unused-vars + for await (const batch of pull(from('x'), tx)) { /* consume */ } + }, + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +// Sync transform returns invalid type → ERR_INVALID_ARG_TYPE +async function testSyncTransformReturnsInvalidType() { + const tx = (chunks) => { + if (chunks === null) return null; + return 42; + }; + assert.throws( + () => { + // eslint-disable-next-line no-unused-vars + for (const batch of pullSync(fromSync('x'), tx)) { /* consume */ } + }, + { code: 'ERR_INVALID_ARG_TYPE' }, + ); +} + +Promise.all([ + testTransformReturnsArrayBuffer(), + testSyncTransformReturnsArrayBuffer(), + testTransformReturnsFloat32Array(), + testSyncTransformReturnsFloat32Array(), + testTransformReturnsGenerator(), + testSyncTransformReturnsGenerator(), + testTransformReturnsAsyncGenerator(), + testStatefulTransformYieldsString(), + testStatefulTransformYieldsArrayBuffer(), + testStatefulSyncTransformYieldsString(), + testFlushReturnsSingleUint8Array(), + testFlushReturnsString(), + testSyncFlushReturnsSingleUint8Array(), + testTransformReturnsInvalidType(), + testSyncTransformReturnsInvalidType(), +]).then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-transform-roundtrip.js b/test/parallel/test-stream-iter-transform-roundtrip.js new file mode 100644 index 00000000000000..df63483d6c8535 --- /dev/null +++ b/test/parallel/test-stream-iter-transform-roundtrip.js @@ -0,0 +1,291 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, + pull, + bytes, + text, +} = require('stream/iter'); +const { + compressGzip, + compressDeflate, + compressBrotli, + compressZstd, + decompressGzip, + decompressDeflate, + decompressBrotli, + decompressZstd, +} = require('zlib/iter'); + +// ============================================================================= +// Helper: compress then decompress, verify round-trip equality +// ============================================================================= + +async function roundTrip(input, compress, decompress) { + return text(pull(pull(from(input), compress), decompress)); +} + +async function roundTripBytes(inputBuf, compress, decompress) { + return bytes(pull(pull(from(inputBuf), compress), decompress)); +} + +// ============================================================================= +// Gzip round-trip tests +// ============================================================================= + +async function testGzipRoundTrip() { + const input = 'Hello, gzip compression!'; + const result = await roundTrip(input, compressGzip(), decompressGzip()); + assert.strictEqual(result, input); +} + +async function testGzipLargeData() { + // 100KB of repeated text - exercises multi-chunk path + const input = 'gzip large data test. '.repeat(5000); + const result = await roundTrip(input, compressGzip(), decompressGzip()); + assert.strictEqual(result, input); +} + +async function testGzipActuallyCompresses() { + const input = 'Repeated data compresses well. '.repeat(1000); + const inputBuf = Buffer.from(input); + const source = from(inputBuf); + const compressed = await bytes(pull(source, compressGzip())); + assert.ok(compressed.byteLength < inputBuf.byteLength, + `Compressed ${compressed.byteLength} should be < original ${inputBuf.byteLength}`); +} + +// ============================================================================= +// Deflate round-trip tests +// ============================================================================= + +async function testDeflateRoundTrip() { + const input = 'Hello, deflate compression!'; + const result = await roundTrip(input, compressDeflate(), decompressDeflate()); + assert.strictEqual(result, input); +} + +async function testDeflateLargeData() { + const input = 'deflate large data test. '.repeat(5000); + const result = await roundTrip(input, compressDeflate(), decompressDeflate()); + assert.strictEqual(result, input); +} + +async function testDeflateActuallyCompresses() { + const input = 'Repeated data compresses well. '.repeat(1000); + const inputBuf = Buffer.from(input); + const source = from(inputBuf); + const compressed = await bytes(pull(source, compressDeflate())); + assert.ok(compressed.byteLength < inputBuf.byteLength, + `Compressed ${compressed.byteLength} should be < original ${inputBuf.byteLength}`); +} + +// ============================================================================= +// Brotli round-trip tests +// ============================================================================= + +async function testBrotliRoundTrip() { + const input = 'Hello, brotli compression!'; + const result = await roundTrip(input, compressBrotli(), decompressBrotli()); + assert.strictEqual(result, input); +} + +async function testBrotliLargeData() { + const input = 'brotli large data test. '.repeat(5000); + const result = await roundTrip(input, compressBrotli(), decompressBrotli()); + assert.strictEqual(result, input); +} + +async function testBrotliActuallyCompresses() { + const input = 'Repeated data compresses well. '.repeat(1000); + const inputBuf = Buffer.from(input); + const compressed = await bytes(pull(from(inputBuf), compressBrotli())); + assert.ok(compressed.byteLength < inputBuf.byteLength, + `Compressed ${compressed.byteLength} should be < original ${inputBuf.byteLength}`); +} + +// ============================================================================= +// Zstd round-trip tests +// ============================================================================= + +async function testZstdRoundTrip() { + const input = 'Hello, zstd compression!'; + const result = await roundTrip(input, compressZstd(), decompressZstd()); + assert.strictEqual(result, input); +} + +async function testZstdLargeData() { + const input = 'zstd large data test. '.repeat(5000); + const result = await roundTrip(input, compressZstd(), decompressZstd()); + assert.strictEqual(result, input); +} + +async function testZstdActuallyCompresses() { + const input = 'Repeated data compresses well. '.repeat(1000); + const inputBuf = Buffer.from(input); + const compressed = await bytes(pull(from(inputBuf), compressZstd())); + assert.ok(compressed.byteLength < inputBuf.byteLength, + `Compressed ${compressed.byteLength} should be < original ${inputBuf.byteLength}`); +} + +// ============================================================================= +// Binary data round-trip - verify no corruption on non-text data +// ============================================================================= + +// Create a buffer with a repeating byte pattern covering all 256 values. +function makeBinaryTestData(size = 1024) { + const buf = Buffer.alloc(size); + for (let i = 0; i < size; i++) buf[i] = i & 0xFF; + return buf; +} + +async function testBinaryRoundTripGzip() { + const input = makeBinaryTestData(); + const result = await roundTripBytes(input, compressGzip(), decompressGzip()); + assert.strictEqual(result.byteLength, input.byteLength); + assert.deepStrictEqual(Buffer.from(result), input); +} + +async function testBinaryRoundTripDeflate() { + const input = makeBinaryTestData(); + const result = await roundTripBytes(input, compressDeflate(), + decompressDeflate()); + assert.strictEqual(result.byteLength, input.byteLength); + assert.deepStrictEqual(Buffer.from(result), input); +} + +async function testBinaryRoundTripBrotli() { + const input = makeBinaryTestData(); + const result = await roundTripBytes(input, compressBrotli(), + decompressBrotli()); + assert.strictEqual(result.byteLength, input.byteLength); + assert.deepStrictEqual(Buffer.from(result), input); +} + +async function testBinaryRoundTripZstd() { + const input = makeBinaryTestData(); + const result = await roundTripBytes(input, compressZstd(), decompressZstd()); + assert.strictEqual(result.byteLength, input.byteLength); + assert.deepStrictEqual(Buffer.from(result), input); +} + +// ============================================================================= +// Empty input +// ============================================================================= + +async function testEmptyInputGzip() { + const result = await roundTrip('', compressGzip(), decompressGzip()); + assert.strictEqual(result, ''); +} + +async function testEmptyInputDeflate() { + const result = await roundTrip('', compressDeflate(), decompressDeflate()); + assert.strictEqual(result, ''); +} + +async function testEmptyInputBrotli() { + const result = await roundTrip('', compressBrotli(), decompressBrotli()); + assert.strictEqual(result, ''); +} + +async function testEmptyInputZstd() { + const result = await roundTrip('', compressZstd(), decompressZstd()); + assert.strictEqual(result, ''); +} + +// ============================================================================= +// Chained transforms - compress with one, then another, decompress in reverse +// ============================================================================= + +async function testChainedGzipDeflate() { + const input = 'Double compression test data. '.repeat(100); + // Compress: gzip then deflate + const compressed = pull(pull(from(input), compressGzip()), compressDeflate()); + // Decompress: deflate then gzip (reverse order) + const decompressed = pull(pull(compressed, decompressDeflate()), + decompressGzip()); + const result = await text(decompressed); + assert.strictEqual(result, input); +} + +// ============================================================================= +// Transform protocol: verify each factory returns a proper transform object +// ============================================================================= + +function testTransformProtocol() { + [ + compressGzip, compressDeflate, compressBrotli, compressZstd, + decompressGzip, decompressDeflate, decompressBrotli, decompressZstd, + ].forEach((factory) => { + const t = factory(); + assert.strictEqual(typeof t.transform, 'function', + `${factory.name}() should have a transform function`); + }); +} + +// ============================================================================= +// Compression with options +// ============================================================================= + +async function testGzipWithLevel() { + const data = 'a'.repeat(10000); + const level1 = await bytes(pull(from(data), compressGzip({ level: 1 }))); + const level9 = await bytes(pull(from(data), compressGzip({ level: 9 }))); + // Higher compression level should produce smaller output + assert.ok(level9.length <= level1.length); + // Both should decompress to original + const dec1 = await text(pull(from(level1), decompressGzip())); + const dec9 = await text(pull(from(level9), decompressGzip())); + assert.strictEqual(dec1, data); + assert.strictEqual(dec9, data); +} + +// ============================================================================= +// Run all tests +// ============================================================================= + +(async () => { + // Gzip + await testGzipRoundTrip(); + await testGzipLargeData(); + await testGzipActuallyCompresses(); + + // Deflate + await testDeflateRoundTrip(); + await testDeflateLargeData(); + await testDeflateActuallyCompresses(); + + // Brotli + await testBrotliRoundTrip(); + await testBrotliLargeData(); + await testBrotliActuallyCompresses(); + + // Zstd + await testZstdRoundTrip(); + await testZstdLargeData(); + await testZstdActuallyCompresses(); + + // Binary data + await testBinaryRoundTripGzip(); + await testBinaryRoundTripDeflate(); + await testBinaryRoundTripBrotli(); + await testBinaryRoundTripZstd(); + + // Empty input + await testEmptyInputGzip(); + await testEmptyInputDeflate(); + await testEmptyInputBrotli(); + await testEmptyInputZstd(); + + // Chained + await testChainedGzipDeflate(); + + // Protocol + testTransformProtocol(); + + // Compression with options + await testGzipWithLevel(); +})().then(common.mustCall()); diff --git a/test/parallel/test-stream-iter-transform-sync.js b/test/parallel/test-stream-iter-transform-sync.js new file mode 100644 index 00000000000000..d674c26cca100a --- /dev/null +++ b/test/parallel/test-stream-iter-transform-sync.js @@ -0,0 +1,227 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + fromSync, + pullSync, + bytesSync, + textSync, +} = require('stream/iter'); +const { + compressGzipSync, + compressDeflateSync, + compressBrotliSync, + compressZstdSync, + decompressGzipSync, + decompressDeflateSync, + decompressBrotliSync, + decompressZstdSync, +} = require('zlib/iter'); + +// ============================================================================= +// Helper: sync compress then decompress, verify round-trip equality +// ============================================================================= + +function roundTrip(input, compress, decompress) { + return textSync(pullSync(pullSync(fromSync(input), compress), decompress)); +} + +function roundTripBytes(inputBuf, compress, decompress) { + return bytesSync(pullSync(pullSync(fromSync(inputBuf), compress), decompress)); +} + +// ============================================================================= +// Gzip sync round-trip tests +// ============================================================================= + +function testGzipRoundTrip() { + const input = 'Hello, sync gzip compression!'; + const result = roundTrip(input, compressGzipSync(), decompressGzipSync()); + assert.strictEqual(result, input); +} + +function testGzipLargeData() { + const input = 'gzip sync large data test. '.repeat(5000); + const result = roundTrip(input, compressGzipSync(), decompressGzipSync()); + assert.strictEqual(result, input); +} + +function testGzipActuallyCompresses() { + const input = 'Repeated data compresses well. '.repeat(1000); + const inputBuf = Buffer.from(input); + const compressed = bytesSync(pullSync(fromSync(inputBuf), + compressGzipSync())); + assert.ok(compressed.byteLength < inputBuf.byteLength, + `Compressed ${compressed.byteLength} should be < ` + + `original ${inputBuf.byteLength}`); +} + +function testGzipBinaryData() { + const inputBuf = Buffer.alloc(10000); + for (let i = 0; i < inputBuf.length; i++) inputBuf[i] = i & 0xff; + const result = roundTripBytes(inputBuf, compressGzipSync(), + decompressGzipSync()); + assert.deepStrictEqual(result, inputBuf); +} + +// ============================================================================= +// Deflate sync round-trip tests +// ============================================================================= + +function testDeflateRoundTrip() { + const input = 'Hello, sync deflate compression!'; + const result = roundTrip(input, compressDeflateSync(), + decompressDeflateSync()); + assert.strictEqual(result, input); +} + +function testDeflateLargeData() { + const input = 'deflate sync large data test. '.repeat(5000); + const result = roundTrip(input, compressDeflateSync(), + decompressDeflateSync()); + assert.strictEqual(result, input); +} + +// ============================================================================= +// Brotli sync round-trip tests +// ============================================================================= + +function testBrotliRoundTrip() { + const input = 'Hello, sync brotli compression!'; + const result = roundTrip(input, compressBrotliSync(), + decompressBrotliSync()); + assert.strictEqual(result, input); +} + +function testBrotliLargeData() { + const input = 'brotli sync large data test. '.repeat(5000); + const result = roundTrip(input, compressBrotliSync(), + decompressBrotliSync()); + assert.strictEqual(result, input); +} + +// ============================================================================= +// Zstd sync round-trip tests +// ============================================================================= + +function testZstdRoundTrip() { + const input = 'Hello, sync zstd compression!'; + const result = roundTrip(input, compressZstdSync(), decompressZstdSync()); + assert.strictEqual(result, input); +} + +function testZstdLargeData() { + const input = 'zstd sync large data test. '.repeat(5000); + const result = roundTrip(input, compressZstdSync(), decompressZstdSync()); + assert.strictEqual(result, input); +} + +// ============================================================================= +// Cross-algorithm: compress async-compatible, decompress sync (and vice versa) +// The sync transforms should produce output compatible with the standard format +// ============================================================================= + +function testGzipWithOptions() { + const input = 'options test data '.repeat(100); + const result = roundTrip(input, + compressGzipSync({ level: 1 }), + decompressGzipSync()); + assert.strictEqual(result, input); +} + +function testBrotliWithOptions() { + const zlib = require('zlib'); + const input = 'brotli options test data '.repeat(100); + const result = roundTrip(input, + compressBrotliSync({ + params: { + [zlib.constants.BROTLI_PARAM_QUALITY]: 3, + }, + }), + decompressBrotliSync()); + assert.strictEqual(result, input); +} + +// ============================================================================= +// Stateless + stateful sync transform pipeline +// ============================================================================= + +function testMixedStatelessAndStateful() { + // Uppercase stateless transform + gzip stateful transform + const upper = (chunks) => { + if (chunks === null) return null; + const out = new Array(chunks.length); + for (let j = 0; j < chunks.length; j++) { + const src = chunks[j]; + const buf = Buffer.allocUnsafe(src.length); + for (let i = 0; i < src.length; i++) { + const b = src[i]; + buf[i] = (b >= 0x61 && b <= 0x7a) ? b - 0x20 : b; + } + out[j] = buf; + } + return out; + }; + + const input = 'hello world '.repeat(100); + const result = textSync( + pullSync( + pullSync(fromSync(input), upper, compressGzipSync()), + decompressGzipSync(), + ), + ); + assert.strictEqual(result, input.toUpperCase()); +} + +// ============================================================================= +// Early consumer exit (break from for-of) triggers cleanup +// ============================================================================= + +function testEarlyExit() { + const input = 'y'.repeat(100_000); + const compressed = pullSync(fromSync(input), compressGzipSync()); + + // eslint-disable-next-line no-unused-vars + for (const batch of compressed) { + break; // Early exit - should trigger finally block cleanup + } + // If we get here without crashing, cleanup worked +} + +// ============================================================================= +// Empty input +// ============================================================================= + +function testEmptyInput() { + const result = textSync( + pullSync( + pullSync(fromSync(''), compressGzipSync()), + decompressGzipSync(), + ), + ); + assert.strictEqual(result, ''); +} + +// ============================================================================= +// Run all tests +// ============================================================================= + +testGzipRoundTrip(); +testGzipLargeData(); +testGzipActuallyCompresses(); +testGzipBinaryData(); +testDeflateRoundTrip(); +testDeflateLargeData(); +testBrotliRoundTrip(); +testBrotliLargeData(); +testZstdRoundTrip(); +testZstdLargeData(); +testGzipWithOptions(); +testBrotliWithOptions(); +testMixedStatelessAndStateful(); +testEarlyExit(); +testEmptyInput(); + +common.mustCall()(); diff --git a/test/parallel/test-stream-iter-validation.js b/test/parallel/test-stream-iter-validation.js new file mode 100644 index 00000000000000..d58eca4e63ac3b --- /dev/null +++ b/test/parallel/test-stream-iter-validation.js @@ -0,0 +1,347 @@ +// Flags: --experimental-stream-iter +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { + from, fromSync, pull, pullSync, pipeTo, + push, duplex, broadcast, Broadcast, share, shareSync, + Share, SyncShare, + bytes, bytesSync, text, textSync, + arrayBuffer, arrayBufferSync, array, arraySync, + tap, tapSync, +} = require('stream/iter'); +const { + compressGzip, compressBrotli, compressZstd, + decompressGzip, decompressBrotli, decompressZstd, +} = require('zlib/iter'); + +// ============================================================================= +// push() validation +// ============================================================================= + +// HighWaterMark must be integer >= 1 +assert.throws(() => push({ highWaterMark: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => push({ highWaterMark: 1.5 }), { code: 'ERR_OUT_OF_RANGE' }); +// Values < 1 are clamped to 1 +assert.strictEqual(push({ highWaterMark: 0 }).writer.desiredSize, 1); +assert.strictEqual(push({ highWaterMark: -1 }).writer.desiredSize, 1); +assert.strictEqual(push({ highWaterMark: -100 }).writer.desiredSize, 1); +// MAX_SAFE_INTEGER is accepted +assert.strictEqual(push({ highWaterMark: Number.MAX_SAFE_INTEGER }).writer.desiredSize, + Number.MAX_SAFE_INTEGER); +// Values above MAX_SAFE_INTEGER are rejected by validateInteger +assert.throws(() => push({ highWaterMark: Number.MAX_SAFE_INTEGER + 1 }), + { code: 'ERR_OUT_OF_RANGE' }); + +// Signal must be AbortSignal +assert.throws(() => push({ signal: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => push({ signal: {} }), { code: 'ERR_INVALID_ARG_TYPE' }); + +// Transforms must be functions or transform objects +assert.throws(() => push(42, {}), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => push('bad', {}), { code: 'ERR_INVALID_ARG_TYPE' }); + +// Writer.writev requires array +{ + const { writer } = push(); + assert.throws(() => writer.writev('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => writer.writev(42), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => writer.writevSync('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); + writer.endSync(); +} + +// Writer.write rejects non-string/non-Uint8Array +{ + const { writer } = push(); + assert.throws(() => writer.writeSync(42), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => writer.writeSync({}), { code: 'ERR_INVALID_ARG_TYPE' }); + assert.throws(() => writer.writeSync(true), { code: 'ERR_INVALID_ARG_TYPE' }); + writer.endSync(); +} + +// ============================================================================= +// duplex() validation +// ============================================================================= + +assert.throws(() => duplex(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => duplex('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => duplex({ a: 42 }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => duplex({ b: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); + +// highWaterMark validation (cascades through to push()) +assert.throws(() => duplex({ highWaterMark: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => duplex({ highWaterMark: 1.5 }), { code: 'ERR_OUT_OF_RANGE' }); +assert.throws(() => duplex({ highWaterMark: Number.MAX_SAFE_INTEGER + 1 }), + { code: 'ERR_OUT_OF_RANGE' }); + +// Values < 1 are clamped to 1 (both directions) +{ + const [a, b] = duplex({ highWaterMark: 0 }); + assert.strictEqual(a.writer.desiredSize, 1); + assert.strictEqual(b.writer.desiredSize, 1); + a.close(); + b.close(); +} +// MAX_SAFE_INTEGER is accepted +{ + const [a, b] = duplex({ highWaterMark: Number.MAX_SAFE_INTEGER }); + assert.strictEqual(a.writer.desiredSize, Number.MAX_SAFE_INTEGER); + assert.strictEqual(b.writer.desiredSize, Number.MAX_SAFE_INTEGER); + a.close(); + b.close(); +} +// Per-direction overrides +{ + const [a, b] = duplex({ a: { highWaterMark: 0 }, b: { highWaterMark: 5 } }); + assert.strictEqual(a.writer.desiredSize, 1); // clamped + assert.strictEqual(b.writer.desiredSize, 5); + a.close(); + b.close(); +} + +assert.throws(() => duplex({ signal: {} }), { code: 'ERR_INVALID_ARG_TYPE' }); + +// ============================================================================= +// pull() / pullSync() validation +// ============================================================================= + +// Signal must be AbortSignal +assert.throws(() => pull(from('a'), { signal: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); + +// Transforms must be functions or transform objects +assert.throws(() => pull(from('a'), 42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => pull(from('a'), 'bad'), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => pullSync(fromSync('a'), 42), { code: 'ERR_INVALID_ARG_TYPE' }); + +// ============================================================================= +// broadcast() validation +// ============================================================================= + +assert.throws(() => broadcast({ highWaterMark: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => broadcast({ highWaterMark: 1.5 }), { code: 'ERR_OUT_OF_RANGE' }); +assert.throws(() => broadcast({ highWaterMark: Number.MAX_SAFE_INTEGER + 1 }), + { code: 'ERR_OUT_OF_RANGE' }); + +// Values < 1 are clamped to 1 (need a consumer for desiredSize to work) +{ + const bc = broadcast({ highWaterMark: 0 }); + bc.broadcast.push(); + assert.strictEqual(bc.writer.desiredSize, 1); + bc.writer.endSync(); +} +{ + const bc = broadcast({ highWaterMark: -1 }); + bc.broadcast.push(); + assert.strictEqual(bc.writer.desiredSize, 1); + bc.writer.endSync(); +} +// MAX_SAFE_INTEGER is accepted +{ + const bc = broadcast({ highWaterMark: Number.MAX_SAFE_INTEGER }); + bc.broadcast.push(); + assert.strictEqual(bc.writer.desiredSize, Number.MAX_SAFE_INTEGER); + bc.writer.endSync(); +} + +assert.throws(() => broadcast({ signal: {} }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => broadcast({ backpressure: 'bad' }), { code: 'ERR_INVALID_ARG_VALUE' }); + +// Broadcast.from rejects non-iterable input +assert.throws(() => Broadcast.from(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => Broadcast.from('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); + +// ============================================================================= +// share() / shareSync() validation +// ============================================================================= + +assert.throws(() => share(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => share(from('a'), { highWaterMark: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => share(from('a'), { highWaterMark: 1.5 }), { code: 'ERR_OUT_OF_RANGE' }); +assert.throws(() => share(from('a'), { highWaterMark: Number.MAX_SAFE_INTEGER + 1 }), + { code: 'ERR_OUT_OF_RANGE' }); +assert.throws(() => share(from('a'), { signal: {} }), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => share(from('a'), { backpressure: 'bad' }), { code: 'ERR_INVALID_ARG_VALUE' }); + +// share() values < 1 are clamped (no desiredSize, but accepts the value) +share(from('a'), { highWaterMark: 0 }).cancel(); +share(from('a'), { highWaterMark: -1 }).cancel(); +share(from('a'), { highWaterMark: Number.MAX_SAFE_INTEGER }).cancel(); + +assert.throws(() => shareSync(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => shareSync(fromSync('a'), { highWaterMark: 'bad' }), + { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => shareSync(fromSync('a'), { highWaterMark: 1.5 }), + { code: 'ERR_OUT_OF_RANGE' }); +assert.throws(() => shareSync(fromSync('a'), { highWaterMark: Number.MAX_SAFE_INTEGER + 1 }), + { code: 'ERR_OUT_OF_RANGE' }); + +// shareSync() values < 1 are clamped (accepts the value) +shareSync(fromSync('a'), { highWaterMark: 0 }).cancel(); +shareSync(fromSync('a'), { highWaterMark: -1 }).cancel(); +shareSync(fromSync('a'), { highWaterMark: Number.MAX_SAFE_INTEGER }).cancel(); + +// Share.from / SyncShare.fromSync reject non-iterable +assert.throws(() => Share.from(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => SyncShare.fromSync(42), { code: 'ERR_INVALID_ARG_TYPE' }); + +// ============================================================================= +// Consumer validation (synchronous) +// ============================================================================= + +// tap / tapSync require function +assert.throws(() => tap(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => tap('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => tapSync(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => tapSync(null), { code: 'ERR_INVALID_ARG_TYPE' }); + +// Sync consumer options +assert.throws(() => bytesSync(fromSync('a'), { limit: 'bad' }), + { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => bytesSync(fromSync('a'), { limit: -1 }), + { code: 'ERR_OUT_OF_RANGE' }); +assert.throws(() => textSync(fromSync('a'), { encoding: 42 }), + { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => textSync(fromSync('a'), { encoding: 'bogus' }), + { code: 'ERR_INVALID_ARG_VALUE' }); +assert.throws(() => arrayBufferSync(fromSync('a'), { limit: 'bad' }), + { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => arraySync(fromSync('a'), { limit: -1 }), + { code: 'ERR_OUT_OF_RANGE' }); + +// Options must be object if provided +assert.throws(() => bytesSync(fromSync('a'), 42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => textSync(fromSync('a'), 'bad'), { code: 'ERR_INVALID_ARG_TYPE' }); + +// Compression options must be object +assert.throws(() => compressGzip(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => decompressGzip('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => compressBrotli(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => decompressBrotli('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => compressZstd(42), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.throws(() => decompressZstd('bad'), { code: 'ERR_INVALID_ARG_TYPE' }); + +// ============================================================================= +// Async consumer and compression validation +// ============================================================================= + +// Helper: consume a transform through a pipeline to trigger lazy validation. +const consume = (transform) => bytes(pull(from('test'), transform)); + +async function testAsyncValidation() { + // pipeTo signal + await assert.rejects( + () => pipeTo(from('a'), { write() {} }, { signal: 'bad' }), + { code: 'ERR_INVALID_ARG_TYPE' }, + ); + + // Async consumer options + await assert.rejects( + () => bytes(from('a'), 42), { code: 'ERR_INVALID_ARG_TYPE' }); + await assert.rejects( + () => bytes(from('a'), { signal: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); + await assert.rejects( + () => bytes(from('a'), { limit: 'bad' }), { code: 'ERR_INVALID_ARG_TYPE' }); + await assert.rejects( + () => bytes(from('a'), { limit: -1 }), { code: 'ERR_OUT_OF_RANGE' }); + await assert.rejects( + () => text(from('a'), { encoding: 42 }), { code: 'ERR_INVALID_ARG_TYPE' }); + await assert.rejects( + () => text(from('a'), { encoding: 'not-a-real-encoding' }), + { code: 'ERR_INVALID_ARG_VALUE' }); + await assert.rejects( + () => arrayBuffer(from('a'), { limit: 'bad' }), + { code: 'ERR_INVALID_ARG_TYPE' }); + await assert.rejects( + () => array(from('a'), { limit: -1 }), { code: 'ERR_OUT_OF_RANGE' }); + + const TYPE = { code: 'ERR_INVALID_ARG_TYPE' }; + const RANGE = { code: 'ERR_OUT_OF_RANGE' }; + const BROTLI = { code: 'ERR_BROTLI_INVALID_PARAM' }; + const ZSTD = { code: 'ERR_ZSTD_INVALID_PARAM' }; + + // ChunkSize + await assert.rejects(consume(compressGzip({ chunkSize: 'bad' })), TYPE); + await assert.rejects(consume(compressGzip({ chunkSize: 0 })), RANGE); + await assert.rejects(consume(compressGzip({ chunkSize: 10 })), RANGE); + + // WindowBits + await assert.rejects(consume(compressGzip({ windowBits: 'bad' })), TYPE); + await assert.rejects(consume(compressGzip({ windowBits: 100 })), RANGE); + + // Level + await assert.rejects(consume(compressGzip({ level: 'bad' })), TYPE); + await assert.rejects(consume(compressGzip({ level: 100 })), RANGE); + + // MemLevel + await assert.rejects(consume(compressGzip({ memLevel: 'bad' })), TYPE); + await assert.rejects(consume(compressGzip({ memLevel: 100 })), RANGE); + + // Strategy + await assert.rejects(consume(compressGzip({ strategy: 'bad' })), TYPE); + await assert.rejects(consume(compressGzip({ strategy: 100 })), RANGE); + + // Dictionary + await assert.rejects(consume(compressGzip({ dictionary: 42 })), TYPE); + await assert.rejects(consume(compressGzip({ dictionary: 'bad' })), TYPE); + + // Brotli params + await assert.rejects(consume(compressBrotli({ params: 42 })), TYPE); + await assert.rejects(consume(compressBrotli({ params: { bad: 1 } })), BROTLI); + await assert.rejects(consume(compressBrotli({ params: { [-1]: 1 } })), BROTLI); + await assert.rejects(consume(compressBrotli({ params: { 0: 'bad' } })), TYPE); + + // Zstd params + await assert.rejects(consume(compressZstd({ params: 42 })), TYPE); + await assert.rejects(consume(compressZstd({ params: { bad: 1 } })), ZSTD); + await assert.rejects(consume(compressZstd({ params: { 0: 'bad' } })), TYPE); + + // Zstd pledgedSrcSize + await assert.rejects(consume(compressZstd({ pledgedSrcSize: 'bad' })), TYPE); + await assert.rejects(consume(compressZstd({ pledgedSrcSize: -1 })), RANGE); +} + +// ============================================================================= +// Valid calls still work +// ============================================================================= + +// Push with valid options +{ + const { writer } = push({ highWaterMark: 2 }); + writer.writeSync('hello'); + writer.endSync(); +} + +// Duplex with valid options +{ + const [a, b] = duplex({ highWaterMark: 2 }); + a.close(); + b.close(); +} + +// Broadcast with valid options +{ + const { writer } = broadcast({ highWaterMark: 4 }); + writer.endSync(); +} + +// Share with valid options +{ + const shared = share(from('hello'), { highWaterMark: 4 }); + shared.cancel(); +} + +// Compression with valid options +{ + const transform = compressGzip({ chunkSize: 1024, level: 6 }); + assert.strictEqual(typeof transform.transform, 'function'); +} + +// Brotli with valid params +{ + const { constants: { BROTLI_PARAM_QUALITY } } = require('zlib'); + const transform = compressBrotli({ params: { [BROTLI_PARAM_QUALITY]: 5 } }); + assert.strictEqual(typeof transform.transform, 'function'); +} + +testAsyncValidation().then(common.mustCall()); diff --git a/test/parallel/test-stream-pipeline.js b/test/parallel/test-stream-pipeline.js index 6220bc15365361..8852f24a75050f 100644 --- a/test/parallel/test-stream-pipeline.js +++ b/test/parallel/test-stream-pipeline.js @@ -1749,3 +1749,24 @@ tmpdir.refresh(); assert.deepStrictEqual(err, new Error('booom')); })); } + +{ + // Errors thrown in Readable.map inside pipeline should not be + // swallowed by AbortError when the source is an infinite stream. + pipeline( + new Readable({ read() { this.push('data'); } }), + new Transform({ + readableObjectMode: true, + transform(chunk, encoding, callback) { + this.push({}); + callback(); + }, + }), + (readable) => readable.map(async () => { + throw new Error('Boom!'); + }), + common.mustCall((err) => { + assert.strictEqual(err.message, 'Boom!'); + }), + ); +} diff --git a/test/parallel/test-tls-client-auth.js b/test/parallel/test-tls-client-auth.js index 04bf40b9a9e1ac..67aed40914c9fe 100644 --- a/test/parallel/test-tls-client-auth.js +++ b/test/parallel/test-tls-client-auth.js @@ -82,8 +82,9 @@ connect({ }, common.mustCall((err, pair, cleanup) => { assert.strictEqual(pair.server.err.code, 'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE'); - const expectedErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; + const expectedErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; assert.strictEqual(pair.client.err.code, expectedErr); return cleanup(); diff --git a/test/parallel/test-tls-empty-sni-context.js b/test/parallel/test-tls-empty-sni-context.js index 79f1ddd341d938..e4136ff71e1d52 100644 --- a/test/parallel/test-tls-empty-sni-context.js +++ b/test/parallel/test-tls-empty-sni-context.js @@ -26,8 +26,9 @@ const server = tls.createServer(options, (c) => { }, common.mustNotCall()); c.on('error', common.mustCall((err) => { - const expectedErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; + const expectedErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; assert.strictEqual(err.code, expectedErr); })); })); diff --git a/test/parallel/test-tls-psk-circuit.js b/test/parallel/test-tls-psk-circuit.js index 9cbc7a91fb852b..bdf9c86c26a7b6 100644 --- a/test/parallel/test-tls-psk-circuit.js +++ b/test/parallel/test-tls-psk-circuit.js @@ -64,8 +64,9 @@ test({ psk: USERS.UserA, identity: 'UserA' }, { minVersion: 'TLSv1.3' }); test({ psk: USERS.UserB, identity: 'UserB' }); test({ psk: USERS.UserB, identity: 'UserB' }, { minVersion: 'TLSv1.3' }); // Unrecognized user should fail handshake -const expectedHandshakeErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; +const expectedHandshakeErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; test({ psk: USERS.UserB, identity: 'UserC' }, {}, expectedHandshakeErr); // Recognized user but incorrect secret should fail handshake const expectedIllegalParameterErr = hasOpenSSL(3, 4) ? 'ERR_SSL_TLSV1_ALERT_DECRYPT_ERROR' : diff --git a/test/parallel/test-tls-set-ciphers.js b/test/parallel/test-tls-set-ciphers.js index 1e63e9376e134b..82a19bb9e90fc2 100644 --- a/test/parallel/test-tls-set-ciphers.js +++ b/test/parallel/test-tls-set-ciphers.js @@ -90,7 +90,9 @@ function test(cciphers, sciphers, cipher, cerr, serr, options) { const U = undefined; let expectedTLSAlertError = 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; -if (hasOpenSSL(3, 2)) { +if (hasOpenSSL(4, 0)) { + expectedTLSAlertError = 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE'; +} else if (hasOpenSSL(3, 2)) { expectedTLSAlertError = 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE'; } diff --git a/test/parallel/test-tls-set-sigalgs.js b/test/parallel/test-tls-set-sigalgs.js index 985ca13ba2ac7d..1bce814f3e8604 100644 --- a/test/parallel/test-tls-set-sigalgs.js +++ b/test/parallel/test-tls-set-sigalgs.js @@ -66,8 +66,9 @@ test('RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256', ['RSA-PSS+SHA256', 'ECDSA+SHA256']); // Do not have shared sigalgs. -const handshakeErr = hasOpenSSL(3, 2) ? - 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; +const handshakeErr = hasOpenSSL(4, 0) ? + 'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ? + 'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'; test('RSA-PSS+SHA384', 'ECDSA+SHA256', undefined, handshakeErr, 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'); diff --git a/test/parallel/test-util-styletext.js b/test/parallel/test-util-styletext.js index 4fdf419143453c..3db01bec1c3acd 100644 --- a/test/parallel/test-util-styletext.js +++ b/test/parallel/test-util-styletext.js @@ -41,6 +41,16 @@ assert.strictEqual( '\u001b[31mtest\u001b[39m', ); +assert.strictEqual( + util.styleText('gray', 'test', { validateStream: false }), + '\u001b[90mtest\u001b[39m', +); + +assert.strictEqual( + util.styleText('grey', 'test', { validateStream: false }), + '\u001b[90mtest\u001b[39m', +); + assert.strictEqual( util.styleText(['bold', 'red'], 'test', { validateStream: false }), '\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m', @@ -144,6 +154,29 @@ assert.throws(() => { code: 'ERR_INVALID_ARG_TYPE', }); +// Color aliases should be accepted (e.g. 'grey' is an alias for 'gray') +// See https://github.com/nodejs/node/issues/62177 +assert.strictEqual( + util.styleText('grey', 'test', { validateStream: false }), + util.styleText('gray', 'test', { validateStream: false }), +); +assert.strictEqual( + util.styleText('bgGrey', 'test', { validateStream: false }), + util.styleText('bgGray', 'test', { validateStream: false }), +); +assert.strictEqual( + util.styleText('blackBright', 'test', { validateStream: false }), + util.styleText('gray', 'test', { validateStream: false }), +); +assert.strictEqual( + util.styleText('faint', 'test', { validateStream: false }), + util.styleText('dim', 'test', { validateStream: false }), +); +assert.strictEqual( + util.styleText(['grey', 'bold'], 'test', { validateStream: false }), + util.styleText(['gray', 'bold'], 'test', { validateStream: false }), +); + // does not throw util.styleText('red', 'text', { stream: {}, validateStream: false }); diff --git a/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js new file mode 100644 index 00000000000000..a96e709095430f --- /dev/null +++ b/test/parallel/test-webcrypto-aead-decrypt-detached-buffer.js @@ -0,0 +1,42 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { hasOpenSSL } = require('../common/crypto'); +const { subtle } = globalThis.crypto; + +async function test(algorithmName, keyLength, ivLength, format = 'raw') { + const key = await subtle.importKey( + format, + new Uint8Array(keyLength), + { name: algorithmName }, + false, + ['encrypt', 'decrypt'], + ); + + const data = new Uint8Array(32); + data.buffer.transfer(); + + await assert.rejects( + subtle.decrypt({ name: algorithmName, iv: new Uint8Array(ivLength) }, key, data), + { name: 'OperationError' }, + ); +} + +const tests = [ + test('AES-GCM', 32, 12), +]; + +if (hasOpenSSL(3)) { + tests.push(test('AES-OCB', 32, 12, 'raw-secret')); +} + +if (!process.features.openssl_is_boringssl) { + tests.push(test('ChaCha20-Poly1305', 32, 12, 'raw-secret')); +} + +Promise.all(tests).then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-digest-turboshake-rfc.js b/test/parallel/test-webcrypto-digest-turboshake-rfc.js new file mode 100644 index 00000000000000..43762fecc2c41e --- /dev/null +++ b/test/parallel/test-webcrypto-digest-turboshake-rfc.js @@ -0,0 +1,399 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +// RFC 9861 Section 5 test vectors + +// Generates a Buffer of length n by repeating the pattern 00 01 02 .. F9 FA. +function ptn(n) { + const buf = Buffer.allocUnsafe(n); + for (let i = 0; i < n; i++) + buf[i] = i % 251; + return buf; +} + +assert.deepStrictEqual( + ptn(17 ** 2).toString('hex'), + '000102030405060708090a0b0c0d0e0f' + + '101112131415161718191a1b1c1d1e1f' + + '202122232425262728292a2b2c2d2e2f' + + '303132333435363738393a3b3c3d3e3f' + + '404142434445464748494a4b4c4d4e4f' + + '505152535455565758595a5b5c5d5e5f' + + '606162636465666768696a6b6c6d6e6f' + + '707172737475767778797a7b7c7d7e7f' + + '808182838485868788898a8b8c8d8e8f' + + '909192939495969798999a9b9c9d9e9f' + + 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' + + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + + 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + + 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + + 'f0f1f2f3f4f5f6f7f8f9fa0001020304' + + '05060708090a0b0c0d0e0f1011121314' + + '15161718191a1b1c1d1e1f2021222324' + + '25', +); + +const turboSHAKE128Vectors = [ + // [input, outputLengthBytes, expected(, domainSeparation)] + [new Uint8Array(0), 32, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c'], + [new Uint8Array(0), 64, + '1e415f1c5983aff2169217277d17bb53' + + '8cd945a397ddec541f1ce41af2c1b74c' + + '3e8ccae2a4dae56c84a04c2385c03c15' + + 'e8193bdf58737363321691c05462c8df'], + [ptn(17 ** 0), 32, + '55cedd6f60af7bb29a4042ae832ef3f5' + + '8db7299f893ebb9247247d856958daa9'], + [ptn(17 ** 1), 32, + '9c97d036a3bac819db70ede0ca554ec6' + + 'e4c2a1a4ffbfd9ec269ca6a111161233'], + [ptn(17 ** 2), 32, + '96c77c279e0126f7fc07c9b07f5cdae1' + + 'e0be60bdbe10620040e75d7223a624d2'], + [ptn(17 ** 3), 32, + 'd4976eb56bcf118520582b709f73e1d6' + + '853e001fdaf80e1b13e0d0599d5fb372'], + [ptn(17 ** 4), 32, + 'da67c7039e98bf530cf7a37830c6664e' + + '14cbab7f540f58403b1b82951318ee5c'], + [ptn(17 ** 5), 32, + 'b97a906fbf83ef7c812517abf3b2d0ae' + + 'a0c4f60318ce11cf103925127f59eecd'], + [ptn(17 ** 6), 32, + '35cd494adeded2f25239af09a7b8ef0c' + + '4d1ca4fe2d1ac370fa63216fe7b4c2b1'], + [Buffer.from('ffffff', 'hex'), 32, + 'bf323f940494e88ee1c540fe660be8a0' + + 'c93f43d15ec006998462fa994eed5dab', 0x01], + [Buffer.from('ff', 'hex'), 32, + '8ec9c66465ed0d4a6c35d13506718d68' + + '7a25cb05c74cca1e42501abd83874a67', 0x06], + [Buffer.from('ffffff', 'hex'), 32, + 'b658576001cad9b1e5f399a9f77723bb' + + 'a05458042d68206f7252682dba3663ed', 0x07], + [Buffer.from('ffffffffffffff', 'hex'), 32, + '8deeaa1aec47ccee569f659c21dfa8e1' + + '12db3cee37b18178b2acd805b799cc37', 0x0b], + [Buffer.from('ff', 'hex'), 32, + '553122e2135e363c3292bed2c6421fa2' + + '32bab03daa07c7d6636603286506325b', 0x30], + [Buffer.from('ffffff', 'hex'), 32, + '16274cc656d44cefd422395d0f9053bd' + + 'a6d28e122aba15c765e5ad0e6eaf26f9', 0x7f], +]; + +const turboSHAKE256Vectors = [ + // [input, outputLengthBytes, expected(, domainSeparation)] + [new Uint8Array(0), 64, + '367a329dafea871c7802ec67f905ae13' + + 'c57695dc2c6663c61035f59a18f8e7db' + + '11edc0e12e91ea60eb6b32df06dd7f00' + + '2fbafabb6e13ec1cc20d995547600db0'], + [ptn(17 ** 0), 64, + '3e1712f928f8eaf1054632b2aa0a246e' + + 'd8b0c378728f60bc970410155c28820e' + + '90cc90d8a3006aa2372c5c5ea176b068' + + '2bf22bae7467ac94f74d43d39b0482e2'], + [ptn(17 ** 1), 64, + 'b3bab0300e6a191fbe61379398359235' + + '78794ea54843f5011090fa2f3780a9e5' + + 'cb22c59d78b40a0fbff9e672c0fbe097' + + '0bd2c845091c6044d687054da5d8e9c7'], + [ptn(17 ** 2), 64, + '66b810db8e90780424c0847372fdc957' + + '10882fde31c6df75beb9d4cd9305cfca' + + 'e35e7b83e8b7e6eb4b78605880116316' + + 'fe2c078a09b94ad7b8213c0a738b65c0'], + [ptn(17 ** 3), 64, + 'c74ebc919a5b3b0dd1228185ba02d29e' + + 'f442d69d3d4276a93efe0bf9a16a7dc0' + + 'cd4eabadab8cd7a5edd96695f5d360ab' + + 'e09e2c6511a3ec397da3b76b9e1674fb'], + [ptn(17 ** 4), 64, + '02cc3a8897e6f4f6ccb6fd46631b1f52' + + '07b66c6de9c7b55b2d1a23134a170afd' + + 'ac234eaba9a77cff88c1f020b7372461' + + '8c5687b362c430b248cd38647f848a1d'], + [ptn(17 ** 5), 64, + 'add53b06543e584b5823f626996aee50' + + 'fe45ed15f20243a7165485acb4aa76b4' + + 'ffda75cedf6d8cdc95c332bd56f4b986' + + 'b58bb17d1778bfc1b1a97545cdf4ec9f'], + [ptn(17 ** 6), 64, + '9e11bc59c24e73993c1484ec66358ef7' + + '1db74aefd84e123f7800ba9c4853e02c' + + 'fe701d9e6bb765a304f0dc34a4ee3ba8' + + '2c410f0da70e86bfbd90ea877c2d6104'], + [Buffer.from('ffffff', 'hex'), 64, + 'd21c6fbbf587fa2282f29aea620175fb' + + '0257413af78a0b1b2a87419ce031d933' + + 'ae7a4d383327a8a17641a34f8a1d1003' + + 'ad7da6b72dba84bb62fef28f62f12424', 0x01], + [Buffer.from('ff', 'hex'), 64, + '738d7b4e37d18b7f22ad1b5313e357e3' + + 'dd7d07056a26a303c433fa3533455280' + + 'f4f5a7d4f700efb437fe6d281405e07b' + + 'e32a0a972e22e63adc1b090daefe004b', 0x06], + [Buffer.from('ffffff', 'hex'), 64, + '18b3b5b7061c2e67c1753a00e6ad7ed7' + + 'ba1c906cf93efb7092eaf27fbeebb755' + + 'ae6e292493c110e48d260028492b8e09' + + 'b5500612b8f2578985ded5357d00ec67', 0x07], + [Buffer.from('ffffffffffffff', 'hex'), 64, + 'bb36764951ec97e9d85f7ee9a67a7718' + + 'fc005cf42556be79ce12c0bde50e5736' + + 'd6632b0d0dfb202d1bbb8ffe3dd74cb0' + + '0834fa756cb03471bab13a1e2c16b3c0', 0x0b], + [Buffer.from('ff', 'hex'), 64, + 'f3fe12873d34bcbb2e608779d6b70e7f' + + '86bec7e90bf113cbd4fdd0c4e2f4625e' + + '148dd7ee1a52776cf77f240514d9ccfc' + + '3b5ddab8ee255e39ee389072962c111a', 0x30], + [Buffer.from('ffffff', 'hex'), 64, + 'abe569c1f77ec340f02705e7d37c9ab7' + + 'e155516e4a6a150021d70b6fac0bb40c' + + '069f9a9828a0d575cd99f9bae435ab1a' + + 'cf7ed9110ba97ce0388d074bac768776', 0x7f], +]; + +const kt128Vectors = [ + // [input, outputLengthBytes, expected(, customization)] + [new Uint8Array(0), 32, + '1ac2d450fc3b4205d19da7bfca1b3751' + + '3c0803577ac7167f06fe2ce1f0ef39e5'], + [new Uint8Array(0), 64, + '1ac2d450fc3b4205d19da7bfca1b3751' + + '3c0803577ac7167f06fe2ce1f0ef39e5' + + '4269c056b8c82e48276038b6d292966c' + + 'c07a3d4645272e31ff38508139eb0a71'], + [ptn(1), 32, + '2bda92450e8b147f8a7cb629e784a058' + + 'efca7cf7d8218e02d345dfaa65244a1f'], + [ptn(17), 32, + '6bf75fa2239198db4772e36478f8e19b' + + '0f371205f6a9a93a273f51df37122888'], + [ptn(17 ** 2), 32, + '0c315ebcdedbf61426de7dcf8fb725d1' + + 'e74675d7f5327a5067f367b108ecb67c'], + [ptn(17 ** 3), 32, + 'cb552e2ec77d9910701d578b457ddf77' + + '2c12e322e4ee7fe417f92c758f0d59d0'], + [ptn(17 ** 4), 32, + '8701045e22205345ff4dda05555cbb5c' + + '3af1a771c2b89baef37db43d9998b9fe'], + [ptn(17 ** 5), 32, + '844d610933b1b9963cbdeb5ae3b6b05c' + + 'c7cbd67ceedf883eb678a0a8e0371682'], + [ptn(17 ** 6), 32, + '3c390782a8a4e89fa6367f72feaaf132' + + '55c8d95878481d3cd8ce85f58e880af8'], + [new Uint8Array(0), 32, + 'fab658db63e94a246188bf7af69a1330' + + '45f46ee984c56e3c3328caaf1aa1a583', ptn(1)], + [Buffer.from('ff', 'hex'), 32, + 'd848c5068ced736f4462159b9867fd4c' + + '20b808acc3d5bc48e0b06ba0a3762ec4', ptn(41)], + [Buffer.from('ffffff', 'hex'), 32, + 'c389e5009ae57120854c2e8c64670ac0' + + '1358cf4c1baf89447a724234dc7ced74', ptn(41 ** 2)], + [Buffer.from('ffffffffffffff', 'hex'), 32, + '75d2f86a2e644566726b4fbcfc5657b9' + + 'dbcf070c7b0dca06450ab291d7443bcf', ptn(41 ** 3)], + [ptn(8191), 32, + '1b577636f723643e990cc7d6a6598374' + + '36fd6a103626600eb8301cd1dbe553d6'], + [ptn(8192), 32, + '48f256f6772f9edfb6a8b661ec92dc93' + + 'b95ebd05a08a17b39ae3490870c926c3'], + [ptn(8192), 32, + '3ed12f70fb05ddb58689510ab3e4d23c' + + '6c6033849aa01e1d8c220a297fedcd0b', ptn(8189)], + [ptn(8192), 32, + '6a7c1b6a5cd0d8c9ca943a4a216cc646' + + '04559a2ea45f78570a15253d67ba00ae', ptn(8190)], +]; + +const kt256Vectors = [ + // [input, outputLengthBytes, expected(, customization)] + [new Uint8Array(0), 64, + 'b23d2e9cea9f4904e02bec06817fc10c' + + 'e38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa3348235' + + '3fd4adc7148ecb782855003aaebde4a9'], + [new Uint8Array(0), 128, + 'b23d2e9cea9f4904e02bec06817fc10c' + + 'e38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa3348235' + + '3fd4adc7148ecb782855003aaebde4a9' + + 'b0925319d8ea1e121a609821ec19efea' + + '89e6d08daee1662b69c840289f188ba8' + + '60f55760b61f82114c030c97e5178449' + + '608ccd2cd2d919fc7829ff69931ac4d0'], + [ptn(1), 64, + '0d005a194085360217128cf17f91e1f7' + + '1314efa5564539d444912e3437efa17f' + + '82db6f6ffe76e781eaa068bce01f2bbf' + + '81eacb983d7230f2fb02834a21b1ddd0'], + [ptn(17), 64, + '1ba3c02b1fc514474f06c8979978a905' + + '6c8483f4a1b63d0dccefe3a28a2f323e' + + '1cdcca40ebf006ac76ef039715234683' + + '7b1277d3e7faa9c9653b19075098527b'], + [ptn(17 ** 2), 64, + 'de8ccbc63e0f133ebb4416814d4c66f6' + + '91bbf8b6a61ec0a7700f836b086cb029' + + 'd54f12ac7159472c72db118c35b4e6aa' + + '213c6562caaa9dcc518959e69b10f3ba'], + [ptn(17 ** 3), 64, + '647efb49fe9d717500171b41e7f11bd4' + + '91544443209997ce1c2530d15eb1ffbb' + + '598935ef954528ffc152b1e4d731ee26' + + '83680674365cd191d562bae753b84aa5'], + [ptn(17 ** 4), 64, + 'b06275d284cd1cf205bcbe57dccd3ec1' + + 'ff6686e3ed15776383e1f2fa3c6ac8f0' + + '8bf8a162829db1a44b2a43ff83dd89c3' + + 'cf1ceb61ede659766d5ccf817a62ba8d'], + [ptn(17 ** 5), 64, + '9473831d76a4c7bf77ace45b59f1458b' + + '1673d64bcd877a7c66b2664aa6dd149e' + + '60eab71b5c2bab858c074ded81ddce2b' + + '4022b5215935c0d4d19bf511aeeb0772'], + [ptn(17 ** 6), 64, + '0652b740d78c5e1f7c8dcc1777097382' + + '768b7ff38f9a7a20f29f413bb1b3045b' + + '31a5578f568f911e09cf44746da84224' + + 'a5266e96a4a535e871324e4f9c7004da'], + [new Uint8Array(0), 64, + '9280f5cc39b54a5a594ec63de0bb9937' + + '1e4609d44bf845c2f5b8c316d72b1598' + + '11f748f23e3fabbe5c3226ec96c62186' + + 'df2d33e9df74c5069ceecbb4dd10eff6', ptn(1)], + [Buffer.from('ff', 'hex'), 64, + '47ef96dd616f200937aa7847e34ec2fe' + + 'ae8087e3761dc0f8c1a154f51dc9ccf8' + + '45d7adbce57ff64b639722c6a1672e3b' + + 'f5372d87e00aff89be97240756998853', ptn(41)], + [Buffer.from('ffffff', 'hex'), 64, + '3b48667a5051c5966c53c5d42b95de45' + + '1e05584e7806e2fb765eda959074172c' + + 'b438a9e91dde337c98e9c41bed94c4e0' + + 'aef431d0b64ef2324f7932caa6f54969', ptn(41 ** 2)], + [Buffer.from('ffffffffffffff', 'hex'), 64, + 'e0911cc00025e1540831e266d94add9b' + + '98712142b80d2629e643aac4efaf5a3a' + + '30a88cbf4ac2a91a2432743054fbcc98' + + '97670e86ba8cec2fc2ace9c966369724', ptn(41 ** 3)], + [ptn(8191), 64, + '3081434d93a4108d8d8a3305b89682ce' + + 'bedc7ca4ea8a3ce869fbb73cbe4a58ee' + + 'f6f24de38ffc170514c70e7ab2d01f03' + + '812616e863d769afb3753193ba045b20'], + [ptn(8192), 64, + 'c6ee8e2ad3200c018ac87aaa031cdac2' + + '2121b412d07dc6e0dccbb53423747e9a' + + '1c18834d99df596cf0cf4b8dfafb7bf0' + + '2d139d0c9035725adc1a01b7230a41fa'], + [ptn(8192), 64, + '74e47879f10a9c5d11bd2da7e194fe57' + + 'e86378bf3c3f7448eff3c576a0f18c5c' + + 'aae0999979512090a7f348af4260d4de' + + '3c37f1ecaf8d2c2c96c1d16c64b12496', ptn(8189)], + [ptn(8192), 64, + 'f4b5908b929ffe01e0f79ec2f21243d4' + + '1a396b2e7303a6af1d6399cd6c7a0a2d' + + 'd7c4f607e8277f9c9b1cb4ab9ddc59d4' + + 'b92d1fc7558441f1832c3279a4241b8b', ptn(8190)], +]; + +async function checkDigest(name, vectors) { + const isKT = name.startsWith('KT'); + for (const [input, outputLength, expected, ...rest] of vectors) { + const algorithm = { name, outputLength: outputLength * 8 }; + if (rest.length) { + if (isKT) + algorithm.customization = rest[0]; + else + algorithm.domainSeparation = rest[0]; + } + const result = await subtle.digest(algorithm, input); + assert.deepStrictEqual( + Buffer.from(result).toString('hex'), + expected, + ); + } +} + +(async () => { + await checkDigest('TurboSHAKE128', turboSHAKE128Vectors); + + // TurboSHAKE128(M=00^0, D=1F, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'TurboSHAKE128', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'a3b9b0385900ce761f22aed548e754da' + + '10a5242d62e8c658e3f3a923a7555607', + ); + } + + await checkDigest('TurboSHAKE256', turboSHAKE256Vectors); + + // TurboSHAKE256(M=00^0, D=1F, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'TurboSHAKE256', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'abefa11630c661269249742685ec082f' + + '207265dccf2f43534e9c61ba0c9d1d75', + ); + } + + await checkDigest('KT128', kt128Vectors); + + // KT128(M=00^0, C=00^0, 10032), last 32 bytes + { + const result = await subtle.digest({ + name: 'KT128', + outputLength: 10032 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-32).toString('hex'), + 'e8dc563642f7228c84684c898405d3a8' + + '34799158c079b12880277a1d28e2ff6d', + ); + } + + await checkDigest('KT256', kt256Vectors); + + // KT256(M=00^0, C=00^0, 10064), last 64 bytes + { + const result = await subtle.digest({ + name: 'KT256', + outputLength: 10064 * 8, + }, new Uint8Array(0)); + assert.deepStrictEqual( + Buffer.from(result).subarray(-64).toString('hex'), + 'ad4a1d718cf950506709a4c33396139b' + + '4449041fc79a05d68da35f1e453522e0' + + '56c64fe94958e7085f2964888259b993' + + '2752f3ccd855288efee5fcbb8b563069', + ); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-digest-turboshake.js b/test/parallel/test-webcrypto-digest-turboshake.js new file mode 100644 index 00000000000000..0b5586b19286be --- /dev/null +++ b/test/parallel/test-webcrypto-digest-turboshake.js @@ -0,0 +1,181 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = globalThis.crypto; + +const kSourceData = { + empty: '', + short: '156eea7cc14c56cb94db030a4a9d95ff', + medium: 'b6c8f9df648cd088b70f38e74197b18cb81e1e435' + + '0d50bccb8fb5a7379c87bb2e3d6ed5461ed1e9f36' + + 'f340a3962a446b815b794b4bd43a4403502077b22' + + '56cc807837f3aacd118eb4b9c2baeb897068625ab' + + 'aca193', + long: null +}; + +kSourceData.long = kSourceData.medium.repeat(1024); + +// Test vectors generated with PyCryptodome as a reference implementation +const kDigestedData = { + 'turboshake128': { + empty: '1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c', + short: 'f8d1ebf3b48b71b0514686090eb25f1de322a00149be9b4dc5f09ac9077cd8a8', + medium: '0d0e7eceb4ae58c3c48f6c2bad56d0f8ff3f887468d3ea55a138aedf395233c0', + long: '5747c06f02ffd9d6c911b6453cc8b717083ab6417319a6ec5c3bb39ed0baf331', + }, + 'turboshake256': { + empty: '367a329dafea871c7802ec67f905ae13c57695dc2c6663c61035f59a18f8e7db' + + '11edc0e12e91ea60eb6b32df06dd7f002fbafabb6e13ec1cc20d995547600db0', + short: 'b47aa0a5b76caf9b10cfaeff036df0cdb86362d2bd036a2fee0cd0d74e79279c' + + 'b9c57a70da1e3dd9e126a469857ba4c82b0efb3ae06d1a3781a6f102c3eb3a1d', + medium: '7fa19fd828762d2dba6eea8407d1fb04302b5a4f1ca3d00b3672c1e3b3331d18' + + '925b7ec380f3f04673a164dab04d2a0c5c12818046284c38d286645741a8aa3e', + long: '12d0b90c08f588710733cc07f0a2d6ab0795a4a24904c111062226fcd9d5dcb2' + + '1d6b5b848c9aebbcab221f031e9b4ea71e099ec785e822b1b83e73d0750ca1a7', + }, + 'kt128': { + empty: '1ac2d450fc3b4205d19da7bfca1b37513c0803577ac7167f06fe2ce1f0ef39e5', + short: '4719a2ac1dc1c592521cf201df3f476ea496fe461abe9a2604527f6bec047579', + medium: '00f3add71679681720b925416953897ac62cfae97060dd5f2e1641a076580cc9', + long: 'c05805c2736deb4be3fca6e3717b9af0aa18ceeaaeeab66b328a3ffebf0a814d', + }, + 'kt256': { + empty: 'b23d2e9cea9f4904e02bec06817fc10ce38ce8e93ef4c89e6537076af8646404' + + 'e3e8b68107b8833a5d30490aa33482353fd4adc7148ecb782855003aaebde4a9', + short: '6709e5e312f2dee4547ecb0ab7d42728ba57985983731afbd6c2a0676c522274' + + 'cf9153064ee07982129d3f58d4dbe00050eb28b392559bdb020aca302b7a28cb', + medium: '9078b6ff78e9c4b3c8ff49e5b9f337b36cc6d6749d23985035886d993db69f7e' + + '05fea97125e0889130da09fc5837761f7793e3e44d85be1ee1f6af7f4a1f50cb', + long: '41f83b7c7d02fc6d98f1fa1474d765caff4673f90cd7204894d7da72d97403b6' + + '2fe5c4bae2bf0ce3dcd51e80c98bd25ce5fe54040259d9466b67f1517dac0712', + }, +}; + +function buildAlg(name) { + const lower = name.toLowerCase(); + if (lower.startsWith('turboshake')) { + const outputLength = lower === 'turboshake128' ? 256 : 512; + return { name, outputLength }; + } + if (lower.startsWith('kt')) { + const outputLength = lower === 'kt128' ? 256 : 512; + return { name, outputLength }; + } + return name; +} + +async function testDigest(size, alg) { + const digest = await subtle.digest( + alg, + Buffer.from(kSourceData[size], 'hex')); + + assert.strictEqual( + Buffer.from(digest).toString('hex'), + kDigestedData[(alg.name || alg).toLowerCase()][size]); +} + +// Known-answer tests +(async function() { + const variations = []; + Object.keys(kSourceData).forEach((size) => { + Object.keys(kDigestedData).forEach((alg) => { + const upCase = alg.toUpperCase(); + const downCase = alg.toLowerCase(); + const mixedCase = upCase.slice(0, 1) + downCase.slice(1); + + variations.push(testDigest(size, buildAlg(upCase))); + variations.push(testDigest(size, buildAlg(downCase))); + variations.push(testDigest(size, buildAlg(mixedCase))); + }); + }); + + await Promise.all(variations); +})().then(common.mustCall()); + +// Edge cases: zero-length output rejects +(async () => { + await assert.rejects( + subtle.digest({ name: 'TurboSHAKE128', outputLength: 0 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid TurboShakeParams outputLength', + }); + + await assert.rejects( + subtle.digest({ name: 'KT128', outputLength: 0 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid KangarooTwelveParams outputLength', + }); +})().then(common.mustCall()); + +// Edge case: non-byte-aligned outputLength rejects +(async () => { + await assert.rejects( + subtle.digest({ name: 'TurboSHAKE128', outputLength: 7 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid TurboShakeParams outputLength', + }); + + await assert.rejects( + subtle.digest({ name: 'KT128', outputLength: 7 }, Buffer.alloc(1)), + { + name: 'OperationError', + message: 'Invalid KangarooTwelveParams outputLength', + }); +})().then(common.mustCall()); + +// TurboSHAKE domain separation byte +(async () => { + // Domain separation 0x07 should produce different output than default 0x1F + const [d07, d1f] = await Promise.all([ + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x07 }, + Buffer.alloc(0)), + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256 }, + Buffer.alloc(0)), + ]); + assert.notDeepStrictEqual( + new Uint8Array(d07), + new Uint8Array(d1f)); + + // Verify D=0x07 against known vector + assert.strictEqual( + Buffer.from(d07).toString('hex'), + '5a223ad30b3b8c66a243048cfced430f54e7529287d15150b973133adfac6a2f'); +})().then(common.mustCall()); + +// KT128 with customization string +(async () => { + const digest = await subtle.digest( + { name: 'KT128', outputLength: 256, customization: Buffer.from('test') }, + Buffer.from('hello')); + assert(digest instanceof ArrayBuffer); + assert.strictEqual(digest.byteLength, 32); +})().then(common.mustCall()); + +// TurboSHAKE domain separation out of range +(async () => { + await assert.rejects( + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x00 }, + Buffer.alloc(0)), + { + name: 'OperationError', + }); + await assert.rejects( + subtle.digest( + { name: 'TurboSHAKE128', outputLength: 256, domainSeparation: 0x80 }, + Buffer.alloc(0)), + { + name: 'OperationError', + }); +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-digest.js b/test/parallel/test-webcrypto-digest.js index 4d22006937f8cb..4a28d88dcd72c3 100644 --- a/test/parallel/test-webcrypto-digest.js +++ b/test/parallel/test-webcrypto-digest.js @@ -19,8 +19,8 @@ const kTests = [ if (!process.features.openssl_is_boringssl) { kTests.push( - [{ name: 'cSHAKE128', length: 256 }, ['shake128', { outputLength: 256 >> 3 }], 256], - [{ name: 'cSHAKE256', length: 512 }, ['shake256', { outputLength: 512 >> 3 }], 512], + [{ name: 'cSHAKE128', outputLength: 256 }, ['shake128', { outputLength: 256 >> 3 }], 256], + [{ name: 'cSHAKE256', outputLength: 512 }, ['shake256', { outputLength: 512 >> 3 }], 512], ['SHA3-256', ['sha3-256'], 256], ['SHA3-384', ['sha3-384'], 384], ['SHA3-512', ['sha3-512'], 512], @@ -223,10 +223,10 @@ async function testDigest(size, alg) { function applyXOF(name) { if (name.match(/cshake128/i)) { - return { name, length: 256 }; + return { name, outputLength: 256 }; } if (name.match(/cshake256/i)) { - return { name, length: 512 }; + return { name, outputLength: 512 }; } return name; @@ -259,13 +259,13 @@ function applyXOF(name) { if (getHashes().includes('shake128')) { (async () => { assert.deepStrictEqual( - new Uint8Array(await subtle.digest({ name: 'cSHAKE128', length: 0 }, Buffer.alloc(1))), + new Uint8Array(await subtle.digest({ name: 'cSHAKE128', outputLength: 0 }, Buffer.alloc(1))), new Uint8Array(0), ); - await assert.rejects(subtle.digest({ name: 'cSHAKE128', length: 7 }, Buffer.alloc(1)), { + await assert.rejects(subtle.digest({ name: 'cSHAKE128', outputLength: 7 }, Buffer.alloc(1)), { name: 'NotSupportedError', - message: 'Unsupported CShakeParams length', + message: 'Unsupported CShakeParams outputLength', }); })().then(common.mustCall()); } diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js index aea9528f2463db..5362484288089d 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-chacha20-poly1305.js @@ -189,7 +189,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)); await assert.rejects( subtle.decrypt({ name: 'ChaCha20-Poly1305', iv }, secretKey, new Uint8Array(8)), - { name: 'OperationError', message: /The provided data is too small/ } + { name: 'OperationError' } ); // Test invalid tagLength values diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js index 38d619eb8c00ec..ceb652955d5929 100644 --- a/test/parallel/test-webcrypto-export-import-ml-dsa.js +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -12,6 +12,7 @@ if (!hasOpenSSL(3, 5)) const assert = require('assert'); const { subtle } = globalThis.crypto; +const { createPrivateKey } = require('crypto'); const fixtures = require('../common/fixtures'); @@ -196,41 +197,32 @@ async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) { } async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) { - const key = await subtle.importKey( - 'pkcs8', - keyData[name].pkcs8_priv_only, - { name }, - extractable, - privateUsages); - assert.strictEqual(key.type, 'private'); - assert.strictEqual(key.extractable, extractable); - assert.deepStrictEqual(key.usages, privateUsages); - assert.deepStrictEqual(key.algorithm.name, name); - assert.strictEqual(key.algorithm, key.algorithm); - assert.strictEqual(key.usages, key.usages); - - if (extractable) { - await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { - assert.strictEqual(err.name, 'OperationError'); - assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); - assert.strictEqual(err.cause.message, 'Failed to get raw seed'); - return true; + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_priv_only, + { name }, + extractable, + privateUsages), + { + name: 'NotSupportedError', + message: 'Importing an ML-DSA PKCS#8 key without a seed is not supported', }); - } else { - await assert.rejects( - subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ - }); - } +} +async function testImportPkcs8MismatchedSeed({ name, privateUsages }, extractable) { + const modified = Buffer.from(keyData[name].pkcs8); + modified[30] ^= 0xff; await assert.rejects( subtle.importKey( 'pkcs8', - keyData[name].pkcs8_seed_only, + modified, { name }, extractable, - [/* empty usages */]), - { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); + privateUsages), + { + name: 'DataError', + }); } async function testImportJwk({ name, publicUsages, privateUsages }, extractable) { @@ -493,6 +485,7 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { tests.push(testImportPkcs8(vector, extractable)); tests.push(testImportPkcs8SeedOnly(vector, extractable)); tests.push(testImportPkcs8PrivOnly(vector, extractable)); + tests.push(testImportPkcs8MismatchedSeed(vector, extractable)); tests.push(testImportJwk(vector, extractable)); tests.push(testImportRawSeed(vector, extractable)); tests.push(testImportRawPublic(vector, extractable)); @@ -509,3 +502,17 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { message: 'Unable to import ML-DSA-44 using raw format', }); })().then(common.mustCall()); + +(async function() { + for (const { name, privateUsages } of testVectors) { + const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii'); + const keyObject = createPrivateKey(pem); + const key = keyObject.toCryptoKey({ name }, true, privateUsages); + await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { + assert.strictEqual(err.name, 'OperationError'); + assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); + assert.strictEqual(err.cause.message, 'Failed to get raw seed'); + return true; + }); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-export-import-ml-kem.js b/test/parallel/test-webcrypto-export-import-ml-kem.js index c927b5571a69da..c33eb2b5993156 100644 --- a/test/parallel/test-webcrypto-export-import-ml-kem.js +++ b/test/parallel/test-webcrypto-export-import-ml-kem.js @@ -12,6 +12,7 @@ if (!hasOpenSSL(3, 5)) const assert = require('assert'); const { subtle } = globalThis.crypto; +const { createPrivateKey } = require('crypto'); const fixtures = require('../common/fixtures'); @@ -179,41 +180,32 @@ async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) { } async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) { - const key = await subtle.importKey( - 'pkcs8', - keyData[name].pkcs8_priv_only, - { name }, - extractable, - privateUsages); - assert.strictEqual(key.type, 'private'); - assert.strictEqual(key.extractable, extractable); - assert.deepStrictEqual(key.usages, privateUsages); - assert.deepStrictEqual(key.algorithm.name, name); - assert.strictEqual(key.algorithm, key.algorithm); - assert.strictEqual(key.usages, key.usages); - - if (extractable) { - await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { - assert.strictEqual(err.name, 'OperationError'); - assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); - assert.strictEqual(err.cause.message, 'Failed to get raw seed'); - return true; + await assert.rejects( + subtle.importKey( + 'pkcs8', + keyData[name].pkcs8_priv_only, + { name }, + extractable, + privateUsages), + { + name: 'NotSupportedError', + message: 'Importing an ML-KEM PKCS#8 key without a seed is not supported', }); - } else { - await assert.rejects( - subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ - }); - } +} +async function testImportPkcs8MismatchedSeed({ name, privateUsages }, extractable) { + const modified = Buffer.from(keyData[name].pkcs8); + modified[30] ^= 0xff; await assert.rejects( subtle.importKey( 'pkcs8', - keyData[name].pkcs8_seed_only, + modified, { name }, extractable, - [/* empty usages */]), - { name: 'SyntaxError', message: 'Usages cannot be empty when importing a private key.' }); + privateUsages), + { + name: 'DataError', + }); } async function testImportRawPublic({ name, publicUsages }, extractable) { @@ -298,6 +290,7 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { tests.push(testImportPkcs8(vector, extractable)); tests.push(testImportPkcs8SeedOnly(vector, extractable)); tests.push(testImportPkcs8PrivOnly(vector, extractable)); + tests.push(testImportPkcs8MismatchedSeed(vector, extractable)); tests.push(testImportRawSeed(vector, extractable)); tests.push(testImportRawPublic(vector, extractable)); } @@ -313,3 +306,17 @@ async function testImportRawSeed({ name, privateUsages }, extractable) { message: 'Unable to import ML-KEM-512 using raw format', }); })().then(common.mustCall()); + +(async function() { + for (const { name, privateUsages } of testVectors) { + const pem = fixtures.readKey(getKeyFileName(name.toLowerCase(), 'private_priv_only'), 'ascii'); + const keyObject = createPrivateKey(pem); + const key = keyObject.toCryptoKey({ name }, true, privateUsages); + await assert.rejects(subtle.exportKey('pkcs8', key), (err) => { + assert.strictEqual(err.name, 'OperationError'); + assert.strictEqual(err.cause.code, 'ERR_CRYPTO_OPERATION_FAILED'); + assert.strictEqual(err.cause.message, 'Failed to get raw seed'); + return true; + }); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-webcrypto-promise-prototype-pollution.mjs b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs new file mode 100644 index 00000000000000..b4fbedba5e3242 --- /dev/null +++ b/test/parallel/test-webcrypto-promise-prototype-pollution.mjs @@ -0,0 +1,95 @@ +import * as common from '../common/index.mjs'; + +if (!common.hasCrypto) common.skip('missing crypto'); + +// WebCrypto subtle methods must not leak intermediate values +// through Promise.prototype.then pollution. +// Regression test for https://github.com/nodejs/node/pull/61492 +// and https://github.com/nodejs/node/issues/59699. + +import { hasOpenSSL } from '../common/crypto.js'; + +const { subtle } = globalThis.crypto; + +Promise.prototype.then = common.mustNotCall('Promise.prototype.then'); + +await subtle.digest('SHA-256', new Uint8Array([1, 2, 3])); + +await subtle.generateKey({ name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); + +await subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']); + +const rawKey = globalThis.crypto.getRandomValues(new Uint8Array(32)); + +const importedKey = await subtle.importKey( + 'raw', rawKey, { name: 'AES-CBC', length: 256 }, false, ['encrypt', 'decrypt']); + +const exportableKey = await subtle.importKey( + 'raw', rawKey, { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); + +await subtle.exportKey('raw', exportableKey); + +const iv = globalThis.crypto.getRandomValues(new Uint8Array(16)); +const plaintext = new TextEncoder().encode('Hello, world!'); + +const ciphertext = await subtle.encrypt({ name: 'AES-CBC', iv }, importedKey, plaintext); + +await subtle.decrypt({ name: 'AES-CBC', iv }, importedKey, ciphertext); + +const signingKey = await subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']); + +const data = new TextEncoder().encode('test data'); + +const signature = await subtle.sign('HMAC', signingKey, data); + +await subtle.verify('HMAC', signingKey, signature, data); + +const pbkdf2Key = await subtle.importKey( + 'raw', rawKey, 'PBKDF2', false, ['deriveBits', 'deriveKey']); + +await subtle.deriveBits( + { name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' }, + pbkdf2Key, 256); + +await subtle.deriveKey( + { name: 'PBKDF2', salt: rawKey, iterations: 1000, hash: 'SHA-256' }, + pbkdf2Key, + { name: 'AES-CBC', length: 256 }, + true, + ['encrypt', 'decrypt']); + +const wrappingKey = await subtle.generateKey( + { name: 'AES-KW', length: 256 }, true, ['wrapKey', 'unwrapKey']); + +const keyToWrap = await subtle.generateKey( + { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); + +const wrapped = await subtle.wrapKey('raw', keyToWrap, wrappingKey, 'AES-KW'); + +await subtle.unwrapKey( + 'raw', wrapped, wrappingKey, 'AES-KW', + { name: 'AES-CBC', length: 256 }, true, ['encrypt', 'decrypt']); + +const { privateKey } = await subtle.generateKey( + { name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']); + +await subtle.getPublicKey(privateKey, ['verify']); + +if (hasOpenSSL(3, 5)) { + const kemPair = await subtle.generateKey( + { name: 'ML-KEM-768' }, false, + ['encapsulateKey', 'encapsulateBits', 'decapsulateKey', 'decapsulateBits']); + + const { ciphertext: ct1 } = await subtle.encapsulateKey( + { name: 'ML-KEM-768' }, kemPair.publicKey, 'HKDF', false, ['deriveBits']); + + await subtle.decapsulateKey( + { name: 'ML-KEM-768' }, kemPair.privateKey, ct1, 'HKDF', false, ['deriveBits']); + + const { ciphertext: ct2 } = await subtle.encapsulateBits( + { name: 'ML-KEM-768' }, kemPair.publicKey); + + await subtle.decapsulateBits( + { name: 'ML-KEM-768' }, kemPair.privateKey, ct2); +} diff --git a/test/parallel/test-webcrypto-sign-verify-kmac.js b/test/parallel/test-webcrypto-sign-verify-kmac.js index c30196a94d9fb6..d41095e0893d97 100644 --- a/test/parallel/test-webcrypto-sign-verify-kmac.js +++ b/test/parallel/test-webcrypto-sign-verify-kmac.js @@ -19,7 +19,7 @@ async function testVerify({ algorithm, key, data, customization, - length, + outputLength, expected }) { const [ verifyKey, @@ -46,7 +46,7 @@ async function testVerify({ algorithm, const signParams = { name: algorithm, - length, + outputLength, customization, }; @@ -112,7 +112,7 @@ async function testVerify({ algorithm, { assert(!(await subtle.verify({ ...signParams, - length: length === 256 ? 512 : 256, + outputLength: outputLength === 256 ? 512 : 256, }, verifyKey, expected, data))); } } @@ -121,7 +121,7 @@ async function testSign({ algorithm, key, data, customization, - length, + outputLength, expected }) { const [ signKey, @@ -148,7 +148,7 @@ async function testSign({ algorithm, const signParams = { name: algorithm, - length, + outputLength, customization, }; diff --git a/test/parallel/test-webcrypto-sign-verify.js b/test/parallel/test-webcrypto-sign-verify.js index 8b034e005b29a4..26e66d9aa0fa8b 100644 --- a/test/parallel/test-webcrypto-sign-verify.js +++ b/test/parallel/test-webcrypto-sign-verify.js @@ -118,12 +118,12 @@ if (hasOpenSSL(3)) { const signature = await subtle.sign({ name, - length: 256, + outputLength: 256, }, key, ec.encode(data)); assert(await subtle.verify({ name, - length: 256, + outputLength: 256, }, key, signature, ec.encode(data))); } diff --git a/test/parallel/test-webcrypto-util.js b/test/parallel/test-webcrypto-util.js index b8d5361433fd0e..89d8575e20ddbd 100644 --- a/test/parallel/test-webcrypto-util.js +++ b/test/parallel/test-webcrypto-util.js @@ -17,3 +17,35 @@ const { assert.strictEqual(normalizeAlgorithm(algorithm, 'sign') !== algorithm, true); assert.deepStrictEqual(algorithm, { name: 'ECDSA', hash: 'SHA-256' }); } + +// The algorithm name getter should only be invoked once during +// normalizeAlgorithm, including for algorithms with a non-null desiredType +// where step 6 runs the specialized dictionary converter. +// Refs: https://github.com/web-platform-tests/wpt/pull/57614#pullrequestreview-3808145365 +{ + let nameReadCount = 0; + const algorithm = { + get name() { + nameReadCount++; + return 'AES-GCM'; + }, + iv: new Uint8Array(12), + }; + const normalized = normalizeAlgorithm(algorithm, 'encrypt'); + assert.strictEqual(normalized.name, 'AES-GCM'); + assert.strictEqual(nameReadCount, 1); +} + +{ + let nameReadCount = 0; + const algorithm = { + get name() { + nameReadCount++; + return 'ECDSA'; + }, + hash: 'SHA-256', + }; + const normalized = normalizeAlgorithm(algorithm, 'sign'); + assert.strictEqual(normalized.name, 'ECDSA'); + assert.strictEqual(nameReadCount, 1); +} diff --git a/test/parallel/test-zlib-reset-during-write.js b/test/parallel/test-zlib-reset-during-write.js new file mode 100644 index 00000000000000..35c4c853e5ea59 --- /dev/null +++ b/test/parallel/test-zlib-reset-during-write.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { createBrotliCompress, createDeflate } = require('zlib'); + +// Tests that calling .reset() while an async write is in progress +// throws an error instead of causing a use-after-free. + +for (const factory of [createBrotliCompress, createDeflate]) { + const stream = factory(); + const input = Buffer.alloc(1024, 0x41); + + stream.write(input, common.mustCall()); + stream.on('error', common.mustNotCall()); + + // The write has been dispatched to the thread pool. + // Calling reset while write is in progress must throw. + assert.throws(() => { + stream._handle.reset(); + }, { + message: 'Cannot reset zlib stream while a write is in progress', + }); +} diff --git a/test/pummel/test-repl-paste-big-data.js b/test/pummel/test-repl-paste-big-data.js index 2265a1af8e393c..46f8415a2b0aae 100644 --- a/test/pummel/test-repl-paste-big-data.js +++ b/test/pummel/test-repl-paste-big-data.js @@ -8,7 +8,7 @@ const { startNewREPLServer } = require('../common/repl'); const cpuUsage = process.cpuUsage(); -const { replServer } = startNewREPLServer({}, { disableDomainErrorAssert: true }); +const { replServer } = startNewREPLServer(); replServer.input.emit('data', '{}'); replServer.input.emit('keypress', '', { name: 'left' }); replServer.input.emit('data', 'node'); diff --git a/test/sea/test-single-executable-application-esm-code-cache.js b/test/sea/test-single-executable-application-esm-code-cache.js new file mode 100644 index 00000000000000..1fe6dff7a3da25 --- /dev/null +++ b/test/sea/test-single-executable-application-esm-code-cache.js @@ -0,0 +1,34 @@ +'use strict'; + +// This tests the creation of a single executable application with an ESM +// entry point using "mainFormat": "module" and "useCodeCache": true. + +require('../common'); + +const { + buildSEA, + skipIfBuildSEAIsNotSupported, +} = require('../common/sea'); + +skipIfBuildSEAIsNotSupported(); + +const tmpdir = require('../common/tmpdir'); +const fixtures = require('../common/fixtures'); +const { spawnSyncAndExitWithoutError } = require('../common/child_process'); + +tmpdir.refresh(); + +const outputFile = buildSEA(fixtures.path('sea', 'esm-code-cache')); + +spawnSyncAndExitWithoutError( + outputFile, + { + env: { + NODE_DEBUG_NATIVE: 'SEA', + ...process.env, + }, + }, + { + stdout: /ESM SEA with code cache executed successfully/, + stderr: /SEA module code cache accepted/, + }); diff --git a/test/test-runner/test-output-coverage-with-mock-cjs.mjs b/test/test-runner/test-output-coverage-with-mock-cjs.mjs new file mode 100644 index 00000000000000..3f8152a5cdcd89 --- /dev/null +++ b/test/test-runner/test-output-coverage-with-mock-cjs.mjs @@ -0,0 +1,22 @@ +import * as common from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { spawnAndAssert, defaultTransform, ensureCwdIsProjectRoot } from '../common/assertSnapshot.js'; + +if (!process.features.inspector) { + common.skip('inspector support required'); +} + +ensureCwdIsProjectRoot(); +await spawnAndAssert( + fixtures.path('test-runner/output/coverage-with-mock-cjs.mjs'), + defaultTransform, + { + flags: [ + '--disable-warning=ExperimentalWarning', + '--test-reporter=tap', + '--experimental-test-module-mocks', + '--experimental-test-coverage', + '--test-coverage-exclude=!test/**', + ], + }, +); diff --git a/test/wpt/status/FileAPI/blob.cjs b/test/wpt/status/FileAPI/blob.cjs new file mode 100644 index 00000000000000..b59756abc70121 --- /dev/null +++ b/test/wpt/status/FileAPI/blob.cjs @@ -0,0 +1,59 @@ +'use strict'; + +const os = require('node:os'); + +// On AIX, V8's OS::DecommitPages() has an inherent race condition caused by +// AIX's non-POSIX MAP_FIXED behavior. The implementation must munmap() then +// mmap(), and another thread can steal the address range in between. The +// Blob.arrayBuffer() tests trigger this by creating enough GC pressure +// (especially the concurrent reads test) to hit the race window. +// See deps/v8/src/base/platform/platform-aix.cc, lines 168-203. +const isAIX = os.type() === 'AIX'; + +const conditionalSkips = {}; + +if (isAIX) { + conditionalSkips['Blob-array-buffer.any.js'] = { + skip: 'V8 DecommitPages race condition on AIX (munmap/mmap non-atomic)', + }; +} + +module.exports = { + ...conditionalSkips, + 'Blob-constructor-dom.window.js': { + skip: 'Depends on DOM API', + }, + 'Blob-constructor.any.js': { + fail: { + expected: [ + 'blobParts not an object: boolean with Boolean.prototype[Symbol.iterator]', + 'blobParts not an object: number with Number.prototype[Symbol.iterator]', + 'blobParts not an object: BigInt with BigInt.prototype[Symbol.iterator]', + 'blobParts not an object: Symbol with Symbol.prototype[Symbol.iterator]', + 'Getters and value conversions should happen in order until an exception is thrown.', + 'Arguments should be evaluated from left to right.', + ], + flaky: [ + 'Passing typed arrays as elements of the blobParts array should work.', + 'Passing a Float16Array as element of the blobParts array should work.', + 'Passing a Float64Array as element of the blobParts array should work.', + 'Passing BigInt typed arrays as elements of the blobParts array should work.', + ], + }, + }, + 'Blob-in-worker.worker.js': { + skip: 'Depends on Web Workers API', + }, + 'Blob-slice.any.js': { + fail: { + expected: [ + 'Slicing test: slice (1,1).', + 'Slicing test: slice (1,3).', + 'Slicing test: slice (1,5).', + 'Slicing test: slice (1,7).', + 'Slicing test: slice (1,8).', + 'Slicing test: slice (1,9).', + ], + }, + }, +}; diff --git a/test/wpt/status/FileAPI/blob.json b/test/wpt/status/FileAPI/blob.json deleted file mode 100644 index a82a9cb0cfdb11..00000000000000 --- a/test/wpt/status/FileAPI/blob.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "Blob-constructor-dom.window.js": { - "skip": "Depends on DOM API" - }, - "Blob-constructor.any.js": { - "fail": { - "expected": [ - "blobParts not an object: boolean with Boolean.prototype[Symbol.iterator]", - "blobParts not an object: number with Number.prototype[Symbol.iterator]", - "blobParts not an object: BigInt with BigInt.prototype[Symbol.iterator]", - "blobParts not an object: Symbol with Symbol.prototype[Symbol.iterator]", - "Getters and value conversions should happen in order until an exception is thrown.", - "Arguments should be evaluated from left to right." - ], - "flaky": [ - "Passing typed arrays as elements of the blobParts array should work.", - "Passing a Float16Array as element of the blobParts array should work.", - "Passing a Float64Array as element of the blobParts array should work.", - "Passing BigInt typed arrays as elements of the blobParts array should work." - ] - } - }, - "Blob-in-worker.worker.js": { - "skip": "Depends on Web Workers API" - }, - "Blob-slice.any.js": { - "fail": { - "expected": [ - "Slicing test: slice (1,1).", - "Slicing test: slice (1,3).", - "Slicing test: slice (1,5).", - "Slicing test: slice (1,7).", - "Slicing test: slice (1,8).", - "Slicing test: slice (1,9)." - ] - } - } -} diff --git a/tools/actions/start-ci.sh b/tools/actions/start-ci.sh index bca1da86074f3b..4d4fadf958a961 100755 --- a/tools/actions/start-ci.sh +++ b/tools/actions/start-ci.sh @@ -10,7 +10,7 @@ for pr in "$@"; do ci_started=yes rm -f output; - ncu-ci run "$pr" >output 2>&1 || ci_started=no + ncu-ci run --check-for-duplicates "$pr" >output 2>&1 || ci_started=no cat output if [ "$ci_started" = "no" ]; then diff --git a/tools/certdata.txt b/tools/certdata.txt index be24d9501c2d12..150f746b62d376 100644 --- a/tools/certdata.txt +++ b/tools/certdata.txt @@ -26591,7 +26591,7 @@ CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE # -# Certificate " OISTE Server Root RSA G1" +# Certificate "OISTE Server Root RSA G1" # # Issuer: CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH # Serial Number:55:a5:d9:67:94:28:c6:ed:0c:fa:27:dd:5b:01:4d:18 @@ -26604,7 +26604,7 @@ CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE CKA_TOKEN CK_BBOOL CK_TRUE CKA_PRIVATE CK_BBOOL CK_FALSE CKA_MODIFIABLE CK_BBOOL CK_FALSE -CKA_LABEL UTF8 " OISTE Server Root RSA G1" +CKA_LABEL UTF8 "OISTE Server Root RSA G1" CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 CKA_SUBJECT MULTILINE_OCTAL \060\113\061\013\060\011\006\003\125\004\006\023\002\103\110\061 @@ -26720,7 +26720,7 @@ CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE -# Trust for " OISTE Server Root RSA G1" +# Trust for "OISTE Server Root RSA G1" # Issuer: CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH # Serial Number:55:a5:d9:67:94:28:c6:ed:0c:fa:27:dd:5b:01:4d:18 # Subject: CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH @@ -26732,7 +26732,7 @@ CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST CKA_TOKEN CK_BBOOL CK_TRUE CKA_PRIVATE CK_BBOOL CK_FALSE CKA_MODIFIABLE CK_BBOOL CK_FALSE -CKA_LABEL UTF8 " OISTE Server Root RSA G1" +CKA_LABEL UTF8 "OISTE Server Root RSA G1" CKA_CERT_SHA1_HASH MULTILINE_OCTAL \367\000\064\045\224\210\150\061\344\064\207\077\160\376\206\263 \206\237\360\156 @@ -26755,3 +26755,133 @@ CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE + +# +# Certificate "e-Szigno TLS Root CA 2023" +# +# Issuer: CN=e-Szigno TLS Root CA 2023,OID.2.5.4.97=VATHU-23584497,O=Microsec Ltd.,L=Budapest,C=HU +# Serial Number:00:e8:6f:18:7b:d6:39:6b:98:4a:49:98:0a +# Subject: CN=e-Szigno TLS Root CA 2023,OID.2.5.4.97=VATHU-23584497,O=Microsec Ltd.,L=Budapest,C=HU +# Not Valid Before: Mon Jul 17 14:00:00 2023 +# Not Valid After : Sat Jul 17 14:00:00 2038 +# Fingerprint (SHA-256): B4:91:41:50:2D:00:66:3D:74:0F:2E:7E:C3:40:C5:28:00:96:26:66:12:1A:36:D0:9C:F7:DD:2B:90:38:4F:B4 +# Fingerprint (SHA1): 6F:9A:D5:D5:DF:E8:2C:EB:BE:37:07:EE:4F:4F:52:58:29:41:D1:FE +CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "e-Szigno TLS Root CA 2023" +CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509 +CKA_SUBJECT MULTILINE_OCTAL +\060\165\061\013\060\011\006\003\125\004\006\023\002\110\125\061 +\021\060\017\006\003\125\004\007\014\010\102\165\144\141\160\145 +\163\164\061\026\060\024\006\003\125\004\012\014\015\115\151\143 +\162\157\163\145\143\040\114\164\144\056\061\027\060\025\006\003 +\125\004\141\014\016\126\101\124\110\125\055\062\063\065\070\064 +\064\071\067\061\042\060\040\006\003\125\004\003\014\031\145\055 +\123\172\151\147\156\157\040\124\114\123\040\122\157\157\164\040 +\103\101\040\062\060\062\063 +END +CKA_ID UTF8 "0" +CKA_ISSUER MULTILINE_OCTAL +\060\165\061\013\060\011\006\003\125\004\006\023\002\110\125\061 +\021\060\017\006\003\125\004\007\014\010\102\165\144\141\160\145 +\163\164\061\026\060\024\006\003\125\004\012\014\015\115\151\143 +\162\157\163\145\143\040\114\164\144\056\061\027\060\025\006\003 +\125\004\141\014\016\126\101\124\110\125\055\062\063\065\070\064 +\064\071\067\061\042\060\040\006\003\125\004\003\014\031\145\055 +\123\172\151\147\156\157\040\124\114\123\040\122\157\157\164\040 +\103\101\040\062\060\062\063 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\015\000\350\157\030\173\326\071\153\230\112\111\230\012 +END +CKA_VALUE MULTILINE_OCTAL +\060\202\002\317\060\202\002\061\240\003\002\001\002\002\015\000 +\350\157\030\173\326\071\153\230\112\111\230\012\060\012\006\010 +\052\206\110\316\075\004\003\004\060\165\061\013\060\011\006\003 +\125\004\006\023\002\110\125\061\021\060\017\006\003\125\004\007 +\014\010\102\165\144\141\160\145\163\164\061\026\060\024\006\003 +\125\004\012\014\015\115\151\143\162\157\163\145\143\040\114\164 +\144\056\061\027\060\025\006\003\125\004\141\014\016\126\101\124 +\110\125\055\062\063\065\070\064\064\071\067\061\042\060\040\006 +\003\125\004\003\014\031\145\055\123\172\151\147\156\157\040\124 +\114\123\040\122\157\157\164\040\103\101\040\062\060\062\063\060 +\036\027\015\062\063\060\067\061\067\061\064\060\060\060\060\132 +\027\015\063\070\060\067\061\067\061\064\060\060\060\060\132\060 +\165\061\013\060\011\006\003\125\004\006\023\002\110\125\061\021 +\060\017\006\003\125\004\007\014\010\102\165\144\141\160\145\163 +\164\061\026\060\024\006\003\125\004\012\014\015\115\151\143\162 +\157\163\145\143\040\114\164\144\056\061\027\060\025\006\003\125 +\004\141\014\016\126\101\124\110\125\055\062\063\065\070\064\064 +\071\067\061\042\060\040\006\003\125\004\003\014\031\145\055\123 +\172\151\147\156\157\040\124\114\123\040\122\157\157\164\040\103 +\101\040\062\060\062\063\060\201\233\060\020\006\007\052\206\110 +\316\075\002\001\006\005\053\201\004\000\043\003\201\206\000\004 +\000\150\017\337\242\174\074\252\164\210\141\012\215\302\114\245 +\001\042\024\324\367\140\167\102\234\012\070\140\241\214\147\076 +\263\143\351\372\221\260\213\113\346\071\337\002\302\060\001\122 +\000\277\337\214\355\131\255\062\145\253\011\131\120\265\031\302 +\150\034\000\340\005\137\332\120\046\034\303\254\004\042\305\072 +\115\357\351\127\130\066\243\301\031\123\020\012\321\315\077\357 +\113\065\032\103\217\102\023\114\271\054\032\234\276\060\266\304 +\336\334\113\235\344\244\074\313\056\331\255\337\337\175\011\337 +\056\222\377\241\243\143\060\141\060\017\006\003\125\035\023\001 +\001\377\004\005\060\003\001\001\377\060\016\006\003\125\035\017 +\001\001\377\004\004\003\002\001\006\060\035\006\003\125\035\016 +\004\026\004\024\131\204\002\142\132\106\170\365\135\334\217\012 +\020\050\043\334\325\326\373\105\060\037\006\003\125\035\043\004 +\030\060\026\200\024\131\204\002\142\132\106\170\365\135\334\217 +\012\020\050\043\334\325\326\373\105\060\012\006\010\052\206\110 +\316\075\004\003\004\003\201\213\000\060\201\207\002\102\001\055 +\332\256\365\056\170\266\146\270\237\266\160\177\146\164\317\354 +\216\174\376\300\001\171\232\316\122\002\347\303\321\014\172\155 +\313\265\136\356\027\244\233\333\004\166\356\051\051\350\257\373 +\255\254\122\364\327\053\326\167\204\020\275\305\322\050\150\064 +\002\101\065\166\132\165\363\222\206\010\365\262\036\012\366\145 +\013\332\166\307\122\377\013\036\200\160\042\060\303\063\333\030 +\355\204\327\213\354\355\323\250\143\201\265\126\174\107\307\126 +\060\224\150\163\153\322\056\251\271\331\054\034\051\275\014\272 +\271\145\213 +END +CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_TRUE +CKA_NSS_SERVER_DISTRUST_AFTER CK_BBOOL CK_FALSE +CKA_NSS_EMAIL_DISTRUST_AFTER CK_BBOOL CK_FALSE + +# Trust for "e-Szigno TLS Root CA 2023" +# Issuer: CN=e-Szigno TLS Root CA 2023,OID.2.5.4.97=VATHU-23584497,O=Microsec Ltd.,L=Budapest,C=HU +# Serial Number:00:e8:6f:18:7b:d6:39:6b:98:4a:49:98:0a +# Subject: CN=e-Szigno TLS Root CA 2023,OID.2.5.4.97=VATHU-23584497,O=Microsec Ltd.,L=Budapest,C=HU +# Not Valid Before: Mon Jul 17 14:00:00 2023 +# Not Valid After : Sat Jul 17 14:00:00 2038 +# Fingerprint (SHA-256): B4:91:41:50:2D:00:66:3D:74:0F:2E:7E:C3:40:C5:28:00:96:26:66:12:1A:36:D0:9C:F7:DD:2B:90:38:4F:B4 +# Fingerprint (SHA1): 6F:9A:D5:D5:DF:E8:2C:EB:BE:37:07:EE:4F:4F:52:58:29:41:D1:FE +CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST +CKA_TOKEN CK_BBOOL CK_TRUE +CKA_PRIVATE CK_BBOOL CK_FALSE +CKA_MODIFIABLE CK_BBOOL CK_FALSE +CKA_LABEL UTF8 "e-Szigno TLS Root CA 2023" +CKA_CERT_SHA1_HASH MULTILINE_OCTAL +\157\232\325\325\337\350\054\353\276\067\007\356\117\117\122\130 +\051\101\321\376 +END +CKA_CERT_MD5_HASH MULTILINE_OCTAL +\152\351\231\164\245\332\136\361\331\056\362\310\321\206\213\161 +END +CKA_ISSUER MULTILINE_OCTAL +\060\165\061\013\060\011\006\003\125\004\006\023\002\110\125\061 +\021\060\017\006\003\125\004\007\014\010\102\165\144\141\160\145 +\163\164\061\026\060\024\006\003\125\004\012\014\015\115\151\143 +\162\157\163\145\143\040\114\164\144\056\061\027\060\025\006\003 +\125\004\141\014\016\126\101\124\110\125\055\062\063\065\070\064 +\064\071\067\061\042\060\040\006\003\125\004\003\014\031\145\055 +\123\172\151\147\156\157\040\124\114\123\040\122\157\157\164\040 +\103\101\040\062\060\062\063 +END +CKA_SERIAL_NUMBER MULTILINE_OCTAL +\002\015\000\350\157\030\173\326\071\153\230\112\111\230\012 +END +CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR +CKA_TRUST_EMAIL_PROTECTION CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_CODE_SIGNING CK_TRUST CKT_NSS_MUST_VERIFY_TRUST +CKA_TRUST_STEP_UP_APPROVED CK_BBOOL CK_FALSE diff --git a/tools/dep_updaters/update-doc.sh b/tools/dep_updaters/update-doc.sh deleted file mode 100755 index 24fc360f925caf..00000000000000 --- a/tools/dep_updaters/update-doc.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -set -ex - -BASE_DIR=$(cd "$(dirname "$0")/../.." && pwd) -[ -z "$NODE" ] && NODE="$BASE_DIR/out/Release/node" -[ -x "$NODE" ] || NODE=$(command -v node) -DEPS_DIR="$BASE_DIR/deps" -NPM="$DEPS_DIR/npm/bin/npm-cli.js" - -# shellcheck disable=SC1091 -. "$BASE_DIR/tools/dep_updaters/utils.sh" - -cd "$BASE_DIR/tools/doc" - -OLD_VERSION=$(jq '.dependencies["@nodejs/doc-kit"]' package.json) -LATEST_COMMIT=$("$NODE" get-latest-commit.mjs) -NEW_VERSION="https://github.com/nodejs/doc-kit/archive/$LATEST_COMMIT.tar.gz" - -compare_dependency_version "doc-kit" "$OLD_VERSION" "$NEW_VERSION" - -rm -rf node_modules/ package-lock.json - -"$NODE" "$NPM" install "$NEW_VERSION" --omit=dev - -finalize_version_update "doc-kit" "$NEW_VERSION" diff --git a/tools/dep_updaters/update-merve.sh b/tools/dep_updaters/update-merve.sh index ec0525aadb48b6..31d9b15148ad5d 100755 --- a/tools/dep_updaters/update-merve.sh +++ b/tools/dep_updaters/update-merve.sh @@ -54,8 +54,8 @@ rm "$MERVE_ZIP" curl -sL -o "$MERVE_LICENSE" "https://raw.githubusercontent.com/anonrig/merve/HEAD/LICENSE-MIT" -echo "Replacing existing merve (except GYP build files)" -mv "$DEPS_DIR/merve/merve.gyp" "$WORKSPACE/" +echo "Replacing existing merve (except GYP/GN build files)" +mv "$DEPS_DIR/merve/merve.gyp" "$DEPS_DIR/merve/BUILD.gn" "$DEPS_DIR/merve/unofficial.gni" "$WORKSPACE/" rm -rf "$DEPS_DIR/merve" mv "$WORKSPACE" "$DEPS_DIR/merve" diff --git a/tools/doc/get-latest-commit.mjs b/tools/doc/get-latest-commit.mjs deleted file mode 100644 index 7ab8992862ef51..00000000000000 --- a/tools/doc/get-latest-commit.mjs +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env node - -const res = await fetch('https://api.github.com/repos/nodejs/doc-kit/commits/main', { - headers: { - accept: 'application/vnd.github.VERSION.sha', - }, -}); - -console.log(await res.text()); diff --git a/tools/doc/package-lock.json b/tools/doc/package-lock.json index 49a34931f135ba..70e5728c99094b 100644 --- a/tools/doc/package-lock.json +++ b/tools/doc/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "doc", "dependencies": { - "@nodejs/doc-kit": "https://github.com/nodejs/doc-kit/archive/64655f3d4492d60be359cae352ddca750bb1278d.tar.gz" + "@node-core/doc-kit": "1.0.2" } }, "node_modules/@actions/core": { @@ -57,20 +57,20 @@ } }, "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", "license": "MIT", "optional": true, "dependencies": { @@ -78,9 +78,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "license": "MIT", "optional": true, "dependencies": { @@ -88,31 +88,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", - "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.5" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -120,9 +120,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, "node_modules/@heroicons/react": { @@ -511,7 +511,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -519,75 +518,33 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@node-core/rehype-shiki": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@node-core/rehype-shiki/-/rehype-shiki-1.4.1.tgz", - "integrity": "sha512-Ku63bR4wOq6MuZPRSNwj5NcVEU1zmQ+YtXRmwjIMm/Fdp8sZQVAhzyVJLjeYXDKTeYLRPYhtWu8jEu2+G9tumg==", - "dependencies": { - "@shikijs/core": "^3.22.0", - "@shikijs/engine-javascript": "^3.22.0", - "@shikijs/engine-oniguruma": "^3.22.0", - "@shikijs/twoslash": "^3.22.0", - "classnames": "~2.5.1", - "hast-util-to-string": "^3.0.1", - "shiki": "~3.22.0", - "typescript": "5.9.3", - "unist-util-visit": "^5.1.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@node-core/ui-components": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@node-core/ui-components/-/ui-components-1.6.0.tgz", - "integrity": "sha512-LUX0y+qHV0rMF2eCXiJ9HjSg4Yuvk+WwjcTq8JC/bCXxUcKvwcqK8N+FjnSaG9xfuZqbaIlCsc4HcpbkJyevDQ==", - "dependencies": { - "@heroicons/react": "^2.2.0", - "@orama/ui": "^1.5.4", - "@radix-ui/react-avatar": "^1.1.11", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "~2.1.16", - "@radix-ui/react-label": "~2.1.8", - "@radix-ui/react-select": "~2.2.6", - "@radix-ui/react-separator": "~1.1.8", - "@radix-ui/react-tabs": "~1.1.13", - "@radix-ui/react-tooltip": "~1.2.8", - "@tailwindcss/postcss": "~4.1.18", - "@vcarl/remark-headings": "~0.1.0", - "classnames": "~2.5.1", - "postcss-calc": "^10.1.1", - "tailwindcss": "~4.1.17" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@nodejs/doc-kit": { - "resolved": "https://github.com/nodejs/doc-kit/archive/64655f3d4492d60be359cae352ddca750bb1278d.tar.gz", - "integrity": "sha512-d9wvIU1Zlme+v0AwwHeTjvK1YEFYpBEx51iPy47RlDK4N2iHgc5jBP1VQvhMZGuHR6QFPff/cGg0F5akZmKwVQ==", + "node_modules/@node-core/doc-kit": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@node-core/doc-kit/-/doc-kit-1.0.2.tgz", + "integrity": "sha512-dYTh29FRH7Qcbz8M1WbAu1tg2YVcmCEdt514QcDqJWsRrYJfvzg10OyDODyEPJfKZ4+OUN/VPi3knGoGfOSNkQ==", "dependencies": { "@actions/core": "^3.0.0", "@heroicons/react": "^2.2.0", "@minify-html/wasm": "^0.18.1", - "@node-core/rehype-shiki": "^1.4.0", - "@node-core/ui-components": "^1.6.0", + "@node-core/rehype-shiki": "^1.4.1", + "@node-core/ui-components": "^1.6.1", "@orama/orama": "^3.1.18", "@orama/ui": "^1.5.4", "@rollup/plugin-virtual": "^3.0.2", - "acorn": "^8.15.0", + "@swc/html-wasm": "^1.15.18", + "acorn": "^8.16.0", "commander": "^14.0.3", "dedent": "^1.7.1", "estree-util-to-js": "^2.0.0", "estree-util-visit": "^2.0.0", "github-slugger": "^2.0.0", - "globals": "^17.2.0", + "globals": "^17.3.0", "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", "lightningcss-wasm": "^1.31.1", "mdast-util-slice-markdown": "^2.0.1", "piscina": "^5.1.4", - "preact": "^11.0.0-beta.0", + "preact": "^10.28.4", "preact-render-to-string": "^6.6.3", "reading-time": "^1.5.0", "recma-jsx": "^1.0.1", @@ -598,9 +555,9 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-stringify": "^11.0.0", - "rolldown": "^1.0.0-rc.2", - "semver": "^7.7.3", - "shiki": "^3.21.0", + "rolldown": "^1.0.0-rc.6", + "semver": "^7.7.4", + "shiki": "^4.0.0", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-builder": "^4.0.0", @@ -616,12 +573,137 @@ "doc-kit": "bin/cli.mjs" } }, + "node_modules/@node-core/rehype-shiki": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@node-core/rehype-shiki/-/rehype-shiki-1.4.1.tgz", + "integrity": "sha512-Ku63bR4wOq6MuZPRSNwj5NcVEU1zmQ+YtXRmwjIMm/Fdp8sZQVAhzyVJLjeYXDKTeYLRPYhtWu8jEu2+G9tumg==", + "dependencies": { + "@shikijs/core": "^3.22.0", + "@shikijs/engine-javascript": "^3.22.0", + "@shikijs/engine-oniguruma": "^3.22.0", + "@shikijs/twoslash": "^3.22.0", + "classnames": "~2.5.1", + "hast-util-to-string": "^3.0.1", + "shiki": "~3.22.0", + "typescript": "5.9.3", + "unist-util-visit": "^5.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@node-core/rehype-shiki/node_modules/@shikijs/core": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@node-core/rehype-shiki/node_modules/@shikijs/engine-javascript": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@node-core/rehype-shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@node-core/rehype-shiki/node_modules/@shikijs/langs": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@node-core/rehype-shiki/node_modules/@shikijs/themes": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@node-core/rehype-shiki/node_modules/@shikijs/types": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@node-core/rehype-shiki/node_modules/shiki": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@node-core/ui-components": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@node-core/ui-components/-/ui-components-1.6.3.tgz", + "integrity": "sha512-Oy/bI4mZC6V/i8CdIX8q62r81zR6bAEARZE0jwTpv7w49SNomB2WAVb5fnFBSumyigOCgQKgR/OJtwh8XQJnkw==", + "dependencies": { + "@heroicons/react": "^2.2.0", + "@orama/core": "^1.2.19", + "@orama/ui": "^1.5.4", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/postcss": "~4.2.1", + "@types/react": "^19.2.13", + "@vcarl/remark-headings": "~0.1.0", + "classnames": "~2.5.1", + "postcss-calc": "10.1.1", + "postcss-cli": "11.0.1", + "react": "^19.2.4", + "tailwindcss": "~4.1.17", + "typescript": "5.9.3" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@orama/core": { "version": "1.2.19", "resolved": "https://registry.npmjs.org/@orama/core/-/core-1.2.19.tgz", "integrity": "sha512-AVEI0eG/a1RUQK+tBloRMppQf46Ky4kIYKEVjo0V0VfIGZHdLOE2PJR4v949kFwiTnfSJCUaxgwM74FCA1uHUA==", "license": "AGPL-3.0", - "peer": true, "dependencies": { "@orama/cuid2": "2.2.3", "@orama/oramacore-events-parser": "0.0.5" @@ -632,7 +714,6 @@ "resolved": "https://registry.npmjs.org/@orama/cuid2/-/cuid2-2.2.3.tgz", "integrity": "sha512-Lcak3chblMejdlSHgYU2lS2cdOhDpU6vkfIJH4m+YKvqQyLqs1bB8+w6NT1MG5bO12NUK2GFc34Mn2xshMIQ1g==", "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "^1.1.5" } @@ -650,8 +731,7 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/@orama/oramacore-events-parser/-/oramacore-events-parser-0.0.5.tgz", "integrity": "sha512-yAuSwog+HQBAXgZ60TNKEwu04y81/09mpbYBCmz1RCxnr4ObNY2JnPZI7HmALbjAhLJ8t5p+wc2JHRK93ubO4w==", - "license": "AGPL-3.0", - "peer": true + "license": "AGPL-3.0" }, "node_modules/@orama/stopwords": { "version": "3.1.18", @@ -679,9 +759,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.114.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.114.0.tgz", - "integrity": "sha512-//nBfbzHQHvJs8oFIjv6coZ6uxQ4alLfiPe6D5vit6c4pmxATHHlVwgB1k+Hv4yoAMyncdxgRBF5K4BYWUCzvA==", + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -1974,9 +2054,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.5.tgz", - "integrity": "sha512-zCEmUrt1bggwgBgeKLxNj217J1OrChrp3jJt24VK9jAharSTeVaHODNL+LpcQVhRz+FktYWfT9cjo5oZ99ZLpg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", "cpu": [ "arm64" ], @@ -1990,9 +2070,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.5.tgz", - "integrity": "sha512-ZP9xb9lPAex36pvkNWCjSEJW/Gfdm9I3ssiqOFLmpZ/vosPXgpoGxCmh+dX1Qs+/bWQE6toNFXWWL8vYoKoK9Q==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", "cpu": [ "arm64" ], @@ -2006,9 +2086,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.5.tgz", - "integrity": "sha512-7IdrPunf6dp9mywMgTOKMMGDnMHQ6+h5gRl6LW8rhD8WK2kXX0IwzcM5Zc0B5J7xQs8QWOlKjv8BJsU/1CD3pg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", "cpu": [ "x64" ], @@ -2022,9 +2102,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.5.tgz", - "integrity": "sha512-o/JCk+dL0IN68EBhZ4DqfsfvxPfMeoM6cJtxORC1YYoxGHZyth2Kb2maXDb4oddw2wu8iIbnYXYPEzBtAF5CAg==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", "cpu": [ "x64" ], @@ -2038,9 +2118,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.5.tgz", - "integrity": "sha512-IIBwTtA6VwxQLcEgq2mfrUgam7VvPZjhd/jxmeS1npM+edWsrrpRLHUdze+sk4rhb8/xpP3flemgcZXXUW6ukw==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", "cpu": [ "arm" ], @@ -2054,9 +2134,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.5.tgz", - "integrity": "sha512-KSol1De1spMZL+Xg7K5IBWXIvRWv7+pveaxFWXpezezAG7CS6ojzRjtCGCiLxQricutTAi/LkNWKMsd2wNhMKQ==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", "cpu": [ "arm64" ], @@ -2070,9 +2150,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.5.tgz", - "integrity": "sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", "cpu": [ "arm64" ], @@ -2085,10 +2165,42 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.5.tgz", - "integrity": "sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", "cpu": [ "x64" ], @@ -2102,9 +2214,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.5.tgz", - "integrity": "sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", "cpu": [ "x64" ], @@ -2118,9 +2230,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.5.tgz", - "integrity": "sha512-0CWY7ubu12nhzz+tkpHjoG3IRSTlWYe0wrfJRf4qqjqQSGtAYgoL9kwzdvlhaFdZ5ffVeyYw9qLsChcjUMEloQ==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", "cpu": [ "arm64" ], @@ -2134,9 +2246,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.5.tgz", - "integrity": "sha512-LztXnGzv6t2u830mnZrFLRVqT/DPJ9DL4ZTz/y93rqUVkeHjMMYIYaFj+BUthiYxbVH9dH0SZYufETspKY/NhA==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", "cpu": [ "wasm32" ], @@ -2150,9 +2262,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.5.tgz", - "integrity": "sha512-jUct1XVeGtyjqJXEAfvdFa8xoigYZ2rge7nYEm70ppQxpfH9ze2fbIrpHmP2tNM2vL/F6Dd0CpXhpjPbC6bSxQ==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", "cpu": [ "arm64" ], @@ -2166,9 +2278,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.5.tgz", - "integrity": "sha512-VQ8F9ld5gw29epjnVGdrx8ugiLTe8BMqmhDYy7nGbdeDo4HAt4bgdZvLbViEhg7DZyHLpiEUlO5/jPSUrIuxRQ==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", "cpu": [ "x64" ], @@ -2182,9 +2294,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.5.tgz", - "integrity": "sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", "license": "MIT" }, "node_modules/@rollup/plugin-virtual": { @@ -2205,64 +2317,102 @@ } }, "node_modules/@shikijs/core": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", - "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", + "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0", + "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, - "node_modules/@shikijs/engine-javascript": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", - "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", + "node_modules/@shikijs/langs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", + "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs/node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^4.3.4" + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", - "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "node_modules/@shikijs/primitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", + "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0", - "@shikijs/vscode-textmate": "^10.0.2" + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, - "node_modules/@shikijs/langs": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", - "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "node_modules/@shikijs/primitive/node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0" + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/themes": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", - "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", + "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.22.0" + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes/node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/twoslash": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/twoslash/-/twoslash-3.22.0.tgz", - "integrity": "sha512-GO27UPN+kegOMQvC+4XcLt0Mttyg+n16XKjmoKjdaNZoW+sOJV7FLdv2QKauqUDws6nE3EQPD+TFHEdyyoUBDw==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/twoslash/-/twoslash-3.23.0.tgz", + "integrity": "sha512-pNaLJWMA3LU7PhT8tm9OQBZ1epy0jmdgeJzntBtr1EVXLbHxGzTj3mnf9vOdcl84l96qnlJXkJ/NGXZYBpXl5g==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.22.0", - "@shikijs/types": "3.22.0", + "@shikijs/core": "3.23.0", + "@shikijs/types": "3.23.0", "twoslash": "^0.3.6" }, "peerDependencies": { @@ -2270,9 +2420,9 @@ } }, "node_modules/@shikijs/types": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", - "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -2285,48 +2435,60 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "node_modules/@swc/html-wasm": { + "version": "1.15.18", + "resolved": "https://registry.npmjs.org/@swc/html-wasm/-/html-wasm-1.15.18.tgz", + "integrity": "sha512-nABVlYRZjfTJA3bTEf7w6Gu8GgRfFJZqTAJ+ehJzwKtCreMy4QFBGiv3KkCjIjxXg+U8qrpnqgo9SjVOq3lPEw==", + "license": "Apache-2.0" + }, "node_modules/@tailwindcss/node": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", - "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", - "lightningcss": "1.30.2", + "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" + "tailwindcss": "4.2.2" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "license": "MIT" + }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", - "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", "cpu": [ "arm64" ], @@ -2336,13 +2498,13 @@ "android" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", - "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", "cpu": [ "arm64" ], @@ -2352,13 +2514,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", "cpu": [ "x64" ], @@ -2368,13 +2530,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", "cpu": [ "x64" ], @@ -2384,13 +2546,13 @@ "freebsd" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", "cpu": [ "arm" ], @@ -2400,13 +2562,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", "cpu": [ "arm64" ], @@ -2416,13 +2578,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", "cpu": [ "arm64" ], @@ -2432,13 +2594,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", "cpu": [ "x64" ], @@ -2448,13 +2610,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", "cpu": [ "x64" ], @@ -2464,13 +2626,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2485,21 +2647,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", "cpu": [ "arm64" ], @@ -2509,13 +2671,13 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", "cpu": [ "x64" ], @@ -2525,22 +2687,28 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", - "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz", + "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "postcss": "^8.4.41", - "tailwindcss": "4.1.18" + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "postcss": "^8.5.6", + "tailwindcss": "4.2.2" } }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2604,7 +2772,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2712,6 +2879,43 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -2743,12 +2947,36 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -2799,12 +3027,68 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -2856,8 +3140,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", @@ -2890,9 +3173,9 @@ } }, "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -2903,6 +3186,15 @@ } } }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2940,10 +3232,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2997,6 +3295,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -3067,21 +3374,53 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", - "engines": { - "node": ">=12.0.0" + "dependencies": { + "to-regex-range": "^5.0.1" }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-nonce": { @@ -3099,10 +3438,22 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "license": "ISC" }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/globals": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", - "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "license": "MIT", "engines": { "node": ">=18" @@ -3365,6 +3716,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -3375,6 +3738,36 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-hexadecimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", @@ -3385,6 +3778,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -3406,10 +3808,22 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -3422,23 +3836,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], @@ -3456,9 +3870,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -3476,9 +3890,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -3496,9 +3910,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -3516,9 +3930,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -3536,9 +3950,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -3556,9 +3970,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -3576,9 +3990,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -3596,9 +4010,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -3616,9 +4030,9 @@ } }, "node_modules/lightningcss-wasm": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-wasm/-/lightningcss-wasm-1.31.1.tgz", - "integrity": "sha512-JYnA3ZpKhF/X+pkLBPMsqDAXZmxd2mQ4nauKd78IfMnioook736ksaCJu6d70Wkq6soD74V+G4fhaOWmWzW5dg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-wasm/-/lightningcss-wasm-1.32.0.tgz", + "integrity": "sha512-SteAkCtRuSCDYPGHKhLV/dDs5Bk+7I4QUxWxfk4xwsTI1rQk8MQyYtpGcd3NECsUGzK0q2/KqoVS+YHCqKHUTQ==", "bundleDependencies": [ "napi-wasm" ], @@ -3640,9 +4054,9 @@ "license": "MIT" }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -3660,9 +4074,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -3679,6 +4093,18 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -4612,6 +5038,15 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -4631,13 +5066,13 @@ "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", - "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.5.tgz", + "integrity": "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==", "license": "MIT", "dependencies": { "oniguruma-parser": "^0.12.1", - "regex": "^6.0.1", + "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, @@ -4685,17 +5120,26 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/piscina": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.4.tgz", @@ -4709,9 +5153,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -4752,6 +5196,99 @@ "postcss": "^8.4.38" } }, + "node_modules/postcss-cli": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz", + "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^1.0.0", + "fs-extra": "^11.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^5.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^5.0.0", + "tinyglobby": "^0.2.12", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz", + "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1", + "yaml": "^2.4.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + } + } + }, + "node_modules/postcss-reporter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", + "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-selector-parser": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", @@ -4772,9 +5309,9 @@ "license": "MIT" }, "node_modules/preact": { - "version": "11.0.0-experimental.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-11.0.0-experimental.1.tgz", - "integrity": "sha512-a5AyuvVRDFkzF/sQNTEk7fhHSssq8fxUML9meWt5gDiS29F0KTnvlaMDWudQ7vuM7NZZPdw8Y2/1KeLUIqN4vw==", + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz", + "integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==", "license": "MIT", "funding": { "type": "opencollective", @@ -4790,6 +5327,15 @@ "preact": ">=10 || >= 11.0.0-0" } }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/prism-react-renderer": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz", @@ -4814,7 +5360,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4928,6 +5473,27 @@ } } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/reading-time": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", @@ -5121,14 +5687,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rolldown": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.5.tgz", - "integrity": "sha512-0AdalTs6hNTioaCYIkAa7+xsmHBfU5hCNclZnM/lp7lGGDuUOb6N4BVNtwiomybbencDjq/waKjTImqiGCs5sw==", + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.114.0", - "@rolldown/pluginutils": "1.0.0-rc.5" + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" }, "bin": { "rolldown": "bin/cli.mjs" @@ -5137,19 +5712,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.5", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.5", - "@rolldown/binding-darwin-x64": "1.0.0-rc.5", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.5", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.5", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.5", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.5", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.5", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.5", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.5", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.5", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.5" + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" } }, "node_modules/scheduler": { @@ -5172,19 +5749,90 @@ } }, "node_modules/shiki": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", - "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", + "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.0.2", + "@shikijs/engine-javascript": "4.0.2", + "@shikijs/engine-oniguruma": "4.0.2", + "@shikijs/langs": "4.0.2", + "@shikijs/themes": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", + "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/engine-javascript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", + "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", + "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/shiki/node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.22.0", - "@shikijs/engine-javascript": "3.22.0", - "@shikijs/engine-oniguruma": "3.22.0", - "@shikijs/langs": "3.22.0", - "@shikijs/themes": "3.22.0", - "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/source-map": { @@ -5215,6 +5863,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -5229,6 +5891,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -5266,6 +5940,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "license": "Apache-2.0" + }, "node_modules/throttleit": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", @@ -5294,6 +5974,47 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -5362,9 +6083,9 @@ } }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "license": "MIT", "engines": { "node": ">=18.17" @@ -5529,6 +6250,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -5639,10 +6369,36 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -5654,6 +6410,33 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/tools/doc/package.json b/tools/doc/package.json index 109efdf665ca1e..9d143e9724d8d4 100644 --- a/tools/doc/package.json +++ b/tools/doc/package.json @@ -2,6 +2,6 @@ "name": "doc", "private": true, "dependencies": { - "@nodejs/doc-kit": "https://github.com/nodejs/doc-kit/archive/64655f3d4492d60be359cae352ddca750bb1278d.tar.gz" + "@node-core/doc-kit": "1.0.2" } } diff --git a/tools/eslint/package-lock.json b/tools/eslint/package-lock.json index b8a33ce4881db3..8db663739af6e5 100644 --- a/tools/eslint/package-lock.json +++ b/tools/eslint/package-lock.json @@ -8,25 +8,26 @@ "name": "eslint-tools", "version": "0.0.0", "dependencies": { - "@babel/core": "^8.0.0-rc.2", - "@babel/eslint-parser": "^8.0.0-rc.2", - "@babel/plugin-syntax-import-source": "^8.0.0-rc.2", + "@babel/core": "^8.0.0-rc.3", + "@babel/eslint-parser": "^8.0.0-rc.3", + "@babel/plugin-syntax-import-source": "^8.0.0-rc.3", "@eslint/js": "^10.0.1", "@eslint/markdown": "^7.5.1", - "@stylistic/eslint-plugin": "^5.9.0", - "eslint": "^10.0.2", + "@stylistic/eslint-plugin": "^5.10.0", + "eslint": "^10.1.0", "eslint-formatter-tap": "^9.0.1", - "eslint-plugin-jsdoc": "^62.7.1", - "globals": "^17.3.0" + "eslint-plugin-jsdoc": "^62.8.0", + "eslint-plugin-regexp": "^3.1.0", + "globals": "^17.4.0" } }, "node_modules/@babel/code-frame": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0-rc.2.tgz", - "integrity": "sha512-zbPFBDbQdChkGN02WRc/BcOvZLDTctFJZVeWkciVr82T5V0GVBXztq4/Wi4Ca+ZKx7U+Kdt5b862cpFJ4Cjf1A==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0-rc.3.tgz", + "integrity": "sha512-585nwYQGQKKc+jMIAPeZJ+aGMn4FHmIUUjLZa9zI7mslvYmShnx0u7rFA4gliRip6S2vkwTYrMxPeXadkKW93Q==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^8.0.0-rc.2", + "@babel/helper-validator-identifier": "^8.0.0-rc.3", "js-tokens": "^10.0.0" }, "engines": { @@ -34,28 +35,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0-rc.2.tgz", - "integrity": "sha512-zDrQeMrDVCkisxxjZmP+xeAyGfZCVOwP+7VECgOvMXttb+1pTUMpeEYI0LaozIzeES/Uvu7OqhHLb3oN1qo6Wg==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0-rc.3.tgz", + "integrity": "sha512-TlS7d9xYz/93xC8ovkRBwTKPodbwPfzTn7TxUgZo4c3LwpPINnScqIlN0QRDE/x1S4C1bmyqxGmAZUIC0buW0A==", "license": "MIT", "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@babel/core": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.0-rc.2.tgz", - "integrity": "sha512-mlBJdKJJEZNGDE+w+P6B5w+FTMkht1liPkxtB4wk39EpGH01Am5tg1htaNlOU5rO9Ge3psMjAFycpc3ru5uaQw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^8.0.0-rc.2", - "@babel/generator": "^8.0.0-rc.2", - "@babel/helper-compilation-targets": "^8.0.0-rc.2", - "@babel/helpers": "^8.0.0-rc.2", - "@babel/parser": "^8.0.0-rc.2", - "@babel/template": "^8.0.0-rc.2", - "@babel/traverse": "^8.0.0-rc.2", - "@babel/types": "^8.0.0-rc.2", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.0-rc.3.tgz", + "integrity": "sha512-ov5mBbHiosqX1eqt8nN0JM7VuhirO6V8lln5rUXNZpNB+a9NnPTVYeDqSTBkf9C6GmFq3fnFlGT8eg3QBI7jJQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0-rc.3", + "@babel/generator": "^8.0.0-rc.3", + "@babel/helper-compilation-targets": "^8.0.0-rc.3", + "@babel/helpers": "^8.0.0-rc.3", + "@babel/parser": "^8.0.0-rc.3", + "@babel/template": "^8.0.0-rc.3", + "@babel/traverse": "^8.0.0-rc.3", + "@babel/types": "^8.0.0-rc.3", "@jridgewell/remapping": "^2.3.5", "@types/gensync": "^1.0.0", "convert-source-map": "^2.0.0", @@ -82,9 +83,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-8.0.0-rc.2.tgz", - "integrity": "sha512-hczs5f2oe/BjS3OpQb2ljVVsauEjBIR3UsTmIPNDECIz02olxaVYDHd4mk3GEx0N7PD8gsz2cZ6sqZTctVaMug==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-8.0.0-rc.3.tgz", + "integrity": "sha512-YsJgMO5TVzHGpX0zNF5WkrKj04QI32kQv0L2r/KKbCOmOt4A/XhM5zctW7WgFiOAEZafWfQyo6dWEwwBjZnHlQ==", "license": "MIT", "dependencies": { "eslint-scope": "^9.1.0", @@ -95,18 +96,18 @@ "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "@babel/core": "^8.0.0-rc.2", + "@babel/core": "^8.0.0-rc.3", "eslint": "^9.0.0 || ^10.0.0" } }, "node_modules/@babel/generator": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.2.tgz", - "integrity": "sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.3.tgz", + "integrity": "sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==", "license": "MIT", "dependencies": { - "@babel/parser": "^8.0.0-rc.2", - "@babel/types": "^8.0.0-rc.2", + "@babel/parser": "^8.0.0-rc.3", + "@babel/types": "^8.0.0-rc.3", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", @@ -117,13 +118,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0-rc.2.tgz", - "integrity": "sha512-oMIhKru9gl3mj0eKDyKW6wBDAvyWoZd28d6V/m4JTeeiFsJLfOYnqu+s+cnK4jSo87cg/oj4hsATgkmZ3AzsDQ==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0-rc.3.tgz", + "integrity": "sha512-UXlT7t103KBJjiphN7Ij9DtELX2Q5uk1xMle7oN/eckxR8dmJyvbFsIm5u+cY0KZyF7qNkAiLsJljEPix1yfKg==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^8.0.0-rc.2", - "@babel/helper-validator-option": "^8.0.0-rc.2", + "@babel/compat-data": "^8.0.0-rc.3", + "@babel/helper-validator-option": "^8.0.0-rc.3", "browserslist": "^4.24.0", "lru-cache": "^7.14.1", "semver": "^7.7.3" @@ -133,73 +134,73 @@ } }, "node_modules/@babel/helper-globals": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0-rc.2.tgz", - "integrity": "sha512-Q1AIOaW4EOxkI/8wYJKyLI59gfqTK3imFUfIqxuve0Q3GlOSrOTVmvHU6Gb3Y5GxtoS1hIzhO47k5GkfyGTQEQ==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0-rc.3.tgz", + "integrity": "sha512-rwtdZPunoa/IAlcZhgDoLxROXPW5evSN7SXMdObS8vNt7Wu6fCf8nLPFcuuhLCzeRoIrGNKx08v/XEQ4riQDGg==", "license": "MIT", "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.0-rc.2.tgz", - "integrity": "sha512-APa2p8RHBNGUmNPDYshswXQkS2sMNthL8VZSc9soe5lQfT2RXRXM6TwOLaktQwnNSwdoEy+Xu9q3qMdFrV92sg==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.0-rc.3.tgz", + "integrity": "sha512-1+lvhojDf75++IwFRWrch4BBaznB2Z99GCfggHFWEKJ8B40iovZ8KqP0kDeb60ThuAa7WxCbtuYHfP5BtJL1kA==", "license": "MIT", "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "@babel/core": "^8.0.0-rc.2" + "@babel/core": "^8.0.0-rc.3" } }, "node_modules/@babel/helper-string-parser": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.2.tgz", - "integrity": "sha512-noLx87RwlBEMrTzncWd/FvTxoJ9+ycHNg0n8yyYydIoDsLZuxknKgWRJUqcrVkNrJ74uGyhWQzQaS3q8xfGAhQ==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.3.tgz", + "integrity": "sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==", "license": "MIT", "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.2.tgz", - "integrity": "sha512-xExUBkuXWJjVuIbO7z6q7/BA9bgfJDEhVL0ggrggLMbg0IzCUWGT1hZGE8qUH7Il7/RD/a6cZ3AAFrrlp1LF/A==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.3.tgz", + "integrity": "sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==", "license": "MIT", "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0-rc.2.tgz", - "integrity": "sha512-EtxQopsocKue0ZdjnX5INoDiRN+RCBb1TDh3d0N8bM6aX0lyUhQfRNRQaKB+vCx+YvGjXWRf3JD6/YvTsf2qgQ==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0-rc.3.tgz", + "integrity": "sha512-wpzZ9KycQDqmJct4ee/ihua2KKdW/MXTDidD4Ya7HdUHL+Jp5zBZTj8oNrxc8wp5Qr4sofKgZF1GIyymfdq7+g==", "license": "MIT", "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@babel/helpers": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0-rc.2.tgz", - "integrity": "sha512-Cc2IpMRiu8PDBUxtQ6oSkML0etJ27kZGnf3XE+qqAJJFGtVl549kyfvDWLywCAFhq16kHUe2WMZMdFUtPz6kWw==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0-rc.3.tgz", + "integrity": "sha512-dbPuulmsPOwIAXP4pGsKiAv1E50pKYmAJfWXYytCwLpgj6IDNUFAzGuVVemaphYP/WniKsCf42+Cfo4xpc4jjQ==", "license": "MIT", "dependencies": { - "@babel/template": "^8.0.0-rc.2", - "@babel/types": "^8.0.0-rc.2" + "@babel/template": "^8.0.0-rc.3", + "@babel/types": "^8.0.0-rc.3" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@babel/parser": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.2.tgz", - "integrity": "sha512-29AhEtcq4x8Dp3T72qvUMZHx0OMXCj4Jy/TEReQa+KWLln524Cj1fWb3QFi0l/xSpptQBR6y9RNEXuxpFvwiUQ==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.3.tgz", + "integrity": "sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==", "license": "MIT", "dependencies": { - "@babel/types": "^8.0.0-rc.2" + "@babel/types": "^8.0.0-rc.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -209,46 +210,46 @@ } }, "node_modules/@babel/plugin-syntax-import-source": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-source/-/plugin-syntax-import-source-8.0.0-rc.2.tgz", - "integrity": "sha512-aB2h3oetnsvPM1RmH5sm17yuMiQ/vXx4YIJS1gaEWvFoZXrQol/lq/pFh4zVp9Y7uB2XfOOxjAigq9vwrMyIBA==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-source/-/plugin-syntax-import-source-8.0.0-rc.3.tgz", + "integrity": "sha512-enWWk7TiwxhNMdLgyY1JKYhpsTHoKh5rotU6RHelLGefWCk6vAA1wamfNhu1wWch/tRUm4UCo8imafyzUem1Mg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^8.0.0-rc.2" + "@babel/helper-plugin-utils": "^8.0.0-rc.3" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "@babel/core": "^8.0.0-rc.2" + "@babel/core": "^8.0.0-rc.3" } }, "node_modules/@babel/template": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0-rc.2.tgz", - "integrity": "sha512-INp+KufeQpvU+V+gxR7xoiVzU6sRRQo8oOsCU/sTe0wtJ/Adrfgyet0i19qvXXSeuyiZ9+PV8IF/eEPzyJ527g==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0-rc.3.tgz", + "integrity": "sha512-9iet2svxZhCGgmt/b6M3Fbmy2i+OgjhXMDWNqDTBrNvb9Cc60NgETNIaJq6b0wICiCqpsFdIt8NYXMXyCQU6jA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^8.0.0-rc.2", - "@babel/parser": "^8.0.0-rc.2", - "@babel/types": "^8.0.0-rc.2" + "@babel/code-frame": "^8.0.0-rc.3", + "@babel/parser": "^8.0.0-rc.3", + "@babel/types": "^8.0.0-rc.3" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@babel/traverse": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.0-rc.2.tgz", - "integrity": "sha512-H9ZChE8gRy4fSloEQaT17ijcFNoayS9JIyE0IUWkjYgldU+Czkg2h5XtuJmfIk6cbuHfDK/FFJox+g/TlmXB7g==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.0-rc.3.tgz", + "integrity": "sha512-3gvZCynaX+zxYZ2v5odaBBcc9eT4Yr7Gf16l3QSEvvBPVyMSZbYhsGAZfO79kjOCNJY2j6rWvNkWl1ZwYa64lQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^8.0.0-rc.2", - "@babel/generator": "^8.0.0-rc.2", - "@babel/helper-globals": "^8.0.0-rc.2", - "@babel/parser": "^8.0.0-rc.2", - "@babel/template": "^8.0.0-rc.2", - "@babel/types": "^8.0.0-rc.2", + "@babel/code-frame": "^8.0.0-rc.3", + "@babel/generator": "^8.0.0-rc.3", + "@babel/helper-globals": "^8.0.0-rc.3", + "@babel/parser": "^8.0.0-rc.3", + "@babel/template": "^8.0.0-rc.3", + "@babel/types": "^8.0.0-rc.3", "obug": "^2.1.1" }, "engines": { @@ -256,13 +257,13 @@ } }, "node_modules/@babel/types": { - "version": "8.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.2.tgz", - "integrity": "sha512-91gAaWRznDwSX4E2tZ1YjBuIfnQVOFDCQ2r0Toby0gu4XEbyF623kXLMA8d4ZbCu+fINcrudkmEcwSUHgDDkNw==", + "version": "8.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.3.tgz", + "integrity": "sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^8.0.0-rc.2", - "@babel/helper-validator-identifier": "^8.0.0-rc.2" + "@babel/helper-string-parser": "^8.0.0-rc.3", + "@babel/helper-validator-identifier": "^8.0.0-rc.3" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -333,35 +334,35 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", - "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", + "version": "0.23.3", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", + "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.2", + "@eslint/object-schema": "^3.0.3", "debug": "^4.3.1", - "minimatch": "^10.2.1" + "minimatch": "^10.2.4" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", - "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", + "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.0" + "@eslint/core": "^1.1.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", - "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -426,9 +427,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", - "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", + "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -553,9 +554,9 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.9.0.tgz", - "integrity": "sha512-FqqSkvDMYJReydrMhlugc71M76yLLQWNfmGq+SIlLa7N3kHp8Qq8i2PyWrVNAfjOyOIY+xv9XaaYwvVW7vroMA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -710,18 +711,18 @@ "license": "Python-2.0" }, "node_modules/balanced-match": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", - "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", + "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -731,15 +732,15 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/browserslist": { @@ -776,9 +777,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001770", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", - "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", "funding": [ { "type": "opencollective", @@ -903,9 +904,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", "license": "ISC" }, "node_modules/escalade": { @@ -930,17 +931,17 @@ } }, "node_modules/eslint": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", - "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", + "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.2", - "@eslint/config-helpers": "^0.5.2", - "@eslint/core": "^1.1.0", - "@eslint/plugin-kit": "^0.6.0", + "@eslint/config-array": "^0.23.3", + "@eslint/config-helpers": "^0.5.3", + "@eslint/core": "^1.1.1", + "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -949,9 +950,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.1", + "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", - "espree": "^11.1.1", + "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -962,7 +963,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.1", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -997,9 +998,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "62.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.7.1.tgz", - "integrity": "sha512-4Zvx99Q7d1uggYBUX/AIjvoyqXhluGbbKrRmG8SQTLprPFg6fa293tVJH1o1GQwNe3lUydd8ZHzn37OaSncgSQ==", + "version": "62.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.8.0.tgz", + "integrity": "sha512-hu3r9/6JBmPG6wTcqtYzgZAnjEG2eqRUATfkFscokESg1VDxZM21ZaMire0KjeMwfj+SXvgB4Rvh5LBuesj92w==", "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.84.0", @@ -1041,10 +1042,31 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-plugin-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-3.1.0.tgz", + "integrity": "sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "comment-parser": "^1.4.0", + "jsdoc-type-pratt-parser": "^7.0.0", + "refa": "^0.12.1", + "regexp-ast-analysis": "^0.7.1", + "scslre": "^0.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "peerDependencies": { + "eslint": ">=9.38.0" + } + }, "node_modules/eslint-scope": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", - "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "license": "BSD-2-Clause", "dependencies": { "@types/esrecurse": "^4.3.1", @@ -1072,9 +1094,9 @@ } }, "node_modules/eslint/node_modules/@eslint/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", - "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -1084,12 +1106,12 @@ } }, "node_modules/eslint/node_modules/@eslint/plugin-kit": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", - "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", + "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.0", + "@eslint/core": "^1.1.1", "levn": "^0.4.1" }, "engines": { @@ -1097,9 +1119,9 @@ } }, "node_modules/eslint/node_modules/espree": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", - "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.16.0", @@ -1257,9 +1279,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, "node_modules/format": { @@ -1298,9 +1320,9 @@ } }, "node_modules/globals": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", - "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "license": "MIT", "engines": { "node": ">=18" @@ -2353,9 +2375,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "license": "MIT" }, "node_modules/object-deep-merge": { @@ -2461,9 +2483,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -2490,6 +2512,31 @@ "node": ">=6" } }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/reserved-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", @@ -2502,6 +2549,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", diff --git a/tools/eslint/package.json b/tools/eslint/package.json index 6225ad5486c26f..75c6ae2346d4d8 100644 --- a/tools/eslint/package.json +++ b/tools/eslint/package.json @@ -3,15 +3,16 @@ "version": "0.0.0", "private": true, "dependencies": { - "@babel/core": "^8.0.0-rc.2", - "@babel/eslint-parser": "^8.0.0-rc.2", - "@babel/plugin-syntax-import-source": "^8.0.0-rc.2", + "@babel/core": "^8.0.0-rc.3", + "@babel/eslint-parser": "^8.0.0-rc.3", + "@babel/plugin-syntax-import-source": "^8.0.0-rc.3", "@eslint/js": "^10.0.1", "@eslint/markdown": "^7.5.1", - "@stylistic/eslint-plugin": "^5.9.0", - "eslint": "^10.0.2", + "@stylistic/eslint-plugin": "^5.10.0", + "eslint": "^10.1.0", "eslint-formatter-tap": "^9.0.1", - "eslint-plugin-jsdoc": "^62.7.1", - "globals": "^17.3.0" + "eslint-plugin-jsdoc": "^62.8.0", + "eslint-plugin-regexp": "^3.1.0", + "globals": "^17.4.0" } } diff --git a/tools/nix/pkgs.nix b/tools/nix/pkgs.nix index 3251e57538a71e..6bda939c699ec8 100644 --- a/tools/nix/pkgs.nix +++ b/tools/nix/pkgs.nix @@ -8,3 +8,14 @@ let }) arg; in nixpkgs +// { + nixfmt-tree = nixpkgs.nixfmt-tree.overrideAttrs (old: { + patches = (old.patches or [ ]) ++ [ + (nixpkgs.fetchpatch2 { + url = "https://github.com/numtide/treefmt/commit/b96016b4e38ffc76518087b3b0c9bbfa190d5225.patch?full_index=1"; + revert = true; + hash = "sha256-DcxT2OGPX6Kmxhqa56DjZsSh2hoyhPyVmE17ULeryv8="; + }) + ]; + }); +} diff --git a/typings/globals.d.ts b/typings/globals.d.ts index 739f3d9a534026..ddd5885faa057f 100644 --- a/typings/globals.d.ts +++ b/typings/globals.d.ts @@ -87,6 +87,20 @@ declare global { | BigUint64Array | BigInt64Array; + type TypedArrayConstructor = + | typeof Uint8Array + | typeof Uint8ClampedArray + | typeof Uint16Array + | typeof Uint32Array + | typeof Int8Array + | typeof Int16Array + | typeof Int32Array + | typeof Float16Array + | typeof Float32Array + | typeof Float64Array + | typeof BigUint64Array + | typeof BigInt64Array; + namespace NodeJS { interface Global { internalBinding(binding: T): InternalBindingMap[T] diff --git a/typings/primordials.d.ts b/typings/primordials.d.ts index 204c12b0087f91..5437ac2736792a 100644 --- a/typings/primordials.d.ts +++ b/typings/primordials.d.ts @@ -19,7 +19,7 @@ type UncurryGetter = type UncurrySetter = O[K] extends infer V ? (self: T, value: V) => void : never; -type TypedArrayContentType = T extends { [k: number]: infer V } ? V : never; +type TypedArrayContentType = InstanceType[number]; /** * Primordials are a way to safely use globals without fear of global mutation @@ -472,14 +472,12 @@ declare namespace primordials { export const SyntaxErrorPrototype: typeof SyntaxError.prototype export import TypeError = globalThis.TypeError; export const TypeErrorPrototype: typeof TypeError.prototype - export function TypedArrayFrom( - constructor: new (length: number) => T, - source: - | Iterable> - | ArrayLike>, - ): T; - export function TypedArrayFrom( - constructor: new (length: number) => T, + export function TypedArrayFrom( + constructor: T, + source: Iterable> | ArrayLike>, + ): InstanceType + export function TypedArrayFrom( + constructor: T, source: Iterable | ArrayLike, mapfn: ( this: THIS_ARG, @@ -487,28 +485,17 @@ declare namespace primordials { index: number, ) => TypedArrayContentType, thisArg?: THIS_ARG, - ): T; - export function TypedArrayOf( - constructor: new (length: number) => T, - ...items: readonly TypedArrayContentType[] - ): T; - export function TypedArrayOfApply( - constructor: new (length: number) => T, + ): InstanceType; + export function TypedArrayOf( + constructor: T, + ...items: TypedArrayContentType[], + ): InstanceType; + export function TypedArrayOfApply( + constructor: T, items: readonly TypedArrayContentType[], - ): T; - export const TypedArray: TypedArray; - export const TypedArrayPrototype: - | typeof Uint8Array.prototype - | typeof Int8Array.prototype - | typeof Uint16Array.prototype - | typeof Int16Array.prototype - | typeof Uint32Array.prototype - | typeof Int32Array.prototype - | typeof Float32Array.prototype - | typeof Float64Array.prototype - | typeof BigInt64Array.prototype - | typeof BigUint64Array.prototype - | typeof Uint8ClampedArray.prototype; + ): InstanceType; + export const TypedArray: TypedArrayConstructor; + export const TypedArrayPrototype: TypedArrayConstructor["prototype"]; export const TypedArrayPrototypeGetBuffer: UncurryGetter; export const TypedArrayPrototypeGetByteLength: UncurryGetter; export const TypedArrayPrototypeGetByteOffset: UncurryGetter; @@ -519,19 +506,7 @@ declare namespace primordials { export function TypedArrayPrototypeSet(self: T, ...args: Parameters): ReturnType; export function TypedArrayPrototypeSubarray(self: T, ...args: Parameters): ReturnType; export function TypedArrayPrototypeSlice(self: T, ...args: Parameters): ReturnType; - export function TypedArrayPrototypeGetSymbolToStringTag(self: unknown): - | 'Int8Array' - | 'Int16Array' - | 'Int32Array' - | 'Uint8Array' - | 'Uint16Array' - | 'Uint32Array' - | 'Uint8ClampedArray' - | 'BigInt64Array' - | 'BigUint64Array' - | 'Float32Array' - | 'Float64Array' - | undefined; + export function TypedArrayPrototypeGetSymbolToStringTag(self: unknown): TypedArray[typeof Symbol.toStringTag] | undefined; export import URIError = globalThis.URIError; export const URIErrorPrototype: typeof URIError.prototype export import Uint16Array = globalThis.Uint16Array; diff --git a/vcbuild.bat b/vcbuild.bat index 5ba3b9ab9efc2f..c4d0782b7cab93 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -184,7 +184,7 @@ if defined package set stage_package=1 set "node_exe=%config%\node.exe" set "node_gyp_exe="%node_exe%" deps\npm\node_modules\node-gyp\bin\node-gyp" set "npm_exe="%~dp0%node_exe%" %~dp0deps\npm\bin\npm-cli.js" -set "doc_kit_exe="%~dp0%node_exe%" %~dp0tools\doc\node_modules\@nodejs\doc-kit\bin\cli.mjs" +set "doc_kit_exe="%~dp0%node_exe%" %~dp0tools\doc\node_modules\@node-core\doc-kit\bin\cli.mjs" if "%target_env%"=="vs2022" set "node_gyp_exe=%node_gyp_exe% --msvs_version=2022" if "%target_env%"=="vs2026" set "node_gyp_exe=%node_gyp_exe% --msvs_version=2026"