Skip to content

Commit dd99525

Browse files
committed
C#: Redefine AccessorCall
The syntactic node assiociated with accessor calls was previously always the underlying member access. For example, in ``` x.Prop = y.Prop; ``` the implicit call to `x.set_Prop()` was at the syntactic node `x.Prop`, while the implicit call to `y.get_Prop()` was at the syntactic node `y.Prop`. However, this breaks the invariant that arguments to calls dominate the call itself, as the argument `y.Prop` for the implicit `value` parameter in `x.set_Prop()` will be evaluated after the call (the left-hand side in an assignment is evaluated before the right-hand side). The solution is to redefine the access call to `x.set_Prop()` to point to the whole assignment `x.Prop = y.Prop`, instead of the access `x.Prop`. For reads, we still want to associate the accessor call with the member access. A corner case arises when multiple setters are called in a tuple assignment: ``` (x.Prop1, x.Prop2) = (0, 1) ``` In this case, we cannot associate the assignment with both `x.set_Prop1()` and `x.set_Prop2()`, so we instead revert to using the underlying member accesses as before.
1 parent 2caf724 commit dd99525

File tree

17 files changed

+551
-415
lines changed

17 files changed

+551
-415
lines changed

csharp/ql/src/semmle/code/csharp/Assignable.qll

Lines changed: 130 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -159,87 +159,104 @@ module AssignableInternal {
159159
}
160160

161161
/**
162-
* Holds if the `ref` assignment to `aa` via call `c` is relevant.
162+
* A `ref` argument in a call.
163+
*
164+
* All predicates in this class deliberatly do not use the `Call` class, or any
165+
* subclass thereof, as that results in too conservative negative recursion
166+
* compilation errors.
163167
*/
164-
private predicate isRelevantRefCall(Call c, AssignableAccess aa) {
165-
isNonAnalyzableRefCall(c, aa) or
166-
exists(getAnAnalyzableRefDef(c, aa, _))
167-
}
168+
private class RefArg extends AssignableAccess {
169+
private Expr call;
168170

169-
private Callable getRefCallTarget(Call c, AssignableAccess aa, Parameter p) {
170-
exists(Parameter parg |
171-
c.getAnArgument() = aa and
172-
aa.isRefArgument() and
173-
getArgumentForParameter(c, parg) = aa and
174-
p = parg.getSourceDeclaration() and
175-
result.getAParameter() = p
176-
)
177-
}
171+
private int position;
178172

179-
/**
180-
* A verbatim copy of `Call.getArgumentForParameter()` specialized to
181-
* `MethodCall`/`ObjectCreation` (needed to avoid too conservative negative
182-
* recursion error).
183-
*/
184-
private Expr getArgumentForParameter(Call c, Parameter p) {
185-
exists(Callable callable | p = callable.getAParameter() |
186-
callable = c.(MethodCall).getTarget() or
187-
callable = c.(ObjectCreation).getTarget()
188-
) and
189-
(
190-
// Appears in the positional part of the call
191-
result = c.getArgument(p.getPosition()) and
192-
not exists(result.getExplicitArgumentName())
193-
or
194-
// Appears in the named part of the call
195-
result = getExplicitArgument(c, p.getName())
196-
)
197-
}
173+
RefArg() {
174+
this.isRefArgument() and
175+
this = call.getChildExpr(position) and
176+
(
177+
call instanceof @method_invocation_expr
178+
or
179+
call instanceof @delegate_invocation_expr
180+
or
181+
call instanceof @local_function_invocation_expr
182+
or
183+
call instanceof @object_creation_expr
184+
)
185+
}
198186

199-
// predicate folding to get proper join-order
200-
pragma[noinline]
201-
private Expr getExplicitArgument(Call c, string name) {
202-
result = c.getAnArgument() and
203-
result.getExplicitArgumentName() = name
204-
}
187+
pragma[noinline]
188+
Parameter getAParameter(string name) {
189+
exists(Callable callable | result = callable.getAParameter() |
190+
expr_call(call, callable) and
191+
result.getName() = name
192+
)
193+
}
205194

206-
/**
207-
* Holds if the `ref` assignment to `aa` via parameter `p` is analyzable. That is,
208-
* the target callable is non-overridable and from source.
209-
*/
210-
private predicate isAnalyzableRefCall(Call c, AssignableAccess aa, Parameter p) {
211-
exists(Callable callable | callable = getRefCallTarget(c, aa, p) |
212-
not callable.(Virtualizable).isOverridableOrImplementable() and
213-
callable.hasBody()
214-
)
215-
}
195+
/** Gets the parameter that this argument corresponds to. */
196+
private Parameter getParameter() {
197+
exists(string name | result = this.getAParameter(name) |
198+
// Appears in the positional part of the call
199+
result.getPosition() = position and
200+
not exists(this.getExplicitArgumentName())
201+
or
202+
// Appears in the named part of the call
203+
name = this.getExplicitArgumentName()
204+
)
205+
}
216206

217-
/**
218-
* Holds if the `ref` assignment to `aa` via parameter `p` is *not* analyzable.
219-
* Equivalent with `not isAnalyzableRefCall(mc, aa, p)`, but avoids negative
220-
* recursion.
221-
*/
222-
private predicate isNonAnalyzableRefCall(Call c, AssignableAccess aa) {
223-
aa = c.getAnArgument() and
224-
aa.isRefArgument() and
225-
(
226-
not exists(getRefCallTarget(c, aa, _))
207+
private Callable getSourceDeclarationTarget(Parameter p) {
208+
exists(Parameter parg |
209+
parg = this.getParameter() and
210+
p = parg.getSourceDeclaration() and
211+
result.getAParameter() = p
212+
)
213+
}
214+
215+
/**
216+
* Holds if the assignment to this `ref` argument via parameter `p` is
217+
* analyzable. That is, the target callable is non-overridable and from
218+
* source.
219+
*/
220+
predicate isAnalyzable(Parameter p) {
221+
exists(Callable callable | callable = this.getSourceDeclarationTarget(p) |
222+
not callable.(Virtualizable).isOverridableOrImplementable() and
223+
callable.hasBody()
224+
)
225+
}
226+
227+
/** Gets an assignment to analyzable parameter `p`. */
228+
AssignableDefinition getAnAnalyzableRefDef(Parameter p) {
229+
this.isAnalyzable(p) and
230+
result.getTarget() = p and
231+
not result = TImplicitParameterDefinition(_)
232+
}
233+
234+
/**
235+
* Holds if this `ref` assignment is *not* analyzable. Equivalent with
236+
* `not this.isAnalyzable(_)`, but avoids negative recursion.
237+
*/
238+
private predicate isNonAnalyzable() {
239+
call instanceof @delegate_invocation_expr
227240
or
228-
exists(Callable callable | callable = getRefCallTarget(c, aa, _) |
241+
exists(Callable callable | callable = this.getSourceDeclarationTarget(_) |
229242
callable.(Virtualizable).isOverridableOrImplementable() or
230243
not callable.hasBody()
231244
)
232-
)
245+
}
246+
247+
/** Holds if this `ref` assignment is relevant. */
248+
predicate isRelevant() {
249+
this.isNonAnalyzable() or
250+
exists(this.getAnAnalyzableRefDef(_))
251+
}
233252
}
234253

235-
/**
236-
* Gets an assignment to parameter `p`, where the `ref` assignment to `aa` via
237-
* parameter `p` is analyzable.
238-
*/
239-
private AssignableDefinition getAnAnalyzableRefDef(Call c, AssignableAccess aa, Parameter p) {
240-
isAnalyzableRefCall(c, aa, p) and
241-
result.getTarget() = p and
242-
not result = TImplicitParameterDefinition(_)
254+
/** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
255+
private predicate basicBlockRefParamDef(
256+
ControlFlow::BasicBlock bb, Parameter p, AssignableDefinition def
257+
) {
258+
def = any(RefArg arg).getAnAnalyzableRefDef(p) and
259+
bb.getANode() = def.getAControlFlowNode()
243260
}
244261

245262
/**
@@ -248,9 +265,11 @@ module AssignableInternal {
248265
* any assignments to `p`.
249266
*/
250267
private predicate parameterReachesWithoutDef(Parameter p, ControlFlow::BasicBlock bb) {
251-
not basicBlockRefParamDef(bb, p) and
268+
forall(AssignableDefinition def | basicBlockRefParamDef(bb, p, def) |
269+
isUncertainRefCall(def.getTargetAccess())
270+
) and
252271
(
253-
isAnalyzableRefCall(_, _, p) and
272+
any(RefArg arg).isAnalyzable(p) and
254273
p.getCallable().getEntryPoint() = bb.getFirstNode()
255274
or
256275
exists(ControlFlow::BasicBlock mid | parameterReachesWithoutDef(p, mid) |
@@ -259,9 +278,23 @@ module AssignableInternal {
259278
)
260279
}
261280

262-
/** Holds if a node in basic block `bb` assigns to `ref` parameter `p`. */
263-
private predicate basicBlockRefParamDef(ControlFlow::BasicBlock bb, Parameter p) {
264-
bb.getANode() = getAnAnalyzableRefDef(_, _, p).getAControlFlowNode()
281+
// Not defined by dispatch in order to avoid too conservative negative recursion error
282+
Expr getExpr(AssignableDefinition def) {
283+
def = TAssignmentDefinition(result)
284+
or
285+
def = TTupleAssignmentDefinition(result, _)
286+
or
287+
def = TOutRefDefinition(any(AssignableAccess aa | result = aa.getParent()))
288+
or
289+
def = TMutationDefinition(result)
290+
or
291+
def = TLocalVariableDefinition(result)
292+
or
293+
def = TAddressOfDefinition(result)
294+
or
295+
def = TIsPatternDefinition(any(IsPatternExpr ipe | result = ipe.getVariableDeclExpr()))
296+
or
297+
def = TTypeCasePatternDefinition(any(TypeCase tc | result = tc.getVariableDeclExpr()))
265298
}
266299

267300
cached
@@ -273,7 +306,7 @@ module AssignableInternal {
273306
TOutRefDefinition(AssignableAccess aa) {
274307
aa.isOutArgument()
275308
or
276-
isRelevantRefCall(_, aa)
309+
aa.(RefArg).isRelevant()
277310
} or
278311
TMutationDefinition(MutatorOperation mo) or
279312
TLocalVariableDefinition(LocalVariableDeclExpr lvde) {
@@ -285,8 +318,10 @@ module AssignableInternal {
285318
} or
286319
TImplicitParameterDefinition(Parameter p) {
287320
exists(Callable c | p = c.getAParameter() |
288-
c.hasBody() or
289-
c.(Constructor).hasInitializer()
321+
c.hasBody()
322+
or
323+
// Same as `c.(Constructor).hasInitializer()`, but avoids negative recursion warning
324+
c.getAChildExpr() instanceof @constructor_init_expr
290325
)
291326
} or
292327
TAddressOfDefinition(AddressOfExpr aoe) or
@@ -311,9 +346,9 @@ module AssignableInternal {
311346
* Holds if the `ref` assignment to `aa` via call `c` is uncertain.
312347
*/
313348
cached
314-
predicate isUncertainRefCall(Call c, AssignableAccess aa) {
315-
isRelevantRefCall(c, aa) and
316-
exists(ControlFlow::BasicBlock bb, Parameter p | isAnalyzableRefCall(c, aa, p) |
349+
predicate isUncertainRefCall(RefArg arg) {
350+
arg.isRelevant() and
351+
exists(ControlFlow::BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
317352
parameterReachesWithoutDef(p, bb) and
318353
bb.getLastNode() = p.getCallable().getExitPoint()
319354
)
@@ -360,18 +395,21 @@ module AssignableInternal {
360395
}
361396

362397
/**
363-
* Gets the argument for the implicit `value` parameter in the accessor call
364-
* `ac`, if any.
398+
* Gets the argument for the implicit `value` parameter in accessor access
399+
* `a`, if any.
365400
*/
366401
cached
367-
Expr getAccessorCallValueArgument(AccessorCall ac) {
368-
exists(AssignExpr ae | tupleAssignmentDefinition(ae, ac) |
369-
tupleAssignmentPair(ae, ac, result)
370-
)
371-
or
372-
exists(Assignment a | ac = a.getLValue() |
373-
result = a.getRValue() and
374-
not a.(AssignOperation).hasExpandedAssignment()
402+
Expr getAccessorCallValueArgument(Access a) {
403+
a.getTarget() instanceof DeclarationWithAccessors and
404+
(
405+
exists(AssignExpr ae | tupleAssignmentDefinition(ae, a) |
406+
tupleAssignmentPair(ae, a, result)
407+
)
408+
or
409+
exists(Assignment ass | a = ass.getLValue() |
410+
result = ass.getRValue() and
411+
not ass.(AssignOperation).hasExpandedAssignment()
412+
)
375413
)
376414
}
377415
}
@@ -410,7 +448,7 @@ class AssignableDefinition extends TAssignableDefinition {
410448
* Not all definitions have an associated expression, for example implicit
411449
* parameter definitions.
412450
*/
413-
Expr getExpr() { none() }
451+
final Expr getExpr() { result = getExpr(this) }
414452

415453
/**
416454
* Gets the underlying element associated with this definition. This is either
@@ -509,8 +547,6 @@ module AssignableDefinitions {
509547
/** Gets the underlying assignment. */
510548
Assignment getAssignment() { result = a }
511549

512-
override Expr getExpr() { result = a }
513-
514550
override Expr getSource() {
515551
result = a.getRValue() and
516552
not a instanceof AssignOperation
@@ -548,8 +584,6 @@ module AssignableDefinitions {
548584
)
549585
}
550586

551-
override Expr getExpr() { result = ae }
552-
553587
override Expr getSource() {
554588
result = getTupleSource(this) // need not exist
555589
}
@@ -585,11 +619,7 @@ module AssignableDefinitions {
585619
)
586620
}
587621

588-
override Expr getExpr() { result = this.getCall() }
589-
590-
override predicate isCertain() {
591-
not isUncertainRefCall(this.getCall(), this.getTargetAccess())
592-
}
622+
override predicate isCertain() { not isUncertainRefCall(this.getTargetAccess()) }
593623

594624
override string toString() { result = aa.toString() }
595625

@@ -607,8 +637,6 @@ module AssignableDefinitions {
607637
/** Gets the underlying mutator operation. */
608638
MutatorOperation getMutatorOperation() { result = mo }
609639

610-
override Expr getExpr() { result = mo }
611-
612640
override string toString() { result = mo.toString() }
613641
}
614642

@@ -623,8 +651,6 @@ module AssignableDefinitions {
623651
/** Gets the underlying local variable declaration. */
624652
LocalVariableDeclExpr getDeclaration() { result = lvde }
625653

626-
override Expr getExpr() { result = lvde }
627-
628654
override string toString() { result = lvde.toString() }
629655
}
630656

@@ -662,8 +688,6 @@ module AssignableDefinitions {
662688
/** Gets the underlying address-of expression. */
663689
AddressOfExpr getAddressOf() { result = aoe }
664690

665-
override Expr getExpr() { result = aoe }
666-
667691
override string toString() { result = aoe.toString() }
668692
}
669693

@@ -678,8 +702,6 @@ module AssignableDefinitions {
678702
/** Gets the underlying local variable declaration. */
679703
LocalVariableDeclExpr getDeclaration() { result = ipe.getVariableDeclExpr() }
680704

681-
override Expr getExpr() { result = this.getDeclaration() }
682-
683705
override Expr getSource() { result = ipe.getExpr() }
684706

685707
override string toString() { result = this.getDeclaration().toString() }
@@ -705,8 +727,6 @@ module AssignableDefinitions {
705727
/** Gets the underlying local variable declaration. */
706728
LocalVariableDeclExpr getDeclaration() { result = tc.getVariableDeclExpr() }
707729

708-
override Expr getExpr() { result = this.getDeclaration() }
709-
710730
override Expr getSource() {
711731
result = any(SwitchStmt ss | ss.getATypeCase() = tc).getCondition()
712732
}

csharp/ql/src/semmle/code/csharp/Property.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ class IndexerProperty extends Property {
513513
IndexerCall getAnIndexerCall() {
514514
result = getType().(RefType).getAnIndexer().getAnAccessor().getACall() and
515515
// The qualifier of this indexer call should be a value returned from an access of this property
516-
exists(Expr qualifier | qualifier = result.(IndexerAccess).getQualifier() |
516+
exists(Expr qualifier | qualifier = result.getAccess().getQualifier() |
517517
DataFlow::localFlow(DataFlow::exprNode(this.getAnAccess()), DataFlow::exprNode(qualifier))
518518
)
519519
}

csharp/ql/src/semmle/code/csharp/dataflow/Nullness.qll

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,11 @@ private predicate defMaybeNull(Ssa::Definition def, string msg, Element reason)
176176
or
177177
// A parameter might be `null` if there is a `null` argument somewhere
178178
isMaybeNullArgument(def, reason) and
179-
msg = "because of $@ null argument"
179+
(
180+
if reason instanceof AlwaysNullExpr
181+
then msg = "because of $@ null argument"
182+
else msg = "because of $@ potential null argument"
183+
)
180184
or
181185
// If the source of a variable is `null` then the variable may be `null`
182186
exists(AssignableDefinition adef | adef = def.(Ssa::ExplicitDefinition).getADefinition() |

csharp/ql/src/semmle/code/csharp/dispatch/Dispatch.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ private module Internal {
477477

478478
override Expr getArgument(int i) { result = getCall().getArgument(i) }
479479

480-
override Expr getQualifier() { result = getCall().(MemberAccess).getQualifier() }
480+
override Expr getQualifier() { result = getCall().getAccess().getQualifier() }
481481

482482
override Accessor getAStaticTarget() { result = getCall().getTarget() }
483483

0 commit comments

Comments
 (0)