◐ Shell
clean mode source ↗

lib: disable REPL completion on proxies and getters · nodejs/node@29f34a7

@@ -96,6 +96,10 @@ const {

9696

globalThis,

9797

} = primordials;

989899+

const {

100+

isProxy,

101+

} = require('internal/util/types');

102+99103

const { BuiltinModule } = require('internal/bootstrap/realm');

100104

const {

101105

makeRequireFunction,

@@ -1328,8 +1332,10 @@ function completeFSFunctions(match) {

13281332

// -> [['util.print', 'util.debug', 'util.log', 'util.inspect'],

13291333

// 'util.' ]

13301334

//

1331-

// Warning: This eval's code like "foo.bar.baz", so it will run property

1332-

// getter code.

1335+

// Warning: This evals code like "foo.bar.baz", so it could run property

1336+

// getter code. To avoid potential triggering side-effects with getters the completion

1337+

// logic is skipped when getters or proxies are involved in the expression.

1338+

// (see: https://github.com/nodejs/node/issues/57829).

13331339

function complete(line, callback) {

13341340

// List of completion lists, one for each inheritance "level"

13351341

let completionGroups = [];

@@ -1525,50 +1531,61 @@ function complete(line, callback) {

15251531

return;

15261532

}

152715331528-

let chaining = '.';

1529-

if (StringPrototypeEndsWith(expr, '?')) {

1530-

expr = StringPrototypeSlice(expr, 0, -1);

1531-

chaining = '?.';

1532-

}

1533-1534-

const memberGroups = [];

1535-

const evalExpr = `try { ${expr} } catch {}`;

1536-

this.eval(evalExpr, this.context, getREPLResourceName(), (e, obj) => {

1537-

try {

1538-

let p;

1539-

if ((typeof obj === 'object' && obj !== null) ||

1540-

typeof obj === 'function') {

1541-

ArrayPrototypePush(memberGroups, filteredOwnPropertyNames(obj));

1542-

p = ObjectGetPrototypeOf(obj);

1543-

} else {

1544-

p = obj.constructor ? obj.constructor.prototype : null;

1534+

return includesProxiesOrGetters(

1535+

StringPrototypeSplit(match, '.'),

1536+

this.eval,

1537+

this.context,

1538+

(includes) => {

1539+

if (includes) {

1540+

// The expression involves proxies or getters, meaning that it

1541+

// can trigger side-effectful behaviors, so bail out

1542+

return completionGroupsLoaded();

15451543

}

1546-

// Circular refs possible? Let's guard against that.

1547-

let sentinel = 5;

1548-

while (p !== null && sentinel-- !== 0) {

1549-

ArrayPrototypePush(memberGroups, filteredOwnPropertyNames(p));

1550-

p = ObjectGetPrototypeOf(p);

1544+1545+

let chaining = '.';

1546+

if (StringPrototypeEndsWith(expr, '?')) {

1547+

expr = StringPrototypeSlice(expr, 0, -1);

1548+

chaining = '?.';

15511549

}

1552-

} catch {

1553-

// Maybe a Proxy object without `getOwnPropertyNames` trap.

1554-

// We simply ignore it here, as we don't want to break the

1555-

// autocompletion. Fixes the bug

1556-

// https://github.com/nodejs/node/issues/2119

1557-

}

155815501559-

if (memberGroups.length) {

1560-

expr += chaining;

1561-

ArrayPrototypeForEach(memberGroups, (group) => {

1562-

ArrayPrototypePush(completionGroups,

1563-

ArrayPrototypeMap(group,

1564-

(member) => `${expr}${member}`));

1565-

});

1566-

filter &&= `${expr}${filter}`;

1567-

}

1551+

const memberGroups = [];

1552+

const evalExpr = `try { ${expr} } catch {}`;

1553+

this.eval(evalExpr, this.context, getREPLResourceName(), (e, obj) => {

1554+

try {

1555+

let p;

1556+

if ((typeof obj === 'object' && obj !== null) ||

1557+

typeof obj === 'function') {

1558+

ArrayPrototypePush(memberGroups, filteredOwnPropertyNames(obj));

1559+

p = ObjectGetPrototypeOf(obj);

1560+

} else {

1561+

p = obj.constructor ? obj.constructor.prototype : null;

1562+

}

1563+

// Circular refs possible? Let's guard against that.

1564+

let sentinel = 5;

1565+

while (p !== null && sentinel-- !== 0) {

1566+

ArrayPrototypePush(memberGroups, filteredOwnPropertyNames(p));

1567+

p = ObjectGetPrototypeOf(p);

1568+

}

1569+

} catch {

1570+

// Maybe a Proxy object without `getOwnPropertyNames` trap.

1571+

// We simply ignore it here, as we don't want to break the

1572+

// autocompletion. Fixes the bug

1573+

// https://github.com/nodejs/node/issues/2119

1574+

}

156815751569-

completionGroupsLoaded();

1570-

});

1571-

return;

1576+

if (memberGroups.length) {

1577+

expr += chaining;

1578+

ArrayPrototypeForEach(memberGroups, (group) => {

1579+

ArrayPrototypePush(completionGroups,

1580+

ArrayPrototypeMap(group,

1581+

(member) => `${expr}${member}`));

1582+

});

1583+

filter &&= `${expr}${filter}`;

1584+

}

1585+1586+

completionGroupsLoaded();

1587+

});

1588+

});

15721589

}

1573159015741591

return completionGroupsLoaded();

@@ -1626,6 +1643,34 @@ function complete(line, callback) {

16261643

}

16271644

}

162816451646+

function includesProxiesOrGetters(exprSegments, evalFn, context, callback, currentExpr = '', idx = 0) {

1647+

const currentSegment = exprSegments[idx];

1648+

currentExpr += `${currentExpr.length === 0 ? '' : '.'}${currentSegment}`;

1649+

evalFn(`try { ${currentExpr} } catch { }`, context, getREPLResourceName(), (_, currentObj) => {

1650+

if (typeof currentObj !== 'object' || currentObj === null) {

1651+

return callback(false);

1652+

}

1653+1654+

if (isProxy(currentObj)) {

1655+

return callback(true);

1656+

}

1657+1658+

const nextIdx = idx + 1;

1659+1660+

if (nextIdx >= exprSegments.length) {

1661+

return callback(false);

1662+

}

1663+1664+

const nextSegmentProp = ObjectGetOwnPropertyDescriptor(currentObj, exprSegments[nextIdx]);

1665+

const nextSegmentPropHasGetter = typeof nextSegmentProp?.get === 'function';

1666+

if (nextSegmentPropHasGetter) {

1667+

return callback(true);

1668+

}

1669+1670+

return includesProxiesOrGetters(exprSegments, evalFn, context, callback, currentExpr, nextIdx);

1671+

});

1672+

}

1673+16291674

REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => {

16301675

if (err) return callback(err);

16311676