Files
davidmarkclements-rfdc/PR.md
T
dualfroz 655cc5eca2 fix: preserve byteLength when cloning a DataView
copyBuffer routed DataView through the TypedArray path, passing cur.length
(undefined on a DataView) as the constructor's third argument. Since that
argument is a byte length for DataView, a partial view was extended to the
end of the buffer. Clone DataView explicitly using byteLength.
2026-09-04 09:22:12 +02:00

2.5 KiB

Fix DataView clones getting the wrong byteLength

Problem

Cloning a DataView that covers only part of its underlying ArrayBuffer produces a clone with the wrong byteLength: the copy spans from the original byteOffset to the end of the buffer instead of keeping the original length.

const clone = require('rfdc')()

const buffer = new ArrayBuffer(16)
const view = new DataView(buffer, 4, 8) // offset 4, length 8

const copy = clone({ a: view }).a
copy.byteLength // 12  (expected 8)

The value at the start of the view is copied correctly and the clone is independent of the source, but the clone can now read 4 bytes past the end of the original view.

Root cause

index.js, in copyBuffer:

return new cur.constructor(cur.buffer.slice(), cur.byteOffset, cur.length)

This path handles every ArrayBuffer.isView value, i.e. both TypedArrays and DataView. It is correct for TypedArrays, whose third constructor argument is an element count matching .length. A DataView is different:

  • it has no .length property, so cur.length is undefined;
  • its third constructor argument is a byte length, not an element count.

new DataView(buffer, byteOffset, undefined) extends the view to the end of the buffer, so a partial view's length is lost. (A DataView over the whole buffer happens to work, because "to the end of the buffer" equals its byteLength.)

Fix

Handle DataView explicitly and size it with byteLength:

if (cur instanceof DataView) {
  return new DataView(cur.buffer.slice(), cur.byteOffset, cur.byteLength)
}

TypedArrays keep using the existing .length-based path.

Test

Added tests (run for all four rfdc variants via the shared types() helper) that clone a full-buffer DataView and a partial DataView, asserting byteOffset, byteLength, the stored value, and independence from the source.

Counterfactual (revert the copyBuffer DataView branch): the partial-DataView test fails with

not ok - byteLength preserved
  is(cloned.byteLength, view.byteLength, 'byteLength preserved')

(expected 8, got 12), while the full-buffer case still passes.

Gate

  • Tests: node test/index.js -> 1..144, 144 ok, 0 failing, exit 0. The repository's npm test invokes the tap CLI, whose bundled esm loader crashes on load under Node 22 (Function.prototype.apply was called on undefined) independently of this change; running the test file directly executes the same assertions.
  • Lint: standard index.js test/index.js -> exit 0.