diff --git a/lib/eslint.config_partial.mjs b/lib/eslint.config_partial.mjs index 305b2b365a6bf6..b919708a978ed6 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/main/worker_thread.js b/lib/internal/main/worker_thread.js index 1e28a3452220e9..ec13dfa2a25a2e 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -11,9 +11,6 @@ const { ObjectDefineProperty, PromisePrototypeThen, RegExpPrototypeExec, - globalThis: { - SharedArrayBuffer, - }, } = primordials; const { @@ -119,23 +116,21 @@ port.on('message', (message) => { require('internal/worker').assignEnvironmentData(environmentData); setupMainThreadPort(mainThreadPort); - if (SharedArrayBuffer !== undefined) { - // The counter is only passed to the workers created by the main thread, - // not to workers created by other workers. - let cachedCwd = ''; - let lastCounter = -1; - const originalCwd = process.cwd; - - process.cwd = function() { - const currentCounter = AtomicsLoad(cwdCounter, 0); - if (currentCounter === lastCounter) - return cachedCwd; - lastCounter = currentCounter; - cachedCwd = originalCwd(); + // The counter is only passed to the workers created by the main thread, + // not to workers created by other workers. + let cachedCwd = ''; + let lastCounter = -1; + const originalCwd = process.cwd; + + process.cwd = function() { + const currentCounter = AtomicsLoad(cwdCounter, 0); + if (currentCounter === lastCounter) return cachedCwd; - }; - workerIo.sharedCwdCounter = cwdCounter; - } + lastCounter = currentCounter; + cachedCwd = originalCwd(); + return cachedCwd; + }; + workerIo.sharedCwdCounter = cwdCounter; const isLoaderHookWorker = (filename === 'internal/modules/esm/worker' && doEval === 'internal'); if (!isLoaderHookWorker) { diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js index ad5a22899176c7..462a5dd7656984 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -15,13 +15,8 @@ const { SafeSet, StringPrototypeSlice, StringPrototypeToUpperCase, - globalThis, } = primordials; -const { - SharedArrayBuffer, -} = globalThis; - const { ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED, ERR_INTERNAL_ASSERTION, @@ -44,6 +39,7 @@ const { validateString, } = require('internal/validators'); const { + constructSharedArrayBuffer, kEmptyObject, } = require('internal/util'); @@ -535,7 +531,7 @@ class AsyncLoaderHookWorker { const { InternalWorker } = require('internal/worker'); MessageChannel ??= require('internal/worker/io').MessageChannel; - const lock = new SharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH); + const lock = constructSharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH); this.#lock = new Int32Array(lock); this.#worker = new InternalWorker('internal/modules/esm/worker', { diff --git a/lib/internal/streams/fast-utf8-stream.js b/lib/internal/streams/fast-utf8-stream.js index 3bfcc494d17b5c..25f4771d052a2a 100644 --- a/lib/internal/streams/fast-utf8-stream.js +++ b/lib/internal/streams/fast-utf8-stream.js @@ -9,13 +9,14 @@ const { AtomicsWait, Int32Array, MathMax, + Number, SymbolDispose, - globalThis: { - Number, - SharedArrayBuffer, - }, } = primordials; +const { + constructSharedArrayBuffer, +} = require('internal/util'); + const { Buffer, } = require('buffer'); @@ -49,7 +50,7 @@ const { const BUSY_WRITE_TIMEOUT = 100; const kEmptyBuffer = Buffer.allocUnsafe(0); -const kNil = new Int32Array(new SharedArrayBuffer(4)); +const kNil = new Int32Array(constructSharedArrayBuffer(4)); function sleep(ms) { // Also filters out NaN, non-number types, including empty strings, but allows bigints diff --git a/lib/internal/util.js b/lib/internal/util.js index 0b1fa03cf1fab0..1018cded979b24 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/lib/internal/worker.js b/lib/internal/worker.js index ed940b5e2c4208..08e87d07e7eb80 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -24,7 +24,6 @@ const { SymbolFor, TypedArrayPrototypeFill, Uint32Array, - globalThis: { SharedArrayBuffer }, } = primordials; const EventEmitter = require('events'); @@ -62,7 +61,10 @@ const { const { createMainThreadPort, destroyMainThreadPort } = require('internal/worker/messaging'); const { deserializeError } = require('internal/error_serdes'); const { fileURLToPath, isURL, pathToFileURL } = require('internal/url'); -const { kEmptyObject } = require('internal/util'); +const { + constructSharedArrayBuffer, + kEmptyObject, +} = require('internal/util'); const { validateArray, validateString, validateObject, validateNumber } = require('internal/validators'); const { throwIfBuildingSnapshot, @@ -106,9 +108,8 @@ let cwdCounter; const environmentData = new SafeMap(); -// SharedArrayBuffers can be disabled with --enable-sharedarraybuffer-per-context. -if (isMainThread && SharedArrayBuffer !== undefined) { - cwdCounter = new Uint32Array(new SharedArrayBuffer(4)); +if (isMainThread) { + cwdCounter = new Uint32Array(constructSharedArrayBuffer(4)); const originalChdir = process.chdir; process.chdir = function(path) { AtomicsAdd(cwdCounter, 0, 1); diff --git a/lib/internal/worker/messaging.js b/lib/internal/worker/messaging.js index c4fcde5a3d4b58..d1573b870ef113 100644 --- a/lib/internal/worker/messaging.js +++ b/lib/internal/worker/messaging.js @@ -6,18 +6,17 @@ const { AtomicsWaitAsync, Int32Array, SafeMap, - globalThis, } = primordials; -const { - SharedArrayBuffer, -} = globalThis; - const { isMainThread, threadId: currentThreadId, } = internalBinding('worker'); +const { + constructSharedArrayBuffer, +} = require('internal/util'); + const { codes: { ERR_WORKER_MESSAGING_ERRORED, @@ -203,7 +202,7 @@ async function postMessageToThread(threadId, value, transferList, timeout) { throw new ERR_WORKER_MESSAGING_SAME_THREAD(); } - const memory = new SharedArrayBuffer(WORKER_MESSAGING_SHARED_DATA); + const memory = constructSharedArrayBuffer(WORKER_MESSAGING_SHARED_DATA); const status = new Int32Array(memory); const promise = AtomicsWaitAsync(status, WORKER_MESSAGING_STATUS_INDEX, 0, timeout).value; diff --git a/src/node_util.cc b/src/node_util.cc index d72216fa2add62..2e4d98a8a66a18 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 00000000000000..5ff9b09f8e7d36 --- /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 2a68699283debe..c6af1ee01e798c 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;