Skip to content

Commit 571cec4

Browse files
dario-piotrowiczRafaelGSS
authored andcommitted
repl: fix getters triggering side effects during completion
PR-URL: #61043 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Aviv Keller <me@aviv.sh>
1 parent 7040ec9 commit 571cec4

3 files changed

Lines changed: 60 additions & 78 deletions

File tree

‎lib/internal/repl/completion.js‎

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -731,35 +731,15 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
731731

732732
if(astProp.type==='Literal'){
733733
// We have something like `obj['foo'].x` where `x` is the literal
734-
735-
if(safeIsProxyAccess(obj,astProp.value)){
736-
returncb(true);
737-
}
738-
739-
constpropDescriptor=ObjectGetOwnPropertyDescriptor(
740-
obj,
741-
`${astProp.value}`,
742-
);
743-
constpropHasGetter=typeofpropDescriptor?.get==='function';
744-
returncb(propHasGetter);
734+
returnpropHasGetterOrIsProxy(obj,astProp.value,cb);
745735
}
746736

747737
if(
748738
astProp.type==='Identifier'&&
749739
exprStr.at(astProp.start-1)==='.'
750740
){
751741
// We have something like `obj.foo.x` where `foo` is the identifier
752-
753-
if(safeIsProxyAccess(obj,astProp.name)){
754-
returncb(true);
755-
}
756-
757-
constpropDescriptor=ObjectGetOwnPropertyDescriptor(
758-
obj,
759-
`${astProp.name}`,
760-
);
761-
constpropHasGetter=typeofpropDescriptor?.get==='function';
762-
returncb(propHasGetter);
742+
returnpropHasGetterOrIsProxy(obj,astProp.name,cb);
763743
}
764744

765745
returnevalFn(
@@ -773,37 +753,48 @@ function includesProxiesOrGetters(expr, exprStr, evalFn, ctx, callback) {
773753
getREPLResourceName(),
774754
(err,evaledProp)=>{
775755
if(err){
776-
returncallback(false);
756+
returncb(false);
777757
}
778758

779759
if(typeofevaledProp==='string'){
780-
if(safeIsProxyAccess(obj,evaledProp)){
781-
returncb(true);
782-
}
783-
784-
constpropDescriptor=ObjectGetOwnPropertyDescriptor(
785-
obj,
786-
evaledProp,
787-
);
788-
constpropHasGetter=typeofpropDescriptor?.get==='function';
789-
returncb(propHasGetter);
760+
returnpropHasGetterOrIsProxy(obj,evaledProp,cb);
790761
}
791762

792-
returncallback(false);
763+
returncb(false);
793764
},
794765
);
795766
}
796767

797-
functionsafeIsProxyAccess(obj,prop){
798-
// Accessing `prop` may trigger a getter that throws, so we use try-catch to guard against it
799-
try{
800-
returnisProxy(obj[prop]);
801-
}catch{
802-
returnfalse;
803-
}
768+
returncallback(false);
769+
}
770+
771+
/**
772+
* Given an object and a property name, checks whether the property has a getter, if not checks whether its
773+
* value is a proxy.
774+
*
775+
* Note: the order is relevant here, we want to check whether the property has a getter _before_ we check
776+
* whether its value is a proxy, to ensure that is the property does have a getter we don't end up
777+
* triggering it when checking its value
778+
* @param {any} obj The target object
779+
* @param {string | number | bigint | boolean | RegExp} prop The target property
780+
* @param {(includes: boolean) => void} cb Callback that will be called with the result of the operation
781+
* @returns {void}
782+
*/
783+
functionpropHasGetterOrIsProxy(obj,prop,cb){
784+
constpropDescriptor=ObjectGetOwnPropertyDescriptor(
785+
obj,
786+
prop,
787+
);
788+
constpropHasGetter=typeofpropDescriptor?.get==='function';
789+
if(propHasGetter){
790+
returncb(true);
804791
}
805792

806-
returncallback(false);
793+
if(isProxy(obj[prop])){
794+
returncb(true);
795+
}
796+
797+
returncb(false);
807798
}
808799

809800
module.exports={

‎test/parallel/test-repl-completion-on-getters-disabled.js‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,33 @@ describe('REPL completion in relation of getters', () => {
115115
['objWithGetters[getGFooKey()].b',[]],
116116
]);
117117
});
118+
119+
test('no side effects are triggered for getters during completion',async()=>{
120+
const{ replServer }=startNewREPLServer();
121+
122+
awaitnewPromise((resolve,reject)=>{
123+
replServer.eval('const foo = { get name() { globalThis.nameGetterRun = true; throw new Error(); } };',
124+
replServer.context,'',(err)=>{
125+
if(err){
126+
reject(err);
127+
}else{
128+
resolve();
129+
}
130+
});
131+
});
132+
133+
['foo.name.','foo["name"].'].forEach((test)=>{
134+
replServer.complete(
135+
test,
136+
common.mustCall((error,data)=>{
137+
// The context's nameGetterRun variable hasn't been set
138+
assert.strictEqual(replServer.context.nameGetterRun,undefined);
139+
// No errors has been thrown
140+
assert.strictEqual(error,null);
141+
})
142+
);
143+
});
144+
});
118145
});
119146

120147
describe('completions on proxies',()=>{

‎test/parallel/test-repl-tab-complete-getter-error.js‎

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)