Skip to content

Commit 15848f3

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

File tree

1 file changed

+41
-20
lines changed

1 file changed

+41
-20
lines changed

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

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ 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
433433
not this instanceof NoNodeExpr and
434434
not this instanceof SwitchExpr and
435435
not this instanceof SwitchCaseExpr and
@@ -448,6 +448,10 @@ module Expressions {
448448
/**
449449
* A qualified write access. In a qualified write access, the access itself is
450450
* not evaluated, only the qualifier and the indexer arguments (if any).
451+
*
452+
* Note that the successor declaration in `QualifiedAccessorWrite` ensures that the access itself
453+
* is evaluated after the qualifier and the indexer arguments (if any)
454+
* and the right hand side of the assignment.
451455
*/
452456
private class QualifiedWriteAccess extends ControlFlowTree instanceof WriteAccess, QualifiableExpr
453457
{
@@ -470,25 +474,25 @@ module Expressions {
470474
final override predicate last(AstNode last, Completion c) {
471475
// Skip the access in a qualified write access
472476
last(getLastExprChild(this), last, c)
477+
or
478+
// Qualifier exits with a null completion
479+
super.isConditional() and
480+
last(super.getQualifier(), last, c) and
481+
c.(NullnessCompletion).isNull()
473482
}
474483

475484
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
476485
exists(int i |
477486
last(getExprChild(this, i), pred, c) and
478487
c instanceof NormalCompletion and
488+
(i != 0 or not c.(NullnessCompletion).isNull()) and
479489
first(getExprChild(this, i + 1), succ)
480490
)
481491
}
482492
}
483493

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-
490494
/**
491-
* An expression that writes via an accessor call, for example `x.Prop = 0`,
495+
* An expression that writes via an accessor, for example `x.Prop = 0`,
492496
* where `Prop` is a property.
493497
*
494498
* Accessor writes need special attention, because we need to model the fact
@@ -498,24 +502,32 @@ module Expressions {
498502
* ```csharp
499503
* x -> 0 -> set_Prop -> x.Prop = 0
500504
* ```
505+
*
506+
* For consistency, control flow is implemented this way for all accessor writes.
507+
* For example, `x.Field = 0`, where `Field` is a field, we want a CFG that looks like
508+
*
509+
* ```csharp
510+
* x -> 0 -> x.Field -> x.Field = 0
511+
* ```
501512
*/
502-
class AccessorWrite extends PostOrderTree instanceof Expr {
513+
private class QualifiedAccessorWrite extends PostOrderTree instanceof Expr {
503514
AssignableDefinition def;
504515

505-
AccessorWrite() {
516+
QualifiedAccessorWrite() {
506517
def.getExpr() = this and
507-
def.getTargetAccess().(WriteAccess) instanceof StatOrDynAccessorCall and
518+
def.getTargetAccess().(WriteAccess) instanceof QualifiableExpr and
508519
not this instanceof AssignOperationWithExpandedAssignment
509520
}
510521

511522
/**
512523
* Gets the `i`th accessor being called in this write. More than one call
513524
* can happen in tuple assignments.
514525
*/
515-
StatOrDynAccessorCall getCall(int i) {
526+
QualifiableExpr getAccess(int i) {
516527
result =
517528
rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef |
518-
tdef.getExpr() = this and tdef.getTargetAccess() instanceof StatOrDynAccessorCall
529+
tdef.getExpr() = this and
530+
tdef.getTargetAccess() instanceof QualifiableExpr
519531
|
520532
tdef order by tdef.getEvaluationOrder()
521533
).getTargetAccess()
@@ -528,7 +540,13 @@ module Expressions {
528540
final override predicate propagatesAbnormal(AstNode child) {
529541
child = getExprChild(this, _)
530542
or
531-
child = this.getCall(_)
543+
child = this.getAccess(_)
544+
}
545+
546+
final override predicate last(AstNode last, Completion c) {
547+
PostOrderTree.super.last(last, c)
548+
or
549+
last(getExprChild(this, 0), last, c) and c.(NullnessCompletion).isNull()
532550
}
533551

534552
final override predicate first(AstNode first) { first(getExprChild(this, 0), first) }
@@ -538,24 +556,25 @@ module Expressions {
538556
exists(int i |
539557
last(getExprChild(this, i), pred, c) and
540558
c instanceof NormalCompletion and
559+
(i != 0 or not c.(NullnessCompletion).isNull()) and
541560
first(getExprChild(this, i + 1), succ)
542561
)
543562
or
544563
// Flow from last element of last child to first accessor call
545564
last(getLastExprChild(this), pred, c) and
546-
succ = this.getCall(0) and
565+
succ = this.getAccess(0) and
547566
c instanceof NormalCompletion
548567
or
549568
// Flow from one call to the next
550-
exists(int i | pred = this.getCall(i) |
551-
succ = this.getCall(i + 1) and
569+
exists(int i | pred = this.getAccess(i) |
570+
succ = this.getAccess(i + 1) and
552571
c.isValidFor(pred) and
553572
c instanceof NormalCompletion
554573
)
555574
or
556575
// 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
576+
exists(int last | last = max(int i | exists(this.getAccess(i))) |
577+
pred = this.getAccess(last) and
559578
succ = this and
560579
c.isValidFor(pred) and
561580
c instanceof NormalCompletion
@@ -704,7 +723,9 @@ module Expressions {
704723
private class ConditionallyQualifiedExpr extends PostOrderTree instanceof QualifiableExpr {
705724
private Expr qualifier;
706725

707-
ConditionallyQualifiedExpr() { this.isConditional() and qualifier = getExprChild(this, 0) }
726+
ConditionallyQualifiedExpr() {
727+
this.isConditional() and qualifier = getExprChild(this, 0) and not this instanceof WriteAccess
728+
}
708729

709730
final override predicate propagatesAbnormal(AstNode child) { child = qualifier }
710731

0 commit comments

Comments
 (0)