Skip to content

Commit 3e910fb

Browse files
committed
assert: do not read Node.js modules
Prevent reading a Node.js module. That could theoretically lead to false errors being thrown otherwise. PR-URL: #18322 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 6101bd2 commit 3e910fb

File tree

3 files changed

+100
-50
lines changed

3 files changed

+100
-50
lines changed

lib/assert.js

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ const {
2525
isDeepEqual,
2626
isDeepStrictEqual
2727
} = require('internal/util/comparisons');
28-
const { AssertionError, TypeError } = require('internal/errors');
28+
const { AssertionError, TypeError, errorCache } = require('internal/errors');
2929
const { openSync, closeSync, readSync } = require('fs');
3030
const { parseExpressionAt } = require('internal/deps/acorn/dist/acorn');
3131
const { inspect } = require('util');
3232
const { EOL } = require('os');
33+
const nativeModule = require('native_module');
3334

34-
const codeCache = new Map();
3535
// Escape control characters but not \n and \t to keep the line breaks and
3636
// indentation intact.
3737
// eslint-disable-next-line no-control-regex
@@ -146,6 +146,60 @@ function getBuffer(fd, assertLine) {
146146
return buffers;
147147
}
148148

149+
function getErrMessage(call) {
150+
const filename = call.getFileName();
151+
const line = call.getLineNumber() - 1;
152+
const column = call.getColumnNumber() - 1;
153+
const identifier = `${filename}${line}${column}`;
154+
155+
if (errorCache.has(identifier)) {
156+
return errorCache.get(identifier);
157+
}
158+
159+
// Skip Node.js modules!
160+
if (filename.endsWith('.js') && nativeModule.exists(filename.slice(0, -3))) {
161+
errorCache.set(identifier, undefined);
162+
return;
163+
}
164+
165+
var fd;
166+
try {
167+
fd = openSync(filename, 'r', 0o666);
168+
const buffers = getBuffer(fd, line);
169+
const code = Buffer.concat(buffers).toString('utf8');
170+
const nodes = parseExpressionAt(code, column);
171+
// Node type should be "CallExpression" and some times
172+
// "SequenceExpression".
173+
const node = nodes.type === 'CallExpression' ? nodes : nodes.expressions[0];
174+
const name = node.callee.name;
175+
// Calling `ok` with .apply or .call is uncommon but we use a simple
176+
// safeguard nevertheless.
177+
if (name !== 'apply' && name !== 'call') {
178+
// Only use `assert` and `assert.ok` to reference the "real API" and
179+
// not user defined function names.
180+
const ok = name === 'ok' ? '.ok' : '';
181+
const args = node.arguments;
182+
var message = code
183+
.slice(args[0].start, args[args.length - 1].end)
184+
.replace(escapeSequencesRegExp, escapeFn);
185+
message = 'The expression evaluated to a falsy value:' +
186+
`${EOL}${EOL} assert${ok}(${message})${EOL}`;
187+
}
188+
// Make sure to always set the cache! No matter if the message is
189+
// undefined or not
190+
errorCache.set(identifier, message);
191+
192+
return message;
193+
194+
} catch (e) {
195+
// Invalidate cache to prevent trying to read this part again.
196+
errorCache.set(identifier, undefined);
197+
} finally {
198+
if (fd !== undefined)
199+
closeSync(fd);
200+
}
201+
}
202+
149203
function innerOk(args, fn) {
150204
var [value, message] = args;
151205

@@ -168,54 +222,12 @@ function innerOk(args, fn) {
168222
const call = err.stack[0];
169223
Error.prepareStackTrace = tmpPrepare;
170224

171-
const filename = call.getFileName();
172-
const line = call.getLineNumber() - 1;
173-
const column = call.getColumnNumber() - 1;
174-
const identifier = `${filename}${line}${column}`;
225+
// TODO(BridgeAR): fix the "generatedMessage property"
226+
// Since this is actually a generated message, it has to be
227+
// determined differently from now on.
175228

176-
if (codeCache.has(identifier)) {
177-
message = codeCache.get(identifier);
178-
} else {
179-
var fd;
180-
try {
181-
fd = openSync(filename, 'r', 0o666);
182-
const buffers = getBuffer(fd, line);
183-
const code = Buffer.concat(buffers).toString('utf8');
184-
const nodes = parseExpressionAt(code, column);
185-
// Node type should be "CallExpression" and some times
186-
// "SequenceExpression".
187-
const node = nodes.type === 'CallExpression' ?
188-
nodes :
189-
nodes.expressions[0];
190-
// TODO: fix the "generatedMessage property"
191-
// Since this is actually a generated message, it has to be
192-
// determined differently from now on.
193-
194-
const name = node.callee.name;
195-
// Calling `ok` with .apply or .call is uncommon but we use a simple
196-
// safeguard nevertheless.
197-
if (name !== 'apply' && name !== 'call') {
198-
// Only use `assert` and `assert.ok` to reference the "real API" and
199-
// not user defined function names.
200-
const ok = name === 'ok' ? '.ok' : '';
201-
const args = node.arguments;
202-
message = code
203-
.slice(args[0].start, args[args.length - 1].end)
204-
.replace(escapeSequencesRegExp, escapeFn);
205-
message = 'The expression evaluated to a falsy value:' +
206-
`${EOL}${EOL} assert${ok}(${message})${EOL}`;
207-
}
208-
// Make sure to always set the cache! No matter if the message is
209-
// undefined or not
210-
codeCache.set(identifier, message);
211-
} catch (e) {
212-
// Invalidate cache to prevent trying to read this part again.
213-
codeCache.set(identifier, undefined);
214-
} finally {
215-
if (fd !== undefined)
216-
closeSync(fd);
217-
}
218-
}
229+
// Make sure it would be "null" in case that is used.
230+
message = getErrMessage(call) || message;
219231
}
220232
innerFail({
221233
actual: value,

lib/internal/errors.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,8 @@ module.exports = exports = {
398398
URIError: makeNodeError(URIError),
399399
AssertionError,
400400
SystemError,
401-
E // This is exported only to facilitate testing.
401+
E, // This is exported only to facilitate testing.
402+
errorCache: new Map() // This is in here only to facilitate testing.
402403
};
403404

404405
// To declare an error message, use the E(sym, val) function above. The sym

test/parallel/test-assert.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@
1919
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

22+
// Flags: --expose-internals
23+
2224
'use strict';
2325

2426
/* eslint-disable prefer-common-expectserror */
2527

2628
const common = require('../common');
2729
const assert = require('assert');
2830
const { EOL } = require('os');
31+
const EventEmitter = require('events');
32+
const { errorCache } = require('internal/errors');
33+
const { writeFileSync, unlinkSync } = require('fs');
2934
const a = assert;
3035

3136
function makeBlock(f) {
@@ -1019,6 +1024,38 @@ common.expectsError(
10191024
}
10201025
);
10211026

1027+
// Do not try to check Node.js modules.
1028+
{
1029+
const e = new EventEmitter();
1030+
1031+
e.on('hello', assert);
1032+
1033+
let threw = false;
1034+
try {
1035+
e.emit('hello', false);
1036+
} catch (err) {
1037+
const frames = err.stack.split('\n');
1038+
const [, filename, line, column] = frames[1].match(/\((.+):(\d+):(\d+)\)/);
1039+
// Reset the cache to check again
1040+
errorCache.delete(`${filename}${line - 1}${column - 1}`);
1041+
const data = `${'\n'.repeat(line - 1)}${' '.repeat(column - 1)}` +
1042+
'ok(failed(badly));';
1043+
try {
1044+
writeFileSync(filename, data);
1045+
assert.throws(
1046+
() => e.emit('hello', false),
1047+
{
1048+
message: 'false == true'
1049+
}
1050+
);
1051+
threw = true;
1052+
} finally {
1053+
unlinkSync(filename);
1054+
}
1055+
}
1056+
assert(threw);
1057+
}
1058+
10221059
common.expectsError(
10231060
// eslint-disable-next-line no-restricted-syntax
10241061
() => assert.throws(() => {}, 'Error message', 'message'),

0 commit comments

Comments
 (0)