'use strict';
const common = require('../common');
common.skipIfFFIMissing();
const { constants: bufferConstants } = require('node:buffer');
const assert = require('node:assert');
const { after, test } = require('node:test');
const ffi = require('node:ffi');
const { fixtureSymbols, libraryPath } = require('./ffi-test-common');
const { lib, functions: symbols } = ffi.dlopen(libraryPath, {
allocate_memory: fixtureSymbols.allocate_memory,
deallocate_memory: fixtureSymbols.deallocate_memory,
pointer_to_usize: fixtureSymbols.pointer_to_usize,
});
after(() => lib.close());
function withAllocations(fn) {
const allocations = new Set();
function alloc(size) {
const ptr = symbols.allocate_memory(BigInt(size));
allocations.add(ptr);
return ptr;
}
try {
fn(alloc);
} finally {
for (const ptr of allocations) {
symbols.deallocate_memory(ptr);
}
}
}
test('ffi reads and writes integer and float values', () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(64);
ffi.setInt8(ptr, 0, -12);
ffi.setUint8(ptr, 1, 250);
ffi.setInt16(ptr, 2, -1234);
ffi.setUint16(ptr, 4, 65000);
ffi.setInt32(ptr, 8, -123456);
ffi.setUint32(ptr, 12, 3000000000);
ffi.setInt64(ptr, 16, -1234567890123n);
ffi.setUint64(ptr, 24, 1234567890123n);
ffi.setFloat32(ptr, 32, 3.5);
ffi.setFloat64(ptr, 40, 7.25);
assert.strictEqual(ffi.getInt8(ptr, 0), -12);
assert.strictEqual(ffi.getUint8(ptr, 1), 250);
assert.strictEqual(ffi.getInt16(ptr, 2), -1234);
assert.strictEqual(ffi.getUint16(ptr, 4), 65000);
assert.strictEqual(ffi.getInt32(ptr, 8), -123456);
assert.strictEqual(ffi.getUint32(ptr, 12), 3000000000);
assert.strictEqual(ffi.getInt64(ptr, 16), -1234567890123n);
assert.strictEqual(ffi.getUint64(ptr, 24), 1234567890123n);
assert.strictEqual(ffi.getFloat32(ptr, 32), 3.5);
assert.strictEqual(ffi.getFloat64(ptr, 40), 7.25);
}));
});
test('ffi supports unaligned memory access', () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(24);
ffi.setInt32(ptr, 1, 0x12345678);
ffi.setUint16(ptr, 7, 0xBEEF);
ffi.setFloat64(ptr, 9, 6.5);
assert.strictEqual(ffi.getInt32(ptr, 1), 0x12345678);
assert.strictEqual(ffi.getUint16(ptr, 7), 0xBEEF);
assert.strictEqual(ffi.getFloat64(ptr, 9), 6.5);
}));
});
const zeroCopy = { skip: common.hasV8Sandbox && 'zero-copy views are unavailable with the V8 sandbox' };
test('ffi toBuffer supports copy and zero-copy views', zeroCopy, () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(8);
ffi.exportBuffer(Buffer.from([1, 2, 3, 4]), ptr, 4);
const copy = ffi.toBuffer(ptr, 4);
const view = ffi.toBuffer(ptr, 4, false);
assert.deepStrictEqual([...copy], [1, 2, 3, 4]);
assert.deepStrictEqual([...view], [1, 2, 3, 4]);
view[1] = 9;
assert.deepStrictEqual([...ffi.toBuffer(ptr, 4)], [1, 9, 3, 4]);
assert.deepStrictEqual([...copy], [1, 2, 3, 4]);
const copyFromUndefined = ffi.toBuffer(ptr, 4, undefined);
copyFromUndefined[0] = 77;
assert.deepStrictEqual([...copyFromUndefined], [77, 9, 3, 4]);
assert.deepStrictEqual([...ffi.toBuffer(ptr, 4)], [1, 9, 3, 4]);
}));
});
test('ffi toArrayBuffer supports copy and zero-copy views', zeroCopy, () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(4);
ffi.exportBuffer(Buffer.from([10, 20, 30, 40]), ptr, 4);
const copied = new Uint8Array(ffi.toArrayBuffer(ptr, 4));
const shared = new Uint8Array(ffi.toArrayBuffer(ptr, 4, false));
assert.deepStrictEqual([...copied], [10, 20, 30, 40]);
assert.deepStrictEqual([...shared], [10, 20, 30, 40]);
shared[2] = 99;
assert.deepStrictEqual([...ffi.toBuffer(ptr, 4)], [10, 20, 99, 40]);
assert.deepStrictEqual([...copied], [10, 20, 30, 40]);
const copiedFromUndefined = new Uint8Array(ffi.toArrayBuffer(ptr, 4, undefined));
copiedFromUndefined[1] = 55;
assert.deepStrictEqual([...copiedFromUndefined], [10, 55, 99, 40]);
assert.deepStrictEqual([...ffi.toBuffer(ptr, 4)], [10, 20, 99, 40]);
}));
});
test('ffi zero-copy views throw with the V8 sandbox', { skip: !common.hasV8Sandbox }, () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(4);
ffi.exportBuffer(Buffer.from([1, 2, 3, 4]), ptr, 4);
assert.deepStrictEqual([...ffi.toBuffer(ptr, 4)], [1, 2, 3, 4]);
assert.throws(() => ffi.toBuffer(ptr, 4, false), { code: 'ERR_OPERATION_FAILED' });
assert.throws(() => ffi.toArrayBuffer(ptr, 4, false), { code: 'ERR_OPERATION_FAILED' });
}));
});
test('ffi toBuffer and toArrayBuffer require a boolean copy argument', () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(4);
const type = { code: 'ERR_INVALID_ARG_TYPE' };
for (const copy of [null, 0, '', 'false', 1, {}]) {
assert.throws(() => ffi.toBuffer(ptr, 4, copy), type);
assert.throws(() => ffi.toArrayBuffer(ptr, 4, copy), type);
}
}));
});
test('ffi getRawPointer returns raw addresses for byte sources', () => {
const buffer = Buffer.from([1, 2, 3]);
const arrayBuffer = new Uint8Array([4, 5, 6, 7]).buffer;
const view = new Uint8Array(arrayBuffer, 2);
const sharedArrayBuffer = new SharedArrayBuffer(4);
const sharedView = new Uint8Array(sharedArrayBuffer, 2);
const bufferPointer = ffi.getRawPointer(buffer);
const arrayBufferPointer = ffi.getRawPointer(arrayBuffer);
const viewPointer = ffi.getRawPointer(view);
const sharedArrayBufferPointer = ffi.getRawPointer(sharedArrayBuffer);
const sharedViewPointer = ffi.getRawPointer(sharedView);
assert.strictEqual(typeof bufferPointer, 'bigint');
assert.strictEqual(typeof arrayBufferPointer, 'bigint');
assert.strictEqual(typeof viewPointer, 'bigint');
assert.strictEqual(typeof sharedArrayBufferPointer, 'bigint');
assert.strictEqual(typeof sharedViewPointer, 'bigint');
assert.strictEqual(bufferPointer, symbols.pointer_to_usize(buffer));
assert.strictEqual(arrayBufferPointer, symbols.pointer_to_usize(arrayBuffer));
assert.strictEqual(viewPointer, arrayBufferPointer + 2n);
assert.strictEqual(sharedViewPointer, sharedArrayBufferPointer + 2n);
});
test('ffi rejects detached array buffers and views as pointers', () => {
const arrayBuffer = new ArrayBuffer(8);
const typedArray = new Uint8Array(arrayBuffer);
const dataView = new DataView(arrayBuffer);
arrayBuffer.transfer();
assert.throws(() => ffi.exportArrayBuffer(arrayBuffer, 0n, 0), {
code: 'ERR_INVALID_ARG_VALUE',
message: 'ArrayBuffer is detached',
});
for (const [value, rawPointerMessage, argumentMessage] of [
[
arrayBuffer,
'ArrayBuffer is detached',
'Argument 0 is a detached ArrayBuffer',
],
...[typedArray, dataView].map((view) => [
view,
'ArrayBufferView is backed by a detached ArrayBuffer',
'Argument 0 is an ArrayBufferView backed by a detached ArrayBuffer',
]),
]) {
assert.throws(() => ffi.getRawPointer(value), {
code: 'ERR_INVALID_ARG_VALUE',
message: rawPointerMessage,
});
assert.throws(() => symbols.pointer_to_usize(value), {
code: 'ERR_INVALID_ARG_VALUE',
message: argumentMessage,
});
}
});
test('ffi exportString and exportBuffer copy data into native memory', () => {
withAllocations(common.mustCall((alloc) => {
const stringPtr = alloc(16);
ffi.exportString('hello ffi', stringPtr, 16);
assert.strictEqual(ffi.toString(stringPtr), 'hello ffi');
assert.throws(() => ffi.exportString('truncated value', stringPtr, 5), {
code: 'ERR_OUT_OF_RANGE',
});
ffi.exportBuffer(Buffer.from([0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f]), stringPtr, 6);
ffi.exportString('A', stringPtr, 6, 'utf16le');
assert.deepStrictEqual([...ffi.toBuffer(stringPtr, 6)], [0x41, 0x00, 0x00, 0x00, 0x7f, 0x7f]);
ffi.exportBuffer(Buffer.from([0x7f, 0x7f, 0x7f, 0x7f, 0x7f]), stringPtr, 5);
assert.throws(() => ffi.exportString('AB', stringPtr, 5, 'utf16le'), {
code: 'ERR_OUT_OF_RANGE',
});
ffi.exportString('AB', stringPtr, 6, 'utf16le');
assert.deepStrictEqual([...ffi.toBuffer(stringPtr, 6)], [0x41, 0x00, 0x42, 0x00, 0x00, 0x00]);
const bufferPtr = alloc(7);
ffi.exportBuffer(Buffer.from([1, 2, 3, 4, 5, 6, 7]), bufferPtr, 7);
assert.deepStrictEqual([...ffi.toBuffer(bufferPtr, 7)], [1, 2, 3, 4, 5, 6, 7]);
assert.throws(() => ffi.exportBuffer(Buffer.from([1, 2, 3, 4, 5, 6, 7]), bufferPtr, 6), {
code: 'ERR_OUT_OF_RANGE',
});
const arrayBufferPtr = alloc(8);
const arrayBuffer = new Uint8Array([8, 9, 10, 11]).buffer;
ffi.exportArrayBuffer(arrayBuffer, arrayBufferPtr, 4);
assert.deepStrictEqual([...ffi.toBuffer(arrayBufferPtr, 4)], [8, 9, 10, 11]);
const taggedArrayBuffer = new Uint8Array([12, 13, 14, 15]).buffer;
Object.defineProperty(taggedArrayBuffer, Symbol.toStringTag, { value: 'Custom' });
ffi.exportArrayBuffer(taggedArrayBuffer, arrayBufferPtr, 4);
assert.deepStrictEqual([...ffi.toBuffer(arrayBufferPtr, 4)], [12, 13, 14, 15]);
const viewPtr = alloc(8);
const viewSource = new Uint16Array([0x0102, 0x0304, 0x0506]);
const middleBytes = new Uint8Array(viewSource.buffer, 2, 2);
ffi.exportArrayBufferView(middleBytes, viewPtr, 2);
assert.deepStrictEqual([...ffi.toBuffer(viewPtr, 2)], [...middleBytes]);
const bufferViewPtr = alloc(8);
const bufferView = Buffer.from([1, 7, 2, 8, 3]);
ffi.exportArrayBufferView(bufferView.subarray(1, 4), bufferViewPtr, 3);
assert.deepStrictEqual([...ffi.toBuffer(bufferViewPtr, 3)], [7, 2, 8]);
}));
});
test('ffi toString returns null for a null pointer', () => {
assert.strictEqual(ffi.toString(0n), null);
});
test('ffi accepts zero-length copies through a null pointer', () => {
assert.strictEqual(ffi.toBuffer(0n, 0).length, 0);
assert.strictEqual(ffi.toArrayBuffer(0n, 0).byteLength, 0);
assert.strictEqual(ffi.exportBuffer(Buffer.alloc(0), 0n, 0), undefined);
assert.strictEqual(ffi.exportArrayBuffer(new ArrayBuffer(0), 0n, 0), undefined);
assert.strictEqual(ffi.exportArrayBufferView(new Uint8Array(0), 0n, 0), undefined);
});
test('ffi validates memory access arguments', () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(8);
const maxPointer = process.arch === 'ia32' || process.arch === 'arm' ?
0xfffffffcn :
0xfffffffffffffffcn;
assert.throws(() => ffi.toBuffer('nope', 4), /The first argument must be a bigint/);
assert.throws(() => ffi.toBuffer(-1n, 4), /The first argument must be a non-negative bigint/);
assert.throws(() => ffi.toBuffer(ptr, -1), /The length must be a non-negative integer/);
assert.throws(() => ffi.toBuffer(0n, 1), /Cannot create a buffer from a null pointer/);
assert.throws(() => ffi.toArrayBuffer(ptr, 'bad'), /The length must be a number/);
assert.throws(() => ffi.toArrayBuffer(-1n, 4), /The first argument must be a non-negative bigint/);
assert.throws(() => ffi.toArrayBuffer(0n, 1), /Cannot create an ArrayBuffer from a null pointer/);
assert.throws(() => ffi.getRawPointer('bad'), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.getRawPointer(1), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.getInt32(0n), /Cannot dereference a null pointer/);
assert.throws(() => ffi.getInt32(-1n), /The pointer must be a non-negative bigint/);
assert.throws(() => ffi.getInt8(maxPointer, 8), /pointer and offset exceed the platform address range/);
assert.throws(() => ffi.getInt32(maxPointer, 2), /accessed range exceeds the platform address range/);
assert.throws(() => ffi.setUint8(ptr), /Expected an offset argument/);
assert.throws(() => ffi.setUint8(-1n, 0, 1), /The pointer must be a non-negative bigint/);
assert.throws(() => ffi.setUint8(maxPointer, 8, 1), /pointer and offset exceed the platform address range/);
assert.throws(() => ffi.setUint32(maxPointer, 2, 1), /accessed range exceeds the platform address range/);
assert.throws(() => ffi.setUint8(ptr, 0), /Expected a value argument/);
assert.throws(() => ffi.setInt8(ptr, 0, 1.5), /Value must be an int8/);
assert.throws(() => ffi.setInt8(ptr, 0, Number.NaN), /Value must be an int8/);
assert.throws(() => ffi.setUint8(ptr, 0, 300), /Value must be a uint8/);
assert.throws(() => ffi.setUint8(ptr, 0, Number.NaN), /Value must be a uint8/);
assert.throws(() => ffi.setInt16(ptr, 'bad', 1), /The offset must be a number/);
assert.throws(() => ffi.setInt16(ptr, 0, 1.5), /Value must be an int16/);
assert.throws(() => ffi.setUint16(ptr, 0, Number.NaN), /Value must be a uint16/);
assert.throws(() => ffi.setInt64(ptr, 0, 1.5), /Value must be an int64/);
assert.throws(() => ffi.setInt64(ptr, 0, Number.NaN), /Value must be an int64/);
assert.throws(() => ffi.setInt64(ptr, 0, 2n ** 63n), /Value must be an int64/);
assert.throws(() => ffi.setInt64(ptr, 0, Number.MAX_SAFE_INTEGER + 1), /Value must be an int64/);
assert.throws(() => ffi.setUint64(ptr, 0, -1), /Value must be a uint64/);
assert.throws(() => ffi.setUint64(ptr, 0, 1.5), /Value must be a uint64/);
assert.throws(() => ffi.setUint64(ptr, 0, -1n), /Value must be a uint64/);
assert.throws(() => ffi.setUint64(ptr, 0, 2n ** 64n), /Value must be a uint64/);
assert.throws(() => ffi.setUint64(ptr, 0, Number.MAX_SAFE_INTEGER + 1), /Value must be a uint64/);
assert.throws(() => ffi.setFloat32(ptr, 0, '1.5'), /Value must be a float/);
assert.throws(() => ffi.setFloat32(ptr, 0, null), /Value must be a float/);
assert.throws(() => ffi.setFloat64(ptr, 0, '1.5'), /Value must be a double/);
assert.throws(() => ffi.setFloat64(ptr, 0, true), /Value must be a double/);
assert.throws(() => ffi.setFloat64(ptr, 0, {}), /Value must be a double/);
assert.throws(() => ffi.setFloat64(ptr, 0, { valueOf: common.mustNotCall() }), /Value must be a double/);
assert.throws(() => ffi.exportString(1, ptr, 4), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.exportString('ok', ptr, -1), { code: 'ERR_OUT_OF_RANGE' });
assert.throws(() => ffi.exportString('ok', ptr, 4, 1), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.exportString('ok', ptr, 2), { code: 'ERR_OUT_OF_RANGE' });
assert.throws(() => ffi.exportBuffer('bad', ptr, 4), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.exportBuffer(Buffer.from([1]), ptr, -1), { code: 'ERR_OUT_OF_RANGE' });
assert.throws(() => ffi.exportBuffer(Buffer.from([1, 2]), ptr, 1), { code: 'ERR_OUT_OF_RANGE' });
assert.throws(() => ffi.exportArrayBuffer('bad', ptr, 4), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.exportArrayBuffer({ [Symbol.toStringTag]: 'ArrayBuffer', byteLength: 1 }, ptr, 4), {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "arrayBuffer" argument must be an instance of ArrayBuffer/,
});
assert.throws(() => ffi.exportArrayBuffer(new ArrayBuffer(1), ptr, -1), { code: 'ERR_OUT_OF_RANGE' });
assert.throws(() => ffi.exportArrayBuffer(new ArrayBuffer(2), ptr, 1), { code: 'ERR_OUT_OF_RANGE' });
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/);
// If MAX_LENGTH is Number.MAX_SAFE_INTEGER, MAX_LENGTH + 1 is an unsafe
// integer and is rejected before the buffer length is checked.
if (bufferConstants.MAX_LENGTH < Number.MAX_SAFE_INTEGER) {
assert.throws(() => ffi.toBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });
assert.throws(() => ffi.toArrayBuffer(1n, bufferConstants.MAX_LENGTH + 1), { code: 'ERR_BUFFER_TOO_LARGE' });
}
if (process.arch === 'ia32' || process.arch === 'arm') {
assert.throws(() => ffi.toBuffer(2n ** 32n, 0), /platform pointer range/);
}
}));
});
test('ffi rejects unsafe integers as an offset or length', () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(8);
const range = { code: 'ERR_OUT_OF_RANGE' };
// On 64-bit platforms SIZE_MAX rounds up to 2 ** 64 as a double.
for (const value of [Number.MAX_SAFE_INTEGER + 1, 2 ** 64]) {
assert.throws(() => ffi.getUint8(ptr, value), range);
assert.throws(() => ffi.setUint8(ptr, value, 42), range);
assert.throws(() => ffi.toBuffer(ptr, value), range);
assert.throws(() => ffi.toArrayBuffer(ptr, value), range);
}
}));
});
test('ffi toBuffer and toArrayBuffer throw when the copy cannot be allocated', {
skip: (bufferConstants.MAX_LENGTH < 2 ** 50 && 'requires a 64-bit buffer length limit') ||
(common.isASan && 'ASan aborts on huge allocations') ||
(common.isAIX && 'huge allocations may succeed on AIX and get the process killed'),
}, () => {
// The allocation fails before the source pointer is read.
const error = { code: 'ERR_MEMORY_ALLOCATION_FAILED' };
assert.throws(() => ffi.toBuffer(1n, 2 ** 50), error);
assert.throws(() => ffi.toArrayBuffer(1n, 2 ** 50), error);
});
test('ffi memory helpers reject missing required arguments', () => {
const widths = ['Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32',
'Int64', 'Uint64', 'Float32', 'Float64'];
// Calling a helper with no arguments must report the missing pointer the
// same way an explicitly passed `undefined` does, instead of returning
// `undefined` as if the read or the write had succeeded.
for (const width of widths) {
for (const name of [`get${width}`, `set${width}`]) {
assert.throws(() => ffi[name](), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi[name](undefined), { code: 'ERR_INVALID_ARG_TYPE' });
}
}
assert.throws(() => ffi.toBuffer(1n), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.toBuffer(1n, undefined), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.toArrayBuffer(1n), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => ffi.toArrayBuffer(1n, undefined), { code: 'ERR_INVALID_ARG_TYPE' });
});
test('ffi memory helpers distinguish wrong-typed from invalid arguments', () => {
withAllocations(common.mustCall((alloc) => {
const ptr = alloc(8);
const type = { code: 'ERR_INVALID_ARG_TYPE' };
const value = { code: 'ERR_INVALID_ARG_VALUE' };
// A pointer that is not a bigint, or an offset or length that is not a
// number, is a type error, like the JavaScript validators report it.
assert.throws(() => ffi.getInt8('x'), type);
assert.throws(() => ffi.getInt8(ptr, 'x'), type);
assert.throws(() => ffi.setInt8('x', 0, 1), type);
assert.throws(() => ffi.setInt8(ptr, 'x', 1), type);
assert.throws(() => ffi.toBuffer(ptr, 'x'), type);
assert.throws(() => ffi.toArrayBuffer(ptr, 'x'), type);
assert.throws(() => ffi.exportBuffer(Buffer.from([1]), 'x', 1), type);
assert.throws(() => ffi.exportArrayBuffer(new ArrayBuffer(1), 'x', 1), type);
assert.throws(() => ffi.exportArrayBufferView(new Uint8Array(1), 'x', 1), type);
// A bigint or number of the right type that is out of range stays a
// value error.
assert.throws(() => ffi.getInt8(-1n), value);
assert.throws(() => ffi.getInt8(ptr, -1), value);
assert.throws(() => ffi.setInt8(ptr, 1.5, 1), value);
assert.throws(() => ffi.toBuffer(ptr, 1.5), value);
assert.throws(() => ffi.exportBuffer(Buffer.from([1]), -1n, 1), value);
}));
});