From 14e3675b13f1a07ec6c500d756914a42e6ea46d2 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Sat, 11 Nov 2023 17:25:08 +0100 Subject: [PATCH] errors: improve hideStackFrames PR-URL: https://github.com/nodejs/node/pull/49990 Reviewed-By: Matteo Collina Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell --- benchmark/error/hidestackframes-noerr.js | 62 +++ benchmark/error/hidestackframes-throw.js | 87 ++++ benchmark/error/hidestackframes.js | 45 -- lib/.eslintrc.yaml | 2 +- lib/_http_client.js | 10 +- lib/_http_outgoing.js | 6 +- lib/_http_server.js | 4 +- lib/_tls_wrap.js | 10 +- lib/buffer.js | 20 +- lib/dgram.js | 47 +- lib/dns.js | 26 +- lib/fs.js | 4 +- lib/internal/child_process.js | 14 +- lib/internal/crypto/hkdf.js | 10 +- lib/internal/crypto/util.js | 4 +- lib/internal/dns/callback_resolver.js | 6 +- lib/internal/dns/promises.js | 16 +- lib/internal/errors.js | 477 +++++++++--------- lib/internal/fs/utils.js | 103 ++-- lib/internal/fs/watchers.js | 12 +- lib/internal/http2/compat.js | 6 +- lib/internal/http2/core.js | 52 +- lib/internal/http2/util.js | 20 +- lib/internal/net.js | 2 +- lib/internal/process/per_thread.js | 4 +- lib/internal/process/signal.js | 4 +- lib/internal/stream_base_commons.js | 10 +- lib/internal/util.js | 16 +- lib/internal/validators.js | 54 +- lib/internal/webstreams/adapters.js | 6 +- lib/net.js | 62 +-- lib/os.js | 2 +- lib/tty.js | 4 +- lib/util.js | 42 +- lib/zlib.js | 10 +- .../errors/error_aggregateTwoErrors.snapshot | 3 +- test/parallel/test-dns-memory-error.js | 4 +- .../parallel/test-error-aggregateTwoErrors.js | 12 + .../parallel/test-errors-hide-stack-frames.js | 242 +++++++++ test/parallel/test-util-callbackify.js | 18 + test/parallel/test-uv-unmapped-exception.js | 6 +- 41 files changed, 978 insertions(+), 566 deletions(-) create mode 100644 benchmark/error/hidestackframes-noerr.js create mode 100644 benchmark/error/hidestackframes-throw.js delete mode 100644 benchmark/error/hidestackframes.js create mode 100644 test/parallel/test-errors-hide-stack-frames.js diff --git a/benchmark/error/hidestackframes-noerr.js b/benchmark/error/hidestackframes-noerr.js new file mode 100644 index 00000000000000..9e662f6a620f13 --- /dev/null +++ b/benchmark/error/hidestackframes-noerr.js @@ -0,0 +1,62 @@ +'use strict'; + +const common = require('../common.js'); +const assert = require('assert'); + +const bench = common.createBenchmark(main, { + type: [ + 'hide-stackframes', + 'direct-call', + ], + n: [1e7], +}, { + flags: ['--expose-internals'], +}); + +function main({ n, type }) { + const { + hideStackFrames, + codes: { + ERR_INVALID_ARG_TYPE, + }, + } = require('internal/errors'); + + const testfn = (value) => { + if (typeof value !== 'number') { + throw new ERR_INVALID_ARG_TYPE('Benchmark', 'number', value); + } + }; + + const hideStackFramesTestfn = hideStackFrames((value) => { + if (typeof value !== 'number') { + throw new ERR_INVALID_ARG_TYPE.HideStackFrameError('Benchmark', 'number', value); + } + }); + + const fn = type === 'hide-stackframe' ? hideStackFramesTestfn : testfn; + + const value = 42; + + const length = 1024; + const array = []; + const errCase = false; + + for (let i = 0; i < length; ++i) { + array.push(fn(value)); + } + + bench.start(); + + for (let i = 0; i < n; i++) { + const index = i % length; + array[index] = fn(value); + } + + bench.end(n); + + // Verify the entries to prevent dead code elimination from making + // the benchmark invalid. + for (let i = 0; i < length; ++i) { + assert.strictEqual(typeof array[i], errCase ? 'object' : 'undefined'); + } +} diff --git a/benchmark/error/hidestackframes-throw.js b/benchmark/error/hidestackframes-throw.js new file mode 100644 index 00000000000000..c7147e39ba57a3 --- /dev/null +++ b/benchmark/error/hidestackframes-throw.js @@ -0,0 +1,87 @@ +'use strict'; + +const common = require('../common.js'); +const assert = require('assert'); + +const bench = common.createBenchmark(main, { + type: [ + 'hide-stackframes', + 'direct-call', + ], + double: ['true', 'false'], + n: [1e5], +}, { + flags: ['--expose-internals'], +}); + +function main({ n, type, double }) { + const { + hideStackFrames, + codes: { + ERR_INVALID_ARG_TYPE, + }, + } = require('internal/errors'); + + const value = 'err'; + + const testfn = (value) => { + if (typeof value !== 'number') { + throw new ERR_INVALID_ARG_TYPE('Benchmark', 'number', value); + } + }; + + const hideStackFramesTestfn = hideStackFrames((value) => { + if (typeof value !== 'number') { + throw new ERR_INVALID_ARG_TYPE.HideStackFrameError('Benchmark', 'number', value); + } + }); + + function doubleTestfn(value) { + testfn(value); + } + + const doubleHideStackFramesTestfn = hideStackFrames((value) => { + hideStackFramesTestfn.withoutStackTrace(value); + }); + + const fn = type === 'hide-stackframe' ? + double === 'true' ? + doubleHideStackFramesTestfn : + hideStackFramesTestfn : + double === 'true' ? + doubleTestfn : + testfn; + + const length = 1024; + const array = []; + let errCase = false; + + // Warm up. + for (let i = 0; i < length; ++i) { + try { + fn(value); + } catch (e) { + errCase = true; + array.push(e); + } + } + + bench.start(); + + for (let i = 0; i < n; i++) { + const index = i % length; + try { + fn(value); + } catch (e) { + array[index] = e; + } + } + + bench.end(n); + + // Verify the entries to prevent dead code elimination from making + // the benchmark invalid. + for (let i = 0; i < length; ++i) { + assert.strictEqual(typeof array[i], errCase ? 'object' : 'undefined'); + } +} diff --git a/benchmark/error/hidestackframes.js b/benchmark/error/hidestackframes.js deleted file mode 100644 index b28be725a30969..00000000000000 --- a/benchmark/error/hidestackframes.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const common = require('../common.js'); - -const bench = common.createBenchmark(main, { - type: ['hide-stackframes-throw', 'direct-call-throw', - 'hide-stackframes-noerr', 'direct-call-noerr'], - n: [10e4], -}, { - flags: ['--expose-internals'], -}); - -function main({ n, type }) { - const { - hideStackFrames, - codes: { - ERR_INVALID_ARG_TYPE, - }, - } = require('internal/errors'); - - const testfn = (value) => { - if (typeof value !== 'number') { - throw new ERR_INVALID_ARG_TYPE('Benchmark', 'number', value); - } - }; - - let fn = testfn; - if (type.startsWith('hide-stackframe')) - fn = hideStackFrames(testfn); - let value = 42; - if (type.endsWith('-throw')) - value = 'err'; - - bench.start(); - - for (let i = 0; i < n; i++) { - try { - fn(value); - } catch { - // No-op - } - } - - bench.end(n); -} diff --git a/lib/.eslintrc.yaml b/lib/.eslintrc.yaml index c197cedfeab3db..3eb3b2089b0596 100644 --- a/lib/.eslintrc.yaml +++ b/lib/.eslintrc.yaml @@ -19,7 +19,7 @@ rules: - selector: ThrowStatement > CallExpression[callee.name=/Error$/] message: Use new keyword when throwing an Error. # Config specific to lib - - selector: NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError)$/]) + - selector: NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError)$/]) message: Use an error exported by the internal/errors module. - selector: CallExpression[callee.object.name='Error'][callee.property.name='captureStackTrace'] message: Please use `require('internal/errors').hideStackFrames()` instead. diff --git a/lib/_http_client.js b/lib/_http_client.js index 9fd07e1676b8ff..8db3f8a2766887 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -72,7 +72,7 @@ const { traceEnd, getNextTraceEventId, } = require('internal/http'); -const { connResetException, codes } = require('internal/errors'); +const { ConnResetException, codes } = require('internal/errors'); const { ERR_HTTP_HEADERS_SENT, ERR_INVALID_ARG_TYPE, @@ -452,7 +452,7 @@ function socketCloseListener() { if (res) { // Socket closed before we emitted 'end' below. if (!res.complete) { - res.destroy(connResetException('aborted')); + res.destroy(new ConnResetException('aborted')); } req._closed = true; req.emit('close'); @@ -465,7 +465,7 @@ function socketCloseListener() { // receive a response. The error needs to // fire on the request. req.socket._hadError = true; - req.emit('error', connResetException('socket hang up')); + req.emit('error', new ConnResetException('socket hang up')); } req._closed = true; req.emit('close'); @@ -516,7 +516,7 @@ function socketOnEnd() { // If we don't have a response then we know that the socket // ended prematurely and we need to emit an error on the request. req.socket._hadError = true; - req.emit('error', connResetException('socket hang up')); + req.emit('error', new ConnResetException('socket hang up')); } if (parser) { parser.finish(); @@ -869,7 +869,7 @@ function onSocketNT(req, socket, err) { function _destroy(req, err) { if (!req.aborted && !err) { - err = connResetException('socket hang up'); + err = new ConnResetException('socket hang up'); } if (err) { req.emit('error', err); diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 64e0d4ab714d79..7303bd88aaa37a 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -663,17 +663,17 @@ function matchHeader(self, state, field, value) { const validateHeaderName = hideStackFrames((name, label) => { if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) { - throw new ERR_INVALID_HTTP_TOKEN(label || 'Header name', name); + throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError(label || 'Header name', name); } }); const validateHeaderValue = hideStackFrames((name, value) => { if (value === undefined) { - throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name); + throw new ERR_HTTP_INVALID_HEADER_VALUE.HideStackFramesError(value, name); } if (checkInvalidHeaderChar(value)) { debug('Header "%s" contains invalid characters', name); - throw new ERR_INVALID_CHAR('header content', name); + throw new ERR_INVALID_CHAR.HideStackFramesError('header content', name); } }); diff --git a/lib/_http_server.js b/lib/_http_server.js index c62ea17599512f..3e19f1ba78e7cc 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -69,7 +69,7 @@ const { } = require('internal/async_hooks'); const { IncomingMessage } = require('_http_incoming'); const { - connResetException, + ConnResetException, codes, } = require('internal/errors'); const { @@ -790,7 +790,7 @@ function socketOnClose(socket, state) { function abortIncoming(incoming) { while (incoming.length) { const req = incoming.shift(); - req.destroy(connResetException('aborted')); + req.destroy(new ConnResetException('aborted')); } // Abort socket._httpMessage ? } diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index e022ed874d5610..95f61cb9f6c289 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -66,7 +66,7 @@ const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap'); const { owner_symbol } = require('internal/async_hooks').symbols; const { isArrayBufferView } = require('internal/util/types'); const { SecureContext: NativeSecureContext } = internalBinding('crypto'); -const { connResetException, codes } = require('internal/errors'); +const { ConnResetException, codes } = require('internal/errors'); const { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -1218,7 +1218,7 @@ function onSocketClose(err) { // Emit ECONNRESET if (!this._controlReleased && !this[kErrorEmitted]) { this[kErrorEmitted] = true; - const connReset = connResetException('socket hang up'); + const connReset = new ConnResetException('socket hang up'); this._tlsOptions.server.emit('tlsClientError', connReset, this); } } @@ -1724,9 +1724,9 @@ function onConnectEnd() { if (!this._hadError) { const options = this[kConnectOptions]; this._hadError = true; - const error = connResetException('Client network socket disconnected ' + - 'before secure TLS connection was ' + - 'established'); + const error = new ConnResetException('Client network socket disconnected ' + + 'before secure TLS connection was ' + + 'established'); error.path = options.path; error.host = options.host; error.port = options.port; diff --git a/lib/buffer.js b/lib/buffer.js index ee8b87a191ddbd..59a125960c276f 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -108,7 +108,6 @@ const { ERR_UNKNOWN_ENCODING, }, genericNodeError, - hideStackFrames, } = require('internal/errors'); const { validateArray, @@ -386,19 +385,12 @@ Buffer.of = of; ObjectSetPrototypeOf(Buffer, Uint8Array); -// The 'assertSize' method will remove itself from the callstack when an error -// occurs. This is done simply to keep the internal details of the -// implementation from bleeding out to users. -const assertSize = hideStackFrames((size) => { - validateNumber(size, 'size', 0, kMaxLength); -}); - /** * Creates a new filled Buffer instance. * alloc(size[, fill[, encoding]]) */ Buffer.alloc = function alloc(size, fill, encoding) { - assertSize(size); + validateNumber(size, 'size', 0, kMaxLength); if (fill !== undefined && fill !== 0 && size > 0) { const buf = createUnsafeBuffer(size); return _fill(buf, fill, 0, buf.length, encoding); @@ -411,7 +403,7 @@ Buffer.alloc = function alloc(size, fill, encoding) { * instance. If `--zero-fill-buffers` is set, will zero-fill the buffer. */ Buffer.allocUnsafe = function allocUnsafe(size) { - assertSize(size); + validateNumber(size, 'size', 0, kMaxLength); return allocate(size); }; @@ -421,15 +413,15 @@ Buffer.allocUnsafe = function allocUnsafe(size) { * If `--zero-fill-buffers` is set, will zero-fill the buffer. */ Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) { - assertSize(size); + validateNumber(size, 'size', 0, kMaxLength); return createUnsafeBuffer(size); }; // If --zero-fill-buffers command line argument is set, a zero-filled // buffer is returned. -function SlowBuffer(length) { - assertSize(length); - return createUnsafeBuffer(length); +function SlowBuffer(size) { + validateNumber(size, 'size', 0, kMaxLength); + return createUnsafeBuffer(size); } ObjectSetPrototypeOf(SlowBuffer.prototype, Uint8ArrayPrototype); diff --git a/lib/dgram.js b/lib/dgram.js index 01b5886d72fa29..8c8da1ad8856b5 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -96,9 +96,10 @@ function lazyLoadCluster() { return _cluster; } -const errnoException = errors.errnoException; -const exceptionWithHostPort = errors.exceptionWithHostPort; - +const { + ErrnoException, + ExceptionWithHostPort, +} = errors; function Socket(type, listener) { FunctionPrototypeCall(EventEmitter, this); @@ -286,7 +287,7 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { flags: null, }, (err) => { // Callback to handle error. - const ex = errnoException(err, 'open'); + const ex = new ErrnoException(err, 'open'); state.bindState = BIND_STATE_UNBOUND; this.emit('error', ex); }); @@ -299,7 +300,7 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { const err = state.handle.open(fd); if (err) - throw errnoException(err, 'open'); + throw new ErrnoException(err, 'open'); startListening(this); return this; @@ -350,7 +351,7 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { flags: flags, }, (err) => { // Callback to handle error. - const ex = exceptionWithHostPort(err, 'bind', ip, port); + const ex = new ExceptionWithHostPort(err, 'bind', ip, port); state.bindState = BIND_STATE_UNBOUND; this.emit('error', ex); }); @@ -360,7 +361,7 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { const err = state.handle.bind(ip, port || 0, flags); if (err) { - const ex = exceptionWithHostPort(err, 'bind', ip, port); + const ex = new ExceptionWithHostPort(err, 'bind', ip, port); state.bindState = BIND_STATE_UNBOUND; this.emit('error', ex); // Todo: close? @@ -429,7 +430,7 @@ function doConnect(ex, self, ip, address, port, callback) { if (!ex) { const err = state.handle.connect(ip, port); if (err) { - ex = exceptionWithHostPort(err, 'connect', address, port); + ex = new ExceptionWithHostPort(err, 'connect', address, port); } } @@ -457,7 +458,7 @@ Socket.prototype.disconnect = function() { const err = state.handle.disconnect(); if (err) - throw errnoException(err, 'connect'); + throw new ErrnoException(err, 'connect'); else state.connectState = CONNECT_STATE_DISCONNECTED; }; @@ -714,14 +715,14 @@ function doSend(ex, self, ip, list, address, port, callback) { if (err && callback) { // Don't emit as error, dgram_legacy.js compatibility - const ex = exceptionWithHostPort(err, 'send', address, port); + const ex = new ExceptionWithHostPort(err, 'send', address, port); process.nextTick(callback, ex); } } function afterSend(err, sent) { if (err) { - err = exceptionWithHostPort(err, 'send', this.address, this.port); + err = new ExceptionWithHostPort(err, 'send', this.address, this.port); } else { err = null; } @@ -772,7 +773,7 @@ Socket.prototype.address = function() { const out = {}; const err = this[kStateSymbol].handle.getsockname(out); if (err) { - throw errnoException(err, 'getsockname'); + throw new ErrnoException(err, 'getsockname'); } return out; @@ -788,7 +789,7 @@ Socket.prototype.remoteAddress = function() { const out = {}; const err = state.handle.getpeername(out); if (err) - throw errnoException(err, 'getpeername'); + throw new ErrnoException(err, 'getpeername'); return out; }; @@ -797,7 +798,7 @@ Socket.prototype.remoteAddress = function() { Socket.prototype.setBroadcast = function(arg) { const err = this[kStateSymbol].handle.setBroadcast(arg ? 1 : 0); if (err) { - throw errnoException(err, 'setBroadcast'); + throw new ErrnoException(err, 'setBroadcast'); } }; @@ -807,7 +808,7 @@ Socket.prototype.setTTL = function(ttl) { const err = this[kStateSymbol].handle.setTTL(ttl); if (err) { - throw errnoException(err, 'setTTL'); + throw new ErrnoException(err, 'setTTL'); } return ttl; @@ -819,7 +820,7 @@ Socket.prototype.setMulticastTTL = function(ttl) { const err = this[kStateSymbol].handle.setMulticastTTL(ttl); if (err) { - throw errnoException(err, 'setMulticastTTL'); + throw new ErrnoException(err, 'setMulticastTTL'); } return ttl; @@ -829,7 +830,7 @@ Socket.prototype.setMulticastTTL = function(ttl) { Socket.prototype.setMulticastLoopback = function(arg) { const err = this[kStateSymbol].handle.setMulticastLoopback(arg ? 1 : 0); if (err) { - throw errnoException(err, 'setMulticastLoopback'); + throw new ErrnoException(err, 'setMulticastLoopback'); } return arg; // 0.4 compatibility @@ -842,7 +843,7 @@ Socket.prototype.setMulticastInterface = function(interfaceAddress) { const err = this[kStateSymbol].handle.setMulticastInterface(interfaceAddress); if (err) { - throw errnoException(err, 'setMulticastInterface'); + throw new ErrnoException(err, 'setMulticastInterface'); } }; @@ -857,7 +858,7 @@ Socket.prototype.addMembership = function(multicastAddress, const { handle } = this[kStateSymbol]; const err = handle.addMembership(multicastAddress, interfaceAddress); if (err) { - throw errnoException(err, 'addMembership'); + throw new ErrnoException(err, 'addMembership'); } }; @@ -873,7 +874,7 @@ Socket.prototype.dropMembership = function(multicastAddress, const { handle } = this[kStateSymbol]; const err = handle.dropMembership(multicastAddress, interfaceAddress); if (err) { - throw errnoException(err, 'dropMembership'); + throw new ErrnoException(err, 'dropMembership'); } }; @@ -890,7 +891,7 @@ Socket.prototype.addSourceSpecificMembership = function(sourceAddress, groupAddress, interfaceAddress); if (err) { - throw errnoException(err, 'addSourceSpecificMembership'); + throw new ErrnoException(err, 'addSourceSpecificMembership'); } }; @@ -908,7 +909,7 @@ Socket.prototype.dropSourceSpecificMembership = function(sourceAddress, groupAddress, interfaceAddress); if (err) { - throw errnoException(err, 'dropSourceSpecificMembership'); + throw new ErrnoException(err, 'dropSourceSpecificMembership'); } }; @@ -935,7 +936,7 @@ function stopReceiving(socket) { function onMessage(nread, handle, buf, rinfo) { const self = handle[owner_symbol]; if (nread < 0) { - return self.emit('error', errnoException(nread, 'recvmsg')); + return self.emit('error', new ErrnoException(nread, 'recvmsg')); } rinfo.size = buf.length; // compatibility self.emit('message', buf, rinfo); diff --git a/lib/dns.js b/lib/dns.js index ca932ad05f4da7..681f0aa3e58ecf 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -30,7 +30,14 @@ const { const cares = internalBinding('cares_wrap'); const { isIP } = require('internal/net'); const { customPromisifyArgs } = require('internal/util'); -const errors = require('internal/errors'); +const { + codes: { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_MISSING_ARGS, + }, + DNSException, +} = require('internal/errors'); const { bindDefaultResolver, setDefaultResolver, @@ -70,11 +77,6 @@ const { ADDRGETNETWORKPARAMS, CANCELLED, } = dnsErrorCodes; -const { - ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE, - ERR_MISSING_ARGS, -} = errors.codes; const { validateBoolean, validateFunction, @@ -98,13 +100,11 @@ const { stopPerf, } = require('internal/perf/observe'); -const dnsException = errors.dnsException; - let promises = null; // Lazy loaded function onlookup(err, addresses) { if (err) { - return this.callback(dnsException(err, 'getaddrinfo', this.hostname)); + return this.callback(new DNSException(err, 'getaddrinfo', this.hostname)); } this.callback(null, addresses[0], this.family || isIP(addresses[0])); if (this[kPerfHooksDnsLookupContext] && hasObserver('dns')) { @@ -115,7 +115,7 @@ function onlookup(err, addresses) { function onlookupall(err, addresses) { if (err) { - return this.callback(dnsException(err, 'getaddrinfo', this.hostname)); + return this.callback(new DNSException(err, 'getaddrinfo', this.hostname)); } const family = this.family; @@ -222,7 +222,7 @@ function lookup(hostname, options, callback) { req, hostname, family, hints, verbatim, ); if (err) { - process.nextTick(callback, dnsException(err, 'getaddrinfo', hostname)); + process.nextTick(callback, new DNSException(err, 'getaddrinfo', hostname)); return {}; } if (hasObserver('dns')) { @@ -243,7 +243,7 @@ ObjectDefineProperty(lookup, customPromisifyArgs, function onlookupservice(err, hostname, service) { if (err) - return this.callback(dnsException(err, 'getnameinfo', this.hostname)); + return this.callback(new DNSException(err, 'getnameinfo', this.hostname)); this.callback(null, hostname, service); if (this[kPerfHooksDnsLookupServiceContext] && hasObserver('dns')) { @@ -272,7 +272,7 @@ function lookupService(address, port, callback) { req.oncomplete = onlookupservice; const err = cares.getnameinfo(req, address, port); - if (err) throw dnsException(err, 'getnameinfo', address); + if (err) throw new DNSException(err, 'getnameinfo', address); if (hasObserver('dns')) { startPerf(req, kPerfHooksDnsLookupServiceContext, { type: 'dns', diff --git a/lib/fs.js b/lib/fs.js index 0d8c5e925989db..1c811ad8332009 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -73,7 +73,7 @@ const { }, AbortError, uvErrmapGet, - uvException, + UVException, } = require('internal/errors'); const { @@ -402,7 +402,7 @@ function tryStatSync(fd, isUserFd) { const stats = binding.fstat(fd, false, undefined, ctx); if (ctx.errno !== undefined && !isUserFd) { fs.closeSync(fd); - throw uvException(ctx); + throw new UVException(ctx); } return stats; } diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index 2cad5aaf71cec9..49edaba5b558e9 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -17,7 +17,7 @@ const { } = primordials; const { - errnoException, + ErrnoException, codes: { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -283,7 +283,7 @@ function ChildProcess() { if (exitCode < 0) { const syscall = this.spawnfile ? 'spawn ' + this.spawnfile : 'spawn'; - const err = errnoException(exitCode, syscall); + const err = new ErrnoException(exitCode, syscall); if (this.spawnfile) err.path = this.spawnfile; @@ -418,7 +418,7 @@ ChildProcess.prototype.spawn = function(options) { this._handle.close(); this._handle = null; - throw errnoException(err, 'spawn'); + throw new ErrnoException(err, 'spawn'); } else { process.nextTick(onSpawnNT, this); } @@ -506,10 +506,10 @@ ChildProcess.prototype.kill = function(sig) { /* Already dead. */ } else if (err === UV_EINVAL || err === UV_ENOSYS) { /* The underlying platform doesn't support this signal. */ - throw errnoException(err, 'kill'); + throw new ErrnoException(err, 'kill'); } else { /* Other error, almost certainly EPERM. */ - this.emit('error', errnoException(err, 'kill')); + this.emit('error', new ErrnoException(err, 'kill')); } } @@ -876,7 +876,7 @@ function setupChannel(target, channel, serializationMode) { obj.postSend(message, handle, options, callback); if (!options.swallowErrors) { - const ex = errnoException(err, 'write'); + const ex = new ErrnoException(err, 'write'); if (typeof callback === 'function') { process.nextTick(callback, ex); } else { @@ -1121,7 +1121,7 @@ function spawnSync(options) { result.stderr = result.output && result.output[2]; if (result.error) { - result.error = errnoException(result.error, 'spawnSync ' + options.file); + result.error = new ErrnoException(result.error, 'spawnSync ' + options.file); result.error.path = options.file; result.error.spawnargs = ArrayPrototypeSlice(options.args, 1); } diff --git a/lib/internal/crypto/hkdf.js b/lib/internal/crypto/hkdf.js index cf3c39e8d9da5a..757a2391a0167f 100644 --- a/lib/internal/crypto/hkdf.js +++ b/lib/internal/crypto/hkdf.js @@ -49,15 +49,15 @@ const { } = require('internal/errors'); const validateParameters = hideStackFrames((hash, key, salt, info, length) => { - validateString(hash, 'digest'); + validateString.withoutStackTrace(hash, 'digest'); key = prepareKey(key); - salt = validateByteSource(salt, 'salt'); - info = validateByteSource(info, 'info'); + salt = validateByteSource.withoutStackTrace(salt, 'salt'); + info = validateByteSource.withoutStackTrace(info, 'info'); - validateInteger(length, 'length', 0, kMaxLength); + validateInteger.withoutStackTrace(length, 'length', 0, kMaxLength); if (info.byteLength > 1024) { - throw new ERR_OUT_OF_RANGE( + throw new ERR_OUT_OF_RANGE.HideStackFramesError( 'info', 'must not contain more than 1024 bytes', info.byteLength); diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 51ca3f4c056fb9..45a236a1130249 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -116,7 +116,7 @@ const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => { return Buffer.from(buffer, encoding); } if (!isArrayBufferView(buffer)) { - throw new ERR_INVALID_ARG_TYPE( + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError( name, [ 'string', @@ -403,7 +403,7 @@ const validateByteSource = hideStackFrames((val, name) => { if (isAnyArrayBuffer(val) || isArrayBufferView(val)) return val; - throw new ERR_INVALID_ARG_TYPE( + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError( name, [ 'string', diff --git a/lib/internal/dns/callback_resolver.js b/lib/internal/dns/callback_resolver.js index e57a597c08c716..5b7758865a85c6 100644 --- a/lib/internal/dns/callback_resolver.js +++ b/lib/internal/dns/callback_resolver.js @@ -12,7 +12,7 @@ const { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, }, - dnsException, + DNSException, } = require('internal/errors'); const { @@ -42,7 +42,7 @@ function onresolve(err, result, ttls) { result, (address, index) => ({ address, ttl: ttls[index] })); if (err) - this.callback(dnsException(err, this.bindingName, this.hostname)); + this.callback(new DNSException(err, this.bindingName, this.hostname)); else { this.callback(null, result); if (this[kPerfHooksDnsLookupResolveContext] && hasObserver('dns')) { @@ -69,7 +69,7 @@ function resolver(bindingName) { req.oncomplete = onresolve; req.ttl = !!(options && options.ttl); const err = this._handle[bindingName](req, name); - if (err) throw dnsException(err, bindingName, name); + if (err) throw new DNSException(err, bindingName, name); if (hasObserver('dns')) { startPerf(req, kPerfHooksDnsLookupResolveContext, { type: 'dns', diff --git a/lib/internal/dns/promises.js b/lib/internal/dns/promises.js index 1169b2735d4efe..56276fdbae7db7 100644 --- a/lib/internal/dns/promises.js +++ b/lib/internal/dns/promises.js @@ -45,7 +45,7 @@ const { ADDRGETNETWORKPARAMS, CANCELLED, } = dnsErrorCodes; -const { codes, dnsException } = require('internal/errors'); +const { codes, DNSException } = require('internal/errors'); const { isIP } = require('internal/net'); const { getaddrinfo, @@ -79,7 +79,7 @@ const { function onlookup(err, addresses) { if (err) { - this.reject(dnsException(err, 'getaddrinfo', this.hostname)); + this.reject(new DNSException(err, 'getaddrinfo', this.hostname)); return; } @@ -92,7 +92,7 @@ function onlookup(err, addresses) { function onlookupall(err, addresses) { if (err) { - this.reject(dnsException(err, 'getaddrinfo', this.hostname)); + this.reject(new DNSException(err, 'getaddrinfo', this.hostname)); return; } @@ -153,7 +153,7 @@ function createLookupPromise(family, hostname, all, hints, verbatim) { const err = getaddrinfo(req, hostname, family, hints, verbatim); if (err) { - reject(dnsException(err, 'getaddrinfo', hostname)); + reject(new DNSException(err, 'getaddrinfo', hostname)); } else if (hasObserver('dns')) { const detail = { hostname, @@ -220,7 +220,7 @@ function lookup(hostname, options) { function onlookupservice(err, hostname, service) { if (err) { - this.reject(dnsException(err, 'getnameinfo', this.host)); + this.reject(new DNSException(err, 'getnameinfo', this.host)); return; } @@ -243,7 +243,7 @@ function createLookupServicePromise(hostname, port) { const err = getnameinfo(req, hostname, port); if (err) - reject(dnsException(err, 'getnameinfo', hostname)); + reject(new DNSException(err, 'getnameinfo', hostname)); else if (hasObserver('dns')) { startPerf(req, kPerfHooksDnsLookupServiceContext, { type: 'dns', @@ -272,7 +272,7 @@ function lookupService(address, port) { function onresolve(err, result, ttls) { if (err) { - this.reject(dnsException(err, this.bindingName, this.hostname)); + this.reject(new DNSException(err, this.bindingName, this.hostname)); return; } @@ -300,7 +300,7 @@ function createResolverPromise(resolver, bindingName, hostname, ttl) { const err = resolver._handle[bindingName](req, hostname); if (err) - reject(dnsException(err, bindingName, hostname)); + reject(new DNSException(err, bindingName, hostname)); else if (hasObserver('dns')) { startPerf(req, kPerfHooksDnsLookupResolveContext, { type: 'dns', diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 363b9d3bb8fe8b..39e512762a470c 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -86,8 +86,7 @@ const kTypes = [ const MainContextError = Error; const overrideStackTrace = new SafeWeakMap(); const kNoOverride = Symbol('kNoOverride'); -let userStackTraceLimit; -const nodeInternalPrefix = '__node_internal_'; + const prepareStackTrace = (globalThis, error, trace) => { // API for node internals to override error stack formatting // without interfering with userland code. @@ -97,21 +96,6 @@ const prepareStackTrace = (globalThis, error, trace) => { return f(error, trace); } - const firstFrame = trace[0]?.getFunctionName(); - if (firstFrame && StringPrototypeStartsWith(firstFrame, nodeInternalPrefix)) { - for (let l = trace.length - 1; l >= 0; l--) { - const fn = trace[l]?.getFunctionName(); - if (fn && StringPrototypeStartsWith(fn, nodeInternalPrefix)) { - ArrayPrototypeSplice(trace, 0, l + 1); - break; - } - } - // `userStackTraceLimit` is the user value for `Error.stackTraceLimit`, - // it is updated at every new exception in `captureLargerStackTrace`. - if (trace.length > userStackTraceLimit) - ArrayPrototypeSplice(trace, userStackTraceLimit); - } - const globalOverride = maybeOverridePrepareStackTrace(globalThis, error, trace); if (globalOverride !== kNoOverride) return globalOverride; @@ -151,30 +135,51 @@ const maybeOverridePrepareStackTrace = (globalThis, error, trace) => { return kNoOverride; }; -const aggregateTwoErrors = hideStackFrames((innerError, outerError) => { +const aggregateTwoErrors = (innerError, outerError) => { if (innerError && outerError && innerError !== outerError) { if (ArrayIsArray(outerError.errors)) { // If `outerError` is already an `AggregateError`. ArrayPrototypePush(outerError.errors, innerError); return outerError; } - // eslint-disable-next-line no-restricted-syntax - const err = new AggregateError(new SafeArrayIterator([ - outerError, - innerError, - ]), outerError.message); + let err; + if (isErrorStackTraceLimitWritable()) { + const limit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + // eslint-disable-next-line no-restricted-syntax + err = new AggregateError(new SafeArrayIterator([ + outerError, + innerError, + ]), outerError.message); + Error.stackTraceLimit = limit; + ErrorCaptureStackTrace(err, aggregateTwoErrors); + } else { + // eslint-disable-next-line no-restricted-syntax + err = new AggregateError(new SafeArrayIterator([ + outerError, + innerError, + ]), outerError.message); + } err.code = outerError.code; return err; } return innerError || outerError; -}); +}; -const aggregateErrors = hideStackFrames((errors, message, code) => { - // eslint-disable-next-line no-restricted-syntax - const err = new AggregateError(new SafeArrayIterator(errors), message); - err.code = errors[0]?.code; - return err; -}); +class NodeAggregateError extends AggregateError { + constructor(errors, message) { + super(new SafeArrayIterator(errors), message); + this.code = errors[0]?.code; + } + + get [kIsNodeError]() { + return true; + } + + get ['constructor']() { + return AggregateError; + } +} const assert = require('internal/assert'); @@ -242,11 +247,7 @@ function inspectWithNoCustomRetry(obj, options) { // and may have .path and .dest. class SystemError extends Error { constructor(key, context) { - const limit = Error.stackTraceLimit; - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; super(); - // Reset the limit and setting the name property. - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit; const prefix = getMessage(key, [], this); let message = `${prefix}: ${context.syscall} returned ` + `${context.code} (${context.message})`; @@ -256,8 +257,6 @@ class SystemError extends Error { if (context.dest !== undefined) message += ` => ${context.dest}`; - captureLargerStackTrace(this); - this.code = key; ObjectDefineProperties(this, { @@ -372,6 +371,33 @@ function makeSystemErrorWithCode(key) { }; } +// This is a special error type that is only used for the E function. +class HideStackFramesError extends Error { +} + +function makeNodeErrorForHideStackFrame(Base, clazz) { + class HideStackFramesError extends Base { + constructor(...args) { + if (isErrorStackTraceLimitWritable()) { + const limit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + super(...args); + Error.stackTraceLimit = limit; + } else { + super(...args); + } + } + + // This is a workaround for wpt tests that expect that the error + // constructor has a `name` property of the base class. + get ['constructor']() { + return clazz; + } + } + + return HideStackFramesError; +} + function makeNodeErrorWithCode(Base, key) { const msg = messages.get(key); const expectedLength = typeof msg !== 'string' ? -1 : getExpectedArgumentLength(msg); @@ -479,11 +505,16 @@ function makeNodeErrorWithCode(Base, key) { * @returns {T} */ function hideStackFrames(fn) { - // We rename the functions that will be hidden to cut off the stacktrace - // at the outermost one - const hidden = nodeInternalPrefix + fn.name; - ObjectDefineProperty(fn, 'name', { __proto__: null, value: hidden }); - return fn; + function wrappedFn(...args) { + try { + return ReflectApply(fn, this, args); + } catch (error) { + Error.stackTraceLimit && ErrorCaptureStackTrace(error, wrappedFn); + throw error; + } + } + wrappedFn.withoutStackTrace = fn; + return wrappedFn; } // Utility function for registering the error codes. Only used here. Exported @@ -492,18 +523,33 @@ function E(sym, val, def, ...otherClasses) { // Special case for SystemError that formats the error message differently // The SystemErrors only have SystemError as their base classes. messages.set(sym, val); - if (def === SystemError) { - def = makeSystemErrorWithCode(sym); - } else { - def = makeNodeErrorWithCode(def, sym); - } + + const ErrClass = def === SystemError ? + makeSystemErrorWithCode(sym) : + makeNodeErrorWithCode(def, sym); if (otherClasses.length !== 0) { - otherClasses.forEach((clazz) => { - def[clazz.name] = makeNodeErrorWithCode(clazz, sym); - }); + if (otherClasses.includes(HideStackFramesError)) { + if (otherClasses.length !== 1) { + otherClasses.forEach((clazz) => { + if (clazz !== HideStackFramesError) { + ErrClass[clazz.name] = makeNodeErrorWithCode(clazz, sym); + ErrClass[clazz.name].HideStackFramesError = makeNodeErrorForHideStackFrame(ErrClass[clazz.name], clazz); + } + }); + } + } else { + otherClasses.forEach((clazz) => { + ErrClass[clazz.name] = makeNodeErrorWithCode(clazz, sym); + }); + } } - codes[sym] = def; + + if (otherClasses.includes(HideStackFramesError)) { + ErrClass.HideStackFramesError = makeNodeErrorForHideStackFrame(ErrClass, def); + } + + codes[sym] = ErrClass; } function getExpectedArgumentLength(msg) { @@ -553,84 +599,67 @@ function uvErrmapGet(name) { return MapPrototypeGet(uvBinding.errmap, name); } -const captureLargerStackTrace = hideStackFrames( - function captureLargerStackTrace(err) { - const stackTraceLimitIsWritable = isErrorStackTraceLimitWritable(); - if (stackTraceLimitIsWritable) { - userStackTraceLimit = Error.stackTraceLimit; - Error.stackTraceLimit = Infinity; - } - ErrorCaptureStackTrace(err); - // Reset the limit - if (stackTraceLimitIsWritable) Error.stackTraceLimit = userStackTraceLimit; - - return err; - }); - /** * This creates an error compatible with errors produced in the C++ * function UVException using a context object with data assembled in C++. * The goal is to migrate them to ERR_* errors later when compatibility is * not a concern. - * @param {object} ctx - * @returns {Error} */ -const uvException = hideStackFrames(function uvException(ctx) { - const { 0: code, 1: uvmsg } = uvErrmapGet(ctx.errno) || uvUnmappedError; - let message = `${code}: ${ctx.message || uvmsg}, ${ctx.syscall}`; - - let path; - let dest; - if (ctx.path) { - path = ctx.path.toString(); - message += ` '${path}'`; - } - if (ctx.dest) { - dest = ctx.dest.toString(); - message += ` -> '${dest}'`; - } +class UVException extends Error { + /** + * @param {object} ctx + */ + constructor(ctx) { + const { 0: code, 1: uvmsg } = uvErrmapGet(ctx.errno) || uvUnmappedError; + let message = `${code}: ${ctx.message || uvmsg}, ${ctx.syscall}`; + + let path; + let dest; + if (ctx.path) { + path = ctx.path.toString(); + message += ` '${path}'`; + } + if (ctx.dest) { + dest = ctx.dest.toString(); + message += ` -> '${dest}'`; + } - // Reducing the limit improves the performance significantly. We do not lose - // the stack frames due to the `captureStackTrace()` function that is called - // later. - const tmpLimit = Error.stackTraceLimit; - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; - // Pass the message to the constructor instead of setting it on the object - // to make sure it is the same as the one created in C++ - // eslint-disable-next-line no-restricted-syntax - const err = new Error(message); - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit; + super(message); - for (const prop of ObjectKeys(ctx)) { - if (prop === 'message' || prop === 'path' || prop === 'dest') { - continue; + for (const prop of ObjectKeys(ctx)) { + if (prop === 'message' || prop === 'path' || prop === 'dest') { + continue; + } + this[prop] = ctx[prop]; } - err[prop] = ctx[prop]; - } - err.code = code; - if (path) { - err.path = path; - } - if (dest) { - err.dest = dest; + this.code = code; + if (path) { + this.path = path; + } + if (dest) { + this.dest = dest; + } } - return captureLargerStackTrace(err); -}); + get ['constructor']() { + return Error; + } +} /** * This creates an error compatible with errors produced in the C++ * This function should replace the deprecated * `exceptionWithHostPort()` function. - * @param {number} err - A libuv error number - * @param {string} syscall - * @param {string} address - * @param {number} [port] - * @returns {Error} */ -const uvExceptionWithHostPort = hideStackFrames( - function uvExceptionWithHostPort(err, syscall, address, port) { +class UVExceptionWithHostPort extends Error { + /** + * @param {number} err - A libuv error number + * @param {string} syscall + * @param {string} address + * @param {number} [port] + */ + constructor(err, syscall, address, port) { const { 0: code, 1: uvmsg } = uvErrmapGet(err) || uvUnmappedError; const message = `${syscall} ${code}: ${uvmsg}`; let details = ''; @@ -641,34 +670,32 @@ const uvExceptionWithHostPort = hideStackFrames( details = ` ${address}`; } - // Reducing the limit improves the performance significantly. We do not - // lose the stack frames due to the `captureStackTrace()` function that - // is called later. - const tmpLimit = Error.stackTraceLimit; - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; - // eslint-disable-next-line no-restricted-syntax - const ex = new Error(`${message}${details}`); - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit; - ex.code = code; - ex.errno = err; - ex.syscall = syscall; - ex.address = address; + super(`${message}${details}`); + + this.code = code; + this.errno = err; + this.syscall = syscall; + this.address = address; if (port) { - ex.port = port; + this.port = port; } + } - return captureLargerStackTrace(ex); - }); + get ['constructor']() { + return Error; + } +} /** * This used to be util._errnoException(). - * @param {number} err - A libuv error number - * @param {string} syscall - * @param {string} [original] - * @returns {Error} */ -const errnoException = hideStackFrames( - function errnoException(err, syscall, original) { +class ErrnoException extends Error { + /** + * @param {number} err - A libuv error number + * @param {string} syscall + * @param {string} [original] err + */ + constructor(err, syscall, original) { // TODO(joyeecheung): We have to use the type-checked // getSystemErrorName(err) to guard against invalid arguments from users. // This can be replaced with [ code ] = errmap.get(err) when this method @@ -678,20 +705,20 @@ const errnoException = hideStackFrames( const message = original ? `${syscall} ${code} ${original}` : `${syscall} ${code}`; - const tmpLimit = Error.stackTraceLimit; - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; - // eslint-disable-next-line no-restricted-syntax - const ex = new Error(message); - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit; - ex.errno = err; - ex.code = code; - ex.syscall = syscall; + super(message); - return captureLargerStackTrace(ex); - }); + this.errno = err; + this.code = code; + this.syscall = syscall; + } + + get ['constructor']() { + return Error; + } +} /** - * Deprecated, new function is `uvExceptionWithHostPort()` + * Deprecated, new Error is `UVExceptionWithHostPort()` * New function added the error description directly * from C++. this method for backwards compatibility * @param {number} err - A libuv error number @@ -701,8 +728,8 @@ const errnoException = hideStackFrames( * @param {string} [additional] * @returns {Error} */ -const exceptionWithHostPort = hideStackFrames( - function exceptionWithHostPort(err, syscall, address, port, additional) { +class ExceptionWithHostPort extends Error { + constructor(err, syscall, address, port, additional) { // TODO(joyeecheung): We have to use the type-checked // getSystemErrorName(err) to guard against invalid arguments from users. // This can be replaced with [ code ] = errmap.get(err) when this method @@ -719,78 +746,75 @@ const exceptionWithHostPort = hideStackFrames( details += ` - Local (${additional})`; } - // Reducing the limit improves the performance significantly. We do not - // lose the stack frames due to the `captureStackTrace()` function that - // is called later. - const tmpLimit = Error.stackTraceLimit; - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; - // eslint-disable-next-line no-restricted-syntax - const ex = new Error(`${syscall} ${code}${details}`); - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit; - ex.errno = err; - ex.code = code; - ex.syscall = syscall; - ex.address = address; + super(`${syscall} ${code}${details}`); + + this.errno = err; + this.code = code; + this.syscall = syscall; + this.address = address; if (port) { - ex.port = port; + this.port = port; } + } - return captureLargerStackTrace(ex); - }); + get ['constructor']() { + return Error; + } +} -/** - * @param {number|string} code - A libuv error number or a c-ares error code - * @param {string} syscall - * @param {string} [hostname] - * @returns {Error} - */ -const dnsException = hideStackFrames(function(code, syscall, hostname) { - let errno; - // If `code` is of type number, it is a libuv error number, else it is a - // c-ares error code. - // TODO(joyeecheung): translate c-ares error codes into numeric ones and - // make them available in a property that's not error.errno (since they - // can be in conflict with libuv error codes). Also make sure - // util.getSystemErrorName() can understand them when an being informed that - // the number is a c-ares error code. - if (typeof code === 'number') { - errno = code; - // ENOTFOUND is not a proper POSIX error, but this error has been in place - // long enough that it's not practical to remove it. - if (code === lazyUv().UV_EAI_NODATA || code === lazyUv().UV_EAI_NONAME) { - code = 'ENOTFOUND'; // Fabricated error name. - } else { - code = lazyInternalUtil().getSystemErrorName(code); +class DNSException extends Error { + /** + * @param {number|string} code - A libuv error number or a c-ares error code + * @param {string} syscall + * @param {string} [hostname] + */ + constructor(code, syscall, hostname) { + let errno; + // If `code` is of type number, it is a libuv error number, else it is a + // c-ares error code. + // TODO(joyeecheung): translate c-ares error codes into numeric ones and + // make them available in a property that's not error.errno (since they + // can be in conflict with libuv error codes). Also make sure + // util.getSystemErrorName() can understand them when an being informed that + // the number is a c-ares error code. + if (typeof code === 'number') { + errno = code; + // ENOTFOUND is not a proper POSIX error, but this error has been in place + // long enough that it's not practical to remove it. + if (code === lazyUv().UV_EAI_NODATA || code === lazyUv().UV_EAI_NONAME) { + code = 'ENOTFOUND'; // Fabricated error name. + } else { + code = lazyInternalUtil().getSystemErrorName(code); + } + } + super(`${syscall} ${code}${hostname ? ` ${hostname}` : ''}`); + this.errno = errno; + this.code = code; + this.syscall = syscall; + if (hostname) { + this.hostname = hostname; } } - const message = `${syscall} ${code}${hostname ? ` ${hostname}` : ''}`; - // Reducing the limit improves the performance significantly. We do not lose - // the stack frames due to the `captureStackTrace()` function that is called - // later. - const tmpLimit = Error.stackTraceLimit; - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; - // eslint-disable-next-line no-restricted-syntax - const ex = new Error(message); - if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = tmpLimit; - ex.errno = errno; - ex.code = code; - ex.syscall = syscall; - if (hostname) { - ex.hostname = hostname; + + get ['constructor']() { + return Error; } +} - return captureLargerStackTrace(ex); -}); +class ConnResetException extends Error { + constructor(msg) { + super(msg); + this.code = 'ECONNRESET'; + } -function connResetException(msg) { - // eslint-disable-next-line no-restricted-syntax - const ex = new Error(msg); - ex.code = 'ECONNRESET'; - return ex; + get ['constructor']() { + return Error; + } } let maxStack_ErrorName; let maxStack_ErrorMessage; + /** * Returns true if `err.name` and `err.message` are equal to engine-specific * values indicating max call stack size has been exceeded. @@ -1011,16 +1035,15 @@ function formatList(array, type = 'and') { module.exports = { AbortError, aggregateTwoErrors, - aggregateErrors, - captureLargerStackTrace, + NodeAggregateError, codes, - connResetException, - dnsException, + ConnResetException, + DNSException, // This is exported only to facilitate testing. determineSpecificType, E, - errnoException, - exceptionWithHostPort, + ErrnoException, + ExceptionWithHostPort, fatalExceptionStackEnhancers, formatList, genericNodeError, @@ -1039,8 +1062,8 @@ module.exports = { setArrowMessage, SystemError, uvErrmapGet, - uvException, - uvExceptionWithHostPort, + UVException, + UVExceptionWithHostPort, }; // To declare an error message, use the E(sym, val, def) function above. The sym @@ -1147,7 +1170,7 @@ E('ERR_EVENT_RECURSION', 'The event "%s" is already being dispatched', Error); E('ERR_FALSY_VALUE_REJECTION', function(reason) { this.reason = reason; return 'Promise was rejected with falsy value'; -}, Error); +}, Error, HideStackFramesError); E('ERR_FEATURE_UNAVAILABLE_ON_PLATFORM', 'The feature %s is unavailable on the current platform' + ', which is being used to run Node.js', @@ -1163,7 +1186,7 @@ E('ERR_FS_CP_SOCKET', 'Cannot copy a socket file', SystemError); E('ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY', 'Cannot overwrite symlink in subdirectory of self', SystemError); E('ERR_FS_CP_UNKNOWN', 'Cannot copy an unknown file type', SystemError); -E('ERR_FS_EISDIR', 'Path is a directory', SystemError); +E('ERR_FS_EISDIR', 'Path is a directory', SystemError, HideStackFramesError); E('ERR_FS_FILE_TOO_LARGE', 'File size (%s) is greater than 2 GiB', RangeError); E('ERR_FS_INVALID_SYMLINK_TYPE', 'Symlink type must be one of "dir", "file", or "junction". Received "%s"', @@ -1190,7 +1213,7 @@ E('ERR_HTTP2_INFO_STATUS_NOT_ALLOWED', E('ERR_HTTP2_INVALID_CONNECTION_HEADERS', 'HTTP/1 Connection specific headers are forbidden: "%s"', TypeError); E('ERR_HTTP2_INVALID_HEADER_VALUE', - 'Invalid value "%s" for header "%s"', TypeError); + 'Invalid value "%s" for header "%s"', TypeError, HideStackFramesError); E('ERR_HTTP2_INVALID_INFO_STATUS', 'Invalid informational status code: %s', RangeError); E('ERR_HTTP2_INVALID_ORIGIN', @@ -1198,7 +1221,7 @@ E('ERR_HTTP2_INVALID_ORIGIN', E('ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH', 'Packed settings length must be a multiple of six', RangeError); E('ERR_HTTP2_INVALID_PSEUDOHEADER', - '"%s" is an invalid pseudoheader or is used incorrectly', TypeError); + '"%s" is an invalid pseudoheader or is used incorrectly', TypeError, HideStackFramesError); E('ERR_HTTP2_INVALID_SESSION', 'The session has been destroyed', Error); E('ERR_HTTP2_INVALID_SETTING_VALUE', // Using default arguments here is important so the arguments are not counted @@ -1210,7 +1233,7 @@ E('ERR_HTTP2_INVALID_SETTING_VALUE', this.max = max; } return `Invalid value for setting "${name}": ${actual}`; - }, TypeError, RangeError); + }, TypeError, RangeError, HideStackFramesError); E('ERR_HTTP2_INVALID_STREAM', 'The stream has been destroyed', Error); E('ERR_HTTP2_MAX_PENDING_SETTINGS_ACK', 'Maximum number of pending settings acknowledgements', Error); @@ -1230,7 +1253,7 @@ E('ERR_HTTP2_PAYLOAD_FORBIDDEN', E('ERR_HTTP2_PING_CANCEL', 'HTTP2 ping cancelled', Error); E('ERR_HTTP2_PING_LENGTH', 'HTTP2 ping payload must be 8 bytes', RangeError); E('ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED', - 'Cannot set HTTP/2 pseudo-headers', TypeError); + 'Cannot set HTTP/2 pseudo-headers', TypeError, HideStackFramesError); E('ERR_HTTP2_PUSH_DISABLED', 'HTTP/2 client has disabled push streams', Error); E('ERR_HTTP2_SEND_FILE', 'Directories cannot be sent', Error); E('ERR_HTTP2_SEND_FILE_NOSEEK', @@ -1270,7 +1293,7 @@ E('ERR_HTTP_CONTENT_LENGTH_MISMATCH', E('ERR_HTTP_HEADERS_SENT', 'Cannot %s headers after they are sent to the client', Error); E('ERR_HTTP_INVALID_HEADER_VALUE', - 'Invalid value "%s" for header "%s"', TypeError); + 'Invalid value "%s" for header "%s"', TypeError, HideStackFramesError); E('ERR_HTTP_INVALID_STATUS_CODE', 'Invalid status code: %s', RangeError); E('ERR_HTTP_REQUEST_TIMEOUT', 'Request timeout', Error); E('ERR_HTTP_SOCKET_ASSIGNED', @@ -1287,7 +1310,7 @@ E('ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE', E('ERR_IMPORT_ATTRIBUTE_UNSUPPORTED', 'Import attribute "%s" with value "%s" is not supported', TypeError); E('ERR_INCOMPATIBLE_OPTION_PAIR', - 'Option "%s" cannot be used in combination with option "%s"', TypeError); + 'Option "%s" cannot be used in combination with option "%s"', TypeError, HideStackFramesError); E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' + 'input via --eval, --print, or STDIN', Error); E('ERR_INSPECTOR_ALREADY_ACTIVATED', @@ -1383,7 +1406,7 @@ E('ERR_INVALID_ARG_TYPE', msg += `. Received ${determineSpecificType(actual)}`; return msg; - }, TypeError); + }, TypeError, HideStackFramesError); E('ERR_INVALID_ARG_VALUE', (name, value, reason = 'is invalid') => { let inspected = lazyInternalUtilInspect().inspect(value); if (inspected.length > 128) { @@ -1391,7 +1414,7 @@ E('ERR_INVALID_ARG_VALUE', (name, value, reason = 'is invalid') => { } const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'; return `The ${type} '${name}' ${reason}. Received ${inspected}`; -}, TypeError, RangeError); +}, TypeError, RangeError, HideStackFramesError); E('ERR_INVALID_ASYNC_ID', 'Invalid %s value: %s', RangeError); E('ERR_INVALID_BUFFER_SIZE', 'Buffer size must be a multiple of %s', RangeError); @@ -1404,7 +1427,7 @@ E('ERR_INVALID_CHAR', msg += ` ["${field}"]`; } return msg; - }, TypeError); + }, TypeError, HideStackFramesError); E('ERR_INVALID_CURSOR_POS', 'Cannot set cursor row without setting its column', TypeError); E('ERR_INVALID_FD', @@ -1414,7 +1437,7 @@ E('ERR_INVALID_FILE_URL_HOST', 'File URL host must be "localhost" or empty on %s', TypeError); E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s', TypeError); E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent', TypeError); -E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError); +E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError, HideStackFramesError); E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s', TypeError); E('ERR_INVALID_MIME_SYNTAX', (production, str, invalidIndex) => { const msg = invalidIndex !== -1 ? ` at ${invalidIndex}` : ''; @@ -1603,7 +1626,7 @@ E('ERR_OUT_OF_RANGE', } msg += ` It must be ${range}. Received ${received}`; return msg; - }, RangeError); + }, RangeError, HideStackFramesError); E('ERR_PACKAGE_IMPORT_NOT_DEFINED', (specifier, packagePath, base) => { return `Package import specifier "${specifier}" is not defined${packagePath ? ` in package ${packagePath}package.json` : ''} imported from ${base}`; @@ -1669,7 +1692,7 @@ E('ERR_SOCKET_BAD_PORT', (name, port, allowZero = true) => { "The 'allowZero' argument must be of type boolean."); const operator = allowZero ? '>=' : '>'; return `${name} should be ${operator} 0 and < 65536. Received ${determineSpecificType(port)}.`; -}, RangeError); +}, RangeError, HideStackFramesError); E('ERR_SOCKET_BAD_TYPE', 'Bad socket type specified. Valid types are: udp4, udp6', TypeError); E('ERR_SOCKET_BUFFER_SIZE', @@ -1700,7 +1723,7 @@ E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT', E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode', Error); E('ERR_STREAM_WRITE_AFTER_END', 'write after end', Error); E('ERR_SYNTHETIC', 'JavaScript Callstack', Error); -E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError); +E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError, HideStackFramesError); E('ERR_TAP_LEXER_ERROR', function(errorMsg) { hideInternalStackFrames(this); return errorMsg; @@ -1794,7 +1817,7 @@ E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError); E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension "%s" for %s', TypeError); E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s for URL %s', RangeError); -E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError); +E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError, HideStackFramesError); E('ERR_UNSUPPORTED_DIR_IMPORT', function(path, base, exactUrl) { lazyInternalUtil().setOwnProperty(this, 'url', exactUrl); return `Directory import '${path}' is not supported ` + diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 611b6c242047b9..9f5ad9bf5f6c0e 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -38,7 +38,7 @@ const { ERR_OUT_OF_RANGE, }, hideStackFrames, - uvException, + UVException, } = require('internal/errors'); const { isArrayBufferView, @@ -348,7 +348,7 @@ function getOptions(options, defaultOptions = kEmptyObject) { */ function handleErrorFromBinding(ctx) { if (ctx.errno !== undefined) { // libuv error numbers - const err = uvException(ctx); + const err = new UVException(ctx); ErrorCaptureStackTrace(err, handleErrorFromBinding); throw err; } @@ -361,30 +361,6 @@ function handleErrorFromBinding(ctx) { } } -// Check if the path contains null types if it is a string nor Uint8Array, -// otherwise return silently. -const nullCheck = hideStackFrames((path, propName, throwError = true) => { - const pathIsString = typeof path === 'string'; - const pathIsUint8Array = isUint8Array(path); - - // We can only perform meaningful checks on strings and Uint8Arrays. - if ((!pathIsString && !pathIsUint8Array) || - (pathIsString && !StringPrototypeIncludes(path, '\u0000')) || - (pathIsUint8Array && !TypedArrayPrototypeIncludes(path, 0))) { - return; - } - - const err = new ERR_INVALID_ARG_VALUE( - propName, - path, - 'must be a string, Uint8Array, or URL without null bytes', - ); - if (throwError) { - throw err; - } - return err; -}); - function preprocessSymlinkDestination(path, type, linkPath) { if (!isWindows) { // No preprocessing is needed on Unix. @@ -664,14 +640,14 @@ function toUnixTimestamp(time, name = 'time') { const validateOffsetLengthRead = hideStackFrames( (offset, length, bufferLength) => { if (offset < 0) { - throw new ERR_OUT_OF_RANGE('offset', '>= 0', offset); + throw new ERR_OUT_OF_RANGE.HideStackFramesError('offset', '>= 0', offset); } if (length < 0) { - throw new ERR_OUT_OF_RANGE('length', '>= 0', length); + throw new ERR_OUT_OF_RANGE.HideStackFramesError('length', '>= 0', length); } if (offset + length > bufferLength) { - throw new ERR_OUT_OF_RANGE('length', - `<= ${bufferLength - offset}`, length); + throw new ERR_OUT_OF_RANGE.HideStackFramesError('length', + `<= ${bufferLength - offset}`, length); } }, ); @@ -679,31 +655,41 @@ const validateOffsetLengthRead = hideStackFrames( const validateOffsetLengthWrite = hideStackFrames( (offset, length, byteLength) => { if (offset > byteLength) { - throw new ERR_OUT_OF_RANGE('offset', `<= ${byteLength}`, offset); + throw new ERR_OUT_OF_RANGE.HideStackFramesError('offset', `<= ${byteLength}`, offset); } if (length > byteLength - offset) { - throw new ERR_OUT_OF_RANGE('length', `<= ${byteLength - offset}`, length); + throw new ERR_OUT_OF_RANGE.HideStackFramesError('length', `<= ${byteLength - offset}`, length); } if (length < 0) { - throw new ERR_OUT_OF_RANGE('length', '>= 0', length); + throw new ERR_OUT_OF_RANGE.HideStackFramesError('length', '>= 0', length); } - validateInt32(length, 'length', 0); + validateInt32.withoutStackTrace(length, 'length', 0); }, ); const validatePath = hideStackFrames((path, propName = 'path') => { if (typeof path !== 'string' && !isUint8Array(path)) { - throw new ERR_INVALID_ARG_TYPE(propName, ['string', 'Buffer', 'URL'], path); + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(propName, ['string', 'Buffer', 'URL'], path); } - const err = nullCheck(path, propName, false); + const pathIsString = typeof path === 'string'; + const pathIsUint8Array = isUint8Array(path); - if (err !== undefined) { - throw err; + // We can only perform meaningful checks on strings and Uint8Arrays. + if ((!pathIsString && !pathIsUint8Array) || + (pathIsString && !StringPrototypeIncludes(path, '\u0000')) || + (pathIsUint8Array && !TypedArrayPrototypeIncludes(path, 0))) { + return; } + + throw new ERR_INVALID_ARG_VALUE.HideStackFramesError( + propName, + path, + 'must be a string, Uint8Array, or URL without null bytes', + ); }); // TODO(rafaelgss): implement the path.resolve on C++ side @@ -742,11 +728,11 @@ const getValidatedFd = hideStackFrames((fd, propName = 'fd') => { const validateBufferArray = hideStackFrames((buffers, propName = 'buffers') => { if (!ArrayIsArray(buffers)) - throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers); + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(propName, 'ArrayBufferView[]', buffers); for (let i = 0; i < buffers.length; i++) { if (!isArrayBufferView(buffers[i])) - throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers); + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(propName, 'ArrayBufferView[]', buffers); } return buffers; @@ -802,7 +788,7 @@ const validateCpOptions = hideStackFrames((options) => { validateBoolean(options.verbatimSymlinks, 'options.verbatimSymlinks'); options.mode = getValidMode(options.mode, 'copyFile'); if (options.dereference === true && options.verbatimSymlinks === true) { - throw new ERR_INCOMPATIBLE_OPTION_PAIR('dereference', 'verbatimSymlinks'); + throw new ERR_INCOMPATIBLE_OPTION_PAIR.HideStackFramesError('dereference', 'verbatimSymlinks'); } if (options.filter !== undefined) { validateFunction(options.filter, 'options.filter'); @@ -827,21 +813,23 @@ const validateRmOptions = hideStackFrames((path, options, expectDir, cb) => { } if (stats.isDirectory() && !options.recursive) { - return cb(new ERR_FS_EISDIR({ + const err = new ERR_FS_EISDIR.HideStackFramesError({ code: 'EISDIR', message: 'is a directory', path, syscall: 'rm', errno: EISDIR, - })); + }); + + return cb(err); } return cb(null, options); }); }); const validateRmOptionsSync = hideStackFrames((path, options, expectDir) => { - options = validateRmdirOptions(options, defaultRmOptions); - validateBoolean(options.force, 'options.force'); + options = validateRmdirOptions.withoutStackTrace(options, defaultRmOptions); + validateBoolean.withoutStackTrace(options.force, 'options.force'); if (!options.force || expectDir || !options.recursive) { const isDirectory = lazyLoadFs() @@ -852,7 +840,7 @@ const validateRmOptionsSync = hideStackFrames((path, options, expectDir) => { } if (isDirectory && !options.recursive) { - throw new ERR_FS_EISDIR({ + throw new ERR_FS_EISDIR.HideStackFramesError({ code: 'EISDIR', message: 'is a directory', path, @@ -882,13 +870,13 @@ const validateRmdirOptions = hideStackFrames( (options, defaults = defaultRmdirOptions) => { if (options === undefined) return defaults; - validateObject(options, 'options'); + validateObject.withoutStackTrace(options, 'options'); options = { ...defaults, ...options }; - validateBoolean(options.recursive, 'options.recursive'); - validateInt32(options.retryDelay, 'options.retryDelay', 0); - validateUint32(options.maxRetries, 'options.maxRetries'); + validateBoolean.withoutStackTrace(options.recursive, 'options.recursive'); + validateInt32.withoutStackTrace(options.retryDelay, 'options.retryDelay', 0); + validateUint32.withoutStackTrace(options.maxRetries, 'options.maxRetries'); return options; }); @@ -907,13 +895,13 @@ const getValidMode = hideStackFrames((mode, type) => { if (mode == null) { return def; } - validateInteger(mode, 'mode', min, max); + validateInteger.withoutStackTrace(mode, 'mode', min, max); return mode; }); const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => { if (typeof buffer !== 'string') { - throw new ERR_INVALID_ARG_TYPE( + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError( name, ['string', 'Buffer', 'TypedArray', 'DataView'], buffer, @@ -923,16 +911,16 @@ const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => { const validatePosition = hideStackFrames((position, name, length) => { if (typeof position === 'number') { - validateInteger(position, name, -1); + validateInteger.withoutStackTrace(position, name, -1); } else if (typeof position === 'bigint') { const maxPosition = 2n ** 63n - 1n - BigInt(length); if (!(position >= -1n && position <= maxPosition)) { - throw new ERR_OUT_OF_RANGE(name, - `>= -1 && <= ${maxPosition}`, - position); + throw new ERR_OUT_OF_RANGE.HideStackFramesError(name, + `>= -1 && <= ${maxPosition}`, + position); } } else { - throw new ERR_INVALID_ARG_TYPE(name, ['integer', 'bigint'], position); + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(name, ['integer', 'bigint'], position); } }); @@ -956,7 +944,6 @@ module.exports = { getValidatedPath, getValidMode, handleErrorFromBinding, - nullCheck, possiblyTransformPath, preprocessSymlinkDestination, realpathCacheKey: Symbol('realpathCacheKey'), diff --git a/lib/internal/fs/watchers.js b/lib/internal/fs/watchers.js index 99212fa713bf3f..f5ecc15159f457 100644 --- a/lib/internal/fs/watchers.js +++ b/lib/internal/fs/watchers.js @@ -9,7 +9,7 @@ const { const { AbortError, - uvException, + UVException, codes: { ERR_INVALID_ARG_VALUE, }, @@ -119,7 +119,7 @@ StatWatcher.prototype[kFSStatWatcherStart] = function(filename, validateUint32(interval, 'interval'); const err = this._handle.start(toNamespacedPath(filename), interval); if (err) { - const error = uvException({ + const error = new UVException({ errno: err, syscall: 'watch', path: filename, @@ -204,7 +204,7 @@ function FSWatcher() { this._handle.close(); this._handle = null; // Make the handle garbage collectable. } - const error = uvException({ + const error = new UVException({ errno: status, syscall: 'watch', path: filename, @@ -244,7 +244,7 @@ FSWatcher.prototype[kFSWatchStart] = function(filename, recursive, encoding); if (err) { - const error = uvException({ + const error = new UVException({ errno: err, syscall: 'watch', path: filename, @@ -338,7 +338,7 @@ async function* watch(filename, options = kEmptyObject) { } handle.onchange = (status, eventType, filename) => { if (status < 0) { - const error = uvException({ + const error = new UVException({ errno: status, syscall: 'watch', path: filename, @@ -354,7 +354,7 @@ async function* watch(filename, options = kEmptyObject) { const err = handle.start(path, persistent, recursive, encoding); if (err) { - const error = uvException({ + const error = new UVException({ errno: err, syscall: 'watch', path: filename, diff --git a/lib/internal/http2/compat.js b/lib/internal/http2/compat.js index 2ab9c70ccd7402..7bf079900c652f 100644 --- a/lib/internal/http2/compat.js +++ b/lib/internal/http2/compat.js @@ -89,13 +89,13 @@ const assertValidHeader = hideStackFrames((name, value) => { if (name === '' || typeof name !== 'string' || StringPrototypeIncludes(name, ' ')) { - throw new ERR_INVALID_HTTP_TOKEN('Header name', name); + throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError('Header name', name); } if (isPseudoHeader(name)) { - throw new ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED(); + throw new ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED.HideStackFramesError(); } if (value === undefined || value === null) { - throw new ERR_HTTP2_INVALID_HEADER_VALUE(value, name); + throw new ERR_HTTP2_INVALID_HEADER_VALUE.HideStackFramesError(value, name); } if (!isConnectionHeaderAllowed(name, value)) { connectionHeaderMessageWarn(); diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 909946816c7495..58d0783701286d 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -766,25 +766,25 @@ const setAndValidatePriorityOptions = hideStackFrames((options) => { if (options.weight === undefined) { options.weight = NGHTTP2_DEFAULT_WEIGHT; } else { - validateNumber(options.weight, 'options.weight'); + validateNumber.withoutStackTrace(options.weight, 'options.weight'); } if (options.parent === undefined) { options.parent = 0; } else { - validateNumber(options.parent, 'options.parent', 0); + validateNumber.withoutStackTrace(options.parent, 'options.parent', 0); } if (options.exclusive === undefined) { options.exclusive = false; } else { - validateBoolean(options.exclusive, 'options.exclusive'); + validateBoolean.withoutStackTrace(options.exclusive, 'options.exclusive'); } if (options.silent === undefined) { options.silent = false; } else { - validateBoolean(options.silent, 'options.silent'); + validateBoolean.withoutStackTrace(options.silent, 'options.silent'); } }); @@ -946,33 +946,33 @@ function pingCallback(cb) { // All settings are optional and may be left undefined const validateSettings = hideStackFrames((settings) => { if (settings === undefined) return; - assertWithinRange('headerTableSize', - settings.headerTableSize, - 0, kMaxInt); - assertWithinRange('initialWindowSize', - settings.initialWindowSize, - 0, kMaxInt); - assertWithinRange('maxFrameSize', - settings.maxFrameSize, - 16384, kMaxFrameSize); - assertWithinRange('maxConcurrentStreams', - settings.maxConcurrentStreams, - 0, kMaxStreams); - assertWithinRange('maxHeaderListSize', - settings.maxHeaderListSize, - 0, kMaxInt); - assertWithinRange('maxHeaderSize', - settings.maxHeaderSize, - 0, kMaxInt); + assertWithinRange.withoutStackTrace('headerTableSize', + settings.headerTableSize, + 0, kMaxInt); + assertWithinRange.withoutStackTrace('initialWindowSize', + settings.initialWindowSize, + 0, kMaxInt); + assertWithinRange.withoutStackTrace('maxFrameSize', + settings.maxFrameSize, + 16384, kMaxFrameSize); + assertWithinRange.withoutStackTrace('maxConcurrentStreams', + settings.maxConcurrentStreams, + 0, kMaxStreams); + assertWithinRange.withoutStackTrace('maxHeaderListSize', + settings.maxHeaderListSize, + 0, kMaxInt); + assertWithinRange.withoutStackTrace('maxHeaderSize', + settings.maxHeaderSize, + 0, kMaxInt); if (settings.enablePush !== undefined && typeof settings.enablePush !== 'boolean') { - throw new ERR_HTTP2_INVALID_SETTING_VALUE('enablePush', - settings.enablePush); + throw new ERR_HTTP2_INVALID_SETTING_VALUE.HideStackFramesError('enablePush', + settings.enablePush); } if (settings.enableConnectProtocol !== undefined && typeof settings.enableConnectProtocol !== 'boolean') { - throw new ERR_HTTP2_INVALID_SETTING_VALUE('enableConnectProtocol', - settings.enableConnectProtocol); + throw new ERR_HTTP2_INVALID_SETTING_VALUE.HideStackFramesError('enableConnectProtocol', + settings.enableConnectProtocol); } }); diff --git a/lib/internal/http2/util.js b/lib/internal/http2/util.js index 6d4a7f94b3d11a..a9d636c2f20581 100644 --- a/lib/internal/http2/util.js +++ b/lib/internal/http2/util.js @@ -8,7 +8,6 @@ const { Error, MathMax, Number, - ObjectDefineProperty, ObjectKeys, SafeSet, String, @@ -23,12 +22,11 @@ const { codes: { ERR_HTTP2_HEADER_SINGLE_VALUE, ERR_HTTP2_INVALID_CONNECTION_HEADERS, - ERR_HTTP2_INVALID_PSEUDOHEADER, + ERR_HTTP2_INVALID_PSEUDOHEADER: { HideStackFramesError: ERR_HTTP2_INVALID_PSEUDOHEADER }, ERR_HTTP2_INVALID_SETTING_VALUE, ERR_INVALID_ARG_TYPE, ERR_INVALID_HTTP_TOKEN, }, - captureLargerStackTrace, getMessage, hideStackFrames, kIsNodeError, @@ -552,14 +550,10 @@ class NghttpError extends Error { binding.nghttp2ErrorString(integerCode)); this.code = customErrorCode || 'ERR_HTTP2_ERROR'; this.errno = integerCode; - captureLargerStackTrace(this); - ObjectDefineProperty(this, kIsNodeError, { - __proto__: null, - value: true, - enumerable: false, - writable: false, - configurable: true, - }); + } + + get [kIsNodeError]() { + return true; } toString() { @@ -572,7 +566,7 @@ const assertIsObject = hideStackFrames((value, name, types) => { (value === null || typeof value !== 'object' || ArrayIsArray(value))) { - throw new ERR_INVALID_ARG_TYPE(name, types || 'Object', value); + throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(name, types || 'Object', value); } }); @@ -580,7 +574,7 @@ const assertWithinRange = hideStackFrames( (name, value, min = 0, max = Infinity) => { if (value !== undefined && (typeof value !== 'number' || value < min || value > max)) { - throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError( + throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError.HideStackFramesError( name, value, min, max); } }, diff --git a/lib/internal/net.js b/lib/internal/net.js index 8b04d5f226eb17..0f2fc5dfff5ea8 100644 --- a/lib/internal/net.js +++ b/lib/internal/net.js @@ -58,7 +58,7 @@ function makeSyncWrite(fd) { const ctx = {}; writeBuffer(fd, chunk, 0, chunk.length, null, undefined, ctx); if (ctx.errno !== undefined) { - const ex = errors.uvException(ctx); + const ex = new errors.UVException(ctx); ex.errno = ctx.errno; return cb(ex); } diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index ce4822af019d5f..fb8014dcb39178 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -32,7 +32,7 @@ const { } = primordials; const { - errnoException, + ErrnoException, codes: { ERR_ASSERTION, ERR_INVALID_ARG_TYPE, @@ -231,7 +231,7 @@ function wrapProcessMethods(binding) { } if (err) - throw errnoException(err, 'kill'); + throw new ErrnoException(err, 'kill'); return true; } diff --git a/lib/internal/process/signal.js b/lib/internal/process/signal.js index 548f19a65c7602..b636a4be086007 100644 --- a/lib/internal/process/signal.js +++ b/lib/internal/process/signal.js @@ -6,7 +6,7 @@ const { } = primordials; const { - errnoException, + ErrnoException, } = require('internal/errors'); const { signals } = internalBinding('constants').os; @@ -33,7 +33,7 @@ function startListeningIfSignal(type) { const err = wrap.start(signum); if (err) { wrap.close(); - throw errnoException(err, 'uv_signal_start'); + throw new ErrnoException(err, 'uv_signal_start'); } signalWraps.set(type, wrap); diff --git a/lib/internal/stream_base_commons.js b/lib/internal/stream_base_commons.js index 1555956affe6c3..b692e3c30c3869 100644 --- a/lib/internal/stream_base_commons.js +++ b/lib/internal/stream_base_commons.js @@ -17,7 +17,7 @@ const { } = internalBinding('stream_wrap'); const { UV_EOF } = internalBinding('uv'); const { - errnoException, + ErrnoException, } = require('internal/errors'); const { owner_symbol } = require('internal/async_hooks').symbols; const { @@ -91,7 +91,7 @@ function onWriteComplete(status) { // TODO (ronag): This should be moved before if(stream.destroyed) // in order to avoid swallowing error. if (status < 0) { - const ex = errnoException(status, 'write', this.error); + const ex = new ErrnoException(status, 'write', this.error); if (typeof this.callback === 'function') this.callback(ex); else @@ -157,7 +157,7 @@ function afterWriteDispatched(req, err, cb) { req.async = !!streamBaseState[kLastWriteWasAsync]; if (err !== 0) - return cb(errnoException(err, 'write', req.error)); + return cb(new ErrnoException(err, 'write', req.error)); if (!req.async && typeof req.callback === 'function') { req.callback(); @@ -194,7 +194,7 @@ function onStreamRead(arrayBuffer) { if (!stream.destroyed) { const err = handle.readStop(); if (err) - stream.destroy(errnoException(err, 'read')); + stream.destroy(new ErrnoException(err, 'read')); } } @@ -214,7 +214,7 @@ function onStreamRead(arrayBuffer) { if (nread !== UV_EOF) { // CallJSOnreadMethod expects the return value to be a buffer. // Ref: https://github.com/nodejs/node/pull/34375 - stream.destroy(errnoException(nread, 'read')); + stream.destroy(new ErrnoException(nread, 'read')); return; } diff --git a/lib/internal/util.js b/lib/internal/util.js index 558a5da69773bb..4149840b244adc 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -8,6 +8,7 @@ const { ArrayPrototypeSlice, ArrayPrototypeSort, Error, + ErrorCaptureStackTrace, FunctionPrototypeCall, ObjectDefineProperties, ObjectDefineProperty, @@ -44,11 +45,11 @@ const { } = primordials; const { - hideStackFrames, codes: { ERR_NO_CRYPTO, ERR_UNKNOWN_SIGNAL, }, + isErrorStackTraceLimitWritable, uvErrmapGet, overrideStackTrace, } = require('internal/errors'); @@ -693,10 +694,19 @@ const lazyDOMExceptionClass = () => { return _DOMException; }; -const lazyDOMException = hideStackFrames((message, name) => { +const lazyDOMException = (message, name) => { _DOMException ??= internalBinding('messaging').DOMException; + if (isErrorStackTraceLimitWritable()) { + const limit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + const ex = new _DOMException(message, name); + Error.stackTraceLimit = limit; + ErrorCaptureStackTrace(ex, lazyDOMException); + return ex; + } return new _DOMException(message, name); -}); + +}; const kEnumerableProperty = { __proto__: null }; kEnumerableProperty.enumerable = true; diff --git a/lib/internal/validators.js b/lib/internal/validators.js index 77b67a4a37ff91..6abf4d9a3cd7d3 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -22,11 +22,11 @@ const { const { hideStackFrames, codes: { - ERR_SOCKET_BAD_PORT, - ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE, - ERR_OUT_OF_RANGE, - ERR_UNKNOWN_SIGNAL, + ERR_SOCKET_BAD_PORT: { HideStackFramesError: ERR_SOCKET_BAD_PORT }, + ERR_INVALID_ARG_TYPE: { HideStackFramesError: ERR_INVALID_ARG_TYPE }, + ERR_INVALID_ARG_VALUE: { HideStackFramesError: ERR_INVALID_ARG_VALUE }, + ERR_OUT_OF_RANGE: { HideStackFramesError: ERR_OUT_OF_RANGE }, + ERR_UNKNOWN_SIGNAL: { HideStackFramesError: ERR_UNKNOWN_SIGNAL }, }, } = require('internal/errors'); const { normalizeEncoding } = require('internal/util'); @@ -157,10 +157,10 @@ const validateUint32 = hideStackFrames((value, name, positive = false) => { */ /** @type {validateString} */ -function validateString(value, name) { +const validateString = hideStackFrames((value, name) => { if (typeof value !== 'string') throw new ERR_INVALID_ARG_TYPE(name, 'string', value); -} +}); /** * @callback validateNumber @@ -172,7 +172,7 @@ function validateString(value, name) { */ /** @type {validateNumber} */ -function validateNumber(value, name, min = undefined, max) { +const validateNumber = hideStackFrames((value, name, min = undefined, max) => { if (typeof value !== 'number') throw new ERR_INVALID_ARG_TYPE(name, 'number', value); @@ -183,7 +183,7 @@ function validateNumber(value, name, min = undefined, max) { `${min != null ? `>= ${min}` : ''}${min != null && max != null ? ' && ' : ''}${max != null ? `<= ${max}` : ''}`, value); } -} +}); /** * @callback validateOneOf @@ -213,10 +213,10 @@ const validateOneOf = hideStackFrames((value, name, oneOf) => { */ /** @type {validateBoolean} */ -function validateBoolean(value, name) { +const validateBoolean = hideStackFrames((value, name) => { if (typeof value !== 'boolean') throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); -} +}); const kValidateObjectNone = 0; const kValidateObjectAllowNullable = 1 << 0; @@ -309,7 +309,7 @@ const validateArray = hideStackFrames((value, name, minLength = 0) => { */ /** @type {validateStringArray} */ -function validateStringArray(value, name) { +const validateStringArray = hideStackFrames((value, name) => { validateArray(value, name); for (let i = 0; i < value.length; ++i) { // Don't use validateString here for performance reasons, as @@ -318,7 +318,7 @@ function validateStringArray(value, name) { throw new ERR_INVALID_ARG_TYPE(`${name}[${i}]`, 'string', value[i]); } } -} +}); /** * @callback validateBooleanArray @@ -328,7 +328,7 @@ function validateStringArray(value, name) { */ /** @type {validateBooleanArray} */ -function validateBooleanArray(value, name) { +const validateBooleanArray = hideStackFrames((value, name) => { validateArray(value, name); for (let i = 0; i < value.length; ++i) { // Don't use validateBoolean here for performance reasons, as @@ -337,7 +337,7 @@ function validateBooleanArray(value, name) { throw new ERR_INVALID_ARG_TYPE(`${name}[${i}]`, 'boolean', value[i]); } } -} +}); /** * @callback validateAbortSignalArray @@ -364,7 +364,7 @@ function validateAbortSignalArray(value, name) { * @param {string} [name='signal'] * @returns {asserts signal is keyof signals} */ -function validateSignalName(signal, name = 'signal') { +const validateSignalName = hideStackFrames((signal, name = 'signal') => { validateString(signal, name); if (signals[signal] === undefined) { @@ -375,7 +375,7 @@ function validateSignalName(signal, name = 'signal') { throw new ERR_UNKNOWN_SIGNAL(signal); } -} +}); /** * @callback validateBuffer @@ -397,7 +397,7 @@ const validateBuffer = hideStackFrames((buffer, name = 'buffer') => { * @param {string} data * @param {string} encoding */ -function validateEncoding(data, encoding) { +const validateEncoding = hideStackFrames((data, encoding) => { const normalizedEncoding = normalizeEncoding(encoding); const length = data.length; @@ -405,7 +405,7 @@ function validateEncoding(data, encoding) { throw new ERR_INVALID_ARG_VALUE('encoding', encoding, `is invalid for data of length ${length}`); } -} +}); /** * Check that the port number is not NaN when coerced to a number, @@ -415,7 +415,7 @@ function validateEncoding(data, encoding) { * @param {boolean} [allowZero=true] * @returns {number} */ -function validatePort(port, name = 'Port', allowZero = true) { +const validatePort = hideStackFrames((port, name = 'Port', allowZero = true) => { if ((typeof port !== 'number' && typeof port !== 'string') || (typeof port === 'string' && StringPrototypeTrim(port).length === 0) || +port !== (+port >>> 0) || @@ -424,7 +424,7 @@ function validatePort(port, name = 'Port', allowZero = true) { throw new ERR_SOCKET_BAD_PORT(name, port, allowZero); } return port | 0; -} +}); /** * @callback validateAbortSignal @@ -507,7 +507,7 @@ const linkValueRegExp = /^(?:<[^>]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/; * @param {any} value * @param {string} name */ -function validateLinkHeaderFormat(value, name) { +const validateLinkHeaderFormat = hideStackFrames((value, name) => { if ( typeof value === 'undefined' || !RegExpPrototypeExec(linkValueRegExp, value) @@ -518,7 +518,7 @@ function validateLinkHeaderFormat(value, name) { 'must be an array or string of format "; rel=preload; as=style"', ); } -} +}); const validateInternalField = hideStackFrames((object, fieldKey, className) => { if (typeof object !== 'object' || object === null || !ObjectPrototypeHasOwnProperty(object, fieldKey)) { @@ -530,9 +530,9 @@ const validateInternalField = hideStackFrames((object, fieldKey, className) => { * @param {any} hints * @return {string} */ -function validateLinkHeaderValue(hints) { +const validateLinkHeaderValue = hideStackFrames((hints) => { if (typeof hints === 'string') { - validateLinkHeaderFormat(hints, 'hints'); + validateLinkHeaderFormat.withoutStackTrace(hints, 'hints'); return hints; } else if (ArrayIsArray(hints)) { const hintsLength = hints.length; @@ -544,7 +544,7 @@ function validateLinkHeaderValue(hints) { for (let i = 0; i < hintsLength; i++) { const link = hints[i]; - validateLinkHeaderFormat(link, 'hints'); + validateLinkHeaderFormat.withoutStackTrace(link, 'hints'); result += link; if (i !== hintsLength - 1) { @@ -560,7 +560,7 @@ function validateLinkHeaderValue(hints) { hints, 'must be an array or string of format "; rel=preload; as=style"', ); -} +}); module.exports = { isInt32, diff --git a/lib/internal/webstreams/adapters.js b/lib/internal/webstreams/adapters.js index 3e5fd69d4d6a03..878fd0a0d4d76b 100644 --- a/lib/internal/webstreams/adapters.js +++ b/lib/internal/webstreams/adapters.js @@ -46,7 +46,7 @@ const { } = require('buffer'); const { - errnoException, + ErrnoException, codes: { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -856,7 +856,7 @@ function newWritableStreamFromStreamBase(streamBase, strategy) { function onWriteComplete(status) { if (status < 0) { - const error = errnoException(status, 'write', this.error); + const error = new ErrnoException(status, 'write', this.error); this.promise.reject(error); this.controller.error(error); return; @@ -879,7 +879,7 @@ function newWritableStreamFromStreamBase(streamBase, strategy) { } if (ret !== 0) - promise.reject(errnoException(ret, 'write', req)); + promise.reject(new ErrnoException(ret, 'write', req)); else if (!req.async) promise.resolve(); diff --git a/lib/net.js b/lib/net.js index 7f9b1e5c084d8c..02bc1cd713770e 100644 --- a/lib/net.js +++ b/lib/net.js @@ -106,11 +106,11 @@ const { ERR_SOCKET_CLOSED_BEFORE_CONNECTION, ERR_MISSING_ARGS, }, - aggregateErrors, - errnoException, - exceptionWithHostPort, + ErrnoException, + ExceptionWithHostPort, genericNodeError, - uvExceptionWithHostPort, + NodeAggregateError, + UVExceptionWithHostPort, } = require('internal/errors'); const { isUint8Array } = require('internal/util/types'); const { queueMicrotask } = require('internal/process/task_queues'); @@ -425,7 +425,7 @@ function Socket(options) { // which cannot be opened. This is difficult to test as most // un-openable fds will throw on `createHandle` if (err) - throw errnoException(err, 'open'); + throw new ErrnoException(err, 'open'); this[async_id_symbol] = this._handle.getAsyncId(); @@ -434,7 +434,7 @@ function Socket(options) { // Make stdout and stderr blocking on Windows err = this._handle.setBlocking(true); if (err) - throw errnoException(err, 'setBlocking'); + throw new ErrnoException(err, 'setBlocking'); this._writev = null; this._write = makeSyncWrite(fd); @@ -533,7 +533,7 @@ Socket.prototype._final = function(cb) { if (err === 1 || err === UV_ENOTCONN) // synchronous finish return cb(); else if (err !== 0) - return cb(errnoException(err, 'shutdown')); + return cb(new ErrnoException(err, 'shutdown')); }; function afterShutdown() { @@ -698,7 +698,7 @@ function tryReadStart(socket) { socket._handle.reading = true; const err = socket._handle.readStart(); if (err) - socket.destroy(errnoException(err, 'read')); + socket.destroy(new ErrnoException(err, 'read')); } // Just call handle.readStart until we have enough in the buffer @@ -747,7 +747,7 @@ Socket.prototype.pause = function() { if (!this.destroyed) { const err = this._handle.readStop(); if (err) - this.destroy(errnoException(err, 'read')); + this.destroy(new ErrnoException(err, 'read')); } } return stream.Duplex.prototype.pause.call(this); @@ -816,7 +816,7 @@ Socket.prototype._destroy = function(exception, cb) { this.emit('close', isException); }); if (err) - this.emit('error', errnoException(err, 'reset')); + this.emit('error', new ErrnoException(err, 'reset')); } else if (this._closeAfterHandlingError) { // Enqueue closing the socket as a microtask, so that the socket can be // accessible when an `error` event is handled in the `next tick queue`. @@ -1051,7 +1051,7 @@ function internalConnect( err = checkBindError(err, localPort, self._handle); if (err) { - const ex = exceptionWithHostPort(err, 'bind', localAddress, localPort); + const ex = new ExceptionWithHostPort(err, 'bind', localAddress, localPort); self.destroy(ex); return; } @@ -1087,7 +1087,7 @@ function internalConnect( details = sockname.address + ':' + sockname.port; } - const ex = exceptionWithHostPort(err, 'connect', address, port, details); + const ex = new ExceptionWithHostPort(err, 'connect', address, port, details); self.destroy(ex); } else if ((addressType === 6 || addressType === 4) && hasObserver('net')) { startPerf(self, kPerfHooksNetConnectContext, { type: 'net', name: 'connect', detail: { host: address, port } }); @@ -1111,7 +1111,7 @@ function internalConnectMultiple(context, canceled) { return; } - self.destroy(aggregateErrors(context.errors)); + self.destroy(new NodeAggregateError(context.errors)); return; } @@ -1142,7 +1142,7 @@ function internalConnectMultiple(context, canceled) { err = checkBindError(err, localPort, self._handle); if (err) { - ArrayPrototypePush(context.errors, exceptionWithHostPort(err, 'bind', localAddress, localPort)); + ArrayPrototypePush(context.errors, new ExceptionWithHostPort(err, 'bind', localAddress, localPort)); internalConnectMultiple(context); return; } @@ -1173,7 +1173,7 @@ function internalConnectMultiple(context, canceled) { details = sockname.address + ':' + sockname.port; } - ArrayPrototypePush(context.errors, exceptionWithHostPort(err, 'connect', address, port, details)); + ArrayPrototypePush(context.errors, new ExceptionWithHostPort(err, 'connect', address, port, details)); internalConnectMultiple(context); return; } @@ -1592,11 +1592,11 @@ function afterConnect(status, handle, req, readable, writable) { if (req.localAddress && req.localPort) { details = req.localAddress + ':' + req.localPort; } - const ex = exceptionWithHostPort(status, - 'connect', - req.address, - req.port, - details); + const ex = new ExceptionWithHostPort(status, + 'connect', + req.address, + req.port, + details); if (details) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; @@ -1631,11 +1631,11 @@ function createConnectionError(req, status) { details = req.localAddress + ':' + req.localPort; } - const ex = exceptionWithHostPort(status, - 'connect', - req.address, - req.port, - details); + const ex = new ExceptionWithHostPort(status, + 'connect', + req.address, + req.port, + details); if (details) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; @@ -1852,7 +1852,7 @@ function setupListenHandle(address, port, addressType, backlog, fd, flags) { rval = createServerHandle(address, port, addressType, fd, flags); if (typeof rval === 'number') { - const error = uvExceptionWithHostPort(rval, 'listen', address, port); + const error = new UVExceptionWithHostPort(rval, 'listen', address, port); process.nextTick(emitErrorNT, this, error); return; } @@ -1869,7 +1869,7 @@ function setupListenHandle(address, port, addressType, backlog, fd, flags) { const err = this._handle.listen(backlog || 511); if (err) { - const ex = uvExceptionWithHostPort(err, 'listen', address, port); + const ex = new UVExceptionWithHostPort(err, 'listen', address, port); this._handle.close(); this._handle = null; defaultTriggerAsyncIdScope(this[async_id_symbol], @@ -1937,7 +1937,7 @@ function listenInCluster(server, address, port, addressType, err = checkBindError(err, port, handle); if (err) { - const ex = exceptionWithHostPort(err, 'bind', address, port); + const ex = new ExceptionWithHostPort(err, 'bind', address, port); return server.emit('error', ex); } @@ -2045,7 +2045,7 @@ Server.prototype.listen = function(...args) { if (err) { this._handle.close(); this._handle = null; - throw errnoException(err, 'uv_pipe_chmod'); + throw new ErrnoException(err, 'uv_pipe_chmod'); } } return this; @@ -2086,7 +2086,7 @@ Server.prototype.address = function() { const out = {}; const err = this._handle.getsockname(out); if (err) { - throw errnoException(err, 'address'); + throw new ErrnoException(err, 'address'); } return out; } else if (this._pipeName) { @@ -2102,7 +2102,7 @@ function onconnection(err, clientHandle) { debug('onconnection'); if (err) { - self.emit('error', errnoException(err, 'accept')); + self.emit('error', new ErrnoException(err, 'accept')); return; } diff --git a/lib/os.js b/lib/os.js index 1ad17c635182d4..bef4936c7c0bbe 100644 --- a/lib/os.js +++ b/lib/os.js @@ -65,7 +65,7 @@ function getCheckedFunction(fn) { const ctx = {}; const ret = fn(ctx); if (ret === undefined) { - throw new ERR_SYSTEM_ERROR(ctx); + throw new ERR_SYSTEM_ERROR.HideStackFramesError(ctx); } return ret; }); diff --git a/lib/tty.js b/lib/tty.js index fb0319f93ebe67..aa929cd9ba0d08 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -74,7 +74,7 @@ ReadStream.prototype.setRawMode = function(flag) { flag = !!flag; const err = this._handle?.setRawMode(flag); if (err) { - this.emit('error', errors.errnoException(err, 'setRawMode')); + this.emit('error', new errors.ErrnoException(err, 'setRawMode')); return this; } this.isRaw = flag; @@ -129,7 +129,7 @@ WriteStream.prototype._refreshSize = function() { const winSize = new Array(2); const err = this._handle.getWindowSize(winSize); if (err) { - this.emit('error', errors.errnoException(err, 'getWindowSize')); + this.emit('error', new errors.ErrnoException(err, 'getWindowSize')); return; } const { 0: newCols, 1: newRows } = winSize; diff --git a/lib/util.js b/lib/util.js index 7fb7994e6536ba..9ddb332f866355 100644 --- a/lib/util.js +++ b/lib/util.js @@ -32,6 +32,7 @@ const { DatePrototypeGetMonth, DatePrototypeGetSeconds, Error, + ErrorCaptureStackTrace, FunctionPrototypeBind, NumberIsSafeInteger, ObjectDefineProperties, @@ -51,9 +52,9 @@ const { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, }, - errnoException, - exceptionWithHostPort, - hideStackFrames, + isErrorStackTraceLimitWritable, + ErrnoException, + ExceptionWithHostPort, } = require('internal/errors'); const { format, @@ -278,16 +279,17 @@ function _extend(target, source) { return target; } -const callbackifyOnRejected = hideStackFrames((reason, cb) => { +const callbackifyOnRejected = (reason, cb) => { // `!reason` guard inspired by bluebird (Ref: https://goo.gl/t5IS6M). // Because `null` is a special error value in callbacks which means "no error // occurred", we error-wrap so the callback consumer can distinguish between // "the promise rejected with null" or "the promise fulfilled with undefined". if (!reason) { - reason = new ERR_FALSY_VALUE_REJECTION(reason); + reason = new ERR_FALSY_VALUE_REJECTION.HideStackFramesError(reason); + ErrorCaptureStackTrace(reason, callbackifyOnRejected); } return cb(reason); -}); +}; /** * @template {(...args: any[]) => Promise} T @@ -345,10 +347,34 @@ function getSystemErrorName(err) { return internalErrorName(err); } +function _errnoException(...args) { + if (isErrorStackTraceLimitWritable()) { + const limit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + const e = new ErrnoException(...args); + Error.stackTraceLimit = limit; + ErrorCaptureStackTrace(e, _exceptionWithHostPort); + return e; + } + return new ErrnoException(...args); +} + +function _exceptionWithHostPort(...args) { + if (isErrorStackTraceLimitWritable()) { + const limit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + const e = new ExceptionWithHostPort(...args); + Error.stackTraceLimit = limit; + ErrorCaptureStackTrace(e, _exceptionWithHostPort); + return e; + } + return new ExceptionWithHostPort(...args); +} + // Keep the `exports =` so that various functions can still be monkeypatched module.exports = { - _errnoException: errnoException, - _exceptionWithHostPort: exceptionWithHostPort, + _errnoException, + _exceptionWithHostPort, _extend, callbackify, debug: debuglog, diff --git a/lib/zlib.js b/lib/zlib.js index 2b90c6f91fed76..3766938f6bc7bb 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -211,10 +211,10 @@ const checkFiniteNumber = hideStackFrames((number, name) => { return false; } - validateNumber(number, name); + validateNumber.withoutStackTrace(number, name); // Infinite numbers - throw new ERR_OUT_OF_RANGE(name, 'a finite number', number); + throw new ERR_OUT_OF_RANGE.HideStackFramesError(name, 'a finite number', number); }); // 1. Returns def for number when it's undefined or NaN @@ -223,12 +223,12 @@ const checkFiniteNumber = hideStackFrames((number, name) => { // 4. Throws ERR_OUT_OF_RANGE for infinite numbers or numbers > upper or < lower const checkRangesOrGetDefault = hideStackFrames( (number, name, lower, upper, def) => { - if (!checkFiniteNumber(number, name)) { + if (!checkFiniteNumber.withoutStackTrace(number, name)) { return def; } if (number < lower || number > upper) { - throw new ERR_OUT_OF_RANGE(name, - `>= ${lower} and <= ${upper}`, number); + throw new ERR_OUT_OF_RANGE.HideStackFramesError(name, + `>= ${lower} and <= ${upper}`, number); } return number; }, diff --git a/test/fixtures/errors/error_aggregateTwoErrors.snapshot b/test/fixtures/errors/error_aggregateTwoErrors.snapshot index a8d13c461b2cb0..77715c40cbfb92 100644 --- a/test/fixtures/errors/error_aggregateTwoErrors.snapshot +++ b/test/fixtures/errors/error_aggregateTwoErrors.snapshot @@ -2,7 +2,8 @@ throw aggregateTwoErrors(err, originalError); ^ -[AggregateError: original] { +AggregateError: original + at Object. (*error_aggregateTwoErrors.js:*:*) { code: 'ERR0', [errors]: [ Error: original diff --git a/test/parallel/test-dns-memory-error.js b/test/parallel/test-dns-memory-error.js index 29907fb1dbfd5e..c95715f80a735c 100644 --- a/test/parallel/test-dns-memory-error.js +++ b/test/parallel/test-dns-memory-error.js @@ -11,6 +11,8 @@ const errors = require('internal/errors'); const { internalBinding } = require('internal/test/binding'); const { UV_EAI_MEMORY } = internalBinding('uv'); -const memoryError = errors.dnsException(UV_EAI_MEMORY, 'fhqwhgads'); +const memoryError = new errors.DNSException(UV_EAI_MEMORY, 'fhqwhgads'); assert.strictEqual(memoryError.code, 'EAI_MEMORY'); +const stack = memoryError.stack.split('\n'); +assert.match(stack[1], /^ {4}at Object/); diff --git a/test/parallel/test-error-aggregateTwoErrors.js b/test/parallel/test-error-aggregateTwoErrors.js index 89224efd6b161f..2332437d990164 100644 --- a/test/parallel/test-error-aggregateTwoErrors.js +++ b/test/parallel/test-error-aggregateTwoErrors.js @@ -57,3 +57,15 @@ assert.strictEqual(aggregateTwoErrors(null, null), null); assert.strictEqual(chainedError.code, err0.code); assert.deepStrictEqual(chainedError.errors, [err0, err1]); } + +{ + const err0 = new Error('original'); + const err1 = new Error('second error'); + + err0.code = 'ERR0'; + err1.code = 'ERR1'; + + const chainedError = aggregateTwoErrors(null, aggregateTwoErrors(err1, err0)); + const stack = chainedError.stack.split('\n'); + assert.match(stack[1], /^ {4}at Object/); +} diff --git a/test/parallel/test-errors-hide-stack-frames.js b/test/parallel/test-errors-hide-stack-frames.js new file mode 100644 index 00000000000000..fdaeb96fad76bb --- /dev/null +++ b/test/parallel/test-errors-hide-stack-frames.js @@ -0,0 +1,242 @@ +// Flags: --expose-internals +'use strict'; + +require('../common'); +const { hideStackFrames, codes } = require('internal/errors'); +const { validateInteger } = require('internal/validators'); +const assert = require('assert'); + +{ + // Test that the a built-in error has the correct name and message. + function a() { + b(); + } + + function b() { + c(); + } + + const c = hideStackFrames(function() { + throw new Error('test'); + }); + + try { + a(); + } catch (e) { + assert.strictEqual(e.name, 'Error'); + assert.strictEqual(e.message, 'test'); + } +} + +{ + // Test that validator errors have the correct name and message. + try { + validateInteger('2', 'test'); + } catch (e) { + assert.strictEqual(e.name, 'TypeError'); + assert.strictEqual(e.message, 'The "test" argument must be of type number. Received type string (\'2\')'); + } +} + +{ + // Test that validator fn is not in the stack trace. + + function a(value) { + validateInteger(value, 'test'); + } + try { + a('2'); + } catch (e) { + assert.strictEqual(Error.stackTraceLimit, 10); + const stack = e.stack.split('\n'); + assert.doesNotMatch(stack[1], /validateInteger/); + assert.match(stack[1], /at a/); + } +} + +{ + // Test that the stack trace is hidden for normal unnamed functions. + function a() { + b(); + } + + function b() { + c(); + } + + const c = hideStackFrames(function() { + throw new Error('test'); + }); + + try { + a(); + } catch (e) { + assert.strictEqual(Error.stackTraceLimit, 10); + const stack = e.stack.split('\n'); + assert.doesNotMatch(stack[1], /at c/); + assert.match(stack[1], /at b/); + assert.strictEqual(Error.stackTraceLimit, 10); + } +} + +{ + // Test that the stack trace is hidden for normal functions. + function a() { + b(); + } + + function b() { + c(); + } + + const c = hideStackFrames(function c() { + throw new Error('test'); + }); + + try { + a(); + } catch (e) { + assert.strictEqual(Error.stackTraceLimit, 10); + const stack = e.stack.split('\n'); + assert.doesNotMatch(stack[1], /at c/); + assert.match(stack[1], /at b/); + assert.strictEqual(Error.stackTraceLimit, 10); + } +} + +{ + // Test that the stack trace is hidden for arrow functions. + function a() { + b(); + } + + function b() { + c(); + } + + const c = hideStackFrames(() => { + throw new Error('test'); + }); + + try { + a(); + } catch (e) { + assert.strictEqual(Error.stackTraceLimit, 10); + const stack = e.stack.split('\n'); + assert.doesNotMatch(stack[1], /at c/); + assert.match(stack[1], /at b/); + assert.strictEqual(Error.stackTraceLimit, 10); + } +} + +{ + // Creating a new Error object without stack trace, then throwing it + // should get a stack trace by hideStackFrames. + function a() { + b(); + } + + function b() { + c(); + } + + const c = hideStackFrames(function() { + throw new Error('test'); + }); + + try { + a(); + } catch (e) { + assert.strictEqual(Error.stackTraceLimit, 10); + const stack = e.stack.split('\n'); + assert.doesNotMatch(stack[1], /at c/); + assert.match(stack[1], /at b/); + assert.strictEqual(Error.stackTraceLimit, 10); + } +} + +{ + const ERR_ACCESS_DENIED = codes.ERR_ACCESS_DENIED; + // Creating a new Error object without stack trace, then throwing it + // should get a stack trace by hideStackFrames. + function a() { + b(); + } + + function b() { + c(); + } + + const c = hideStackFrames(function() { + throw new ERR_ACCESS_DENIED.NoStackError('test'); + }); + + try { + a(); + } catch (e) { + assert.strictEqual(Error.stackTraceLimit, 10); + const stack = e.stack.split('\n'); + assert.doesNotMatch(stack[1], /at c/); + assert.match(stack[1], /at b/); + assert.strictEqual(Error.stackTraceLimit, 10); + } +} + +{ + // Creating a new Error object with stack trace, then throwing it + // should get a stack trace by hideStackFrames. + function a() { + b(); + } + + const b = hideStackFrames(function b() { + c(); + }); + + const c = hideStackFrames(function() { + throw new Error('test'); + }); + + try { + a(); + } catch (e) { + assert.strictEqual(Error.stackTraceLimit, 10); + const stack = e.stack.split('\n'); + assert.match(stack[1], /at a/); + assert.strictEqual(Error.stackTraceLimit, 10); + } +} + +{ + // Binding passes the value of this to the wrapped function. + let called = false; + function a() { + b.bind({ key: 'value' })(); + } + + const b = hideStackFrames(function b() { + assert.strictEqual(this.key, 'value'); + called = true; + }); + + a(); + + assert.strictEqual(called, true); +} + +{ + // Binding passes the value of this to the withoutStackTrace function. + let called = false; + function a() { + b.withoutStackTrace.bind({ key: 'value' })(); + } + + const b = hideStackFrames(function b() { + assert.strictEqual(this.key, 'value'); + called = true; + }); + + a(); + + assert.strictEqual(called, true); +} diff --git a/test/parallel/test-util-callbackify.js b/test/parallel/test-util-callbackify.js index 444e10b16bf85a..f287c91e946217 100644 --- a/test/parallel/test-util-callbackify.js +++ b/test/parallel/test-util-callbackify.js @@ -225,6 +225,7 @@ const values = [ const errLines = stderr.trim().split(/[\r\n]+/); const errLine = errLines.find((l) => /^Error/.exec(l)); assert.strictEqual(errLine, `Error: ${fixture}`); + assert.strictEqual(errLines.length, 7); }) ); } @@ -279,3 +280,20 @@ const values = [ }); }); } + +{ + // Test Promise factory + function promiseFn(value) { + return Promise.reject(value); + } + + const cbPromiseFn = callbackify(promiseFn); + + cbPromiseFn(null, (err) => { + assert.strictEqual(err.message, 'Promise was rejected with falsy value'); + assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION'); + assert.strictEqual(err.reason, null); + const stack = err.stack.split(/[\r\n]+/); + assert.match(stack[1], /at process\.processTicksAndRejections/); + }); +} diff --git a/test/parallel/test-uv-unmapped-exception.js b/test/parallel/test-uv-unmapped-exception.js index 0b63461dd25dee..02a52062228ab5 100644 --- a/test/parallel/test-uv-unmapped-exception.js +++ b/test/parallel/test-uv-unmapped-exception.js @@ -2,10 +2,10 @@ 'use strict'; require('../common'); const assert = require('assert'); -const { uvException, uvExceptionWithHostPort } = require('internal/errors'); +const { UVException, UVExceptionWithHostPort } = require('internal/errors'); { - const exception = uvException({ errno: 100, syscall: 'open' }); + const exception = new UVException({ errno: 100, syscall: 'open' }); assert.strictEqual(exception.message, 'UNKNOWN: unknown error, open'); assert.strictEqual(exception.errno, 100); @@ -14,7 +14,7 @@ const { uvException, uvExceptionWithHostPort } = require('internal/errors'); } { - const exception = uvExceptionWithHostPort(100, 'listen', '127.0.0.1', 80); + const exception = new UVExceptionWithHostPort(100, 'listen', '127.0.0.1', 80); assert.strictEqual(exception.message, 'listen UNKNOWN: unknown error 127.0.0.1:80');