Skip to content

Commit 48cc81f

Browse files
fix: serialize DataView subviews with correct byte offset and length (#166)
A DataView subview (created with a byteOffset/byteLength) was serialized incorrectly in two code paths: - uneval used a nonexistent `startOffset` property, which is `undefined` and coerces to 0 on revival, silently placing the view at the wrong offset. - stringify reused the typed-array branch and emitted `.length`, which is `undefined` for a DataView, producing invalid JSON that fails to parse. Both now emit `byteOffset,byteLength`, matching the `new DataView(buffer, byteOffset, byteLength)` constructor. Adds regression fixtures for both an inline and a named (deduplicated) DataView subview.
1 parent 12f54cf commit 48cc81f

4 files changed

Lines changed: 36 additions & 4 deletions

File tree

‎.changeset/dataview-subview.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"devalue": patch
3+
---
4+
5+
fix: serialize DataView subviews with the correct byte offset and length

‎src/stringify.js‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,22 +289,33 @@ function run(async, value, reducers) {
289289
case 'Float32Array':
290290
case 'Float64Array':
291291
case 'BigInt64Array':
292-
case 'BigUint64Array':
293-
case 'DataView': {
292+
case 'BigUint64Array': {
294293
/** @type {import("./types.js").TypedArray} */
295294
const typedArray = thing;
296295
str = '["' + type + '",' + flatten(typedArray.buffer);
297296

298297
// handle subarrays
299298
if (typedArray.byteLength !== typedArray.buffer.byteLength) {
300-
// to be used with `new TypedArray(buffer, byteOffset, length)`
301299
str += `,${typedArray.byteOffset},${typedArray.length}`;
302300
}
303301

304302
str += ']';
305303
break;
306304
}
307305

306+
case 'DataView': {
307+
/** @type {DataView} */
308+
const view = thing;
309+
str = '["' + type + '",' + flatten(view.buffer);
310+
311+
if (view.byteLength !== view.buffer.byteLength) {
312+
str += `,${view.byteOffset},${view.byteLength}`;
313+
}
314+
315+
str += ']';
316+
break;
317+
}
318+
308319
case 'ArrayBuffer': {
309320
/** @type {ArrayBuffer} */
310321
const arraybuffer = thing;

‎src/uneval.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ export function uneval(value, replacer) {
318318

319319
// handle subviews
320320
if (thing.byteLength !== thing.buffer.byteLength) {
321-
str += `,${thing.startOffset},${thing.byteLength}`;
321+
str += `,${thing.byteOffset},${thing.byteLength}`;
322322
}
323323

324324
return str + ')';

‎test/index.test.js‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,12 @@ const fixtures = {
311311
js: 'new DataView(new Uint8Array([1,2,3]).buffer)',
312312
json: '[["DataView",1],["ArrayBuffer","AQID"]]'
313313
},
314+
{
315+
name: 'DataView subview',
316+
value: new DataView(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer, 2, 4),
317+
js: 'new DataView(new Uint8Array([0,1,2,3,4,5,6,7,8,9]).buffer,2,4)',
318+
json: '[["DataView",1,2,4],["ArrayBuffer","AAECAwQFBgcICQ=="]]'
319+
},
314320
{
315321
name: 'URL',
316322
value: new URL('https://user:password@example.com/<script>/path?foo=bar#hash'),
@@ -698,6 +704,16 @@ const fixtures = {
698704
})(),
699705
js: '(function(a,b){a=new DataView(b);return [a,a,b]}({},new Uint8Array([0,1,2,3,4,5,6,7,8,9]).buffer))',
700706
json: '[[1,1,2],["DataView",2],["ArrayBuffer","AAECAwQFBgcICQ=="]]'
707+
},
708+
{
709+
name: 'DataView subview (repetition)',
710+
value: (() => {
711+
const uint8 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
712+
const dv = new DataView(uint8.buffer, 2, 4);
713+
return [dv, dv];
714+
})(),
715+
js: '(function(a){a=new DataView(new Uint8Array([0,1,2,3,4,5,6,7,8,9]).buffer,2,4);return [a,a]}({}))',
716+
json: '[[1,1],["DataView",2,2,4],["ArrayBuffer","AAECAwQFBgcICQ=="]]'
701717
}
702718
],
703719

0 commit comments

Comments
 (0)