Skip to content

Commit 209289c

Browse files
committed
buffer: add assertions, document copy behavior
1 parent 02e8c57 commit 209289c

File tree

4 files changed

+54
-18
lines changed

4 files changed

+54
-18
lines changed

doc/api/buffer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,11 @@ added: v0.1.90
17521752
Copies data from a region of `buf` to a region in `target`, even if the `target`
17531753
memory region overlaps with `buf`.
17541754

1755+
If `sourceEnd` exceeds `buf.bytesLength`, only the bytes up to
1756+
`buf.bytesLength` will be copied.
1757+
1758+
The source slice is truncated if it does not fit into the target slice.
1759+
17551760
[`TypedArray.prototype.set()`][] performs the same operation, and is available
17561761
for all TypedArrays, including Node.js `Buffer`s, although it takes
17571762
different function arguments.

lib/buffer.js

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ const {
100100
inspect: utilInspect,
101101
} = require('internal/util/inspect');
102102

103+
const assert = require('internal/assert');
104+
103105
const {
104106
codes: {
105107
ERR_BUFFER_OUT_OF_BOUNDS,
@@ -205,12 +207,12 @@ function toInteger(n, defaultVal) {
205207
return defaultVal;
206208
}
207209

210+
208211
function copyImpl(source, target, targetStart, sourceStart, sourceEnd) {
209212
if (!ArrayBufferIsView(source))
210213
throw new ERR_INVALID_ARG_TYPE('source', ['Buffer', 'Uint8Array'], source);
211214
if (!ArrayBufferIsView(target))
212215
throw new ERR_INVALID_ARG_TYPE('target', ['Buffer', 'Uint8Array'], target);
213-
// Guaranteed real buffers
214216

215217
if (targetStart === undefined) {
216218
targetStart = 0;
@@ -219,7 +221,7 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) {
219221
if (targetStart < 0)
220222
throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart);
221223
}
222-
// Guaranteed targetStart >= 0
224+
// Guaranteed 0 <= targetStart
223225

224226
if (sourceStart === undefined) {
225227
sourceStart = 0;
@@ -239,26 +241,43 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) {
239241
}
240242
// Guaranteed 0 <= sourceEnd
241243

244+
// Clamp sourceEnd to be inbounds of source buffer.
245+
// This is expected behavior and not to throw.
246+
sourceEnd = MathMin(sourceEnd, source.byteLength);
247+
242248
if (targetStart >= target.byteLength || sourceStart >= sourceEnd)
243249
return 0;
244250

245-
// Guaranteed 0 <= sourceStart < sourceEnd (NO GUARANTEE <= source.byteLnegth)
246-
// Guaranteed 0 <= targetStart < target.byteLength here
251+
// Assert source slice in bounds of source buffer.
252+
// 0 <= sourceStart < sourceEnd <= source.byteLength
253+
assert(0 <= sourceStart && sourceStart < sourceEnd && sourceEnd <= source.byteLength);
254+
255+
// Assert target slice in bounds of target buffer.
256+
// 0 <= targetStart < target.byteLength
257+
assert(0 <= targetStart && targetStart < target.byteLength);
258+
259+
// Truncate source so that its length doesn't exceed targets.
260+
// This is the expected behavior, not to throw.
261+
const copyLength = MathMin(sourceEnd - sourceStart, target.byteLength - targetStart);
262+
sourceEnd = sourceStart + copyLength;
247263

248-
return _copyActual(source, target, targetStart, sourceStart, sourceEnd);
264+
return _copyActual(source, target, targetStart, sourceStart, sourceStart + copyLength);
249265
}
250266

251-
// Assumes `source`, `target` are real buffers.
252-
// Assumes 0 <= sourceStart <= sourceEnd
253-
// Assumes 0 <= targetStart <= target.byteLength
254-
//
255-
// This function clamps sourceEnd such that the source subarray will not exceed the length
256-
// of the target subarray.
267+
// Safely performs native copy from valid source slice to valid target slice.
268+
// - The source slice is not clamped to fit into the target slice. If it won't fit, this throws.
269+
// - If either the source or target slice are out of bounds, this throws.
257270
function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
258-
sourceEnd = MathMin(sourceEnd, source.byteLength);
259-
const sourceLength = sourceEnd - sourceStart;
260-
const targetLength = target.byteLength - targetStart;
261-
const copyLength = MathMin(sourceLength, targetLength);
271+
assert(isUint8Array(source) && isUint8Array(target));
272+
// Enforce: 0 <= sourceStart <= sourceEnd <= source.byteLength
273+
assert(0 <= sourceStart && sourceStart <= sourceEnd && sourceEnd <= source.byteLength);
274+
// Enforce: 0 <= targetStart<= target.byteLength
275+
assert(0 <= targetStart && targetStart <= target.byteLength);
276+
277+
const copyLength = sourceEnd - sourceStart;
278+
const targetCapacity = target.byteLength - targetStart;
279+
assert(copyLength <= targetCapacity);
280+
262281
_copy(source, target, targetStart, sourceStart, copyLength);
263282
return copyLength;
264283
}
@@ -607,8 +626,9 @@ Buffer.concat = function concat(list, length) {
607626
throw new ERR_INVALID_ARG_TYPE(
608627
`list[${i}]`, ['Buffer', 'Uint8Array'], list[i]);
609628
}
610-
// (src, target, t_beg, s_beg, s_end)
611-
pos += _copyActual(buf, buffer, pos, 0, buf.length);
629+
630+
const copyLength = MathMin(buf.length, buffer.length - pos);
631+
pos += _copyActual(buf, buffer, pos, 0, copyLength);
612632
}
613633

614634
// Note: `length` is always equal to `buffer.length` at this point

test/parallel/test-buffer-concat.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ assert.deepStrictEqual(Buffer.concat([new Uint8Array([0x41, 0x42]),
100100
new Uint8Array([0x43, 0x44])]),
101101
Buffer.from('ABCD'));
102102

103-
103+
// Test its possible to concat buffers where the total length of the result in bytes
104+
// is greater than 2^32.
104105
if (2 ** 32 + 1 <= kMaxLength) {
105106
const a = Buffer.alloc(2 ** 31, 0);
106107
const b = Buffer.alloc(2 ** 31, 1);

test/parallel/test-buffer-copy.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
require('../common');
4+
const { kMaxLength } = require('buffer');
45
const assert = require('assert');
56

67
const b = Buffer.allocUnsafe(1024);
@@ -234,3 +235,12 @@ assert.deepStrictEqual(c, b.slice(0, c.length));
234235
// No copying took place:
235236
assert.deepStrictEqual(c.toString(), 'C'.repeat(c.length));
236237
}
238+
239+
240+
// Test its possible to copy buffer with length > 2^32
241+
if (2 ** 32 + 1 <= kMaxLength) {
242+
const src = Buffer.alloc(2 ** 32 + 1, 1);
243+
const dst = Buffer.alloc(2 ** 32 + 1, 2);
244+
src.copy(dst);
245+
assert.deepStrictEqual(src, dst);
246+
}

0 commit comments

Comments
 (0)