-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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: fix range checking for slowToString #2919
buffer: fix range checking for slowToString #2919
Conversation
I would change the commit message to start with
|
end = this.length; | ||
} | ||
end = +end; | ||
end = end < 0 ? this.length : end >>> 0; |
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.
Do we want NaN
values to coerce to 0
? (realize that is what was done before, but while were 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.
Sure, I ll make that to this.length
:-)
@trevnorris Updated for |
Great. If CI is happy then LGTM. This is a semver-major, so will go in the v5 release. |
I am not able to test this now. |
@ChALkeR They'll wrap. Same as they always have. |
@trevnorris Ah. So that is out of scope for this commit. This commit now converts negative and LGTM, though such behavior is a bit strange. |
Judging from the surrounding code, it looks like negative values of |
@ChALkeR Good catch. You are correct. This PR is a great fix for existing undefined behavior, and making |
@trevnorris @ChALkeR Made sure that the negative value for |
I'd still recommend putting the reference issue as I posted above, but if CI is good then LGTM. |
CI Run before landing: https://ci.nodejs.org/job/node-test-pull-request/364/ @trevnorris I'll fix that while landing. @ChALkeR LGTY? |
LGTM code-wise. Only a minor notice that I would re-order the lines and group together the checks based on the variable that they are related to. Something like this: start = +start;
if (!Number.isInteger(start)) {
start = start >>> 0;
}
if (start < 0) start = 0;
if (end === undefined || end === Infinity || end !== end) {
end = this.length;
}
end = +end < 0 ? 0 : end >>> 0;
if (end > this.length) end = this.length;
if (end <= start) return '';
if (!encoding) encoding = 'utf8';
var loweredCase = false; But that is not required and not everyone might agree with this change, I guess. |
Eh? this is semver-major? If yes, should the Here is what I mean: start = +start;
if (start >= this.length) return '';
start = (start > 0) ? start >>> 0 : 0;
if (end === undefined || end === Infinity || end !== end) {
end = this.length;
} else {
end = +end;
if (end <= start) return '';
end = (end <= this.length) ? end >>> 0 : this.length;
}
if (!encoding) encoding = 'utf8';
var loweredCase = false; |
Was going to save some of this for later, but while we're here. I'd say while your here we should make sure parsing is consistent. For example This would mean the values will go through Open to ideas on how we handle out of range values. I suppose we'll floor any non-integers that are in the uint32 range with the |
Yeah, the internals should have unsigned 32-bit numbers, and the js side should validate the indicies for them to be in the allowable range (from Atm, this PR converts the |
okay. so how about this: if (start >= this.length || end <= 0)
return '';
if (start < 0 || start >>> 0 != parseInt(+start))
start = 0;
else
start >>>= 0;
if (end > this.length || end >>> 0 != parseInt(+end))
end = this.length;
else
end >>>= 0;
if (end <= start)
return ''; That should handle all the cases discussed on IRC. UPDATE: Based on IRC discussion. |
@trevnorris That code LGTM. |
@ChALkeR awesome. Great team work everyone putting together these coercion checks. |
If `start` is not a valid number in the range, then the default value zero will be used. Same way, if `end` is not a valid number in the accepted range, then, by default, the length of the buffer is assumed. Ref: nodejs#2668 PR-URL: nodejs#2919
b2b9e5d
to
ac60740
Compare
assert.equal(rangeBuffer.toString('ascii', 0, NaN), 'abc'); | ||
assert.equal(rangeBuffer.toString('ascii', 0, undefined), 'abc'); | ||
assert.equal(rangeBuffer.toString('ascii', 0), 'abc'); | ||
assert.equal(rangeBuffer.toString('ascii', 0, null), ''); |
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 am not sure if these four tests returning empty strings is okay though.
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 end
be coerced to 0
instead of this.length
?
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.
If the end value is not valid, simply ignoring it and assuming the actual length would be logical, wouldn't it?
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.
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.
if (start >= this.length || end <= 0)
Let's try if (start >= this.length || end < 0)
.
@ChALkeR @trevnorris I updated the PR as per our discussion. PTAL. |
* Note that, the comparison is done with abstract equality operator (!=) to | ||
* allow stringified numbers ('1') and Number objects (new Number(5)). | ||
*/ | ||
if (start < 0 || start >>> 0 != parseInt(+start)) |
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.
Not sure if you want to, but maybe also add to comments why both parseInt()
and the +
are necessary. For example:
parseInt('0o1101') === 0
parseInt(+'0o1101') === 577
but also that parseInt()
is necessary to do int coercion at 2^53 to make sure there's no wrap around.
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.
Actually, should be able to use !==
here b/c start
is being coerced to a number before comparison.
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.
but may also want to note that this is technically an int32 check, not uint32, b/c the value will be coerced to a uint32 below with start >>>= 0
. so we're saving ourselves an operation.
two comments, and amazing job on the tests. |
@@ -322,13 +322,31 @@ Object.defineProperty(Buffer.prototype, 'offset', { | |||
function slowToString(encoding, start, end) { | |||
var loweredCase = false; | |||
|
|||
start = start >>> 0; | |||
end = end === undefined || end === Infinity ? this.length : end >>> 0; | |||
if (start >= this.length || end <= 0) |
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.
Maybe end < 0
would better here instead of end <= 0
? In order to treat false
as this.length
, for example.
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 all falsey values will evaluate to default value? I'd be cool with that, except we also assert that true
is coerced to 1
. seems like a small inconsistency to evaluate the two types differently.
eh. guess it's instead falsey vs. truthy. okay, i'm cool with it.
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.
Going to revise my opinion. I think we should imitate similar behavior as v8 in other scenarios. Here's one using Typed Arrays:
let ab = new ArrayBuffer(8);
new Uint8Array(ab, 0, false).length === 0;
new Uint8Array(ab, 0, null).length === 0;
new Uint8Array(ab, 0, NaN).length === 0;
new Uint8Array(ab, 0, undefined).length === 8;
So end only receives the default length iff end === undefined
.
There are cases where Typed Arrays throw where we can't, but we'll leave those aside.
@thefourtheye want to move forward with this? |
@thefourtheye I can take this over and move it forward if you're ok with that. |
@matthewloring please go ahead. Thanks for taking it up :) |
If `start` is not a valid number in the range, then the default value zero will be used. Same way, if `end` is not a valid number in the accepted range, then, by default, the length of the buffer is assumed. Fixes: nodejs#2668 Ref: nodejs#2919 PR-URL: nodejs#4019 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Verify that start and end are coerced properly. Ref: nodejs#2919 PR-URL: nodejs#4019 Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Following the discussion #2668,
this patch fixes the first problem discussed there. If
start
is nota valid number in the range, then the default value zero will be used.
Same way, if
end
is not a valid number in the accepted range, then,by default, the length of the buffer is assumed.
cc @trevnorris @ChALkeR