Skip to content

Commit 3c5eff4

Browse files
committed
C#: Add CFG support for null conditional assignments and include eg. field access in the CFG.
1 parent 8257475 commit 3c5eff4

File tree

1 file changed

+51
-17
lines changed

1 file changed

+51
-17
lines changed

csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,8 @@ module Expressions {
429429
not this instanceof ObjectCreation and
430430
not this instanceof ArrayCreation and
431431
not this instanceof QualifiedWriteAccess and
432-
not this instanceof AccessorWrite and
432+
not this instanceof QualifiedAssignments and
433+
not this instanceof QualifiedAccessorWrite and
433434
not this instanceof NoNodeExpr and
434435
not this instanceof SwitchExpr and
435436
not this instanceof SwitchCaseExpr and
@@ -481,11 +482,35 @@ module Expressions {
481482
}
482483
}
483484

484-
private class StatOrDynAccessorCall_ =
485-
@dynamic_member_access_expr or @dynamic_element_access_expr or @call_access_expr;
485+
private class QualifiedAssignments extends PostOrderTree instanceof Assignment {
486+
QualifiedWriteAccess left;
487+
Expr right;
486488

487-
/** A normal or a (potential) dynamic call to an accessor. */
488-
private class StatOrDynAccessorCall extends Expr, StatOrDynAccessorCall_ { }
489+
QualifiedAssignments() {
490+
left = this.getLValue() and
491+
right = this.getRValue()
492+
}
493+
494+
final override predicate propagatesAbnormal(AstNode child) { child = [left, right] }
495+
496+
final override predicate first(AstNode first) { first(left, first) }
497+
498+
final override predicate last(AstNode last, Completion c) {
499+
PostOrderTree.super.last(last, c)
500+
or
501+
last(left, last, c) and
502+
left.(QualifiableExpr).isConditional() and
503+
c.(NullnessCompletion).isNull()
504+
}
505+
506+
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
507+
// Standard left-to-right evaluation
508+
last(left, pred, c) and
509+
c instanceof NormalCompletion and
510+
not c.(NullnessCompletion).isNull() and
511+
first(right, succ)
512+
}
513+
}
489514

490515
/**
491516
* An expression that writes via an accessor call, for example `x.Prop = 0`,
@@ -499,23 +524,23 @@ module Expressions {
499524
* x -> 0 -> set_Prop -> x.Prop = 0
500525
* ```
501526
*/
502-
class AccessorWrite extends PostOrderTree instanceof Expr {
527+
private class QualifiedAccessorWrite extends PostOrderTree instanceof Expr {
503528
AssignableDefinition def;
504529

505-
AccessorWrite() {
530+
QualifiedAccessorWrite() {
506531
def.getExpr() = this and
507-
def.getTargetAccess().(WriteAccess) instanceof StatOrDynAccessorCall and
532+
def.getTargetAccess() instanceof QualifiedWriteAccess and
508533
not this instanceof AssignOperationWithExpandedAssignment
509534
}
510535

511536
/**
512537
* Gets the `i`th accessor being called in this write. More than one call
513538
* can happen in tuple assignments.
514539
*/
515-
StatOrDynAccessorCall getCall(int i) {
540+
WriteAccess getAccess(int i) {
516541
result =
517542
rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef |
518-
tdef.getExpr() = this and tdef.getTargetAccess() instanceof StatOrDynAccessorCall
543+
tdef.getExpr() = this
519544
|
520545
tdef order by tdef.getEvaluationOrder()
521546
).getTargetAccess()
@@ -528,7 +553,13 @@ module Expressions {
528553
final override predicate propagatesAbnormal(AstNode child) {
529554
child = getExprChild(this, _)
530555
or
531-
child = this.getCall(_)
556+
child = this.getAccess(_)
557+
}
558+
559+
final override predicate last(AstNode last, Completion c) {
560+
PostOrderTree.super.last(last, c)
561+
or
562+
last(getExprChild(this, 0), last, c) and c.(NullnessCompletion).isNull()
532563
}
533564

534565
final override predicate first(AstNode first) { first(getExprChild(this, 0), first) }
@@ -538,24 +569,25 @@ module Expressions {
538569
exists(int i |
539570
last(getExprChild(this, i), pred, c) and
540571
c instanceof NormalCompletion and
572+
(i != 0 or not c.(NullnessCompletion).isNull()) and
541573
first(getExprChild(this, i + 1), succ)
542574
)
543575
or
544576
// Flow from last element of last child to first accessor call
545577
last(getLastExprChild(this), pred, c) and
546-
succ = this.getCall(0) and
578+
succ = this.getAccess(0) and
547579
c instanceof NormalCompletion
548580
or
549581
// Flow from one call to the next
550-
exists(int i | pred = this.getCall(i) |
551-
succ = this.getCall(i + 1) and
582+
exists(int i | pred = this.getAccess(i) |
583+
succ = this.getAccess(i + 1) and
552584
c.isValidFor(pred) and
553585
c instanceof NormalCompletion
554586
)
555587
or
556588
// Post-order: flow from last call to element itself
557-
exists(int last | last = max(int i | exists(this.getCall(i))) |
558-
pred = this.getCall(last) and
589+
exists(int last | last = max(int i | exists(this.getAccess(i))) |
590+
pred = this.getAccess(last) and
559591
succ = this and
560592
c.isValidFor(pred) and
561593
c instanceof NormalCompletion
@@ -704,7 +736,9 @@ module Expressions {
704736
private class ConditionallyQualifiedExpr extends PostOrderTree instanceof QualifiableExpr {
705737
private Expr qualifier;
706738

707-
ConditionallyQualifiedExpr() { this.isConditional() and qualifier = getExprChild(this, 0) }
739+
ConditionallyQualifiedExpr() {
740+
this.isConditional() and qualifier = getExprChild(this, 0) and not this instanceof WriteAccess
741+
}
708742

709743
final override predicate propagatesAbnormal(AstNode child) { child = qualifier }
710744

0 commit comments

Comments
 (0)