-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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
zlib: improve zlib errors #18675
zlib: improve zlib errors #18675
Conversation
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.
Nice work!
@@ -12,83 +12,201 @@ assert.ok(new zlib.Deflate() instanceof zlib.Deflate); | |||
assert.ok(zlib.DeflateRaw() instanceof zlib.DeflateRaw); | |||
assert.ok(new zlib.DeflateRaw() instanceof zlib.DeflateRaw); | |||
|
|||
// Throws if `opts.chunkSize` is invalid | |||
// Throws if `optionss.chunkSize` is invalid |
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.
typo: options
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. Just two nits that are not blocking.
lib/zlib.js
Outdated
!Number.isFinite(strategy))) { | ||
throw new errors.TypeError('ERR_INVALID_ARG_VALUE', 'strategy', strategy); | ||
if (validateFiniteNumber(level, 'level')) { | ||
if (level < Z_MIN_LEVEL || level > Z_MAX_LEVEL) { |
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 statements could be combined. So it would only say:
if (validateFiniteNumber(level, 'level') && (level < ... || level > ...)) {
// ...
}
The same for the one below.
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.
@BridgeAR That does not work because a valid value would be overwritten to the default in the else block.
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.
@BridgeAR Ah..right, this is not one of the blocks that sets default values. Nevermind.
lib/zlib.js
Outdated
if (chunkSize < Z_MIN_CHUNK) { | ||
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'options.chunkSize', | ||
`>= ${Z_MIN_CHUNK}`, chunkSize); | ||
} | ||
} else { | ||
chunkSize = Z_DEFAULT_CHUNK; | ||
} |
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.
Non blocking: A very personal point about readability: I feel like it would be better to write it as:
if (!validateFiniteNumber(...) {
chunkSize = Z_...
} else if (chunkSize < Z_MIN_CHUNK) {
throw new ...
}
1e05358
to
7cbfa65
Compare
@BridgeAR Updated, PTAL. |
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, just needs a rebase and I left two suggestions.
lib/zlib.js
Outdated
throw err; | ||
} | ||
|
||
if (!Number.isFinite(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.
Using Number.isFinite(number)
will implicitly also test for NaN
and for the type number
. Therefore those two checks could be moved inside of this block to make the average case faster.
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 I am following...NaN returns false here (no errors) while non-numbers throws a different error, how could we move those checks into this block?
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, sorry, you can only move the typeof number !== 'number'
in here. I overlooked that NaN
should be accepted and gets a default value (do we really want that? I suggest to throw an error in that case as well).
The reason why the typecheck can be moved in here is:
Number.isFinite(nonNumber) === false
. So the average case does not have to test for that. Only in case the isFinite
test fails, it should be checked if it is the wrong type.
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 also doubt if it's necessary to use default values for NaNs, but several tests fail if I change that so I would like to investigate this behavior later.
I get what you were suggesting now. That makes sense to me, I'll change that later, thanks
lib/zlib.js
Outdated
} else if (flush < Z_NO_FLUSH || flush > Z_BLOCK) { | ||
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'options.flush', | ||
`>= ${Z_NO_FLUSH} and <= ${Z_BLOCK}`, | ||
flush); |
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.
Suggestion: all cases that check for both bounds (that are all but one if I checked correct) could also be moved in a separate functions.
That way it would look like e.g.:
if (!validateFiniteNumber(flush, 'options.flush')) {
arg = foo;
} else {
validateRange(flush, Z_NO_FLUSH, Z_BLOCK, 'options.flush')
}
Or you actually combine the check and move the range check into the validateFiniteNumber
:
if (!validateNumberRange(flush, Z_NO_FLUSH, Z_BLOCK, 'options.flush'))
flush = Z_NO_FLUSH;
- Use assert to check mode in the Zlib constructor since it should only be passed by us. - Introduce checkRangesOrGetDefault() and checkFiniteNumber() to simplify type and range checking for numeric arguments - Instead of `ERR_INVALID_OPT_VALUE`, throw `ERR_OUT_OF_RANGE` and `ERR_INVALID_ARG_TYPE` with descriptions of the expected ranges or types to make the errors more user-friendly. - Add message tests for the changed errors
7cbfa65
to
22c7c8d
Compare
Rebased and updated. Introduced CI: https://ci.nodejs.org/job/node-test-pull-request/13250/ @BridgeAR PTAL, thanks! |
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.
Still LGTM but I left a few suggestions. They are not blocking though.
lib/zlib.js
Outdated
// 4. Throws ERR_OUT_OF_RANGE for infinite numbers or numbers > upper or < lower | ||
function checkRangesOrGetDefault(number, name, lower, upper, def) { | ||
if (checkFiniteNumber(number, name, lower, upper)) { | ||
checkRanges(number, name, lower, upper); |
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.
checkRanges
is only used here, so I guess it would be best to inline that.
} | ||
flush = checkRangesOrGetDefault( | ||
opts.flush, 'options.flush', | ||
Z_NO_FLUSH, Z_BLOCK, Z_NO_FLUSH); |
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.
Hm, I just had a closer look and the values get initialized with their default value at the top of the function. So we can actually skip setting the variable again.
It is only necessary to set the variable in case a finite number got passed in. So I personally think it would still be best to have:
if (checkRange(opts.flush, 'options.flush', Z_NO_FLUSH, Z_BLOCK))
flush = opts.flush;
But setting the value again is of course also not much overhead.
lib/zlib.js
Outdated
// undefined or NaN | ||
if (number === undefined || Number.isNaN(number)) { | ||
return false; | ||
} |
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 would check for undefined
first because it is the cheapest check and it is the most likely one (I guess the common use case is to rely on defaults).
So:
if (number === undefined)
return false;
if (Number.isFinite(number)
return true;
if (Number.isNaN(number)
return false;
...
lib/zlib.js
Outdated
// Infinite numbers | ||
const err = new errors.RangeError('ERR_OUT_OF_RANGE', name, | ||
'a finite number', number); | ||
Error.captureStackTrace(err, checkFiniteNumber); |
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.
Right now this does not hide the checkRangesOrGetDefault
frame anymore. This is problematic right now because the function is called from different functions and we can not just put in checkRangesOrGetDefault
. I would probably just inline the checkFiniteNumber
into checkRangesOrGetDefault
and just have a bit of code copied for the single entry that does not fit into the pattern.
And it would be possible to combine this by doing:
let err;
if (typeif number !== 'number') {
err = ...
} else {
err = ...
}
Error.captureStackTrace(err, fn);
throw err;
Updated again, I don't feel too strongly about hiding all the frames or resetting the value so I left it there. @BridgeAR PTAL, thanks! |
Going to land this tomorrow...@BridgeAR |
CI before landing: https://ci.nodejs.org/job/node-test-pull-request/13357/ |
Landed in da886d9, thanks! |
- Use assert to check mode in the Zlib constructor since it should only be passed by us. - Introduce checkRangesOrGetDefault() and checkFiniteNumber() to simplify type and range checking for numeric arguments - Instead of `ERR_INVALID_OPT_VALUE`, throw `ERR_OUT_OF_RANGE` and `ERR_INVALID_ARG_TYPE` with descriptions of the expected ranges or types to make the errors more user-friendly. - Add message tests for the changed errors PR-URL: #18675 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
- Use assert to check mode in the Zlib constructor since it should only be passed by us. - Introduce checkRangesOrGetDefault() and checkFiniteNumber() to simplify type and range checking for numeric arguments - Instead of `ERR_INVALID_OPT_VALUE`, throw `ERR_OUT_OF_RANGE` and `ERR_INVALID_ARG_TYPE` with descriptions of the expected ranges or types to make the errors more user-friendly. - Add message tests for the changed errors PR-URL: nodejs#18675 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
zlib: improve zlib errors
only be passed by us.
to simplify type and range checking for numeric arguments
ERR_INVALID_OPT_VALUE
, throwERR_OUT_OF_RANGE
andERR_INVALID_ARG_TYPE
with descriptions of the expected rangesor types to make the errors more user-friendly.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
zlib