-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
buffer: add a check for byteLength in readIntBE() and readIntLE() #11146
buffer: add a check for byteLength in readIntBE() and readIntLE() #11146
Conversation
Added semver-major due to the addition of the new throw conditions. |
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.
LGTM with one suggestion
lib/buffer.js
Outdated
if (typeof byteLength !== 'number') | ||
throw new TypeError('"byteLength" argument must be a number'); | ||
else if (byteLength === 0) | ||
throw new RangeError('"byteLength" argument cannot equal zero'); |
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.
Perhaps these can just be condensed into a single check
if (typeof byteLength !== 'number' || byteLength <= 0)
throw new TypeError('"byteLength" must be a positive number greater than zero');
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.
Thank you for your suggestion. I made the changes and amended them to the previous commit.
ca0c9be
to
a8d1fa8
Compare
lib/buffer.js
Outdated
@@ -852,6 +852,12 @@ function checkOffset(offset, ext, length) { | |||
throw new RangeError('Index out of range'); | |||
} | |||
|
|||
function checkByteLength(byteLength) { | |||
if (typeof byteLength !== 'number' || byteLength <= 0) | |||
throw new TypeError('"byteLength" must be a positive number ' + |
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.
byteLength <= 0
should be a RangeError
I think.
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.
So should we throw 2 separate errors (TypeError
when it's not a Number and RangeError
when byteLength is <= 0
) or just one RangeError
?
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 separate errors would be better, because a TypeError
for byteLength <= 0
doesn't look right IMO.
According to the docs:
byteLength
{Integer} How many bytes to read. Must satisfy:0 < byteLength <= 6
and
Supports up to 48 bits of accuracy.
So probably check byteLength > 6
too?
Also a doc update would be great :D
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.
good point about the RangeError
. Yes, in that case keeping them separate is best.
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.
Thanks again for the feedback. I amended the proposed changes to the previous commit. I also updated the docs.
f3644da
to
ed56860
Compare
Should a benchmark be run to ascertain the performance impact of this change? |
doc/api/buffer.md
Outdated
@@ -1588,6 +1588,14 @@ console.log(buf.readIntBE(0, 6).toString(16)); | |||
|
|||
// Throws an exception: RangeError: Index out of range | |||
console.log(buf.readIntBE(1, 6).toString(16)); | |||
|
|||
// Throws an exception: "byteLength" argument must be a number | |||
console.log(buf.readIntBE(1).toString(16)); |
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.
Might as well adding TypeError:
and RangeError:
as the previous example, just to be a little bit more consistent.
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.
done
@Trott +1 on running a benchmark. FYI: how to compare different implementations using the benchmark suite. The whole buffer benchmarks can take a long time IIRC, so probably running the benchmarks for these specific functions only (using |
ed56860
to
46ac4ad
Compare
@joyeecheung Thanks for advice on running the benchmark suite. I will look into it. |
I think the actual byte length range checking should go inside the
|
@mscdex so should both the RangeError and TypeError checks be moved inside the |
+1 on putting it in |
@seppevs I think putting both in there would be alright, plus it would allow us to avoid any potential performance regressions. |
46ac4ad
to
25668ef
Compare
Code is updated. The checks are now inside an I also added a benchmark for the
|
benchmark/buffers/buffer-read.js
Outdated
var testFunction = new Function('buff', [ | ||
'for (var i = 0; i !== ' + len + '; i++) {', | ||
' buff.' + fn + '(0, ' + JSON.stringify(noAssert) + ');', | ||
' if (\'' + fn + '\' === \'readIntLE\' ' + |
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 if branch should be moved outside new Function
to avoid the runtime cost. Also would be easier to read if we use template literals here, something like:
var call;
if (fn === 'readIntLE' || conf.type === 'readIntBE') {
call = `buff.${fn}(0, 1, ${noAssert})`;
} else {
call = `buff.${fn}(0, ${noAssert})`;
}
var testFunction = new Function('buff',
` for (var i = 0; i !== ${len}; ++i) {
${call};
}`);
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.
Code updated
lib/buffer.js
Outdated
@@ -933,8 +940,12 @@ Buffer.prototype.readUInt32BE = function(offset, noAssert) { | |||
|
|||
|
|||
Buffer.prototype.readIntLE = function(offset, byteLength, noAssert) { | |||
if (!noAssert) | |||
checkByteLength(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.
Probably best moving checkByteLength
and checkOffset
in the same branch. Also not sure if the assertion should happen before or after offset
and byteLength
get converted into numbers, I am inclined to checking it before the conversion.
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 checkOffset
uses the byteLength
, and assumes it is defined and already converted to a number. The offset
and byteLength
are converted to numbers before the checkOffset
function is invoked.
I think it's better to check the byteLength
before it's converted to a number. If we do the conversion first, we can't trust the converted value in the checkByteLength
function
console.log(undefined >>> 0); // -> 0
console.log(null >>> 0); // -> 0
console.log('a string' >>> 0); // -> 0
console.log(true >>> 0); // -> 1 .... this is in the expected range and would pass the check
So what do you suggest me to do?
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 ideally we would combine the two, but there could easily be breakage if the current (prior to this PR) assertions are moved, so that would definitely be another semver-major change if we wanted to make that change at all. Thoughts @nodejs/collaborators?
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.
@seppevs No matter the order of the assertions, I the typechecking part of checkByteLength
should be consistent with checkOffset
. If checkOffset
doesn't check the type, checkByteLength
should not do it either. If we do want to check the type, then we need to add this to checkOffset
too.
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.
Also if they are consistent, then they can be moved into the same branch I think? If we check the type, they can both be placed before the conversion. If we don't, they can both be placed after the conversion.
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.
So should I check the type, or not? Do you think the type check will affect the performance?
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.
@seppevs I can think of good arguments for both sides, so I think we can wait for thoughts from other collaborators.
25668ef
to
d71fc82
Compare
What is the status on this one? @seppevs ... can you please rebase this? |
lib/buffer.js
Outdated
function checkByteLength(byteLength) { | ||
if (typeof byteLength !== 'number') | ||
throw new TypeError('"byteLength" argument must be a number'); | ||
else if (byteLength < 1 || byteLength > 6) |
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.
You don't need the else
here.
8d4fbf6
to
10c78f4
Compare
@jasnell rebase done |
Ping @nodejs/buffer @nodejs/ctc ... thoughts on this? |
ping @nodejs/ctc |
64ff873
to
43e161e
Compare
@targos That would make sense. Will do... |
Oh, yes, probably! I'm intrigued, but I am inclined to save that for a subsequent PR. This PR is 11 months old at this point. I'd rather get the functionality landed and then have the performance vs. Crankshaft-script maintenance costs debate separately. |
Benchmark results with the additional changes: improvement confidence p.value
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntBE" buffer="fast" noAssert="false" -4.06 % *** 0.0002808920
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntBE" buffer="fast" noAssert="true" -0.87 % 0.5179050330
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntBE" buffer="slow" noAssert="false" -2.66 % 0.2197501164
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntBE" buffer="slow" noAssert="true" 0.94 % 0.3536060899
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntLE" buffer="fast" noAssert="false" -3.18 % 0.0840983107
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntLE" buffer="fast" noAssert="true" -0.39 % 0.7203410009
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntLE" buffer="slow" noAssert="false" -0.79 % 0.6929134114
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="IntLE" buffer="slow" noAssert="true" -1.10 % 0.4140083284
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntBE" buffer="fast" noAssert="false" -5.58 % *** 0.0009982531
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntBE" buffer="fast" noAssert="true" 2.90 % 0.1010858492
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntBE" buffer="slow" noAssert="false" -3.64 % * 0.0394332017
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntBE" buffer="slow" noAssert="true" -0.92 % 0.6825518346
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntLE" buffer="fast" noAssert="false" -2.84 % 0.0620117183
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntLE" buffer="fast" noAssert="true" -1.18 % 0.2374233691
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntLE" buffer="slow" noAssert="false" -5.59 % ** 0.0022420004
buffers/buffer-read-with-byteLength.js byteLength=1 millions=1 type="UIntLE" buffer="slow" noAssert="true" -0.38 % 0.8336763163
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntBE" buffer="fast" noAssert="false" -4.56 % *** 0.0001955189
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntBE" buffer="fast" noAssert="true" -1.70 % 0.1608865221
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntBE" buffer="slow" noAssert="false" -1.56 % 0.3447368715
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntBE" buffer="slow" noAssert="true" 0.28 % 0.8424978551
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntLE" buffer="fast" noAssert="false" -4.78 % ** 0.0025353959
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntLE" buffer="fast" noAssert="true" -3.36 % 0.1101136262
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntLE" buffer="slow" noAssert="false" 1.68 % 0.2885417168
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="IntLE" buffer="slow" noAssert="true" -0.36 % 0.8360745581
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntBE" buffer="fast" noAssert="false" -2.91 % 0.0563485510
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntBE" buffer="fast" noAssert="true" 2.73 % 0.0570619342
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntBE" buffer="slow" noAssert="false" -2.16 % 0.1467243882
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntBE" buffer="slow" noAssert="true" 1.09 % 0.5036428118
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntLE" buffer="fast" noAssert="false" -3.13 % * 0.0167492226
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntLE" buffer="fast" noAssert="true" 2.18 % 0.3465909913
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntLE" buffer="slow" noAssert="false" -3.78 % *** 0.0008500509
buffers/buffer-read-with-byteLength.js byteLength=2 millions=1 type="UIntLE" buffer="slow" noAssert="true" -1.13 % 0.5597913248
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntBE" buffer="fast" noAssert="false" 1.31 % 0.5323692216
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntBE" buffer="fast" noAssert="true" 0.36 % 0.7901715876
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntBE" buffer="slow" noAssert="false" -0.03 % 0.9858863031
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntBE" buffer="slow" noAssert="true" -1.51 % 0.4410258539
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntLE" buffer="fast" noAssert="false" -3.08 % * 0.0126980478
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntLE" buffer="fast" noAssert="true" 0.54 % 0.7528102406
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntLE" buffer="slow" noAssert="false" 1.42 % 0.4579209139
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="IntLE" buffer="slow" noAssert="true" -4.31 % * 0.0289903090
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntBE" buffer="fast" noAssert="false" -0.22 % 0.8495333840
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntBE" buffer="fast" noAssert="true" 2.97 % 0.0837406345
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntBE" buffer="slow" noAssert="false" -1.74 % 0.2207386083
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntBE" buffer="slow" noAssert="true" 0.78 % 0.7013668938
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntLE" buffer="fast" noAssert="false" -0.83 % 0.5471758131
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntLE" buffer="fast" noAssert="true" 0.57 % 0.7022090281
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntLE" buffer="slow" noAssert="false" -2.32 % 0.0827266784
buffers/buffer-read-with-byteLength.js byteLength=4 millions=1 type="UIntLE" buffer="slow" noAssert="true" 2.56 % 0.0564231616
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntBE" buffer="fast" noAssert="false" 1.54 % 0.3133448827
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntBE" buffer="fast" noAssert="true" 1.99 % 0.2231816540
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntBE" buffer="slow" noAssert="false" -1.60 % 0.3031854740
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntBE" buffer="slow" noAssert="true" -1.76 % 0.2391806630
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntLE" buffer="fast" noAssert="false" 1.23 % 0.4651066104
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntLE" buffer="fast" noAssert="true" 2.49 % 0.3296908645
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntLE" buffer="slow" noAssert="false" 3.36 % 0.1483373386
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="IntLE" buffer="slow" noAssert="true" 0.09 % 0.9615904952
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntBE" buffer="fast" noAssert="false" -1.27 % 0.4341473943
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntBE" buffer="fast" noAssert="true" 2.97 % 0.0836560690
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntBE" buffer="slow" noAssert="false" -1.14 % 0.5116753365
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntBE" buffer="slow" noAssert="true" 2.78 % 0.0815530806
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntLE" buffer="fast" noAssert="false" -2.89 % 0.2026249081
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntLE" buffer="fast" noAssert="true" -0.88 % 0.4983282113
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntLE" buffer="slow" noAssert="false" -0.26 % 0.8405160659
buffers/buffer-read-with-byteLength.js byteLength=6 millions=1 type="UIntLE" buffer="slow" noAssert="true" -0.34 % 0.7890584482 |
New benchmark results show an even smaller perf hit than before. 🎉 |
I imagine the |
Lone CI failure is a known flaky for which there is a fix pending (#17958). CI is good. |
Previous reviewers, a re-review is probably in order. Sorry about that. On the upside, I think this is probably good to go at this point. PTAL |
|
||
function main(conf) { | ||
const noAssert = conf.noAssert === 'true'; | ||
const len = +conf.millions * 1e6; |
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: the +
is obsolete.
The 'byteLength' argument should be required and of type 'number'. It should have a value between 1 and 6. This is a fix for issue: nodejs#10515
Split them into their own benhmark file and use different byteLength values.
4a5d043
to
710d1dc
Compare
The 'byteLength' argument should be required and of type 'number'. It should have a value between 1 and 6. PR-URL: nodejs#11146 Fixes: nodejs#10515 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Split them into their own benhmark file and use different byteLength values. PR-URL: nodejs#11146 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
PR-URL: nodejs#11146 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
The 'byteLength' argument should be required and of type 'number'. It should have a value between 1 and 6.
This is a fix for issue: #10515
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
buffer