Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions csharp/ql/src/semmle/code/csharp/commons/Assertions.qll
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class Assertion extends MethodCall {

pragma[nomagic]
private JoinBlockPredecessor getAPossiblyDominatedPredecessor(JoinBlock jb) {
// Only calculate dominance by explicit recursion for split nodes;
// all other nodes can use regular CFG dominance
this instanceof ControlFlow::Internal::SplitControlFlowElement and
exists(BasicBlock bb |
bb = this.getAControlFlowNode().getBasicBlock() |
result = bb.getASuccessor*()
Expand All @@ -70,6 +73,22 @@ class Assertion extends MethodCall {
)
}

pragma[nomagic]
private predicate strictlyDominatesSplit(BasicBlock bb) {
this.getAControlFlowNode().getBasicBlock().immediatelyDominates(bb)
or
if bb instanceof JoinBlock then
this.isPossiblyDominatedJoinBlock(bb) and
forall(BasicBlock pred |
pred = this.getAPossiblyDominatedPredecessor(bb) |
this.strictlyDominatesSplit(pred)
or
this.getAControlFlowNode().getBasicBlock() = pred
)
else
this.strictlyDominatesSplit(bb.getAPredecessor())
}

/**
* Holds if this assertion strictly dominates basic block `bb`. That is, `bb`
* can only be reached from the callable entry point by going via *some* basic
Expand All @@ -81,18 +100,9 @@ class Assertion extends MethodCall {
*/
pragma[nomagic]
predicate strictlyDominates(BasicBlock bb) {
this.getAControlFlowNode().getBasicBlock().immediatelyDominates(bb)
this.strictlyDominatesSplit(bb)
or
if bb instanceof JoinBlock then
this.isPossiblyDominatedJoinBlock(bb) and
forall(BasicBlock pred |
pred = this.getAPossiblyDominatedPredecessor(bb) |
this.strictlyDominates(pred)
or
this.getAControlFlowNode().getBasicBlock() = pred
)
else
this.strictlyDominates(bb.getAPredecessor())
this.getAControlFlowNode().getBasicBlock().strictlyDominates(bb)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does make sense - my only question is whether strictlyDominates belongs in this class - as it seems to be a very general predicate that isn't necessarily tied to class Assertion. Perhaps it should be reversed to bb.isDominatedBy(this) and implemented there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with that approach is that there is then no restriction on the control flow elements to calculate dominance for, and the result will simply be too big. By adding it here, we restrict the calculation to assertions, which is a much smaller subset.

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ class ConditionBlock extends BasicBlock {
* successor of this block, and `succ` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
pragma[nomagic]
predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
succ = this.getASuccessorByType(s) and
forall(BasicBlock pred |
Expand Down
72 changes: 53 additions & 19 deletions csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,23 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
* `pred` ending with this element, and `pred` is an immediate predecessor
* of `succ`.
*
* This predicate is different from
* `this.getAControlFlowNode().getBasicBlock().(ConditionBlock).immediatelyControls(succ, s)`
* in that it takes control flow splitting into account.
* Moreover, this control flow element corresponds to multiple control flow nodes,
* which is why
*
* ```
* exists(ConditionBlock cb |
* cb.getLastNode() = this.getAControlFlowNode() |
* cb.immediatelyControls(succ, s)
* )
* ```
*
* does not work.
*/
pragma[nomagic]
private predicate immediatelyControls(BasicBlock succ, ConditionalSuccessor s) {
private predicate immediatelyControlsBlockSplit(BasicBlock succ, ConditionalSuccessor s) {
// Only calculate dominance by explicit recursion for split nodes;
// all other nodes can use regular CFG dominance
this instanceof ControlFlow::Internal::SplitControlFlowElement and
exists(ConditionBlock cb |
cb.getLastNode() = this.getAControlFlowNode() |
succ = cb.getASuccessorByType(s) and
Expand All @@ -103,7 +114,7 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
pragma[nomagic]
private JoinBlockPredecessor getAPossiblyControlledPredecessor(JoinBlock controlled, ConditionalSuccessor s) {
exists(BasicBlock mid |
this.immediatelyControls(mid, s) |
this.immediatelyControlsBlockSplit(mid, s) |
result = mid.getASuccessor*()
) and
result.getASuccessor() = controlled and
Expand All @@ -121,28 +132,44 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
)
}

cached
private predicate controlsBlockSplit(BasicBlock controlled, ConditionalSuccessor s) {
this.immediatelyControlsBlockSplit(controlled, s)
or
if controlled instanceof JoinBlock then
this.isPossiblyControlledJoinBlock(controlled, s) and
forall(BasicBlock pred |
pred = this.getAPossiblyControlledPredecessor(controlled, s) |
this.controlsBlock(pred, s)
)
else
this.controlsBlockSplit(controlled.getAPredecessor(), s)
}

/**
* Holds if basic block `controlled` is controlled by this control flow element
* with conditional value `s`. That is, `controlled` can only be reached from
* the callable entry point by going via the `s` edge out of *some* basic block
* ending with this element.
*
* This predicate is different from
* `this.getAControlFlowNode().getBasicBlock().(ConditionBlock).controls(controlled, s)`
* in that it takes control flow splitting into account.
*
* ```
* exists(ConditionBlock cb |
* cb.getLastNode() = this.getAControlFlowNode() |
* cb.controls(controlled, s)
* )
* ```
*
* as control flow splitting is taken into account.
*/
cached
predicate controlsBlock(BasicBlock controlled, ConditionalSuccessor s) {
this.immediatelyControls(controlled, s)
this.controlsBlockSplit(controlled, s)
or
if controlled instanceof JoinBlock then
this.isPossiblyControlledJoinBlock(controlled, s) and
forall(BasicBlock pred |
pred = this.getAPossiblyControlledPredecessor(controlled, s) |
this.controlsBlock(pred, s)
)
else
this.controlsBlock(controlled.getAPredecessor(), s)
exists(ConditionBlock cb |
cb.getLastNode() = this.getAControlFlowNode() |
cb.controls(controlled, s)
)
}

/**
Expand All @@ -151,8 +178,15 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
* from the callable entry point by going via the `s` edge out of this element.
*
* This predicate is different from
* `this.getAControlFlowNode().getBasicBlock().(ConditionBlock).controls(controlled.getAControlFlowNode().getBasicBlock(), s)`
* in that it takes control flow splitting into account.
*
* ```
* exists(ConditionBlock cb |
* cb.getLastNode() = this.getAControlFlowNode() |
* cb.controls(controlled.getAControlFlowNode().getBasicBlock(), s)
* )
* ```
*
* as control flow splitting is taken into account.
*/
pragma[inline] // potentially very large predicate, so must be inlined
predicate controlsElement(ControlFlowElement controlled, ConditionalSuccessor s) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4185,6 +4185,13 @@ module ControlFlow {
}
}
import Cached

/** A control flow element that is split into multiple control flow nodes. */
class SplitControlFlowElement extends ControlFlowElement {
SplitControlFlowElement() {
strictcount(this.getAControlFlowNode()) > 1
}
}
}
private import Internal
}
Expand Down