Skip to content

Commit 2ece2cc

Browse files
committed
C#: Add field access for out/ref assignments in the CFG.
1 parent 15e9685 commit 2ece2cc

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ module Expressions {
430430
not this instanceof ArrayCreation and
431431
not this instanceof QualifiedWriteAccess and
432432
not this instanceof QualifiedAccessorWrite and
433+
not this instanceof QualifiedWriteAccessOutRef and
433434
not this instanceof NoNodeExpr and
434435
not this instanceof SwitchExpr and
435436
not this instanceof SwitchCaseExpr and
@@ -491,6 +492,86 @@ module Expressions {
491492
}
492493
}
493494

495+
/**
496+
* An expression that writes via an accessor in an out/ref parameter, for example `s = M(out x.Field)`,
497+
* where `Field` is a field.
498+
*
499+
* Accessor writes need special attention, because we need to model that the
500+
* access is written after the method call.
501+
*
502+
* In the example above, this means we want a CFG that looks like
503+
*
504+
* ```csharp
505+
* x -> call M -> x.Field -> s = M(out x.Field)
506+
* ```
507+
*/
508+
private class QualifiedWriteAccessOutRef extends PostOrderTree instanceof Expr {
509+
final override predicate propagatesAbnormal(AstNode child) { child = getExprChild(this, _) }
510+
511+
QualifiedWriteAccessOutRef() {
512+
exists(AssignableDefinitions::OutRefDefinition def |
513+
def.getExpr() = this and
514+
def.getTargetAccess() instanceof QualifiableExpr
515+
)
516+
}
517+
518+
final override predicate first(AstNode first) {
519+
first(getExprChild(this, 0), first)
520+
or
521+
not exists(getExprChild(this, 0)) and
522+
first = this
523+
}
524+
525+
private QualifiableExpr getOutRefAccess(int i) {
526+
result =
527+
rank[i + 1](AssignableDefinitions::OutRefDefinition def |
528+
def.getExpr() = this and
529+
def.getTargetAccess() instanceof QualifiableExpr
530+
|
531+
def order by def.getIndex()
532+
).getTargetAccess()
533+
}
534+
535+
private QualifiableExpr getLastOutRefAccess() {
536+
exists(int last |
537+
result = this.getOutRefAccess(last) and
538+
not exists(this.getOutRefAccess(last + 1))
539+
)
540+
}
541+
542+
final override predicate last(AstNode last, Completion c) {
543+
last = this.getLastOutRefAccess() and
544+
c.isValidFor(last)
545+
}
546+
547+
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
548+
exists(int i |
549+
last(getExprChild(this, i), pred, c) and
550+
c instanceof NormalCompletion
551+
|
552+
// Post-order: flow from last element of last child to element itself
553+
i = max(int j | exists(getExprChild(this, j))) and
554+
succ = this
555+
or
556+
// Standard left-to-right evaluation
557+
first(getExprChild(this, i + 1), succ)
558+
)
559+
or
560+
// Flow from this element to the first write access.
561+
pred = this and
562+
succ = this.getOutRefAccess(0) and
563+
c.isValidFor(pred) and
564+
c instanceof NormalCompletion
565+
or
566+
// Flow from one access to the next
567+
exists(int i | pred = this.getOutRefAccess(i) |
568+
succ = this.getOutRefAccess(i + 1) and
569+
c.isValidFor(pred) and
570+
c instanceof NormalCompletion
571+
)
572+
}
573+
}
574+
494575
/**
495576
* An expression that writes via an accessor, for example `x.Prop = 0`,
496577
* where `Prop` is a property.
@@ -516,6 +597,7 @@ module Expressions {
516597
QualifiedAccessorWrite() {
517598
def.getExpr() = this and
518599
def.getTargetAccess().(WriteAccess) instanceof QualifiableExpr and
600+
not def instanceof AssignableDefinitions::OutRefDefinition and
519601
not this instanceof AssignOperationWithExpandedAssignment
520602
}
521603

0 commit comments

Comments
 (0)