Skip to content

Commit b2ef853

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

File tree

1 file changed

+68
-20
lines changed

1 file changed

+68
-20
lines changed

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

Lines changed: 68 additions & 20 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 QualifiedAccessorWrite and
433+
not this instanceof QualifiedAssignments and
433434
not this instanceof NoNodeExpr and
434435
not this instanceof SwitchExpr and
435436
not this instanceof SwitchCaseExpr and
@@ -481,14 +482,8 @@ module Expressions {
481482
}
482483
}
483484

484-
private class StatOrDynAccessorCall_ =
485-
@dynamic_member_access_expr or @dynamic_element_access_expr or @call_access_expr;
486-
487-
/** A normal or a (potential) dynamic call to an accessor. */
488-
private class StatOrDynAccessorCall extends Expr, StatOrDynAccessorCall_ { }
489-
490485
/**
491-
* An expression that writes via an accessor call, for example `x.Prop = 0`,
486+
* An expression that writes via an accessor, for example `x.Prop = 0`,
492487
* where `Prop` is a property.
493488
*
494489
* Accessor writes need special attention, because we need to model the fact
@@ -498,24 +493,31 @@ module Expressions {
498493
* ```csharp
499494
* x -> 0 -> set_Prop -> x.Prop = 0
500495
* ```
496+
*
497+
* For consistency, control flow is implemented this way for all accessor writes.
498+
* For example, `x.Field = 0`, where `Field` is a field, we want a CFG that looks like
499+
*
500+
* ```csharp
501+
* x -> 0 -> x.Field -> x.Field = 0
502+
* ```
501503
*/
502-
class AccessorWrite extends PostOrderTree instanceof Expr {
504+
private class QualifiedAccessorWrite extends PostOrderTree instanceof Expr {
503505
AssignableDefinition def;
504506

505-
AccessorWrite() {
507+
QualifiedAccessorWrite() {
506508
def.getExpr() = this and
507-
def.getTargetAccess().(WriteAccess) instanceof StatOrDynAccessorCall and
509+
def.getTargetAccess() instanceof QualifiedWriteAccess and
508510
not this instanceof AssignOperationWithExpandedAssignment
509511
}
510512

511513
/**
512514
* Gets the `i`th accessor being called in this write. More than one call
513515
* can happen in tuple assignments.
514516
*/
515-
StatOrDynAccessorCall getCall(int i) {
517+
WriteAccess getAccess(int i) {
516518
result =
517519
rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef |
518-
tdef.getExpr() = this and tdef.getTargetAccess() instanceof StatOrDynAccessorCall
520+
tdef.getExpr() = this
519521
|
520522
tdef order by tdef.getEvaluationOrder()
521523
).getTargetAccess()
@@ -528,7 +530,13 @@ module Expressions {
528530
final override predicate propagatesAbnormal(AstNode child) {
529531
child = getExprChild(this, _)
530532
or
531-
child = this.getCall(_)
533+
child = this.getAccess(_)
534+
}
535+
536+
final override predicate last(AstNode last, Completion c) {
537+
PostOrderTree.super.last(last, c)
538+
or
539+
last(getExprChild(this, 0), last, c) and c.(NullnessCompletion).isNull()
532540
}
533541

534542
final override predicate first(AstNode first) { first(getExprChild(this, 0), first) }
@@ -538,31 +546,69 @@ module Expressions {
538546
exists(int i |
539547
last(getExprChild(this, i), pred, c) and
540548
c instanceof NormalCompletion and
549+
(i != 0 or not c.(NullnessCompletion).isNull()) and
541550
first(getExprChild(this, i + 1), succ)
542551
)
543552
or
544553
// Flow from last element of last child to first accessor call
545554
last(getLastExprChild(this), pred, c) and
546-
succ = this.getCall(0) and
555+
succ = this.getAccess(0) and
547556
c instanceof NormalCompletion
548557
or
549558
// Flow from one call to the next
550-
exists(int i | pred = this.getCall(i) |
551-
succ = this.getCall(i + 1) and
559+
exists(int i | pred = this.getAccess(i) |
560+
succ = this.getAccess(i + 1) and
552561
c.isValidFor(pred) and
553562
c instanceof NormalCompletion
554563
)
555564
or
556565
// 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
566+
exists(int last | last = max(int i | exists(this.getAccess(i))) |
567+
pred = this.getAccess(last) and
559568
succ = this and
560569
c.isValidFor(pred) and
561570
c instanceof NormalCompletion
562571
)
563572
}
564573
}
565574

575+
/**
576+
* An assignment where the left hand side is a qualified write access.
577+
*
578+
* This is needed to allow "null" completion to propagate from the qualifier
579+
* to after the assignment.
580+
*/
581+
private class QualifiedAssignments extends PostOrderTree instanceof Assignment {
582+
QualifiedWriteAccess left;
583+
Expr right;
584+
585+
QualifiedAssignments() {
586+
left = this.getLValue() and
587+
right = this.getRValue() and
588+
not this instanceof AssignOperationWithExpandedAssignment
589+
}
590+
591+
final override predicate propagatesAbnormal(AstNode child) { child = [left, right] }
592+
593+
final override predicate first(AstNode first) { first(left, first) }
594+
595+
final override predicate last(AstNode last, Completion c) {
596+
PostOrderTree.super.last(last, c)
597+
or
598+
last(left, last, c) and
599+
left.(QualifiableExpr).isConditional() and
600+
c.(NullnessCompletion).isNull()
601+
}
602+
603+
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
604+
// Standard left-to-right evaluation
605+
last(left, pred, c) and
606+
c instanceof NormalCompletion and
607+
not c.(NullnessCompletion).isNull() and
608+
first(right, succ)
609+
}
610+
}
611+
566612
private class LogicalNotExprTree extends PostOrderTree instanceof LogicalNotExpr {
567613
private Expr operand;
568614

@@ -704,7 +750,9 @@ module Expressions {
704750
private class ConditionallyQualifiedExpr extends PostOrderTree instanceof QualifiableExpr {
705751
private Expr qualifier;
706752

707-
ConditionallyQualifiedExpr() { this.isConditional() and qualifier = getExprChild(this, 0) }
753+
ConditionallyQualifiedExpr() {
754+
this.isConditional() and qualifier = getExprChild(this, 0) and not this instanceof WriteAccess
755+
}
708756

709757
final override predicate propagatesAbnormal(AstNode child) { child = qualifier }
710758

0 commit comments

Comments
 (0)