-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
fs: make ReadStream throw error on NaN #19732
Conversation
@lpinca should I also add a regression test for it? |
@ryzokuken yes please. Any code change should ideally come with a test. |
lib/fs.js
Outdated
throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start); | ||
} | ||
if (this.end === undefined) { | ||
this.end = Infinity; | ||
} else if (typeof this.end !== 'number') { | ||
} else if (typeof this.end !== 'number' || Number.isNaN(this.end)) { |
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.
Can you add this for the other typeof this.end !== 'number'
check below as well? The one that is supposed to be merged with this one? :)
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.
@addaleax Sure! On 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.
@addaleax done.
This test makes fs.ReadStream throw an error when either start or end is NaN. Fixes: nodejs#19715
@BridgeAR I'd love to add the test, but because this would be my first bugfix and thus the first test I'd be writing from scratch, I'd need some help with it. For starters, does it need to be a |
https://github.com/nodejs/node/blob/master/doc/guides/writing-tests.md :)
|
Add regression tests for nodejs#19732, making sure appropraite TypeErrors are thrown for NaN values. Refs: nodejs#19732
@addaleax there was an existing test, so I added assertions to that instead. Let me know if that doesn't work, and I'd be happy to make a separate test file. |
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, thanks!
In particular, I think this is semver-patch because the issue shows that this would never actually work to begin with, and break creation of other ReadStream
instances.
@addaleax Thanks. That's a relief. |
@@ -18,10 +22,16 @@ const createReadStreamErr = (path, opt) => { | |||
{ | |||
code: 'ERR_INVALID_ARG_TYPE', | |||
type: TypeError | |||
}); |
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: unnecessary change.
lib/fs.js
Outdated
@@ -2008,12 +2008,12 @@ function ReadStream(path, options) { | |||
this.closed = false; | |||
|
|||
if (this.start !== undefined) { | |||
if (typeof this.start !== 'number') { | |||
if (typeof this.start !== 'number' || Number.isNaN(this.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.
As suggested by @anliting we can probably use just Number.isInteger()
.
}; | ||
|
||
createReadStreamErr(example, 123); | ||
createReadStreamErr(example, 0); | ||
createReadStreamErr(example, true); | ||
createReadStreamErr(example, false); | ||
|
||
// Should also throw on NaN (for https://github.com/nodejs/node/pull/19732) | ||
createReadStreamErr(example, { start: NaN }); |
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 can leave only createReadStreamErr(example, { start: NaN })
, because if start
is not passed, end
is not defined (https://github.com/ryzokuken/node/blob/473dd3d03fe4d08445518f9ab8e2b78892c22e27/lib/fs.js#L2010).
And at least we need to delete createReadStreamErr(example, { end: NaN })
to pass node-test-commit-linux
.
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.
@anliting If start
is not passed but end
is, then the value is still set and type-checked: https://github.com/ryzokuken/node/blob/473dd3d03fe4d08445518f9ab8e2b78892c22e27/lib/fs.js#L2031-L2032
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 we are going to test the end
part, we need have start
to pass the range test first: createReadStreamErr(example, { start: 0, end: NaN })
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.
@anliting That would take a different branch, 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.
Oh, I see that line (which makes end
is still set and type-checked) now, my fault.
"If we are going to test the end part ..." means if we are going to test if this line work:
https://github.com/ryzokuken/node/blob/473dd3d03fe4d08445518f9ab8e2b78892c22e27/lib/fs.js#L2016
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.
We might need to change
if (typeof this.end !== 'number' || Number.isNaN(this.end))
this.end = Infinity;
to
if (typeof this.end !== 'number')
this.end = Infinity;
else if (Number.isNaN(this.end))
throw new ERR_INVALID_ARG_TYPE('end', 'integer', this.end);
to pass node-test-commit-linux
.
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.
@anliting Ah, right! Yes, we should probably do that.
lib/fs.js
Outdated
@@ -2008,12 +2008,12 @@ function ReadStream(path, options) { | |||
this.closed = false; | |||
|
|||
if (this.start !== undefined) { | |||
if (typeof this.start !== 'number') { | |||
if (typeof this.start !== 'number' || Number.isNaN(this.start)) { | |||
throw new ERR_INVALID_ARG_TYPE('start', 'number', this.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.
Would throw new ERR_INVALID_ARG_TYPE('start', 'integer', this.start);
be more appropriate? Since typeof NaN=='number'
.
looks better now? |
lib/fs.js
Outdated
@@ -2008,13 +2008,13 @@ function ReadStream(path, options) { | |||
this.closed = false; | |||
|
|||
if (this.start !== undefined) { | |||
if (typeof this.start !== 'number') { | |||
throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start); | |||
if (typeof this.start !== 'number' || !Number.isInteger(this.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.
Nit: the condition can be just !Number.isInteger()
as that returns true
when passing something that is not a 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.
@ryzokuken |
Oops, I think I found another problem. It seems to throw when we pass Change if (this.start !== undefined) {
if (!Number.isInteger(this.start)) {
throw new ERR_INVALID_ARG_TYPE('start', 'integer', this.start);
}
if (this.end === undefined) {
this.end = Infinity;
} else if (!Number.isInteger(this.end)) {
throw new ERR_INVALID_ARG_TYPE('end', 'integer', this.end);
}
if (this.start > this.end) {
const errVal = `{start: ${this.start}, end: ${this.end}}`;
throw new ERR_OUT_OF_RANGE('start', '<= "end"', errVal);
}
this.pos = this.start;
}
// Backwards compatibility: Make sure `end` is a number regardless of `start`.
// TODO(addaleax): Make the above typecheck not depend on `start` instead.
// (That is a semver-major change).
if (typeof this.end !== 'number')
this.end = Infinity;
else if (!Number.isInteger(this.end))
throw new ERR_INVALID_ARG_TYPE('end', 'integer', this.end); to if (this.start !== undefined) {
if (!Number.isInteger(this.start)) {
throw new ERR_INVALID_ARG_TYPE('start', 'integer', this.start);
}
if (this.end === undefined) {
this.end = Infinity;
} else if (!Number.isInteger(this.end)) {
throw new ERR_INVALID_ARG_TYPE('end', 'integer', this.end);
}
if (this.start > this.end) {
const errVal = `{start: ${this.start}, end: ${this.end}}`;
throw new ERR_OUT_OF_RANGE('start', '<= "end"', errVal);
}
this.pos = this.start;
}else{
// Backwards compatibility: Make sure `end` is a number regardless of `start`.
// TODO(addaleax): Make the above typecheck not depend on `start` instead.
// (That is a semver-major change).
if (typeof this.end !== 'number')
this.end = Infinity;
else if (!Number.isInteger(this.end))
throw new ERR_INVALID_ARG_TYPE('end', 'integer', this.end);
} . |
@anliting better now? |
Better ^^ |
Yes, that understanding is right. What I like more would be "until end bytes and reads end-start bytes". If you define the behavior as "read from |
I think the both behaviors make a sense of its own; so I have no idea to choose. |
Hmm, so I'd consult either @addaleax or someone else from the TSC regarding that particular behavior. Regarding fractions, Anna didn't want to |
"Exception" means those situations which we cannot handle; "exception handling" means we throw them back and hope if someone else handles. If we don't throw on fractions, we will have to handle them. For example: options.start=Math.ceil(options.start)
options.end=Math.floor(options.end) or options.start=Math.floor(options.start)
options.end=Math.floor(options.end) If we current have no idea how to handle it, we can throw on them first, and wait for the idea comes. However, that makes another semver-major. |
Again, I'd defer to @addaleax regarding that. |
I think what’s currently happening is that both values are being rounded down? That sounds okay, especially because we have this very weird behaviour where I mean, honestly, I don’t think it’d be the end of the world if we threw on non-integer numbers, and my opinion isn’t always gold either. I’m just thinking, JS is a language where the math just happens to be defined in terms of floats, so it’s probably better to be on the safe side and implement something that handles that somewhat gracefully. |
As long as we are "handling" fractions internally in a predictable manner, I think @anliting's very helpful suggestion is satisfied as well. I mean, we only need to My suggestion: let's make this behavior of handling fractional arguments well-documented though, so that anyone doing complex math that might return a fraction (our target audience for this, basically), are well-informed and can reason about this behavior rationally. @addaleax so, should we just round down on both arguments? Or maybe round down on |
When your math gives you Honestly, I don’t think many people would be affected by this either way, so it’s probably more important to pick some consistent behaviour than determining which exactly it should be? |
Agreed. That makes sense.
You're right, but we'll still need to pick one anyway. Let me know whichever seems more reasonable to you. |
After reading your conversation, I would vote for " |
Updated table: So, for the course of this pull request:
@addaleax @anliting I think we all agree on this now, right? |
I prefer |
Let me guess, because it's not the wrong type (still a That does make a lot of sense. |
So, finally:
@addaleax @anliting this looks good? I've been thinking of making a new PR so that others don't get bogged down by the conversation in here. |
@ryzokuken Yes, I think that seems fine, assuming that “Invalid Arguments” means |
@addaleax they do. Great. Making another PR for this today. |
LGTM. ^^ |
Improve handling of erratic arguments in fs.ReadStream Refs: nodejs#19732
Closing in favor of the other two PRs. |
Improve handling of erratic arguments in fs.ReadStream Refs: #19732 PR-URL: #19898 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ron Korving <ron@ronkorving.nl> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This test makes fs.ReadStream throw an error when either start or
end is NaN.
Fixes: #19715
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes/cc @lpinca