UNPKG

9.58 kBJavaScriptView Raw
1// https://github.com/nodejs/node/blob/1aab13cad9c800f4121c1d35b554b78c1b17bdbd/lib/internal/errors.js
2
3'use strict'
4
5const {
6 ArrayPrototypeFilter,
7 ArrayPrototypeJoin,
8 ArrayPrototypeUnshift,
9 Error,
10 ErrorCaptureStackTrace,
11 ObjectDefineProperty,
12 ObjectDefineProperties,
13 ObjectIsExtensible,
14 ObjectGetOwnPropertyDescriptor,
15 ObjectPrototypeHasOwnProperty,
16 ReflectApply,
17 SafeMap,
18 SafeWeakMap,
19 StringPrototypeMatch,
20 StringPrototypeStartsWith,
21 Symbol,
22 SymbolFor
23} = require('#internal/per_context/primordials')
24
25const kIsNodeError = Symbol('kIsNodeError')
26
27const messages = new SafeMap()
28const codes = {}
29
30const overrideStackTrace = new SafeWeakMap()
31let userStackTraceLimit
32const nodeInternalPrefix = '__node_internal_'
33
34// Lazily loaded
35let assert
36
37let internalUtilInspect = null
38function lazyInternalUtilInspect () {
39 if (!internalUtilInspect) {
40 internalUtilInspect = require('#internal/util/inspect')
41 }
42 return internalUtilInspect
43}
44
45let buffer
46function lazyBuffer () {
47 if (buffer === undefined) { buffer = require('buffer').Buffer }
48 return buffer
49}
50
51function isErrorStackTraceLimitWritable () {
52 const desc = ObjectGetOwnPropertyDescriptor(Error, 'stackTraceLimit')
53 if (desc === undefined) {
54 return ObjectIsExtensible(Error)
55 }
56
57 return ObjectPrototypeHasOwnProperty(desc, 'writable')
58 ? desc.writable
59 : desc.set !== undefined
60}
61
62function inspectWithNoCustomRetry (obj, options) {
63 const utilInspect = lazyInternalUtilInspect()
64
65 try {
66 return utilInspect.inspect(obj, options)
67 } catch {
68 return utilInspect.inspect(obj, { ...options, customInspect: false })
69 }
70}
71
72// A specialized Error that includes an additional info property with
73// additional information about the error condition.
74// It has the properties present in a UVException but with a custom error
75// message followed by the uv error code and uv error message.
76// It also has its own error code with the original uv error context put into
77// `err.info`.
78// The context passed into this error must have .code, .syscall and .message,
79// and may have .path and .dest.
80class SystemError extends Error {
81 constructor (key, context) {
82 const limit = Error.stackTraceLimit
83 if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0
84 super()
85 // Reset the limit and setting the name property.
86 if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit
87 const prefix = getMessage(key, [], this)
88 let message = `${prefix}: ${context.syscall} returned ` +
89 `${context.code} (${context.message})`
90
91 if (context.path !== undefined) { message += ` ${context.path}` }
92 if (context.dest !== undefined) { message += ` => ${context.dest}` }
93
94 captureLargerStackTrace(this)
95
96 this.code = key
97
98 ObjectDefineProperties(this, {
99 [kIsNodeError]: {
100 value: true,
101 enumerable: false,
102 writable: false,
103 configurable: true
104 },
105 name: {
106 value: 'SystemError',
107 enumerable: false,
108 writable: true,
109 configurable: true
110 },
111 message: {
112 value: message,
113 enumerable: false,
114 writable: true,
115 configurable: true
116 },
117 info: {
118 value: context,
119 enumerable: true,
120 configurable: true,
121 writable: false
122 },
123 errno: {
124 get () {
125 return context.errno
126 },
127 set: (value) => {
128 context.errno = value
129 },
130 enumerable: true,
131 configurable: true
132 },
133 syscall: {
134 get () {
135 return context.syscall
136 },
137 set: (value) => {
138 context.syscall = value
139 },
140 enumerable: true,
141 configurable: true
142 }
143 })
144
145 if (context.path !== undefined) {
146 // TODO(BridgeAR): Investigate why and when the `.toString()` was
147 // introduced. The `path` and `dest` properties in the context seem to
148 // always be of type string. We should probably just remove the
149 // `.toString()` and `Buffer.from()` operations and set the value on the
150 // context as the user did.
151 ObjectDefineProperty(this, 'path', {
152 get () {
153 return context.path != null
154 ? context.path.toString()
155 : context.path
156 },
157 set: (value) => {
158 context.path = value
159 ? lazyBuffer().from(value.toString())
160 : undefined
161 },
162 enumerable: true,
163 configurable: true
164 })
165 }
166
167 if (context.dest !== undefined) {
168 ObjectDefineProperty(this, 'dest', {
169 get () {
170 return context.dest != null
171 ? context.dest.toString()
172 : context.dest
173 },
174 set: (value) => {
175 context.dest = value
176 ? lazyBuffer().from(value.toString())
177 : undefined
178 },
179 enumerable: true,
180 configurable: true
181 })
182 }
183 }
184
185 toString () {
186 return `${this.name} [${this.code}]: ${this.message}`
187 }
188
189 [SymbolFor('nodejs.util.inspect.custom')] (recurseTimes, ctx) {
190 return lazyInternalUtilInspect().inspect(this, {
191 ...ctx,
192 getters: true,
193 customInspect: false
194 })
195 }
196}
197
198function makeSystemErrorWithCode (key) {
199 return class NodeError extends SystemError {
200 constructor (ctx) {
201 super(key, ctx)
202 }
203 }
204}
205
206function makeNodeErrorWithCode (Base, key) {
207 return function NodeError (...args) {
208 const limit = Error.stackTraceLimit
209 if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0
210 const error = new Base()
211 // Reset the limit and setting the name property.
212 if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit
213 const message = getMessage(key, args, error)
214 ObjectDefineProperties(error, {
215 [kIsNodeError]: {
216 value: true,
217 enumerable: false,
218 writable: false,
219 configurable: true
220 },
221 message: {
222 value: message,
223 enumerable: false,
224 writable: true,
225 configurable: true
226 },
227 toString: {
228 value () {
229 return `${this.name} [${key}]: ${this.message}`
230 },
231 enumerable: false,
232 writable: true,
233 configurable: true
234 }
235 })
236 captureLargerStackTrace(error)
237 error.code = key
238 return error
239 }
240}
241
242/**
243 * This function removes unnecessary frames from Node.js core errors.
244 * @template {(...args: any[]) => any} T
245 * @type {(fn: T) => T}
246 */
247function hideStackFrames (fn) {
248 // We rename the functions that will be hidden to cut off the stacktrace
249 // at the outermost one
250 const hidden = nodeInternalPrefix + fn.name
251 ObjectDefineProperty(fn, 'name', { value: hidden })
252 return fn
253}
254
255// Utility function for registering the error codes. Only used here. Exported
256// *only* to allow for testing.
257function E (sym, val, def, ...otherClasses) {
258 // Special case for SystemError that formats the error message differently
259 // The SystemErrors only have SystemError as their base classes.
260 messages.set(sym, val)
261 if (def === SystemError) {
262 def = makeSystemErrorWithCode(sym)
263 } else {
264 def = makeNodeErrorWithCode(def, sym)
265 }
266
267 if (otherClasses.length !== 0) {
268 otherClasses.forEach((clazz) => {
269 def[clazz.name] = makeNodeErrorWithCode(clazz, sym)
270 })
271 }
272 codes[sym] = def
273}
274
275function getMessage (key, args, self) {
276 const msg = messages.get(key)
277
278 if (assert === undefined) assert = require('#internal/assert')
279
280 if (typeof msg === 'function') {
281 assert(
282 msg.length <= args.length, // Default options do not count.
283 `Code: ${key}; The provided arguments length (${args.length}) does not ` +
284 `match the required ones (${msg.length}).`
285 )
286 return ReflectApply(msg, self, args)
287 }
288
289 const expectedLength =
290 (StringPrototypeMatch(msg, /%[dfijoOs]/g) || []).length
291 assert(
292 expectedLength === args.length,
293 `Code: ${key}; The provided arguments length (${args.length}) does not ` +
294 `match the required ones (${expectedLength}).`
295 )
296 if (args.length === 0) { return msg }
297
298 ArrayPrototypeUnshift(args, msg)
299 return ReflectApply(lazyInternalUtilInspect().format, null, args)
300}
301
302const captureLargerStackTrace = hideStackFrames(
303 function captureLargerStackTrace (err) {
304 const stackTraceLimitIsWritable = isErrorStackTraceLimitWritable()
305 if (stackTraceLimitIsWritable) {
306 userStackTraceLimit = Error.stackTraceLimit
307 Error.stackTraceLimit = Infinity
308 }
309 ErrorCaptureStackTrace(err)
310 // Reset the limit
311 if (stackTraceLimitIsWritable) Error.stackTraceLimit = userStackTraceLimit
312
313 return err
314 })
315
316// Hide stack lines before the first user code line.
317function hideInternalStackFrames (error) {
318 overrideStackTrace.set(error, (error, stackFrames) => {
319 let frames = stackFrames
320 if (typeof stackFrames === 'object') {
321 frames = ArrayPrototypeFilter(
322 stackFrames,
323 (frm) => !StringPrototypeStartsWith(frm.getFileName() || '',
324 'node:internal')
325 )
326 }
327 ArrayPrototypeUnshift(frames, error)
328 return ArrayPrototypeJoin(frames, '\n at ')
329 })
330}
331
332module.exports = {
333 codes,
334 inspectWithNoCustomRetry,
335 kIsNodeError
336}
337
338E('ERR_TEST_FAILURE', function (error, failureType) {
339 hideInternalStackFrames(this)
340 assert(typeof failureType === 'string',
341 "The 'failureType' argument must be of type string.")
342
343 let msg = error?.message ?? error
344
345 if (typeof msg !== 'string') {
346 msg = inspectWithNoCustomRetry(msg)
347 }
348
349 this.failureType = failureType
350 this.cause = error
351 return msg
352}, Error)