Skip to content

buffer: coerce slice parameters consistenly - #9101

Closed
thefourtheye wants to merge 1 commit into
nodejs:masterfrom
thefourtheye:make-buffer-slice-offset-integer
Closed

thefourtheye wants to merge 1 commit into
nodejs:masterfrom
thefourtheye:make-buffer-slice-offset-integer

Conversation

@thefourtheye

Copy link
Copy Markdown
Contributor
Checklist
  • make -j8 test (UNIX), or vcbuild test nosign (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

buffer

Description of change

As shown in #9096, the offset and
end value of the slice call are just coerced to numbers and then
passed to FastBuffer, which internally truncates the mantissa part
if the number is actually a floating point number. This actually
affects the new length of the slice calculation. For example,

> const original = Buffer.from('abcd');
undefined
> original.slice(original.length / 3).toString()
'bc'

This happens because, starting value of the slice is 4 / 3, which is
1.33 (approximately). Now, the length of the slice is calculated as
the difference between the actual length of the buffer and the starting
offset. So, it becomes 2.67 (4 - 1.33). Now, a new FastBuffer is
constructed, with these values, actual buffer object, starting value
which is 1.33 and the the length 2.67. The underlying C++ code
truncates the numbers and they become 1 and 2. That is why the result
is just bc.

This patch makes sure that all the offsets are coerced to an integer.

Fixes: #9096


cc @nodejs/buffer

@nodejs-github-bot nodejs-github-bot added the buffer Issues and PRs related to the buffer subsystem. label Oct 14, 2016

@jasnell jasnell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM if CI is green

Comment thread lib/buffer.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like | 0 is used more frequently than ~~ throughout the lib/ code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cjihrig Sure. Changed it now.

@thefourtheye
thefourtheye force-pushed the make-buffer-slice-offset-integer branch from 17bcccd to 6929c09 Compare October 14, 2016 19:45
Comment thread lib/buffer.js Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extremely minor nit... offset |= 0; is even more concise ;-)

@targos targos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread test/parallel/test-buffer-slice.js Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: consistently

@thefourtheye
thefourtheye force-pushed the make-buffer-slice-offset-integer branch from 6929c09 to 94d3642 Compare October 14, 2016 20:23
@thefourtheye

Copy link
Copy Markdown
Contributor Author

@jasnell @targos Changed according to your suggestions..

Comment thread lib/buffer.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Number.isNaN(offset) portion of this conditional should be able to be removed with this change since | will cause invalid inputs to always be zero (whereas + will not).

ghost left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with @mscdex’ suggestion

Comment thread test/parallel/test-buffer-slice.js Outdated

ghost Oct 14, 2016

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: 17 can probably be original.length.

Comment thread test/parallel/test-buffer-slice.js Outdated

ghost Oct 15, 2016 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems a little confusing to me. The offsets are consistently converted to integers (in C++). The real problem is that the end value is calculated in javascript land before it gets passed to C++. If the length calculation (used to determine end) was done inside C++ this wouldn't be an issue.

It's also possible to use floating point numbers with slice() and still get a reasonable result. The only time it becomes an issue is when the end argument is not supplied and start is a floating point number.

ghost Oct 15, 2016

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean to say the slice implementation would be better done in C++?

Honestly, I would prefer to throw an error if the input is not an integer, which would avoid all this.

ghost Oct 15, 2016

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the text in the comments now and improved the commit log a little.

@thefourtheye
thefourtheye force-pushed the make-buffer-slice-offset-integer branch from 94d3642 to e648180 Compare October 15, 2016 02:57
As shown in nodejs#9096, the offset and
end value of the `slice` call are coerced to numbers and then passed to
`FastBuffer`, which internally truncates the mantissa part if the number
is actually a floating point number. This actually affects the new
length of the slice calculation. For example,

    > const original = Buffer.from('abcd');
    undefined
    > original.slice(original.length / 3).toString()
    'bc'

This happens because, starting value of the slice is 4 / 3, which is
1.33 (approximately). Now, the length of the slice is calculated as
the difference between the actual length of the buffer and the starting
offset. So, it becomes 2.67 (4 - 1.33). Now, a new `FastBuffer` is
constructed, with the following values as parameters,

 1. actual buffer object,
 2. starting value, which is 1.33 and
 3. the length 2.67.

The underlying C++ code truncates the numbers and they become 1 and 2.
That is why the result is just `bc`.

This patch makes sure that all the offsets are coerced to integers
before any calculations are done.

Fixes: nodejs#9096
@thefourtheye
thefourtheye force-pushed the make-buffer-slice-offset-integer branch from e648180 to 9fc562c Compare October 15, 2016 03:02
@mscdex

ghost commented Oct 15, 2016 •

Copy link
Copy Markdown
Contributor

LGTM if CI is ok with it: https://ci.nodejs.org/job/node-test-pull-request/4526/

@trevnorris

ghost commented Oct 17, 2016

Copy link
Copy Markdown
Contributor

For any gage how this should work, would be most similar to String#slice().

@thefourtheye

ghost commented Oct 17, 2016

Copy link
Copy Markdown
Contributor Author

@trevnorris String's slice also works like this only

> const str = "abcd"
undefined
> str.slice(str.length / 3)
'bcd'
> str.slice(str.length / 3, str.length)
'bcd'

@jasnell

ghost commented Oct 17, 2016

Copy link
Copy Markdown
Member

What would the semver-iness of this change be?

ghost left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lpinca

ghost commented Oct 17, 2016

Copy link
Copy Markdown
Member

I think it's a bugfix.

@cjihrig

ghost commented Oct 17, 2016

Copy link
Copy Markdown
Contributor

@jasnell I'd classify it as a bug fix, but if anything substantial relies on the old behavior a lot of angry people could show up :-P

@ChALkeR

ghost commented Oct 17, 2016

Copy link
Copy Markdown
Member

+1 for a bugfix.

@addaleax

ghost commented Oct 18, 2016

Copy link
Copy Markdown
Member

+1 to considering this a bugfix, too.

CITGM to account for the “but if anything substantial relies on the old behavior” part: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/425

@rvagg
rvagg force-pushed the master branch 2 times, most recently from c133999 to 83c7a88 Compare October 18, 2016 17:02
@jasnell jasnell self-assigned this Oct 18, 2016
@jasnell

ghost commented Oct 18, 2016

Copy link
Copy Markdown
Member

Landing as a semver-patch!

@jasnell

ghost commented Oct 18, 2016

Copy link
Copy Markdown
Member

Landed in ee14690

@jasnell jasnell closed this Oct 18, 2016
@thefourtheye
thefourtheye deleted the make-buffer-slice-offset-integer branch October 19, 2016 04:19
@trevnorris

ghost commented Oct 27, 2016 •

Copy link
Copy Markdown
Contributor

@jasnell Did my comment about introducing another bug by using |=, and the test that accompanied that, get overlooked?

@cjihrig

ghost commented Oct 27, 2016

Copy link
Copy Markdown
Contributor

@trevnorris it looks like there is a good chance of that. What comment are you referring to? This is the only old comment I'm seeing on this thread.

Comment thread lib/buffer.js
function adjustOffset(offset, length) {
offset = +offset;
if (offset === 0 || Number.isNaN(offset)) {
offset |= 0;

ghost Oct 17, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want the floor then please use Math.floor(). Using | allows wrap around. One reason why + was used before.

ghost Oct 17, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thefourtheye

String's slice also works like this only

This fails edge cases. For example:

'abcdefg'.slice(-(-1 >>> 0) - 1) === 'abcdefg';

But as this currently stands offset will be coerced to 2 with the above example.

Comment thread lib/buffer.js
return 0;
}
if (offset < 0) {
} else if (offset < 0) {

ghost Oct 17, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's where the problem lies. There's no range check before the int32 coercion. A large number could fail this.

assert.strictEqual(
buf.slice(buf.length / 3, buf.length).toString(),
'bcd'
);

ghost Oct 17, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're making the change to operate like String#slice() then edge case tests should be included. e.g.:

const b = Buffer.from('abcdefg');
assert.strictEqual(b.slice(-(-1 >>> 0) + 1).toString(), b.toString());

@trevnorris

ghost commented Oct 28, 2016

Copy link
Copy Markdown
Contributor

@cjihrig oh shiet. this was my first time using the review thing before, and didn't realize I needed to properly submit it before the notes could be seen. just did, and that'll show the regression this introduced.

@cjihrig

ghost commented Oct 28, 2016

Copy link
Copy Markdown
Contributor

The old "forgot to submit the review" problem. Got me on multiple PRs the first day I used it :-)

@cjihrig cjihrig mentioned this pull request Oct 28, 2016
3 tasks done
@cjihrig

ghost commented Oct 28, 2016

Copy link
Copy Markdown
Contributor

@trevnorris reverted the lib/buffer.js changes and used Math.floor() in #9341.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

buffer Issues and PRs related to the buffer subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.