@ 261105.16
current
// Flags: --expose-internals
'use strict';
require('../common');
const assert = require('assert');
const { Buffer, constants } = require('buffer');
const { internalBinding } = require('internal/test/binding');
const { arrayBufferAlignedOffset } = internalBinding('buffer');

// Buffer.allocUnsafe(size, alignment) and Buffer.allocUnsafeSlow(size,
// alignment) return a buffer whose memory starts at an address that is a
// multiple of `alignment`.

// Addresses are not observable from JS, so alignment cannot be checked against
// `byteOffset` alone: the padding an allocation needs depends on where its
// backing store happens to land. `arrayBufferAlignedOffset(ab, alignment)`
// returns an offset into `ab` that is known to be aligned, so any other offset
// is aligned exactly when it is congruent to that one.
function assertAligned(buf, alignment) {
  const aligned = arrayBufferAlignedOffset(buf.buffer, alignment);
  // `aligned` is in [0, alignment), so adding `alignment` keeps this positive.
  const skew = (buf.byteOffset - aligned + alignment) % alignment;
  assert.strictEqual(skew, 0,
                     `byteOffset ${buf.byteOffset} is not ${alignment} byte ` +
                     `aligned (aligned offsets are ${aligned} mod ${alignment})`);
}

const alignments = [1, 2, 4, 8, 16, 64, 512, 4096, 65536];
const sizes = [0, 1, 7, 64, 65, 512, 4096, 100000];

for (const alloc of [Buffer.allocUnsafe, Buffer.allocUnsafeSlow]) {
  for (const alignment of alignments) {
    for (const size of sizes) {
      const buf = alloc(size, alignment);
      assert.strictEqual(buf.length, size);
      if (size > 0) {
        assertAligned(buf, alignment);
      }
      // The view must fit inside the (over-allocated) ArrayBuffer.
      assert.ok(buf.byteOffset + size <= buf.buffer.byteLength);
      // The whole buffer must be writable through the aligned view.
      buf.fill(0x61);
      if (size > 0) {
        assert.strictEqual(buf[0], 0x61);
        assert.strictEqual(buf[size - 1], 0x61);
      }
    }
  }

  // A zero length buffer is returned for size 0, whatever the alignment.
  assert.strictEqual(alloc(0, 4096).length, 0);

  // An aligned buffer is a normal Buffer.
  {
    const buf = alloc(32, 4096);
    assert.ok(Buffer.isBuffer(buf));
    buf.write('hello');
    assert.strictEqual(buf.toString('latin1', 0, 5), 'hello');
    assert.strictEqual(buf.subarray(1, 3).length, 2);
  }

  // Consecutive allocations do not overlap.
  {
    const a = alloc(64, 64).fill(0x01);
    const b = alloc(64, 64).fill(0x02);
    assert.strictEqual(a[0], 0x01);
    assert.strictEqual(a[63], 0x01);
    assert.strictEqual(b[0], 0x02);
    assert.strictEqual(b[63], 0x02);
  }

  // Invalid alignments.
  for (const alignment of [0, -1, -4096, 3, 5, 100, 1000, 2 ** 30 + 1]) {
    assert.throws(() => alloc(10, alignment), {
      name: /^(RangeError|TypeError)$/,
    });
  }

  for (const alignment of [1.5, NaN, Infinity]) {
    assert.throws(() => alloc(10, alignment), { code: 'ERR_OUT_OF_RANGE' });
  }

  for (const alignment of [null, '64', 64n, {}, [], true]) {
    assert.throws(() => alloc(10, alignment), {
      code: 'ERR_INVALID_ARG_TYPE',
    });
  }

  // Alignments that are not a power of two report the reason.
  assert.throws(() => alloc(10, 3), {
    code: 'ERR_INVALID_ARG_VALUE',
    message: /must be a power of two/,
  });

  // `size` plus the padding must still fit within the maximum buffer length.
  assert.throws(() => alloc(constants.MAX_LENGTH, 4096), {
    code: 'ERR_OUT_OF_RANGE',
  });

  // `size` itself is validated before `alignment`.
  assert.throws(() => alloc(-1, 64), { code: 'ERR_OUT_OF_RANGE' });
  assert.throws(() => alloc(constants.MAX_LENGTH + 1, 64), {
    code: 'ERR_OUT_OF_RANGE',
  });
}

// Omitting the alignment keeps the previous behaviour.
{
  const buf = Buffer.allocUnsafeSlow(100);
  assert.strictEqual(buf.length, 100);
  assert.strictEqual(buf.byteOffset, 0);
  assert.strictEqual(buf.buffer.byteLength, 100);
}

// The padding an aligned allocation needs does not leave a partial element
// behind it, so a typed array view running from the buffer to the end of its
// ArrayBuffer can be created without an explicit length.
{
  const bufs = [Buffer.allocUnsafe(8), Buffer.allocUnsafeSlow(4096, 4096)];
  for (const alignment of alignments) {
    bufs.push(Buffer.allocUnsafe(8, alignment),
              Buffer.allocUnsafeSlow(8, alignment));
  }
  for (const buf of bufs) {
    const rest = buf.buffer.byteLength - buf.byteOffset;
    assert.strictEqual(new BigUint64Array(buf.buffer, buf.byteOffset).byteLength,
                       rest);
    assert.strictEqual(new Uint32Array(buf.buffer, buf.byteOffset).byteLength,
                       rest);
  }
}

// Buffer.allocUnsafeSlow() is never pooled, even when aligned.
{
  const a = Buffer.allocUnsafeSlow(64, 64);
  const b = Buffer.allocUnsafeSlow(64, 64);
  assert.notStrictEqual(a.buffer, b.buffer);
}

// Buffer.allocUnsafe() serves alignments up to the pool alignment from the pool,
// and allocates on its own beyond that.
{
  const bufs = [];
  for (let i = 0; i < 8; i++) {
    bufs.push(Buffer.allocUnsafe(64, 64));
  }
  // Pooled, so consecutive allocations share an ArrayBuffer. A pool may be
  // exhausted in between, hence checking that any two neighbours share one.
  assert.ok(bufs.some((buf, i) => i > 0 && buf.buffer === bufs[i - 1].buffer));
  for (const buf of bufs) {
    assertAligned(buf, 64);
  }

  // Stricter than the pool alignment, so this gets its own ArrayBuffer.
  const own = Buffer.allocUnsafe(64, 128);
  assert.ok(bufs.every((buf) => buf.buffer !== own.buffer));
}

// Aligned pooled allocations do not disturb unaligned ones. Interleave the two
// and make sure every buffer keeps its own contents.
{
  const bufs = [];
  for (let i = 0; i < 256; i++) {
    const buf = i % 2 === 0 ?
      Buffer.allocUnsafe(24) :
      Buffer.allocUnsafe(24, 16);
    if (i % 2 === 1) {
      assertAligned(buf, 16);
    }
    buf.fill(i % 256);
    bufs.push(buf);
  }
  for (let i = 0; i < bufs.length; i++) {
    assert.strictEqual(bufs[i][0], i % 256);
    assert.strictEqual(bufs[i][23], i % 256);
  }
}