Skip to content

Commit

Permalink
assert: detect faulty throws usage
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
  • Loading branch information
BridgeAR authored and jasnell committed Apr 16, 2018
1 parent 15e8bdf commit 1964978
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 0 deletions.
8 changes: 8 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,14 @@ found [here][online].
<a id="nodejs-error-codes"></a>
## Node.js Error Codes

<a id="ERR_AMBIGUOUS_ARGUMENT"></a>
### ERR_AMBIGUOUS_ARGUMENT

This is triggered by the `assert` module in case e.g.,
`assert.throws(fn, message)` is used in a way that the message is the thrown
error message. This is ambiguous because the message is not verifying the error
message and will only be thrown in case no error is thrown.

<a id="ERR_ARG_NOT_ITERABLE"></a>
### ERR_ARG_NOT_ITERABLE

Expand Down
14 changes: 14 additions & 0 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const {
AssertionError,
errorCache,
codes: {
ERR_AMBIGUOUS_ARGUMENT,
ERR_INVALID_ARG_TYPE
}
} = require('internal/errors');
Expand Down Expand Up @@ -470,6 +471,19 @@ function expectsError(stackStartFn, actual, error, message) {
['Object', 'Error', 'Function', 'RegExp'],
error);
}
if (typeof actual === 'object' && actual !== null) {
if (actual.message === error) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'error/message',
`The error message "${actual.message}" is identical to the message.`
);
}
} else if (actual === error) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'error/message',
`The error "${actual}" is identical to the message.`
);
}
message = error;
error = null;
}
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ module.exports = exports = {
//
// Note: Node.js specific errors must begin with the prefix ERR_

E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
E('ERR_ASSERTION', '%s', Error);
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);
Expand Down
29 changes: 29 additions & 0 deletions test/parallel/test-assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -836,3 +836,32 @@ common.expectsError(
}
);
}

assert.throws(
() => a.throws(
// eslint-disable-next-line no-throw-literal
() => { throw 'foo'; },
'foo'
),
{
code: 'ERR_AMBIGUOUS_ARGUMENT',
message: 'The "error/message" argument is ambiguous. ' +
'The error "foo" is identical to the message.'
}
);

assert.throws(
() => a.throws(
() => { throw new TypeError('foo'); },
'foo'
),
{
code: 'ERR_AMBIGUOUS_ARGUMENT',
message: 'The "error/message" argument is ambiguous. ' +
'The error message "foo" is identical to the message.'
}
);

// Should not throw.
// eslint-disable-next-line no-restricted-syntax, no-throw-literal
assert.throws(() => { throw null; }, 'foo');

0 comments on commit 1964978

Please sign in to comment.