current commit
261004.2 · ffi: fix exportArrayBuffer type check
'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);
  }));
});