diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs index 305b2b365a6..b919708a978 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 0b1fa03cf1f..1018cded979 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 d72216fa2ad..2e4d98a8a66 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 00000000000..5ff9b09f8e7 --- /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 2a68699283d..c6af1ee01e7 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;