Skip to content

Commit c6d0600

Browse files
authored
Merge pull request #798 from hvitved/csharp/accessor-calls
C#: Redefine `AccessorCall`
2 parents 0a4f2e8 + 779039b commit c6d0600

25 files changed

+897
-705
lines changed

change-notes/1.20/analysis-csharp.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@
2424

2525
## Changes to QL libraries
2626

27+
* The class `AccessorCall` (and subclasses `PropertyCall`, `IndexerCall`, and `EventCall`) have been redefined, so the expressions they represent are not necessarily the accesses themselves, but rather the expressions that give rise to the accessor calls. For example, in the property assignment `x.Prop = 0`, the call to the setter for `Prop` is no longer represented by the access `x.Prop`, but instead the whole assignment. Consequently, it is no longer safe to cast directly between `AccessorCall`s and `Access`es, and the predicate `AccessorCall::getAccess()` should be used instead.
28+
2729
## Changes to the autobuilder

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

Lines changed: 127 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -159,87 +159,101 @@ 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 deliberately 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+
p = this.getParameter().getSourceDeclaration() and
209+
result.getAParameter() = p
210+
}
211+
212+
/**
213+
* Holds if the assignment to this `ref` argument via parameter `p` is
214+
* analyzable. That is, the target callable is non-overridable and from
215+
* source.
216+
*/
217+
predicate isAnalyzable(Parameter p) {
218+
exists(Callable callable | callable = this.getSourceDeclarationTarget(p) |
219+
not callable.(Virtualizable).isOverridableOrImplementable() and
220+
callable.hasBody()
221+
)
222+
}
223+
224+
/** Gets an assignment to analyzable parameter `p`. */
225+
AssignableDefinition getAnAnalyzableRefDef(Parameter p) {
226+
this.isAnalyzable(p) and
227+
result.getTarget() = p and
228+
not result = TImplicitParameterDefinition(_)
229+
}
230+
231+
/**
232+
* Holds if this `ref` assignment is *not* analyzable. Equivalent with
233+
* `not this.isAnalyzable(_)`, but avoids negative recursion.
234+
*/
235+
private predicate isNonAnalyzable() {
236+
call instanceof @delegate_invocation_expr
227237
or
228-
exists(Callable callable | callable = getRefCallTarget(c, aa, _) |
238+
exists(Callable callable | callable = this.getSourceDeclarationTarget(_) |
229239
callable.(Virtualizable).isOverridableOrImplementable() or
230240
not callable.hasBody()
231241
)
232-
)
242+
}
243+
244+
/** Holds if this `ref` access is a potential assignment. */
245+
predicate isPotentialAssignment() {
246+
this.isNonAnalyzable() or
247+
exists(this.getAnAnalyzableRefDef(_))
248+
}
233249
}
234250

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(_)
251+
/** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
252+
private predicate basicBlockRefParamDef(
253+
ControlFlow::BasicBlock bb, Parameter p, AssignableDefinition def
254+
) {
255+
def = any(RefArg arg).getAnAnalyzableRefDef(p) and
256+
bb.getANode() = def.getAControlFlowNode()
243257
}
244258

245259
/**
@@ -248,9 +262,11 @@ module AssignableInternal {
248262
* any assignments to `p`.
249263
*/
250264
private predicate parameterReachesWithoutDef(Parameter p, ControlFlow::BasicBlock bb) {
251-
not basicBlockRefParamDef(bb, p) and
265+
forall(AssignableDefinition def | basicBlockRefParamDef(bb, p, def) |
266+
isUncertainRefCall(def.getTargetAccess())
267+
) and
252268
(
253-
isAnalyzableRefCall(_, _, p) and
269+
any(RefArg arg).isAnalyzable(p) and
254270
p.getCallable().getEntryPoint() = bb.getFirstNode()
255271
or
256272
exists(ControlFlow::BasicBlock mid | parameterReachesWithoutDef(p, mid) |
@@ -259,9 +275,23 @@ module AssignableInternal {
259275
)
260276
}
261277

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()
278+
// Not defined by dispatch in order to avoid too conservative negative recursion error
279+
Expr getExpr(AssignableDefinition def) {
280+
def = TAssignmentDefinition(result)
281+
or
282+
def = TTupleAssignmentDefinition(result, _)
283+
or
284+
def = TOutRefDefinition(any(AssignableAccess aa | result = aa.getParent()))
285+
or
286+
def = TMutationDefinition(result)
287+
or
288+
def = TLocalVariableDefinition(result)
289+
or
290+
def = TAddressOfDefinition(result)
291+
or
292+
def = TIsPatternDefinition(any(IsPatternExpr ipe | result = ipe.getVariableDeclExpr()))
293+
or
294+
def = TTypeCasePatternDefinition(any(TypeCase tc | result = tc.getVariableDeclExpr()))
265295
}
266296

267297
cached
@@ -273,7 +303,7 @@ module AssignableInternal {
273303
TOutRefDefinition(AssignableAccess aa) {
274304
aa.isOutArgument()
275305
or
276-
isRelevantRefCall(_, aa)
306+
aa.(RefArg).isPotentialAssignment()
277307
} or
278308
TMutationDefinition(MutatorOperation mo) or
279309
TLocalVariableDefinition(LocalVariableDeclExpr lvde) {
@@ -285,8 +315,10 @@ module AssignableInternal {
285315
} or
286316
TImplicitParameterDefinition(Parameter p) {
287317
exists(Callable c | p = c.getAParameter() |
288-
c.hasBody() or
289-
c.(Constructor).hasInitializer()
318+
c.hasBody()
319+
or
320+
// Same as `c.(Constructor).hasInitializer()`, but avoids negative recursion warning
321+
c.getAChildExpr() instanceof @constructor_init_expr
290322
)
291323
} or
292324
TAddressOfDefinition(AddressOfExpr aoe) or
@@ -311,9 +343,9 @@ module AssignableInternal {
311343
* Holds if the `ref` assignment to `aa` via call `c` is uncertain.
312344
*/
313345
cached
314-
predicate isUncertainRefCall(Call c, AssignableAccess aa) {
315-
isRelevantRefCall(c, aa) and
316-
exists(ControlFlow::BasicBlock bb, Parameter p | isAnalyzableRefCall(c, aa, p) |
346+
predicate isUncertainRefCall(RefArg arg) {
347+
arg.isPotentialAssignment() and
348+
exists(ControlFlow::BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
317349
parameterReachesWithoutDef(p, bb) and
318350
bb.getLastNode() = p.getCallable().getExitPoint()
319351
)
@@ -360,18 +392,21 @@ module AssignableInternal {
360392
}
361393

362394
/**
363-
* Gets the argument for the implicit `value` parameter in the accessor call
364-
* `ac`, if any.
395+
* Gets the argument for the implicit `value` parameter in accessor access
396+
* `a`, if any.
365397
*/
366398
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()
399+
Expr getAccessorCallValueArgument(Access a) {
400+
a.getTarget() instanceof DeclarationWithAccessors and
401+
(
402+
exists(AssignExpr ae | tupleAssignmentDefinition(ae, a) |
403+
tupleAssignmentPair(ae, a, result)
404+
)
405+
or
406+
exists(Assignment ass | a = ass.getLValue() |
407+
result = ass.getRValue() and
408+
not ass.(AssignOperation).hasExpandedAssignment()
409+
)
375410
)
376411
}
377412
}
@@ -410,7 +445,7 @@ class AssignableDefinition extends TAssignableDefinition {
410445
* Not all definitions have an associated expression, for example implicit
411446
* parameter definitions.
412447
*/
413-
Expr getExpr() { none() }
448+
final Expr getExpr() { result = getExpr(this) }
414449

415450
/**
416451
* Gets the underlying element associated with this definition. This is either
@@ -509,8 +544,6 @@ module AssignableDefinitions {
509544
/** Gets the underlying assignment. */
510545
Assignment getAssignment() { result = a }
511546

512-
override Expr getExpr() { result = a }
513-
514547
override Expr getSource() {
515548
result = a.getRValue() and
516549
not a instanceof AssignOperation
@@ -548,8 +581,6 @@ module AssignableDefinitions {
548581
)
549582
}
550583

551-
override Expr getExpr() { result = ae }
552-
553584
override Expr getSource() {
554585
result = getTupleSource(this) // need not exist
555586
}
@@ -585,11 +616,7 @@ module AssignableDefinitions {
585616
)
586617
}
587618

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

594621
override string toString() { result = aa.toString() }
595622

@@ -607,8 +634,6 @@ module AssignableDefinitions {
607634
/** Gets the underlying mutator operation. */
608635
MutatorOperation getMutatorOperation() { result = mo }
609636

610-
override Expr getExpr() { result = mo }
611-
612637
override string toString() { result = mo.toString() }
613638
}
614639

@@ -623,8 +648,6 @@ module AssignableDefinitions {
623648
/** Gets the underlying local variable declaration. */
624649
LocalVariableDeclExpr getDeclaration() { result = lvde }
625650

626-
override Expr getExpr() { result = lvde }
627-
628651
override string toString() { result = lvde.toString() }
629652
}
630653

@@ -662,8 +685,6 @@ module AssignableDefinitions {
662685
/** Gets the underlying address-of expression. */
663686
AddressOfExpr getAddressOf() { result = aoe }
664687

665-
override Expr getExpr() { result = aoe }
666-
667688
override string toString() { result = aoe.toString() }
668689
}
669690

@@ -678,8 +699,6 @@ module AssignableDefinitions {
678699
/** Gets the underlying local variable declaration. */
679700
LocalVariableDeclExpr getDeclaration() { result = ipe.getVariableDeclExpr() }
680701

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

685704
override string toString() { result = this.getDeclaration().toString() }
@@ -705,8 +724,6 @@ module AssignableDefinitions {
705724
/** Gets the underlying local variable declaration. */
706725
LocalVariableDeclExpr getDeclaration() { result = tc.getVariableDeclExpr() }
707726

708-
override Expr getExpr() { result = this.getDeclaration() }
709-
710727
override Expr getSource() {
711728
result = any(SwitchStmt ss | ss.getATypeCase() = tc).getCondition()
712729
}

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() |

0 commit comments

Comments
 (0)