From c4ef8b529bf998c6e0fcf1dca76ca2db1cbf592d Mon Sep 17 00:00:00 2001 From: Aditi <62544124+Aditi-1400@users.noreply.github.com> Date: Fri, 12 Sep 2025 16:30:39 +0530 Subject: [PATCH 1/8] src: add an option to make compile cache portable Adds an option (NODE_COMPILE_CACHE_PORTABLE) for the built-in compile cache to encode the hashes with relative file paths. On enabling the option, the source directory along with cache directory can be bundled and moved, and the cache continues to work. When enabled, paths encoded in hash are relative to compile cache directory. PR-URL: https://github.com/nodejs/node/pull/58797 Fixes: https://github.com/nodejs/node/issues/58755 Refs: https://github.com/nodejs/node/issues/52696 Reviewed-By: Joyee Cheung Reviewed-By: James M Snell --- doc/api/cli.md | 5 + doc/api/module.md | 23 ++++ doc/node.1 | 7 ++ lib/internal/modules/helpers.js | 25 ++++- src/compile_cache.cc | 52 ++++++++- src/compile_cache.h | 8 +- src/env.cc | 16 ++- src/env.h | 3 +- src/node_modules.cc | 8 +- src/path.cc | 46 ++++++++ src/path.h | 3 + test/parallel/test-compile-cache-api-error.js | 2 +- .../test-compile-cache-api-portable.js | 106 ++++++++++++++++++ .../test-compile-cache-portable-esm.js | 84 ++++++++++++++ test/parallel/test-compile-cache-portable.js | 75 +++++++++++++ 15 files changed, 448 insertions(+), 15 deletions(-) create mode 100644 test/parallel/test-compile-cache-api-portable.js create mode 100644 test/parallel/test-compile-cache-portable-esm.js create mode 100644 test/parallel/test-compile-cache-portable.js diff --git a/doc/api/cli.md b/doc/api/cli.md index 5e61ba21283697..35620859d64a9f 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -3254,6 +3254,11 @@ added: v22.1.0 Enable the [module compile cache][] for the Node.js instance. See the documentation of [module compile cache][] for details. +### `NODE_COMPILE_CACHE_PORTABLE=1` + +When set to 1, the [module compile cache][] can be reused across different directory +locations as long as the module layout relative to the cache directory remains the same. + ### `NODE_DEBUG=module[,…]` + +Type: Documentation-only + +Allowing a [`fs.Dir`][] object to be closed on garbage collection is +deprecated. In the future, doing so might result in a thrown error that will +terminate the process. + +Please ensure that all `fs.Dir` objects are explicitly closed using +`Dir.prototype.close()` or `using` keyword: + +```mjs +import { opendir } from 'node:fs/promises'; + +{ + await using dir = await opendir('/async/disposable/directory'); +} // Closed by dir[Symbol.asyncDispose]() + +{ + using dir = await opendir('/sync/disposable/directory'); +} // Closed by dir[Symbol.dispose]() + +{ + const dir = await opendir('/unconditionally/iterated/directory'); + for await (const entry of dir) { + // process an entry + } // Closed by iterator +} + +{ + let dir; + try { + dir = await opendir('/legacy/closeable/directory'); + } finally { + await dir?.close(); + } +} +``` + [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 @@ -4083,6 +4129,7 @@ an internal nodejs implementation rather than a public facing API, use `node:htt [`ecdh.setPublicKey()`]: crypto.md#ecdhsetpublickeypublickey-encoding [`emitter.listenerCount(eventName)`]: events.md#emitterlistenercounteventname-listener [`events.listenerCount(emitter, eventName)`]: events.md#eventslistenercountemitter-eventname +[`fs.Dir`]: fs.md#class-fsdir [`fs.FileHandle`]: fs.md#class-filehandle [`fs.access()`]: fs.md#fsaccesspath-mode-callback [`fs.appendFile()`]: fs.md#fsappendfilepath-data-options-callback From 6c82c2d63cd9357ee9eeef43a7652407cf882587 Mon Sep 17 00:00:00 2001 From: simon-id Date: Fri, 12 Sep 2025 14:59:26 +0200 Subject: [PATCH 3/8] url: add type checking to urlToHttpOptions() PR-URL: https://github.com/nodejs/node/pull/59753 Reviewed-By: James M Snell Reviewed-By: Ruben Bridgewater Reviewed-By: Bryan English --- lib/internal/url.js | 3 +++ test/parallel/test-url-urltooptions.js | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/internal/url.js b/lib/internal/url.js index 9105940b2a45a0..a1473fdac8aba3 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -91,6 +91,8 @@ const { Buffer } = require('buffer'); const { validateFunction, + validateObject, + kValidateObjectAllowObjects, } = require('internal/validators'); const { percentDecode } = require('internal/data_url'); @@ -1431,6 +1433,7 @@ function domainToUnicode(domain) { * @returns {Record} */ function urlToHttpOptions(url) { + validateObject(url, 'url', kValidateObjectAllowObjects); const { hostname, pathname, port, username, password, search } = url; const options = { __proto__: null, diff --git a/test/parallel/test-url-urltooptions.js b/test/parallel/test-url-urltooptions.js index cc4838eeecb00f..8e7e5dc89409a1 100644 --- a/test/parallel/test-url-urltooptions.js +++ b/test/parallel/test-url-urltooptions.js @@ -35,3 +35,12 @@ assert.strictEqual(copiedOpts.pathname, undefined); assert.strictEqual(copiedOpts.search, undefined); assert.strictEqual(copiedOpts.hash, undefined); assert.strictEqual(copiedOpts.href, undefined); + +// Test when passed an invalid argument +assert.throws(() => { + urlToHttpOptions('http://127.0.0.1'); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "url" argument must be of type object. Received type string (\'http://127.0.0.1\')', + name: 'TypeError' +}); From 7cfd85fc0e11aa0d616373b9673d10ed04f48816 Mon Sep 17 00:00:00 2001 From: Nam Yooseong <102887277+meteorqz6@users.noreply.github.com> Date: Fri, 12 Sep 2025 21:59:37 +0900 Subject: [PATCH 4/8] benchmark: calibrate config cluster/echo.js PR-URL: https://github.com/nodejs/node/pull/59836 Reviewed-By: Rafael Gonzaga Reviewed-By: Daeyeon Jeong --- benchmark/cluster/echo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/cluster/echo.js b/benchmark/cluster/echo.js index 71ded75c9d2572..a3f103bc0b8987 100644 --- a/benchmark/cluster/echo.js +++ b/benchmark/cluster/echo.js @@ -8,7 +8,7 @@ if (cluster.isPrimary) { payload: ['string', 'object'], sendsPerBroadcast: [1, 10], serialization: ['json', 'advanced'], - n: [1e5], + n: [1e3], }); function main({ From 0407b58c04d33e556703f1dd2bff95b307599068 Mon Sep 17 00:00:00 2001 From: Renegade334 Date: Tue, 9 Sep 2025 21:28:14 +0100 Subject: [PATCH 5/8] crypto: avoid calls to `promise.catch()` This avoids explicit calls to the user-mutable `%Promise.prototype%.catch`, and by association, implicit calls to the user-mutable `%Promise.prototype%.then`. PR-URL: https://github.com/nodejs/node/pull/59841 Reviewed-By: James M Snell Reviewed-By: Jordan Harband Reviewed-By: Filip Skokan --- lib/internal/crypto/aes.js | 7 +++++-- lib/internal/crypto/cfrg.js | 7 +++++-- lib/internal/crypto/chacha20_poly1305.js | 7 +++++-- lib/internal/crypto/ec.js | 11 +++++++---- lib/internal/crypto/mac.js | 14 ++++++++++---- lib/internal/crypto/ml_dsa.js | 7 +++++-- lib/internal/crypto/ml_kem.js | 7 +++++-- lib/internal/crypto/rsa.js | 17 ++++++++++------- 8 files changed, 52 insertions(+), 25 deletions(-) diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 0abffe85c9881b..792f5ea804916a 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -245,12 +245,15 @@ async function aesGenerateKey(algorithm, extractable, keyUsages) { 'SyntaxError'); } - const key = await generateKey('aes', { length }).catch((err) => { + let key; + try { + key = await generateKey('aes', { length }); + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason' + `[${err.message}]`, { name: 'OperationError', cause: err }); - }); + } return new InternalCryptoKey( key, diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 9380e2a3e746ac..de1a022c4638c6 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -149,11 +149,14 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) { break; } - const keyPair = await generateKeyPair(genKeyType).catch((err) => { + let keyPair; + try { + keyPair = await generateKeyPair(genKeyType); + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); - }); + } let publicUsages; let privateUsages; diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js index bcc778b24d7738..21e7be9841b98a 100644 --- a/lib/internal/crypto/chacha20_poly1305.js +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -91,12 +91,15 @@ async function c20pGenerateKey(algorithm, extractable, keyUsages) { 'SyntaxError'); } - const keyData = await randomBytes(32).catch((err) => { + let keyData; + try { + keyData = await randomBytes(32); + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason' + `[${err.message}]`, { name: 'OperationError', cause: err }); - }); + } return new InternalCryptoKey( createSecretKey(keyData), diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index c417aa09670f0b..24f02249d247d5 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -97,11 +97,14 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) { // Fall through } - const keypair = await generateKeyPair('ec', { namedCurve }).catch((err) => { + let keyPair; + try { + keyPair = await generateKeyPair('ec', { namedCurve }); + } catch(err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); - }); + } let publicUsages; let privateUsages; @@ -120,14 +123,14 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) { const publicKey = new InternalCryptoKey( - keypair.publicKey, + keyPair.publicKey, keyAlgorithm, publicUsages, true); const privateKey = new InternalCryptoKey( - keypair.privateKey, + keyPair.privateKey, keyAlgorithm, privateUsages, extractable); diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 0564f6c19d285f..a31c3ddb0d9484 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -64,11 +64,14 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) { 'SyntaxError'); } - const key = await generateKey('hmac', { length }).catch((err) => { + let key; + try { + key = await generateKey('hmac', { length }); + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); - }); + } return new InternalCryptoKey( key, @@ -94,12 +97,15 @@ async function kmacGenerateKey(algorithm, extractable, keyUsages) { 'SyntaxError'); } - const keyData = await randomBytes(length / 8).catch((err) => { + let keyData; + try { + keyData = await randomBytes(length / 8); + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason' + `[${err.message}]`, { name: 'OperationError', cause: err }); - }); + } return new InternalCryptoKey( createSecretKey(keyData), diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index f1e6594f9e1a68..6f6e16dd9601d1 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -88,11 +88,14 @@ async function mlDsaGenerateKey(algorithm, extractable, keyUsages) { 'SyntaxError'); } - const keyPair = await generateKeyPair(name.toLowerCase()).catch((err) => { + let keyPair; + try { + keyPair = await generateKeyPair(name.toLowerCase()); + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); - }); + } const publicUsages = getUsagesUnion(usageSet, 'verify'); const privateUsages = getUsagesUnion(usageSet, 'sign'); diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index 5f6efc01125a4b..2d69790c21c55e 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -59,11 +59,14 @@ async function mlKemGenerateKey(algorithm, extractable, keyUsages) { 'SyntaxError'); } - const keyPair = await generateKeyPair(name.toLowerCase()).catch((err) => { + let keyPair; + try { + keyPair = await generateKeyPair(name.toLowerCase()); + } catch(err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); - }); + } const publicUsages = getUsagesUnion(usageSet, 'encapsulateBits', 'encapsulateKey'); const privateUsages = getUsagesUnion(usageSet, 'decapsulateBits', 'decapsulateKey'); diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index e3567a98c41878..97192d8aa0c974 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -150,14 +150,17 @@ async function rsaKeyGenerate( } } - const keypair = await generateKeyPair('rsa', { - modulusLength, - publicExponent: publicExponentConverted, - }).catch((err) => { + let keyPair; + try { + keyPair = await generateKeyPair('rsa', { + modulusLength, + publicExponent: publicExponentConverted, + }); + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); - }); + } const keyAlgorithm = { name, @@ -183,14 +186,14 @@ async function rsaKeyGenerate( const publicKey = new InternalCryptoKey( - keypair.publicKey, + keyPair.publicKey, keyAlgorithm, publicUsages, true); const privateKey = new InternalCryptoKey( - keypair.privateKey, + keyPair.privateKey, keyAlgorithm, privateUsages, extractable); From 64cfb65ae92fffe3544526fa4d2249d97fa9cb04 Mon Sep 17 00:00:00 2001 From: Renegade334 Date: Tue, 9 Sep 2025 21:57:38 +0100 Subject: [PATCH 6/8] crypto: use async functions for non-stub Promise-returning functions These were intended to mimic simple async functions, but exceptions thrown in the function body would be returned synchronously, not wrapped in a rejected Promise. PR-URL: https://github.com/nodejs/node/pull/59841 Reviewed-By: James M Snell Reviewed-By: Jordan Harband Reviewed-By: Filip Skokan --- lib/internal/crypto/aes.js | 13 ++++++------- lib/internal/crypto/cfrg.js | 2 +- lib/internal/crypto/chacha20_poly1305.js | 7 +++---- lib/internal/crypto/ec.js | 2 +- lib/internal/crypto/ml_dsa.js | 2 +- lib/internal/crypto/rsa.js | 4 ++-- lib/internal/crypto/webcrypto.js | 2 +- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 792f5ea804916a..e8125fbc40f2e2 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -5,7 +5,6 @@ const { ArrayBufferPrototypeSlice, ArrayFrom, ArrayPrototypePush, - PromiseReject, SafeSet, TypedArrayPrototypeSlice, } = primordials; @@ -144,7 +143,7 @@ function asyncAesKwCipher(mode, key, data) { getVariant('AES-KW', key[kAlgorithm].length))); } -function asyncAesGcmCipher(mode, key, data, algorithm) { +async function asyncAesGcmCipher(mode, key, data, algorithm) { const { tagLength = 128 } = algorithm; const tagByteLength = tagLength / 8; @@ -160,9 +159,9 @@ function asyncAesGcmCipher(mode, key, data, algorithm) { // > If *plaintext* has a length less than *tagLength* bits, then `throw` // > an `OperationError`. if (tagByteLength > tag.byteLength) { - return PromiseReject(lazyDOMException( + throw lazyDOMException( 'The provided data is too small.', - 'OperationError')); + 'OperationError'); } data = slice(data, 0, -tagByteLength); @@ -184,7 +183,7 @@ function asyncAesGcmCipher(mode, key, data, algorithm) { algorithm.additionalData)); } -function asyncAesOcbCipher(mode, key, data, algorithm) { +async function asyncAesOcbCipher(mode, key, data, algorithm) { const { tagLength = 128 } = algorithm; const tagByteLength = tagLength / 8; @@ -197,9 +196,9 @@ function asyncAesOcbCipher(mode, key, data, algorithm) { // Similar to GCM, OCB requires the tag to be present for decryption if (tagByteLength > tag.byteLength) { - return PromiseReject(lazyDOMException( + throw lazyDOMException( 'The provided data is too small.', - 'OperationError')); + 'OperationError'); } data = slice(data, 0, -tagByteLength); diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index de1a022c4638c6..98fff5224952e4 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -343,7 +343,7 @@ function cfrgImportKey( extractable); } -function eddsaSignVerify(key, data, algorithm, signature) { +async function eddsaSignVerify(key, data, algorithm, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js index 21e7be9841b98a..7b0f963531135a 100644 --- a/lib/internal/crypto/chacha20_poly1305.js +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -4,7 +4,6 @@ const { ArrayBufferIsView, ArrayBufferPrototypeSlice, ArrayFrom, - PromiseReject, SafeSet, TypedArrayPrototypeSlice, } = primordials; @@ -47,7 +46,7 @@ function validateKeyLength(length) { throw lazyDOMException('Invalid key length', 'DataError'); } -function c20pCipher(mode, key, data, algorithm) { +async function c20pCipher(mode, key, data, algorithm) { let tag; switch (mode) { case kWebCryptoCipherDecrypt: { @@ -55,9 +54,9 @@ function c20pCipher(mode, key, data, algorithm) { TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice; if (data.byteLength < 16) { - return PromiseReject(lazyDOMException( + throw lazyDOMException( 'The provided data is too small.', - 'OperationError')); + 'OperationError'); } tag = slice(data, -16); diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index 24f02249d247d5..059936cb5032e3 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -284,7 +284,7 @@ function ecImportKey( extractable); } -function ecdsaSignVerify(key, data, { name, hash }, signature) { +async function ecdsaSignVerify(key, data, { name, hash }, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 6f6e16dd9601d1..8810091dc0e6ca 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -287,7 +287,7 @@ function mlDsaImportKey( extractable); } -function mlDsaSignVerify(key, data, algorithm, signature) { +async function mlDsaSignVerify(key, data, algorithm, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index 97192d8aa0c974..b3ed81d046b509 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -93,7 +93,7 @@ function validateRsaOaepAlgorithm(algorithm) { } } -function rsaOaepCipher(mode, key, data, algorithm) { +async function rsaOaepCipher(mode, key, data, algorithm) { validateRsaOaepAlgorithm(algorithm); const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private'; @@ -330,7 +330,7 @@ function rsaImportKey( }, keyUsages, extractable); } -function rsaSignVerify(key, data, { saltLength }, signature) { +async function rsaSignVerify(key, data, { saltLength }, signature) { const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; const type = mode === kSignJobModeSign ? 'private' : 'public'; diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index ba5632c24df7ef..14e81d8eda2196 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -1022,7 +1022,7 @@ async function unwrapKey( ); } -function signVerify(algorithm, key, data, signature) { +async function signVerify(algorithm, key, data, signature) { let usage = 'sign'; if (signature !== undefined) { usage = 'verify'; From 634a8895e45881e9c9715c0c2c9a2685243ac79e Mon Sep 17 00:00:00 2001 From: Renegade334 Date: Tue, 9 Sep 2025 21:48:37 +0100 Subject: [PATCH 7/8] crypto: use `return await` when returning Promises from async functions This offers _some_ resistance to `%Promise.prototype%` pollution. Refs: https://github.com/nodejs/node/issues/59699 PR-URL: https://github.com/nodejs/node/pull/59841 Reviewed-By: James M Snell Reviewed-By: Jordan Harband Reviewed-By: Filip Skokan --- lib/internal/crypto/aes.js | 4 +- lib/internal/crypto/cfrg.js | 2 +- lib/internal/crypto/chacha20_poly1305.js | 2 +- lib/internal/crypto/ec.js | 4 +- lib/internal/crypto/hash.js | 2 +- lib/internal/crypto/ml_dsa.js | 2 +- lib/internal/crypto/ml_kem.js | 2 +- lib/internal/crypto/rsa.js | 4 +- lib/internal/crypto/webcrypto.js | 80 +++++++++++++++--------- 9 files changed, 61 insertions(+), 41 deletions(-) diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index e8125fbc40f2e2..0474060d394c99 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -172,7 +172,7 @@ async function asyncAesGcmCipher(mode, key, data, algorithm) { break; } - return jobPromise(() => new AESCipherJob( + return await jobPromise(() => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], @@ -209,7 +209,7 @@ async function asyncAesOcbCipher(mode, key, data, algorithm) { break; } - return jobPromise(() => new AESCipherJob( + return await jobPromise(() => new AESCipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 98fff5224952e4..c5bbaae90cf595 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -350,7 +350,7 @@ async function eddsaSignVerify(key, data, algorithm, signature) { if (key[kKeyType] !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); - return jobPromise(() => new SignJob( + return await jobPromise(() => new SignJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js index 7b0f963531135a..0979d7aaddbb61 100644 --- a/lib/internal/crypto/chacha20_poly1305.js +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -68,7 +68,7 @@ async function c20pCipher(mode, key, data, algorithm) { break; } - return jobPromise(() => new ChaCha20Poly1305CipherJob( + return await jobPromise(() => new ChaCha20Poly1305CipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index 059936cb5032e3..dd7997c82cbf91 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -100,7 +100,7 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) { let keyPair; try { keyPair = await generateKeyPair('ec', { namedCurve }); - } catch(err) { + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); @@ -293,7 +293,7 @@ async function ecdsaSignVerify(key, data, { name, hash }, signature) { const hashname = normalizeHashName(hash.name); - return jobPromise(() => new SignJob( + return await jobPromise(() => new SignJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index e4d94da1c5ee96..ef8020ebb587bf 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -221,7 +221,7 @@ async function asyncDigest(algorithm, data) { case 'cSHAKE128': // Fall through case 'cSHAKE256': - return jobPromise(() => new HashJob( + return await jobPromise(() => new HashJob( kCryptoJobAsync, normalizeHashName(algorithm.name), data, diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 8810091dc0e6ca..ebe3bfe3d17ca0 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -294,7 +294,7 @@ async function mlDsaSignVerify(key, data, algorithm, signature) { if (key[kKeyType] !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); - return jobPromise(() => new SignJob( + return await jobPromise(() => new SignJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index 2d69790c21c55e..f6eb76cef10b20 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -62,7 +62,7 @@ async function mlKemGenerateKey(algorithm, extractable, keyUsages) { let keyPair; try { keyPair = await generateKeyPair(name.toLowerCase()); - } catch(err) { + } catch (err) { throw lazyDOMException( 'The operation failed for an operation-specific reason', { name: 'OperationError', cause: err }); diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index b3ed81d046b509..c6b3985dbaee66 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -103,7 +103,7 @@ async function rsaOaepCipher(mode, key, data, algorithm) { 'InvalidAccessError'); } - return jobPromise(() => new RSACipherJob( + return await jobPromise(() => new RSACipherJob( kCryptoJobAsync, mode, key[kKeyObject][kHandle], @@ -337,7 +337,7 @@ async function rsaSignVerify(key, data, { saltLength }, signature) { if (key[kKeyType] !== type) throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); - return jobPromise(() => { + return await jobPromise(() => { if (key[kAlgorithm].name === 'RSA-PSS') { validateInt32( saltLength, diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 14e81d8eda2196..869c07ef87fbe6 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -84,7 +84,7 @@ async function digest(algorithm, data) { algorithm = normalizeAlgorithm(algorithm, 'digest'); - return ReflectApply(asyncDigest, this, [algorithm, data]); + return await ReflectApply(asyncDigest, this, [algorithm, data]); } function randomUUID() { @@ -246,20 +246,20 @@ async function deriveBits(algorithm, baseKey, length = null) { case 'X448': // Fall through case 'ECDH': - return require('internal/crypto/diffiehellman') + return await require('internal/crypto/diffiehellman') .ecdhDeriveBits(algorithm, baseKey, length); case 'HKDF': - return require('internal/crypto/hkdf') + return await require('internal/crypto/hkdf') .hkdfDeriveBits(algorithm, baseKey, length); case 'PBKDF2': - return require('internal/crypto/pbkdf2') + return await require('internal/crypto/pbkdf2') .pbkdf2DeriveBits(algorithm, baseKey, length); case 'Argon2d': // Fall through case 'Argon2i': // Fall through case 'Argon2id': - return require('internal/crypto/argon2') + return await require('internal/crypto/argon2') .argon2DeriveBits(algorithm, baseKey, length); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); @@ -391,12 +391,12 @@ async function exportKeySpki(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - return require('internal/crypto/rsa') + return await require('internal/crypto/rsa') .rsaExportKey(key, kWebCryptoKeyFormatSPKI); case 'ECDSA': // Fall through case 'ECDH': - return require('internal/crypto/ec') + return await require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatSPKI); case 'Ed25519': // Fall through @@ -405,13 +405,14 @@ async function exportKeySpki(key) { case 'X25519': // Fall through case 'X448': - return require('internal/crypto/cfrg') + return await require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': + // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatSPKI); case 'ML-KEM-512': @@ -419,6 +420,7 @@ async function exportKeySpki(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': + // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatSPKI); default: @@ -433,12 +435,12 @@ async function exportKeyPkcs8(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - return require('internal/crypto/rsa') + return await require('internal/crypto/rsa') .rsaExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ECDSA': // Fall through case 'ECDH': - return require('internal/crypto/ec') + return await require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatPKCS8); case 'Ed25519': // Fall through @@ -447,13 +449,14 @@ async function exportKeyPkcs8(key) { case 'X25519': // Fall through case 'X448': - return require('internal/crypto/cfrg') + return await require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': + // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ML-KEM-512': @@ -461,6 +464,7 @@ async function exportKeyPkcs8(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': + // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatPKCS8); default: @@ -473,7 +477,7 @@ async function exportKeyRawPublic(key, format) { case 'ECDSA': // Fall through case 'ECDH': - return require('internal/crypto/ec') + return await require('internal/crypto/ec') .ecExportKey(key, kWebCryptoKeyFormatRaw); case 'Ed25519': // Fall through @@ -482,7 +486,7 @@ async function exportKeyRawPublic(key, format) { case 'X25519': // Fall through case 'X448': - return require('internal/crypto/cfrg') + return await require('internal/crypto/cfrg') .cfrgExportKey(key, kWebCryptoKeyFormatRaw); case 'ML-DSA-44': // Fall through @@ -493,6 +497,7 @@ async function exportKeyRawPublic(key, format) { if (format !== 'raw-public') { return undefined; } + // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); } @@ -505,6 +510,7 @@ async function exportKeyRawPublic(key, format) { if (format !== 'raw-public') { return undefined; } + // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatRaw); } @@ -520,6 +526,7 @@ async function exportKeyRawSeed(key) { case 'ML-DSA-65': // Fall through case 'ML-DSA-87': + // Note: mlDsaExportKey does not return a Promise. return require('internal/crypto/ml_dsa') .mlDsaExportKey(key, kWebCryptoKeyFormatRaw); case 'ML-KEM-512': @@ -527,6 +534,7 @@ async function exportKeyRawSeed(key) { case 'ML-KEM-768': // Fall through case 'ML-KEM-1024': + // Note: mlKemExportKey does not return a Promise. return require('internal/crypto/ml_kem') .mlKemExportKey(key, kWebCryptoKeyFormatRaw); default: @@ -933,7 +941,7 @@ async function wrapKey(format, key, wrappingKey, algorithm) { } } - return cipherOrWrap( + return await cipherOrWrap( kWebCryptoCipherEncrypt, algorithm, wrappingKey, @@ -1040,31 +1048,31 @@ async function signVerify(algorithm, key, data, signature) { case 'RSA-PSS': // Fall through case 'RSASSA-PKCS1-v1_5': - return require('internal/crypto/rsa') + return await require('internal/crypto/rsa') .rsaSignVerify(key, data, algorithm, signature); case 'ECDSA': - return require('internal/crypto/ec') + return await require('internal/crypto/ec') .ecdsaSignVerify(key, data, algorithm, signature); case 'Ed25519': // Fall through case 'Ed448': // Fall through - return require('internal/crypto/cfrg') + return await require('internal/crypto/cfrg') .eddsaSignVerify(key, data, algorithm, signature); case 'HMAC': - return require('internal/crypto/mac') + return await require('internal/crypto/mac') .hmacSignVerify(key, data, algorithm, signature); case 'ML-DSA-44': // Fall through case 'ML-DSA-65': // Fall through case 'ML-DSA-87': - return require('internal/crypto/ml_dsa') + return await require('internal/crypto/ml_dsa') .mlDsaSignVerify(key, data, algorithm, signature); case 'KMAC128': // Fall through case 'KMAC256': - return require('internal/crypto/mac') + return await require('internal/crypto/mac') .kmacSignVerify(key, data, algorithm, signature); } throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); @@ -1089,7 +1097,7 @@ async function sign(algorithm, key, data) { context: '3rd argument', }); - return signVerify(algorithm, key, data); + return await signVerify(algorithm, key, data); } async function verify(algorithm, key, signature, data) { @@ -1115,7 +1123,7 @@ async function verify(algorithm, key, signature, data) { context: '4th argument', }); - return signVerify(algorithm, key, data, signature); + return await signVerify(algorithm, key, data, signature); } async function cipherOrWrap(mode, algorithm, key, data, op) { @@ -1138,7 +1146,7 @@ async function cipherOrWrap(mode, algorithm, key, data, op) { switch (algorithm.name) { case 'RSA-OAEP': - return require('internal/crypto/rsa') + return await require('internal/crypto/rsa') .rsaCipher(mode, key, data, algorithm); case 'AES-CTR': // Fall through @@ -1147,14 +1155,14 @@ async function cipherOrWrap(mode, algorithm, key, data, op) { case 'AES-GCM': // Fall through case 'AES-OCB': - return require('internal/crypto/aes') + return await require('internal/crypto/aes') .aesCipher(mode, key, data, algorithm); case 'ChaCha20-Poly1305': - return require('internal/crypto/chacha20_poly1305') + return await require('internal/crypto/chacha20_poly1305') .c20pCipher(mode, key, data, algorithm); case 'AES-KW': if (op === 'wrapKey' || op === 'unwrapKey') { - return require('internal/crypto/aes') + return await require('internal/crypto/aes') .aesCipher(mode, key, data, algorithm); } } @@ -1181,7 +1189,13 @@ async function encrypt(algorithm, key, data) { }); algorithm = normalizeAlgorithm(algorithm, 'encrypt'); - return cipherOrWrap(kWebCryptoCipherEncrypt, algorithm, key, data, 'encrypt'); + return await cipherOrWrap( + kWebCryptoCipherEncrypt, + algorithm, + key, + data, + 'encrypt', + ); } async function decrypt(algorithm, key, data) { @@ -1204,7 +1218,13 @@ async function decrypt(algorithm, key, data) { }); algorithm = normalizeAlgorithm(algorithm, 'decrypt'); - return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt'); + return await cipherOrWrap( + kWebCryptoCipherDecrypt, + algorithm, + key, + data, + 'decrypt', + ); } // Implements https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey @@ -1267,7 +1287,7 @@ async function encapsulateBits(encapsulationAlgorithm, encapsulationKey) { case 'ML-KEM-512': case 'ML-KEM-768': case 'ML-KEM-1024': - return require('internal/crypto/ml_kem') + return await require('internal/crypto/ml_kem') .mlKemEncapsulate(encapsulationKey); } @@ -1381,7 +1401,7 @@ async function decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphert case 'ML-KEM-512': case 'ML-KEM-768': case 'ML-KEM-1024': - return require('internal/crypto/ml_kem') + return await require('internal/crypto/ml_kem') .mlKemDecapsulate(decapsulationKey, ciphertext); } From c67a3314244de6000b83d89daa5a338f5f8e505b Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 18 May 2025 13:52:12 -0700 Subject: [PATCH 8/8] sqlite: cleanup ERM support and export Session class Update sqlite Session to support Symbol.dispose and move the definition of the dispose methods to c++ to close the open TODO PR-URL: https://github.com/nodejs/node/pull/58378 Backport-PR-URL: https://github.com/nodejs/node/pull/59895 Reviewed-By: Colin Ihrig Reviewed-By: Antoine du Hamel --- lib/sqlite.js | 15 +-------------- src/node_sqlite.cc | 20 ++++++++++++++++++++ src/node_sqlite.h | 2 ++ src/util.cc | 26 ++++++++++++++++++++++++++ src/util.h | 10 ++++++++++ test/parallel/test-sqlite-session.js | 15 +++++++++++++++ 6 files changed, 74 insertions(+), 14 deletions(-) diff --git a/lib/sqlite.js b/lib/sqlite.js index b011fd0921b0a8..6d6ada72008f1c 100644 --- a/lib/sqlite.js +++ b/lib/sqlite.js @@ -1,19 +1,6 @@ 'use strict'; -const { - SymbolDispose, -} = primordials; const { emitExperimentalWarning } = require('internal/util'); -const binding = internalBinding('sqlite'); emitExperimentalWarning('SQLite'); -// TODO(cjihrig): Move this to C++ once Symbol.dispose reaches Stage 4. -binding.DatabaseSync.prototype[SymbolDispose] = function() { - try { - this.close(); - } catch { - // Ignore errors. - } -}; - -module.exports = binding; +module.exports = internalBinding('sqlite'); diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 0df20be77ce860..9140c63440c88a 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -1080,6 +1080,14 @@ void DatabaseSync::Close(const FunctionCallbackInfo& args) { db->connection_ = nullptr; } +void DatabaseSync::Dispose(const v8::FunctionCallbackInfo& args) { + v8::TryCatch try_catch(args.GetIsolate()); + Close(args); + if (try_catch.HasCaught()) { + CHECK(try_catch.CanContinue()); + } +} + void DatabaseSync::Prepare(const FunctionCallbackInfo& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); @@ -2629,6 +2637,7 @@ Local Session::GetConstructorTemplate(Environment* env) { SetProtoMethod( isolate, tmpl, "patchset", Session::Changeset); SetProtoMethod(isolate, tmpl, "close", Session::Close); + SetProtoDispose(isolate, tmpl, Session::Dispose); env->set_sqlite_session_constructor_template(tmpl); } return tmpl; @@ -2673,6 +2682,14 @@ void Session::Close(const FunctionCallbackInfo& args) { session->Delete(); } +void Session::Dispose(const v8::FunctionCallbackInfo& args) { + v8::TryCatch try_catch(args.GetIsolate()); + Close(args); + if (try_catch.HasCaught()) { + CHECK(try_catch.CanContinue()); + } +} + void Session::Delete() { if (!database_ || !database_->connection_ || session_ == nullptr) return; sqlite3session_delete(session_); @@ -2708,6 +2725,7 @@ static void Initialize(Local target, SetProtoMethod(isolate, db_tmpl, "open", DatabaseSync::Open); SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close); + SetProtoDispose(isolate, db_tmpl, DatabaseSync::Dispose); SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare); SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec); SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction); @@ -2745,6 +2763,8 @@ static void Initialize(Local target, target, "StatementSync", StatementSync::GetConstructorTemplate(env)); + SetConstructorFunction( + context, target, "Session", Session::GetConstructorTemplate(env)); target->Set(context, env->constants_string(), constants).Check(); diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 3a9f08c16573b2..5bc6366398406b 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -92,6 +92,7 @@ class DatabaseSync : public BaseObject { static void IsTransactionGetter( const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); + static void Dispose(const v8::FunctionCallbackInfo& args); static void Prepare(const v8::FunctionCallbackInfo& args); static void Exec(const v8::FunctionCallbackInfo& args); static void Location(const v8::FunctionCallbackInfo& args); @@ -230,6 +231,7 @@ class Session : public BaseObject { template static void Changeset(const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); + static void Dispose(const v8::FunctionCallbackInfo& args); static v8::Local GetConstructorTemplate( Environment* env); static BaseObjectPtr Create(Environment* env, diff --git a/src/util.cc b/src/util.cc index 78326b56eab457..660cfff6b8a0c5 100644 --- a/src/util.cc +++ b/src/util.cc @@ -598,6 +598,32 @@ void SetMethodNoSideEffect(Isolate* isolate, that->Set(name_string, t); } +void SetProtoDispose(v8::Isolate* isolate, + v8::Local that, + v8::FunctionCallback callback) { + Local signature = v8::Signature::New(isolate, that); + Local t = + NewFunctionTemplate(isolate, + callback, + signature, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffect); + that->PrototypeTemplate()->Set(v8::Symbol::GetDispose(isolate), t); +} + +void SetProtoAsyncDispose(v8::Isolate* isolate, + v8::Local that, + v8::FunctionCallback callback) { + Local signature = v8::Signature::New(isolate, that); + Local t = + NewFunctionTemplate(isolate, + callback, + signature, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffect); + that->PrototypeTemplate()->Set(v8::Symbol::GetAsyncDispose(isolate), t); +} + void SetProtoMethod(v8::Isolate* isolate, Local that, const std::string_view name, diff --git a/src/util.h b/src/util.h index 9eb7034e378f0d..b6f49bcf8e7eab 100644 --- a/src/util.h +++ b/src/util.h @@ -936,6 +936,16 @@ void SetMethodNoSideEffect(v8::Isolate* isolate, const std::string_view name, v8::FunctionCallback callback); +// Set the Symbol.dispose method on the prototype of the class. +void SetProtoDispose(v8::Isolate* isolate, + v8::Local that, + v8::FunctionCallback callback); + +// Set the Symbol.asyncDispose method on the prototype of the class. +void SetProtoAsyncDispose(v8::Isolate* isolate, + v8::Local that, + v8::FunctionCallback callback); + enum class SetConstructorFunctionFlag { NONE, SET_CLASS_NAME, diff --git a/test/parallel/test-sqlite-session.js b/test/parallel/test-sqlite-session.js index 6c638b8e4a3965..1fe78c6ec6622a 100644 --- a/test/parallel/test-sqlite-session.js +++ b/test/parallel/test-sqlite-session.js @@ -540,3 +540,18 @@ test('session.close() - closing twice', (t) => { message: 'session is not open' }); }); + +test('session supports ERM', (t) => { + const database = new DatabaseSync(':memory:'); + let afterDisposeSession; + { + using session = database.createSession(); + afterDisposeSession = session; + const changeset = session.changeset(); + t.assert.ok(changeset instanceof Uint8Array); + t.assert.strictEqual(changeset.length, 0); + } + t.assert.throws(() => afterDisposeSession.changeset(), { + message: /session is not open/, + }); +});