Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e8cd993

Browse files
committed
C#: Simple Boolean control flow graph splitting
1 parent d0f63ca commit e8cd993

17 files changed

Lines changed: 2684 additions & 16 deletions

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

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,7 @@ class AssignableDefinition extends TAssignableDefinition {
464464
ControlFlow::Node getControlFlowNode() { result = this.getAControlFlowNode() }
465465

466466
/** Gets the enclosing callable of this definition. */
467-
Callable getEnclosingCallable() {
468-
result = this.getAControlFlowNode().getBasicBlock().getCallable()
469-
}
467+
Callable getEnclosingCallable() { result = this.getExpr().getEnclosingCallable() }
470468

471469
/**
472470
* Gets the assigned expression, if any. For example, the expression assigned
@@ -592,7 +590,7 @@ class AssignableDefinition extends TAssignableDefinition {
592590

593591
/** Gets the location of this assignable definition. */
594592
Location getLocation() {
595-
result = this.getAControlFlowNode().getLocation()
593+
result = this.getExpr().getLocation()
596594
}
597595
}
598596

@@ -642,24 +640,15 @@ module AssignableDefinitions {
642640
result = ae
643641
}
644642

645-
private Expr getLeaf() {
646-
result = leaf
647-
}
648-
649643
/**
650644
* Gets the evaluation order of this definition among the other definitions
651645
* in the compound tuple assignment. For example, in `(x, (y, z)) = ...` the
652646
* orders of the definitions of `x`, `y`, and `z` are 0, 1, and 2, respectively.
653647
*/
654648
int getEvaluationOrder() {
655-
exists(ControlFlow::BasicBlock bb, int i |
656-
bb.getNode(i).getElement() = leaf |
657-
i = rank[result + 1](int j, TupleAssignmentDefinition def |
658-
bb.getNode(j).getElement() = def.getLeaf() and
659-
ae = def.getAssignment()
660-
|
661-
j
662-
)
649+
leaf = rank[result + 1](Expr leaf0 |
650+
exists(TTupleAssignmentDefinition(ae, leaf0)) |
651+
leaf0 order by leaf0.getLocation().getStartLine(), leaf0.getLocation().getStartColumn()
663652
)
664653
}
665654

@@ -786,6 +775,10 @@ module AssignableDefinitions {
786775
result = p.getCallable().getEntryPoint()
787776
}
788777

778+
override Callable getEnclosingCallable() {
779+
result = p.getCallable()
780+
}
781+
789782
override string toString() {
790783
result = p.toString()
791784
}

csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ module ControlFlow {
360360
class Split = SplitImpl;
361361
class FinallySplit = FinallySplitting::FinallySplitImpl;
362362
class ExceptionHandlerSplit = ExceptionHandlerSplitting::ExceptionHandlerSplitImpl;
363+
class BooleanSplit = BooleanSplitting::BooleanSplitImpl;
363364
}
364365

365366
class BasicBlock = BBs::BasicBlock;
@@ -3204,6 +3205,275 @@ module ControlFlow {
32043205
}
32053206
}
32063207

3208+
module BooleanSplitting {
3209+
private import PreBasicBlocks
3210+
private import PreSsa
3211+
3212+
/** A sub-classification of Boolean splits. */
3213+
abstract class BooleanSplitSubKind extends TBooleanSplitSubKind {
3214+
/**
3215+
* Holds if the branch taken by condition `cb1` should be recorded in
3216+
* this split, and the recorded value determines the branch taken by a
3217+
* later condition `cb2`, possibly inverted.
3218+
*
3219+
* For example, in
3220+
*
3221+
* ```
3222+
* var b = GetB();
3223+
* if (b)
3224+
* Console.WriteLine("b is true");
3225+
* if (!b)
3226+
* Console.WriteLine("b is false");
3227+
* ```
3228+
*
3229+
* the branch taken in the condition on line 2 can be recorded, and the
3230+
* recorded value will detmine the branch taken in the condition on line 4.
3231+
*/
3232+
abstract predicate correlatesConditions(ConditionBlock cb1, ConditionBlock cb2, boolean inverted);
3233+
3234+
/** Holds if control flow element `cfe` starts a split of this kind. */
3235+
predicate startsSplit(ControlFlowElement cfe) {
3236+
this.correlatesConditions(any(ConditionBlock cb | cb.getLastElement() = cfe), _, _)
3237+
}
3238+
3239+
/** Gets the callable that this Boolean split kind belongs to. */
3240+
abstract Callable getEnclosingCallable();
3241+
3242+
/** Gets a textual representation of this Boolean split kind. */
3243+
abstract string toString();
3244+
3245+
/** Gets the location of this Boolean split kind. */
3246+
abstract Location getLocation();
3247+
}
3248+
3249+
/**
3250+
* A Boolean split that records the value of a Boolean SSA variable.
3251+
*
3252+
* For example, in
3253+
*
3254+
* ```
3255+
* var b = GetB();
3256+
* if (b)
3257+
* Console.WriteLine("b is true");
3258+
* if (!b)
3259+
* Console.WriteLine("b is false");
3260+
* ```
3261+
*
3262+
* there is a Boolean split on the SSA variable for `b` at line 1.
3263+
*/
3264+
class SsaBooleanSplitSubKind extends BooleanSplitSubKind, TSsaBooleanSplitSubKind {
3265+
private PreSsa::Definition def;
3266+
3267+
SsaBooleanSplitSubKind() {
3268+
this = TSsaBooleanSplitSubKind(def)
3269+
}
3270+
3271+
/**
3272+
* Holds if condition `cb` is a read of the SSA variable in this split.
3273+
*/
3274+
private predicate defCondition(ConditionBlock cb) {
3275+
exists(LocalScopeVariableRead read1, LocalScopeVariableRead read2 |
3276+
firstReadSameVar(def, read1) |
3277+
adjacentReadPairSameVar*(read1, read2) and
3278+
read2 = cb.getLastElement()
3279+
)
3280+
}
3281+
3282+
/**
3283+
* Holds if condition `cb` is a read of the SSA variable in this split,
3284+
* and `cb` can be reached from `read` without passing through another
3285+
* condition that reads the same SSA variable.
3286+
*/
3287+
private predicate defConditionReachableFromRead(ConditionBlock cb, LocalScopeVariableRead read) {
3288+
this.defCondition(cb) and
3289+
read = cb.getLastElement()
3290+
or
3291+
exists(LocalScopeVariableRead mid |
3292+
this.defConditionReachableFromRead(cb, mid) |
3293+
adjacentReadPairSameVar(read, mid) and
3294+
not this.defCondition(read)
3295+
)
3296+
}
3297+
3298+
/**
3299+
* Holds if condition `cb` is a read of the SSA variable in this split,
3300+
* and `cb` can be reached from the SSA definition without passing through
3301+
* another condition that reads the same SSA variable.
3302+
*/
3303+
private predicate firstDefCondition(ConditionBlock cb) {
3304+
exists(LocalScopeVariableRead read |
3305+
this.defConditionReachableFromRead(cb, read) |
3306+
firstReadSameVar(def, read)
3307+
)
3308+
}
3309+
3310+
override predicate correlatesConditions(ConditionBlock cb1, ConditionBlock cb2, boolean inverted) {
3311+
this.firstDefCondition(cb1) and
3312+
exists(LocalScopeVariableRead read1, LocalScopeVariableRead read2 |
3313+
read1 = cb1.getLastElement() and
3314+
adjacentReadPairSameVar+(read1, read2) and
3315+
read2 = cb2.getLastElement() and
3316+
inverted = false
3317+
)
3318+
}
3319+
3320+
override Callable getEnclosingCallable() {
3321+
result = def.getVariable().getCallable()
3322+
}
3323+
3324+
override string toString() {
3325+
result = def.getVariable().toString()
3326+
}
3327+
3328+
override Location getLocation() {
3329+
result = def.getLocation()
3330+
}
3331+
}
3332+
3333+
/**
3334+
* A split for elements that can reach a condition where this split determines
3335+
* the Boolean value that the condition evaluates to. For example, in
3336+
*
3337+
* ```
3338+
* if (b)
3339+
* Console.WriteLine("b is true");
3340+
* if (!b)
3341+
* Console.WriteLine("b is false");
3342+
* ```
3343+
*
3344+
* all control flow nodes on line 2 and line 3 have two splits: one representing
3345+
* that the condition on line 1 took the `true` branch, and one representing that
3346+
* the condition on line 1 took the `false` branch.
3347+
*/
3348+
class BooleanSplitImpl extends SplitImpl, TBooleanSplit {
3349+
private BooleanSplitSubKind kind;
3350+
private boolean branch;
3351+
3352+
BooleanSplitImpl() {
3353+
this = TBooleanSplit(kind, branch)
3354+
}
3355+
3356+
/** Gets the kind of this Boolean split. */
3357+
BooleanSplitSubKind getSubKind() { result = kind }
3358+
3359+
/** Gets the branch taken in this split. */
3360+
boolean getBranch() { result = branch }
3361+
3362+
override string toString() {
3363+
exists(int line |
3364+
line = kind.getLocation().getStartLine() and
3365+
result = kind.toString() + " (line " + line + "): " + branch.toString()
3366+
)
3367+
}
3368+
}
3369+
3370+
private class BooleanSplitKind extends SplitKind, TBooleanSplitKind {
3371+
private BooleanSplitSubKind kind;
3372+
3373+
BooleanSplitKind() {
3374+
this = TBooleanSplitKind(kind)
3375+
}
3376+
3377+
/** Gets the sub kind of this Boolean split kind. */
3378+
BooleanSplitSubKind getSubKind() { result = kind }
3379+
3380+
override int getListOrder() {
3381+
exists(Callable c, int r |
3382+
c = kind.getEnclosingCallable() |
3383+
result = r + 1 and // start the ordering from 2
3384+
kind = rank[r](BooleanSplitSubKind kind0 |
3385+
kind0.getEnclosingCallable() = c and
3386+
kind0.startsSplit(_) |
3387+
kind0 order by kind0.getLocation().getStartLine(), kind0.getLocation().getStartColumn()
3388+
)
3389+
)
3390+
}
3391+
3392+
override string toString() { result = kind.toString() }
3393+
}
3394+
3395+
private class BooleanSplitInternal extends SplitInternal, BooleanSplitImpl {
3396+
override BooleanSplitKind getKind() {
3397+
result.getSubKind() = this.getSubKind()
3398+
}
3399+
3400+
override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
3401+
succ = succ(pred, c) and
3402+
this.getSubKind().startsSplit(pred) and
3403+
c = any(BooleanCompletion bc | bc.getOuterValue() = this.getBranch())
3404+
}
3405+
3406+
private ConditionBlock getACorrelatedCondition(boolean inverted) {
3407+
this.getSubKind().correlatesConditions(_, result, inverted)
3408+
}
3409+
3410+
/**
3411+
* Holds if this split applies to basic block `bb`, where the the last
3412+
* element of `bb` can have completion `c`.
3413+
*/
3414+
private predicate appliesToBlock(PreBasicBlock bb, Completion c) {
3415+
this.appliesTo(bb) and
3416+
exists(ControlFlowElement last |
3417+
last = bb.getLastElement() |
3418+
(exists(succ(last, c)) or exists(succExit(last, c))) and
3419+
// Respect the value recorded in this split for all correlated conditions
3420+
forall(boolean inverted |
3421+
bb = this.getACorrelatedCondition(inverted) |
3422+
c instanceof BooleanCompletion
3423+
implies
3424+
c = any(BooleanCompletion bc | bc.getInnerValue() = this.getBranch().booleanXor(inverted))
3425+
)
3426+
)
3427+
}
3428+
3429+
/**
3430+
* Holds if basic block `bb` can reach a condition correlated with the value
3431+
* recorded in this split.
3432+
*/
3433+
private predicate canReachCorrelatedCondition(PreBasicBlock bb) {
3434+
bb = this.getACorrelatedCondition(_)
3435+
or
3436+
exists(PreBasicBlock mid |
3437+
this.canReachCorrelatedCondition(mid) |
3438+
bb = mid.getAPredecessor()
3439+
)
3440+
}
3441+
3442+
override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
3443+
exists(PreBasicBlock bb |
3444+
this.appliesToBlock(bb, c) |
3445+
pred = bb.getLastElement() and
3446+
succ = succ(pred, c) and
3447+
// Exit this split if we can no longer reach a correlated condition
3448+
not this.canReachCorrelatedCondition(succ)
3449+
)
3450+
}
3451+
3452+
override predicate hasExit(ControlFlowElement pred, Completion c) {
3453+
exists(PreBasicBlock bb |
3454+
this.appliesToBlock(bb, c) |
3455+
pred = bb.getLastElement() and
3456+
exists(succExit(pred, c))
3457+
)
3458+
}
3459+
3460+
override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
3461+
exists(PreBasicBlock bb, Completion c0 |
3462+
this.appliesToBlock(bb, c0) |
3463+
pred = bb.getAnElement() and
3464+
succ = succ(pred, c) and
3465+
(
3466+
pred = bb.getLastElement()
3467+
implies
3468+
// We must still be able to reach a correlated condition to stay in this split
3469+
this.canReachCorrelatedCondition(succ) and
3470+
c = c0
3471+
)
3472+
)
3473+
}
3474+
}
3475+
}
3476+
32073477
/**
32083478
* A set of control flow node splits. The set is represented by a list of splits,
32093479
* ordered by ascending rank.
@@ -3512,17 +3782,30 @@ module ControlFlow {
35123782
)
35133783
}
35143784

3785+
cached
3786+
newtype TBooleanSplitSubKind =
3787+
TSsaBooleanSplitSubKind(PreSsa::Definition def)
3788+
35153789
cached
35163790
newtype TSplitKind =
35173791
TFinallySplitKind()
35183792
or
35193793
TExceptionHandlerSplitKind()
3794+
or
3795+
TBooleanSplitKind(BooleanSplitting::BooleanSplitSubKind kind) {
3796+
kind.startsSplit(_)
3797+
}
35203798

35213799
cached
35223800
newtype TSplit =
35233801
TFinallySplit(FinallySplitting::FinallySplitType type)
35243802
or
35253803
TExceptionHandlerSplit(ExceptionClass ec)
3804+
or
3805+
TBooleanSplit(BooleanSplitting::BooleanSplitSubKind kind, boolean branch) {
3806+
kind.startsSplit(_) and
3807+
(branch = true or branch = false)
3808+
}
35263809

35273810
cached
35283811
newtype TSplits =

0 commit comments

Comments
 (0)