Skip to content

Commit da886d9

Browse files
committed
zlib: improve zlib errors
- 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 <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent 15e41a9 commit da886d9

File tree

4 files changed

+317
-130
lines changed

4 files changed

+317
-130
lines changed

lib/zlib.js

+78-80
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,52 @@ function flushCallback(level, strategy, callback) {
156156
}
157157
}
158158

159+
// 1. Returns false for undefined and NaN
160+
// 2. Returns true for finite numbers
161+
// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers
162+
// 4. Throws ERR_OUT_OF_RANGE for infinite numbers
163+
function checkFiniteNumber(number, name) {
164+
// Common case
165+
if (number === undefined || Number.isNaN(number)) {
166+
return false;
167+
}
168+
169+
if (Number.isFinite(number)) {
170+
return true; // is a valid number
171+
}
172+
173+
// Other non-numbers
174+
if (typeof number !== 'number') {
175+
const err = new errors.TypeError('ERR_INVALID_ARG_TYPE', name,
176+
'number', number);
177+
Error.captureStackTrace(err, checkFiniteNumber);
178+
throw err;
179+
}
180+
181+
// Infinite numbers
182+
const err = new errors.RangeError('ERR_OUT_OF_RANGE', name,
183+
'a finite number', number);
184+
Error.captureStackTrace(err, checkFiniteNumber);
185+
throw err;
186+
}
187+
188+
// 1. Returns def for undefined and NaN
189+
// 2. Returns number for finite numbers >= lower and <= upper
190+
// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers
191+
// 4. Throws ERR_OUT_OF_RANGE for infinite numbers or numbers > upper or < lower
192+
function checkRangesOrGetDefault(number, name, lower, upper, def) {
193+
if (!checkFiniteNumber(number, name, lower, upper)) {
194+
return def;
195+
}
196+
if (number < lower || number > upper) {
197+
const err = new errors.RangeError('ERR_OUT_OF_RANGE', name,
198+
`>= ${lower} and <= ${upper}`, number);
199+
Error.captureStackTrace(err, checkRangesOrGetDefault);
200+
throw err;
201+
}
202+
return number;
203+
}
204+
159205
// the Zlib class they all inherit from
160206
// This thing manages the queue of requests, and returns
161207
// true or false if there is anything in the queue when
@@ -170,95 +216,53 @@ function Zlib(opts, mode) {
170216
var strategy = Z_DEFAULT_STRATEGY;
171217
var dictionary;
172218

173-
if (typeof mode !== 'number')
174-
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', 'number');
175-
if (mode < DEFLATE || mode > UNZIP)
176-
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'mode');
219+
// The Zlib class is not exported to user land, the mode should only be
220+
// passed in by us.
221+
assert(typeof mode === 'number');
222+
assert(mode >= DEFLATE && mode <= UNZIP);
177223

178224
if (opts) {
179225
chunkSize = opts.chunkSize;
180-
if (chunkSize !== undefined && !Number.isNaN(chunkSize)) {
181-
if (chunkSize < Z_MIN_CHUNK || !Number.isFinite(chunkSize))
182-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
183-
'chunkSize',
184-
chunkSize);
185-
} else {
226+
if (!checkFiniteNumber(chunkSize, 'options.chunkSize')) {
186227
chunkSize = Z_DEFAULT_CHUNK;
228+
} else if (chunkSize < Z_MIN_CHUNK) {
229+
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'options.chunkSize',
230+
`>= ${Z_MIN_CHUNK}`, chunkSize);
187231
}
188232

189-
flush = opts.flush;
190-
if (flush !== undefined && !Number.isNaN(flush)) {
191-
if (flush < Z_NO_FLUSH || flush > Z_BLOCK || !Number.isFinite(flush))
192-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE', 'flush', flush);
193-
} else {
194-
flush = Z_NO_FLUSH;
195-
}
233+
flush = checkRangesOrGetDefault(
234+
opts.flush, 'options.flush',
235+
Z_NO_FLUSH, Z_BLOCK, Z_NO_FLUSH);
196236

197-
finishFlush = opts.finishFlush;
198-
if (finishFlush !== undefined && !Number.isNaN(finishFlush)) {
199-
if (finishFlush < Z_NO_FLUSH || finishFlush > Z_BLOCK ||
200-
!Number.isFinite(finishFlush)) {
201-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
202-
'finishFlush',
203-
finishFlush);
204-
}
205-
} else {
206-
finishFlush = Z_FINISH;
207-
}
237+
finishFlush = checkRangesOrGetDefault(
238+
opts.finishFlush, 'options.finishFlush',
239+
Z_NO_FLUSH, Z_BLOCK, Z_FINISH);
208240

209-
windowBits = opts.windowBits;
210-
if (windowBits !== undefined && !Number.isNaN(windowBits)) {
211-
if (windowBits < Z_MIN_WINDOWBITS || windowBits > Z_MAX_WINDOWBITS ||
212-
!Number.isFinite(windowBits)) {
213-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
214-
'windowBits',
215-
windowBits);
216-
}
217-
} else {
218-
windowBits = Z_DEFAULT_WINDOWBITS;
219-
}
241+
windowBits = checkRangesOrGetDefault(
242+
opts.windowBits, 'options.windowBits',
243+
Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_DEFAULT_WINDOWBITS);
220244

221-
level = opts.level;
222-
if (level !== undefined && !Number.isNaN(level)) {
223-
if (level < Z_MIN_LEVEL || level > Z_MAX_LEVEL ||
224-
!Number.isFinite(level)) {
225-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
226-
'level', level);
227-
}
228-
} else {
229-
level = Z_DEFAULT_COMPRESSION;
230-
}
245+
level = checkRangesOrGetDefault(
246+
opts.level, 'options.level',
247+
Z_MIN_LEVEL, Z_MAX_LEVEL, Z_DEFAULT_COMPRESSION);
231248

232-
memLevel = opts.memLevel;
233-
if (memLevel !== undefined && !Number.isNaN(memLevel)) {
234-
if (memLevel < Z_MIN_MEMLEVEL || memLevel > Z_MAX_MEMLEVEL ||
235-
!Number.isFinite(memLevel)) {
236-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
237-
'memLevel', memLevel);
238-
}
239-
} else {
240-
memLevel = Z_DEFAULT_MEMLEVEL;
241-
}
249+
memLevel = checkRangesOrGetDefault(
250+
opts.memLevel, 'options.memLevel',
251+
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_MEMLEVEL);
242252

243-
strategy = opts.strategy;
244-
if (strategy !== undefined && !Number.isNaN(strategy)) {
245-
if (strategy < Z_DEFAULT_STRATEGY || strategy > Z_FIXED ||
246-
!Number.isFinite(strategy)) {
247-
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
248-
'strategy', strategy);
249-
}
250-
} else {
251-
strategy = Z_DEFAULT_STRATEGY;
252-
}
253+
strategy = checkRangesOrGetDefault(
254+
opts.strategy, 'options.strategy',
255+
Z_DEFAULT_STRATEGY, Z_FIXED, Z_DEFAULT_STRATEGY);
253256

254257
dictionary = opts.dictionary;
255258
if (dictionary !== undefined && !isArrayBufferView(dictionary)) {
256259
if (isAnyArrayBuffer(dictionary)) {
257260
dictionary = Buffer.from(dictionary);
258261
} else {
259-
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
260-
'dictionary',
261-
dictionary);
262+
throw new errors.TypeError(
263+
'ERR_INVALID_ARG_TYPE', 'options.dictionary',
264+
['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
265+
dictionary);
262266
}
263267
}
264268

@@ -310,14 +314,8 @@ Object.defineProperty(Zlib.prototype, '_closed', {
310314
});
311315

312316
Zlib.prototype.params = function params(level, strategy, callback) {
313-
if (level < Z_MIN_LEVEL || level > Z_MAX_LEVEL)
314-
throw new errors.RangeError('ERR_INVALID_ARG_VALUE', 'level', level);
315-
316-
if (strategy !== undefined &&
317-
(strategy < Z_DEFAULT_STRATEGY || strategy > Z_FIXED ||
318-
!Number.isFinite(strategy))) {
319-
throw new errors.TypeError('ERR_INVALID_ARG_VALUE', 'strategy', strategy);
320-
}
317+
checkRangesOrGetDefault(level, 'level', Z_MIN_LEVEL, Z_MAX_LEVEL);
318+
checkRangesOrGetDefault(strategy, 'strategy', Z_DEFAULT_STRATEGY, Z_FIXED);
321319

322320
if (this._level !== level || this._strategy !== strategy) {
323321
this.flush(Z_SYNC_FLUSH,

0 commit comments

Comments
 (0)