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 5e61ba212836..35620859d64a 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 9105940b2a45..a1473fdac8ab 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 cc4838eeecb0..8e7e5dc89409 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 71ded75c9d25..a3f103bc0b89 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 0abffe85c988..792f5ea80491 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 9380e2a3e746..de1a022c4638 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 bcc778b24d77..21e7be9841b9 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 c417aa09670f..24f02249d247 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 0564f6c19d28..a31c3ddb0d94 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 f1e6594f9e1a..6f6e16dd9601 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 5f6efc01125a..2d69790c21c5 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 e3567a98c418..97192d8aa0c9 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 792f5ea80491..e8125fbc40f2 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 de1a022c4638..98fff5224952 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 21e7be9841b9..7b0f96353113 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 24f02249d247..059936cb5032 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 6f6e16dd9601..8810091dc0e6 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 97192d8aa0c9..b3ed81d046b5 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 ba5632c24df7..14e81d8eda21 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 e8125fbc40f2..0474060d394c 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 98fff5224952..c5bbaae90cf5 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 7b0f96353113..0979d7aaddbb 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 059936cb5032..dd7997c82cbf 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 e4d94da1c5ee..ef8020ebb587 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 8810091dc0e6..ebe3bfe3d17c 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 2d69790c21c5..f6eb76cef10b 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 b3ed81d046b5..c6b3985dbaee 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 14e81d8eda21..869c07ef87fb 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 b011fd0921b0..6d6ada72008f 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 0df20be77ce8..9140c63440c8 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 3a9f08c16573..5bc636639840 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 78326b56eab4..660cfff6b8a0 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 9eb7034e378f..b6f49bcf8e7e 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 6c638b8e4a39..1fe78c6ec662 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/, + }); +});