Skip to content

Commit a12adde

Browse files
committed
errors: improve hideStackFrames
1 parent 092fb9f commit a12adde

16 files changed

+397
-166
lines changed
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const assert = require('assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
type: [
8+
'hide-stackframes',
9+
'direct-call',
10+
],
11+
n: [1e7],
12+
}, {
13+
flags: ['--expose-internals'],
14+
});
15+
16+
function main({ n, type }) {
17+
const {
18+
hideStackFrames,
19+
codes: {
20+
ERR_INVALID_ARG_TYPE,
21+
},
22+
} = require('internal/errors');
23+
24+
const testfn = (value) => {
25+
if (typeof value !== 'number') {
26+
throw new ERR_INVALID_ARG_TYPE('Benchmark', 'number', value);
27+
}
28+
};
29+
30+
const hideStackFramesTestfn = hideStackFrames((value) => {
31+
if (typeof value !== 'number') {
32+
throw new ERR_INVALID_ARG_TYPE.HideStackFrameError('Benchmark', 'number', value);
33+
}
34+
});
35+
36+
const fn = type === 'hide-stackframe' ? hideStackFramesTestfn : testfn;
37+
38+
const value = 42;
39+
40+
const length = 1024;
41+
const array = [];
42+
const errCase = false;
43+
44+
for (let i = 0; i < length; ++i) {
45+
array.push(fn(value));
46+
}
47+
48+
bench.start();
49+
50+
for (let i = 0; i < n; i++) {
51+
const index = i % length;
52+
array[index] = fn(value);
53+
}
54+
55+
bench.end(n);
56+
57+
// Verify the entries to prevent dead code elimination from making
58+
// the benchmark invalid.
59+
for (let i = 0; i < length; ++i) {
60+
assert.strictEqual(typeof array[i], errCase ? 'object' : 'undefined');
61+
}
62+
}
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const assert = require('assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
type: [
8+
'hide-stackframes',
9+
'direct-call',
10+
],
11+
n: [1e5],
12+
}, {
13+
flags: ['--expose-internals'],
14+
});
15+
16+
function main({ n, type }) {
17+
const {
18+
hideStackFrames,
19+
codes: {
20+
ERR_INVALID_ARG_TYPE,
21+
},
22+
} = require('internal/errors');
23+
24+
const value = 'err';
25+
26+
const testfn = (value) => {
27+
if (typeof value !== 'number') {
28+
throw new ERR_INVALID_ARG_TYPE('Benchmark', 'number', value);
29+
}
30+
};
31+
32+
const hideStackFramesTestfn = hideStackFrames((value) => {
33+
if (typeof value !== 'number') {
34+
throw new ERR_INVALID_ARG_TYPE.HideStackFrameError('Benchmark', 'number', value);
35+
}
36+
});
37+
38+
const fn = type === 'hide-stackframe' ? hideStackFramesTestfn : testfn;
39+
40+
const length = 1024;
41+
const array = [];
42+
let errCase = false;
43+
44+
// Warm up.
45+
for (let i = 0; i < length; ++i) {
46+
try {
47+
fn(value);
48+
} catch (e) {
49+
errCase = true;
50+
array.push(e);
51+
}
52+
}
53+
54+
bench.start();
55+
56+
for (let i = 0; i < n; i++) {
57+
const index = i % length;
58+
try {
59+
fn(value);
60+
} catch (e) {
61+
array[index] = e;
62+
}
63+
}
64+
65+
bench.end(n);
66+
67+
// Verify the entries to prevent dead code elimination from making
68+
// the benchmark invalid.
69+
for (let i = 0; i < length; ++i) {
70+
assert.strictEqual(typeof array[i], errCase ? 'object' : 'undefined');
71+
}
72+
}

benchmark/error/hidestackframes.js

-45
This file was deleted.

lib/_http_outgoing.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -619,17 +619,17 @@ function matchHeader(self, state, field, value) {
619619

620620
const validateHeaderName = hideStackFrames((name, label) => {
621621
if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
622-
throw new ERR_INVALID_HTTP_TOKEN(label || 'Header name', name);
622+
throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError(label || 'Header name', name);
623623
}
624624
});
625625

626626
const validateHeaderValue = hideStackFrames((name, value) => {
627627
if (value === undefined) {
628-
throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
628+
throw new ERR_HTTP_INVALID_HEADER_VALUE.HideStackFramesError(value, name);
629629
}
630630
if (checkInvalidHeaderChar(value)) {
631631
debug('Header "%s" contains invalid characters', name);
632-
throw new ERR_INVALID_CHAR('header content', name);
632+
throw new ERR_INVALID_CHAR.HideStackFramesError('header content', name);
633633
}
634634
});
635635

lib/buffer.js

+6-14
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ const {
108108
ERR_UNKNOWN_ENCODING,
109109
},
110110
genericNodeError,
111-
hideStackFrames,
112111
} = require('internal/errors');
113112
const {
114113
validateArray,
@@ -386,19 +385,12 @@ Buffer.of = of;
386385

387386
ObjectSetPrototypeOf(Buffer, Uint8Array);
388387

389-
// The 'assertSize' method will remove itself from the callstack when an error
390-
// occurs. This is done simply to keep the internal details of the
391-
// implementation from bleeding out to users.
392-
const assertSize = hideStackFrames((size) => {
393-
validateNumber(size, 'size', 0, kMaxLength);
394-
});
395-
396388
/**
397389
* Creates a new filled Buffer instance.
398390
* alloc(size[, fill[, encoding]])
399391
*/
400392
Buffer.alloc = function alloc(size, fill, encoding) {
401-
assertSize(size);
393+
validateNumber(size, 'size', 0, kMaxLength);
402394
if (fill !== undefined && fill !== 0 && size > 0) {
403395
const buf = createUnsafeBuffer(size);
404396
return _fill(buf, fill, 0, buf.length, encoding);
@@ -411,7 +403,7 @@ Buffer.alloc = function alloc(size, fill, encoding) {
411403
* instance. If `--zero-fill-buffers` is set, will zero-fill the buffer.
412404
*/
413405
Buffer.allocUnsafe = function allocUnsafe(size) {
414-
assertSize(size);
406+
validateNumber(size, 'size', 0, kMaxLength);
415407
return allocate(size);
416408
};
417409

@@ -421,15 +413,15 @@ Buffer.allocUnsafe = function allocUnsafe(size) {
421413
* If `--zero-fill-buffers` is set, will zero-fill the buffer.
422414
*/
423415
Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) {
424-
assertSize(size);
416+
validateNumber(size, 'size', 0, kMaxLength);
425417
return createUnsafeBuffer(size);
426418
};
427419

428420
// If --zero-fill-buffers command line argument is set, a zero-filled
429421
// buffer is returned.
430-
function SlowBuffer(length) {
431-
assertSize(length);
432-
return createUnsafeBuffer(length);
422+
function SlowBuffer(size) {
423+
validateNumber(size, 'size', 0, kMaxLength);
424+
return createUnsafeBuffer(size);
433425
}
434426

435427
ObjectSetPrototypeOf(SlowBuffer.prototype, Uint8ArrayPrototype);

lib/internal/errors.js

+40-28
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ const MainContextError = Error;
8787
const overrideStackTrace = new SafeWeakMap();
8888
const kNoOverride = Symbol('kNoOverride');
8989
let userStackTraceLimit;
90-
const nodeInternalPrefix = '__node_internal_';
9190
const prepareStackTrace = (globalThis, error, trace) => {
9291
// API for node internals to override error stack formatting
9392
// without interfering with userland code.
@@ -97,21 +96,6 @@ const prepareStackTrace = (globalThis, error, trace) => {
9796
return f(error, trace);
9897
}
9998

100-
const firstFrame = trace[0]?.getFunctionName();
101-
if (firstFrame && StringPrototypeStartsWith(firstFrame, nodeInternalPrefix)) {
102-
for (let l = trace.length - 1; l >= 0; l--) {
103-
const fn = trace[l]?.getFunctionName();
104-
if (fn && StringPrototypeStartsWith(fn, nodeInternalPrefix)) {
105-
ArrayPrototypeSplice(trace, 0, l + 1);
106-
break;
107-
}
108-
}
109-
// `userStackTraceLimit` is the user value for `Error.stackTraceLimit`,
110-
// it is updated at every new exception in `captureLargerStackTrace`.
111-
if (trace.length > userStackTraceLimit)
112-
ArrayPrototypeSplice(trace, userStackTraceLimit);
113-
}
114-
11599
const globalOverride =
116100
maybeOverridePrepareStackTrace(globalThis, error, trace);
117101
if (globalOverride !== kNoOverride) return globalOverride;
@@ -372,6 +356,29 @@ function makeSystemErrorWithCode(key) {
372356
};
373357
}
374358

359+
function makeNodeErrorForHideStackFrame(Base, clazz) {
360+
class HideStackFramesError extends Base {
361+
constructor(...args) {
362+
if (isErrorStackTraceLimitWritable()) {
363+
const limit = Error.stackTraceLimit;
364+
Error.stackTraceLimit = 0;
365+
super(...args);
366+
Error.stackTraceLimit = limit;
367+
} else {
368+
super(...args);
369+
}
370+
}
371+
372+
// This is a workaround for wpt tests that expect that the error
373+
// constructor has a `name` property of the base class.
374+
get ['constructor']() {
375+
return clazz;
376+
}
377+
}
378+
379+
return HideStackFramesError;
380+
}
381+
375382
function makeNodeErrorWithCode(Base, key) {
376383
const msg = messages.get(key);
377384
const expectedLength = typeof msg !== 'string' ? -1 : getExpectedArgumentLength(msg);
@@ -479,11 +486,14 @@ function makeNodeErrorWithCode(Base, key) {
479486
* @returns {T}
480487
*/
481488
function hideStackFrames(fn) {
482-
// We rename the functions that will be hidden to cut off the stacktrace
483-
// at the outermost one
484-
const hidden = nodeInternalPrefix + fn.name;
485-
ObjectDefineProperty(fn, 'name', { __proto__: null, value: hidden });
486-
return fn;
489+
return function wrappedFn() {
490+
try {
491+
return ReflectApply(fn, fn, arguments);
492+
} catch (error) {
493+
Error.stackTraceLimit && ErrorCaptureStackTrace(error, wrappedFn);
494+
throw error;
495+
}
496+
};
487497
}
488498

489499
// Utility function for registering the error codes. Only used here. Exported
@@ -492,18 +502,20 @@ function E(sym, val, def, ...otherClasses) {
492502
// Special case for SystemError that formats the error message differently
493503
// The SystemErrors only have SystemError as their base classes.
494504
messages.set(sym, val);
495-
if (def === SystemError) {
496-
def = makeSystemErrorWithCode(sym);
497-
} else {
498-
def = makeNodeErrorWithCode(def, sym);
499-
}
505+
506+
const ErrClass = def === SystemError ?
507+
makeSystemErrorWithCode(sym) :
508+
makeNodeErrorWithCode(def, sym);
500509

501510
if (otherClasses.length !== 0) {
502511
otherClasses.forEach((clazz) => {
503-
def[clazz.name] = makeNodeErrorWithCode(clazz, sym);
512+
ErrClass[clazz.name] = makeNodeErrorWithCode(clazz, sym);
504513
});
505514
}
506-
codes[sym] = def;
515+
516+
ErrClass.HideStackFramesError = makeNodeErrorForHideStackFrame(ErrClass, def);
517+
518+
codes[sym] = ErrClass;
507519
}
508520

509521
function getExpectedArgumentLength(msg) {

0 commit comments

Comments
 (0)