diff --git a/lib/ffi.js b/lib/ffi.js index cde6cca7a86e..99a583363b90 100644 --- a/lib/ffi.js +++ b/lib/ffi.js @@ -1,6 +1,8 @@ 'use strict'; const { + ArrayBufferPrototypeGetDetached, + DataViewPrototypeGetBuffer, FunctionPrototypeCall, ObjectDefineProperty, ObjectFreeze, @@ -8,11 +10,14 @@ const { ObjectKeys, ObjectPrototypeToString, SymbolDispose, + TypedArrayPrototypeGetBuffer, } = primordials; const { Buffer } = require('buffer'); const { emitExperimentalWarning } = require('internal/util'); const { + isDataView, isArrayBufferView, + isSharedArrayBuffer, } = require('internal/util/types'); const { codes: { @@ -284,7 +289,13 @@ function exportArrayBufferView(source, data, len) { validateInteger(len, 'len', 0); - if (len < source.byteLength) { + // Reading byteLength throws for a detached DataView. Let the native binding + // reject detached views consistently with detached ArrayBuffers. + const buffer = isDataView(source) ? + DataViewPrototypeGetBuffer(source) : TypedArrayPrototypeGetBuffer(source); + if ((isSharedArrayBuffer(buffer) || + !ArrayBufferPrototypeGetDetached(buffer)) && + len < source.byteLength) { throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len); } diff --git a/src/util-inl.h b/src/util-inl.h index e357d15a1449..19a497c03bd1 100644 --- a/src/util-inl.h +++ b/src/util-inl.h @@ -573,6 +573,7 @@ ArrayBufferViewContents::ArrayBufferViewContents( template void ArrayBufferViewContents::Read(v8::Local abv) { static_assert(sizeof(T) == 1, "Only supports one-byte data at the moment"); + was_detached_ = abv->Buffer()->WasDetached(); length_ = abv->ByteLength(); if (length_ > sizeof(stack_storage_) || abv->HasBuffer()) { auto buf_data = abv->Buffer()->Data(); diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index f17f56c410f8..ae95851f8f76 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -251,6 +251,23 @@ test('ffi validates memory access arguments', () => { assert.throws(() => ffi.exportArrayBufferView('bad', ptr, 4), { code: 'ERR_INVALID_ARG_TYPE' }); assert.throws(() => ffi.exportArrayBufferView(new Uint8Array([1]), ptr, -1), { code: 'ERR_OUT_OF_RANGE' }); assert.throws(() => ffi.exportArrayBufferView(new Uint8Array([1, 2]), ptr, 1), { code: 'ERR_OUT_OF_RANGE' }); + ffi.exportArrayBufferView(new Uint8Array(new SharedArrayBuffer(1)), ptr, 1); + + const detachedArrayBuffer = new ArrayBuffer(1); + detachedArrayBuffer.transfer(); + assert.throws(() => ffi.exportArrayBuffer(detachedArrayBuffer, ptr, 1), { + code: 'ERR_INVALID_ARG_VALUE', + }); + + for (const View of [Uint8Array, DataView]) { + const arrayBuffer = new ArrayBuffer(1); + const view = new View(arrayBuffer); + arrayBuffer.transfer(); + assert.throws(() => ffi.exportArrayBufferView(view, ptr, 1), { + code: 'ERR_INVALID_ARG_VALUE', + }); + } + assert.throws(() => ffi.toBuffer(maxPointer, 8), /pointer and length exceed the platform address range/); assert.throws(() => ffi.toArrayBuffer(maxPointer, 8), /pointer and length exceed the platform address range/); assert.throws(() => ffi.toBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });