Skip to content

Commit 1964978

Browse files
BridgeARjasnell
authored andcommitted
assert: detect faulty throws usage
One of the biggest downsides to the `assert.throws` API is that it does not check for the error message in case that is used as second argument. It will instead be used in case no error is thrown. This improves the situation by checking the actual error message against the provided one and throws an error in case they are identical. It is very unlikely that the user wants to use that error message as information instead of checking against that message. PR-URL: #19867 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 15e8bdf commit 1964978

File tree

4 files changed

+52
-0
lines changed

4 files changed

+52
-0
lines changed

doc/api/errors.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,14 @@ found [here][online].
586586
<a id="nodejs-error-codes"></a>
587587
## Node.js Error Codes
588588

589+
<a id="ERR_AMBIGUOUS_ARGUMENT"></a>
590+
### ERR_AMBIGUOUS_ARGUMENT
591+
592+
This is triggered by the `assert` module in case e.g.,
593+
`assert.throws(fn, message)` is used in a way that the message is the thrown
594+
error message. This is ambiguous because the message is not verifying the error
595+
message and will only be thrown in case no error is thrown.
596+
589597
<a id="ERR_ARG_NOT_ITERABLE"></a>
590598
### ERR_ARG_NOT_ITERABLE
591599

lib/assert.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const {
2929
AssertionError,
3030
errorCache,
3131
codes: {
32+
ERR_AMBIGUOUS_ARGUMENT,
3233
ERR_INVALID_ARG_TYPE
3334
}
3435
} = require('internal/errors');
@@ -470,6 +471,19 @@ function expectsError(stackStartFn, actual, error, message) {
470471
['Object', 'Error', 'Function', 'RegExp'],
471472
error);
472473
}
474+
if (typeof actual === 'object' && actual !== null) {
475+
if (actual.message === error) {
476+
throw new ERR_AMBIGUOUS_ARGUMENT(
477+
'error/message',
478+
`The error message "${actual.message}" is identical to the message.`
479+
);
480+
}
481+
} else if (actual === error) {
482+
throw new ERR_AMBIGUOUS_ARGUMENT(
483+
'error/message',
484+
`The error "${actual}" is identical to the message.`
485+
);
486+
}
473487
message = error;
474488
error = null;
475489
}

lib/internal/errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ module.exports = exports = {
640640
//
641641
// Note: Node.js specific errors must begin with the prefix ERR_
642642

643+
E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
643644
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
644645
E('ERR_ASSERTION', '%s', Error);
645646
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);

test/parallel/test-assert.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,3 +836,32 @@ common.expectsError(
836836
}
837837
);
838838
}
839+
840+
assert.throws(
841+
() => a.throws(
842+
// eslint-disable-next-line no-throw-literal
843+
() => { throw 'foo'; },
844+
'foo'
845+
),
846+
{
847+
code: 'ERR_AMBIGUOUS_ARGUMENT',
848+
message: 'The "error/message" argument is ambiguous. ' +
849+
'The error "foo" is identical to the message.'
850+
}
851+
);
852+
853+
assert.throws(
854+
() => a.throws(
855+
() => { throw new TypeError('foo'); },
856+
'foo'
857+
),
858+
{
859+
code: 'ERR_AMBIGUOUS_ARGUMENT',
860+
message: 'The "error/message" argument is ambiguous. ' +
861+
'The error message "foo" is identical to the message.'
862+
}
863+
);
864+
865+
// Should not throw.
866+
// eslint-disable-next-line no-restricted-syntax, no-throw-literal
867+
assert.throws(() => { throw null; }, 'foo');

0 commit comments

Comments
 (0)