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

78 lines
2.5 KiB
Markdown

# 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.
```js
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`:
```js
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`:
```js
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.