repl: handle errors from getters during completion

PR-URL: https://github.com/nodejs/node/pull/59044
Reviewed-By: Dario Piotrowicz <dario.piotrowicz@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Shima Ryuhei 2025-07-19 21:32:19 +09:00 committed by GitHub
parent 35e599b3d0
commit c8d5b394e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 63 additions and 3 deletions

View file

@ -1837,7 +1837,7 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
if (astProp.type === 'Literal') {
// We have something like `obj['foo'].x` where `x` is the literal
if (isProxy(obj[astProp.value])) {
if (safeIsProxyAccess(obj, astProp.value)) {
return cb(true);
}
@ -1855,7 +1855,7 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
) {
// We have something like `obj.foo.x` where `foo` is the identifier
if (isProxy(obj[astProp.name])) {
if (safeIsProxyAccess(obj, astProp.name)) {
return cb(true);
}
@ -1882,7 +1882,7 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
}
if (typeof evaledProp === 'string') {
if (isProxy(obj[evaledProp])) {
if (safeIsProxyAccess(obj, evaledProp)) {
return cb(true);
}
@ -1899,6 +1899,15 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
);
}
function safeIsProxyAccess(obj, prop) {
// Accessing `prop` may trigger a getter that throws, so we use try-catch to guard against it
try {
return isProxy(obj[prop]);
} catch {
return false;
}
}
return callback(false);
}

View file

@ -0,0 +1,51 @@
'use strict';
const common = require('../common');
const repl = require('repl');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');
(async function() {
await runTest();
})().then(common.mustCall());
async function runTest() {
const input = new ArrayStream();
const output = new ArrayStream();
const replServer = repl.start({
prompt: '',
input,
output: output,
allowBlockingCompletions: true,
terminal: true
});
replServer._domain.on('error', (e) => {
assert.fail(`Error in REPL domain: ${e}`);
});
await new Promise((resolve, reject) => {
replServer.eval(`
const getNameText = () => "name";
const foo = { get name() { throw new Error(); } };
`, replServer.context, '', (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
['foo.name.', 'foo["name"].', 'foo[getNameText()].'].forEach((test) => {
replServer.complete(
test,
common.mustCall((error, data) => {
assert.strictEqual(error, null);
assert.strictEqual(data.length, 2);
assert.strictEqual(data[1], test);
})
);
});
}