diff --git a/lib/repl.js b/lib/repl.js index 17204c98e9f..443971df63b 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -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); } diff --git a/test/parallel/test-repl-tab-complete-getter-error.js b/test/parallel/test-repl-tab-complete-getter-error.js new file mode 100644 index 00000000000..e2e36b85c58 --- /dev/null +++ b/test/parallel/test-repl-tab-complete-getter-error.js @@ -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); + }) + ); + }); +}