From 48d0cd7fdcf24e14bf65eb4b2d0aa52c9eacb6eb Mon Sep 17 00:00:00 2001 From: Renegade334 Date: Tue, 28 Oct 2025 19:33:31 +0000 Subject: [PATCH] src: add internal binding for constructing SharedArrayBuffers PR-URL: https://github.com/nodejs/node/pull/60497 Reviewed-By: Antoine du Hamel Reviewed-By: Shelley Vohr Reviewed-By: Colin Ihrig --- lib/eslint.config_partial.mjs | 4 +-- lib/internal/util.js | 2 ++ src/node_util.cc | 32 ++++++++++++++++++- .../test-internal-util-construct-sab.js | 18 +++++++++++ typings/internalBinding/util.d.ts | 1 + 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-internal-util-construct-sab.js diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs index 305b2b365a..b919708a97 100644 --- a/lib/eslint.config_partial.mjs +++ b/lib/eslint.config_partial.mjs @@ -225,10 +225,10 @@ export default [ message: 'Use `const { ShadowRealm } = globalThis;` instead of the global.', }, // SharedArrayBuffer is not available in primordials because it can be - // disabled with --no-harmony-sharedarraybuffer CLI flag. + // disabled with --enable-sharedarraybuffer-per-context CLI flag. { name: 'SharedArrayBuffer', - message: 'Use `const { SharedArrayBuffer } = globalThis;` instead of the global.', + message: "Use `const { constructSharedArrayBuffer } = require('internal/util');` instead of the global.", }, { name: 'TextDecoder', diff --git a/lib/internal/util.js b/lib/internal/util.js index 0b1fa03cf1..1018cded97 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -59,6 +59,7 @@ const { } = require('internal/errors'); const { signals } = internalBinding('constants').os; const { + constructSharedArrayBuffer, guessHandleType: _guessHandleType, defineLazyProperties, privateSymbols: { @@ -954,6 +955,7 @@ module.exports = { assertTypeScript, assignFunctionName, cachedResult, + constructSharedArrayBuffer, convertToValidSignal, createClassWrapper, decorateErrorStack, diff --git a/src/node_util.cc b/src/node_util.cc index d72216fa2a..2e4d98a8a6 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -10,6 +10,7 @@ namespace util { using v8::ALL_PROPERTIES; using v8::Array; +using v8::ArrayBuffer; using v8::ArrayBufferView; using v8::BigInt; using v8::Boolean; @@ -34,6 +35,7 @@ using v8::ONLY_WRITABLE; using v8::Promise; using v8::PropertyFilter; using v8::Proxy; +using v8::SharedArrayBuffer; using v8::SKIP_STRINGS; using v8::SKIP_SYMBOLS; using v8::StackFrame; @@ -438,6 +440,30 @@ static void DefineLazyProperties(const FunctionCallbackInfo& args) { } } +void ConstructSharedArrayBuffer(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + int64_t length; + // Note: IntegerValue() clamps its output, so excessively large input values + // will not overflow + if (!args[0]->IntegerValue(env->context()).To(&length)) { + return; + } + if (length < 0 || + static_cast(length) > ArrayBuffer::kMaxByteLength) { + env->ThrowRangeError("Invalid array buffer length"); + return; + } + Local sab; + if (!SharedArrayBuffer::MaybeNew(env->isolate(), static_cast(length)) + .ToLocal(&sab)) { + // Note: SharedArrayBuffer::MaybeNew doesn't schedule an exception if it + // fails + env->ThrowRangeError("Array buffer allocation failed"); + return; + } + args.GetReturnValue().Set(sab); +} + void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GetPromiseDetails); registry->Register(GetProxyDetails); @@ -455,6 +481,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(IsInsideNodeModules); registry->Register(DefineLazyProperties); registry->Register(DefineLazyPropertiesGetter); + registry->Register(ConstructSharedArrayBuffer); } void Initialize(Local target, @@ -554,9 +581,12 @@ void Initialize(Local target, SetMethodNoSideEffect(context, target, "getCallSites", GetCallSites); SetMethod(context, target, "sleep", Sleep); SetMethod(context, target, "parseEnv", ParseEnv); - SetMethod( context, target, "arrayBufferViewHasBuffer", ArrayBufferViewHasBuffer); + SetMethod(context, + target, + "constructSharedArrayBuffer", + ConstructSharedArrayBuffer); Local should_abort_on_uncaught_toggle = FIXED_ONE_BYTE_STRING(env->isolate(), "shouldAbortOnUncaughtToggle"); diff --git a/test/parallel/test-internal-util-construct-sab.js b/test/parallel/test-internal-util-construct-sab.js new file mode 100644 index 0000000000..5ff9b09f8e --- /dev/null +++ b/test/parallel/test-internal-util-construct-sab.js @@ -0,0 +1,18 @@ +// Flags: --enable-sharedarraybuffer-per-context --expose-internals +'use strict'; + +require('../common'); +const assert = require('assert'); +const { isSharedArrayBuffer } = require('util/types'); +const { constructSharedArrayBuffer } = require('internal/util'); + +// We're testing that we can construct a SAB even when the global is not exposed. +assert.strictEqual(typeof SharedArrayBuffer, 'undefined'); + +for (const length of [undefined, 0, 1, 2 ** 32]) { + assert(isSharedArrayBuffer(constructSharedArrayBuffer(length))); +} + +for (const length of [-1, Number.MAX_SAFE_INTEGER + 1, 2 ** 64]) { + assert.throws(() => constructSharedArrayBuffer(length), RangeError); +} diff --git a/typings/internalBinding/util.d.ts b/typings/internalBinding/util.d.ts index 2a68699283..c6af1ee01e 100644 --- a/typings/internalBinding/util.d.ts +++ b/typings/internalBinding/util.d.ts @@ -46,6 +46,7 @@ export interface UtilBinding { parseEnv(content: string): Record; styleText(format: Array | string, text: string): string; isInsideNodeModules(frameLimit: number, defaultValue: unknown): boolean; + constructSharedArrayBuffer(length?: number): SharedArrayBuffer; constants: { kPending: 0;