-
Notifications
You must be signed in to change notification settings - Fork 30.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: support buffers greater than 2^32
bytes in length in Buffer.concat
and Buffer.copy
#55492
base: main
Are you sure you want to change the base?
Conversation
03d27e2
to
d15c192
Compare
Hi! Can you please amend your commit message to follow the guidelines of this repository? It needs to be under 80 characters |
Additionally, can you add a test to validate this behavior |
d15c192
to
cd0a59a
Compare
I noticed that |
No need, it's a flaky test. It's a known process deadlock that's being investigated. |
@ronag I've requested your review because you were involved in the issue this closes, and you know a lot about buffers that I don't |
Don't merge this yet. It's possible to read bytes from outside the source buffer into the target buffer. For example... function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
if (sourceEnd - sourceStart > target.byteLength - targetStart)
sourceEnd = sourceStart + target.byteLength - targetStart;
let nb = sourceEnd - sourceStart;
const sourceLen = source.byteLength - sourceStart;
if (nb > sourceLen)
nb = sourceLen;
if (nb <= 0)
return 0;
// _copy(source, target, targetStart, sourceStart, nb);
// return nb;
return [source, target, targetStart, sourceStart, nb];
}
let src = Buffer.alloc(50);
let dest = Buffer.alloc(100);
const [_, __, ___, sourceStart, copy_len] = _copyActual(src, dest, 0, -10, 0);
console.log([sourceStart, copy_len]); // [ -10, 10 ] Its probably better here not to trust the caller of Resolved by: 3c18a13 |
@nodejs/buffer PTAL ☝️ |
I've updated The expected behavior for an out of bounds copy is actually specified by At the latest commit 3c18a13, |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #55492 +/- ##
========================================
Coverage 88.41% 88.41%
========================================
Files 653 654 +1
Lines 187435 187575 +140
Branches 36077 36081 +4
========================================
+ Hits 165714 165851 +137
+ Misses 14960 14954 -6
- Partials 6761 6770 +9
|
Not sure what do about code coverage issue. It seems to me that the range check should stay in as preventative measure. |
No need to worry about codecov, it's not 100% accurate |
lib/buffer.js
Outdated
|
||
if (targetStart >= target.byteLength || sourceStart >= sourceEnd) | ||
return 0; | ||
|
||
// Guaranteed 0 <= sourceStart < sourceEnd (NO GUARANTEE <= source.byteLnegth) | ||
// Guaranteed 0 <= targetStart < target.byteLength here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could just make these asserts. The perf impact is negligable...
lib/buffer.js
Outdated
return _copyActual(source, target, targetStart, sourceStart, sourceEnd); | ||
} | ||
|
||
// Assumes `source`, `target` are real buffers. | ||
// Assumes 0 <= sourceStart <= sourceEnd | ||
// Assumes 0 <= targetStart <= target.byteLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could just make these asserts. The perf impact is negligable...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 271 to 275 in 209289c
assert(isUint8Array(source) && isUint8Array(target)); | |
// Enforce: 0 <= sourceStart <= sourceEnd <= source.byteLength | |
assert(0 <= sourceStart && sourceStart <= sourceEnd && sourceEnd <= source.byteLength); | |
// Enforce: 0 <= targetStart<= target.byteLength | |
assert(0 <= targetStart && targetStart <= target.byteLength); |
Turns out the issue also occurs in |
Buffer.concat
and Buffer.copy
Buffer.concat
and Buffer.copy
Buffer.concat
and Buffer.copy
Buffer.concat
and Buffer.copy
2^32
bytes in length in Buffer.concat
and Buffer.copy
lib/buffer.js
Outdated
@@ -218,6 +221,7 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) { | |||
if (targetStart < 0) | |||
throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart); | |||
} | |||
// Guaranteed 0 <= targetStart |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean?
Shouldn't an assert be used instead?
If not, IMO a comment isn't needed at all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it is redundant. This comment has been removed as of commit 5ca703f.
test/parallel/test-buffer-concat.js
Outdated
// Test its possible to concat buffers where the total length of the result in bytes | ||
// is greater than 2^32. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Test its possible to concat buffers where the total length of the result in bytes | |
// is greater than 2^32. | |
// Ref: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/parallel/test-buffer-copy.js
Outdated
@@ -234,3 +235,12 @@ assert.deepStrictEqual(c, b.slice(0, c.length)); | |||
// No copying took place: | |||
assert.deepStrictEqual(c.toString(), 'C'.repeat(c.length)); | |||
} | |||
|
|||
|
|||
// Test its possible to copy buffer with length > 2^32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
21b9ce0
to
5ca703f
Compare
@redyetidev This is no longer WIP unless you have anymore concerns or suggestions. |
I believe CI is failing because the runner is exhausting its memory. We do some pretty large allocations in the unit tests. node/test/parallel/test-buffer-copy.js Lines 239 to 245 in 5ca703f
node/test/parallel/test-buffer-concat.js Lines 103 to 113 in 5ca703f
Perhaps the tests should be skipped? |
// - The source slice is not clamped to fit into the target slice. If it won't fit, this throws. | ||
// - If either the source or target slice are out of bounds, this throws. | ||
function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { | ||
assert(isUint8Array(source) && isUint8Array(target)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assert here is a bit redundant given the checks that are made in the copyImpl
call site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion this assert is useful since it makes _copyActual
's invariant that source
and target
need to be Uint8Array
explicit.
It indicates to any future/new maintainers needing to call _copyActual
what the invariants are. They don't have to guess at the invariants, or deduce the preconditions by exploring all the call-sites of _copyActual
.
@@ -98,3 +99,15 @@ assert.deepStrictEqual( | |||
assert.deepStrictEqual(Buffer.concat([new Uint8Array([0x41, 0x42]), | |||
new Uint8Array([0x43, 0x44])]), | |||
Buffer.from('ABCD')); | |||
|
|||
// Ref: https://github.com/nodejs/node/issues/55422#issue-2594486812 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please consider expanding the comment a bit here with a short summary of what is being tested for so folks don't have to necessarily follow the link to get the gist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it is better to have the description of the test inlined.
However I was under the impression that the policy was to use refs?
See #55492 (comment) where I actually removed the explanation at the request of another maintainer.
Closes #55422
This pull request...
Buffer.copy
with lengths greater than or equal to 2^32 bytes.Buffer.concat
where the result buffer has length greater than or equal to 2^32 bytes.Buffer.copy
.sourceEnd
exceeds the total number of bytes in the source buffer.Buffer.copy
andBuffer.concat
.