mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
C#: Clean up.
This commit is contained in:
@@ -294,13 +294,7 @@ module ControlFlow {
|
||||
|
||||
class Split = Splitting::Split;
|
||||
|
||||
class FinallySplit = Splitting::FinallySplitting::FinallySplit;
|
||||
|
||||
class ExceptionHandlerSplit = Splitting::ExceptionHandlerSplitting::ExceptionHandlerSplit;
|
||||
|
||||
class BooleanSplit = Splitting::BooleanSplitting::BooleanSplit;
|
||||
|
||||
class LoopSplit = Splitting::LoopSplitting::LoopSplit;
|
||||
}
|
||||
|
||||
class BasicBlock = BBs::BasicBlock;
|
||||
|
||||
@@ -292,87 +292,6 @@ private module LogicInput implements GuardsImpl::LogicInputSig {
|
||||
|
||||
module Guards = GuardsImpl::Logic<LogicInput>;
|
||||
|
||||
/*
|
||||
* Temporary debug predicates:
|
||||
*/
|
||||
|
||||
predicate debug_newcontrols(Guards::Guard g, BasicBlock bb, GuardValue v) { g.valueControls(bb, v) }
|
||||
|
||||
predicate debug_oldconvert(Guards::Guard g, BasicBlock bb, GuardValue v) {
|
||||
exists(AbstractValue av |
|
||||
g.(Guard).controlsBasicBlock(bb, av) and
|
||||
debug_convVals(av, v)
|
||||
)
|
||||
or
|
||||
debug_oldconvertCase(_, _, g, bb, v)
|
||||
}
|
||||
|
||||
predicate debug_oldconvertCase(Guard g1, MatchValue av, Guards::Guard g2, BasicBlock bb, GuardValue v) {
|
||||
g1.controlsBasicBlock(bb, av) and
|
||||
av.getCase() = g2 and
|
||||
if av.isMatch() then v.asBooleanValue() = true else v.asBooleanValue() = false
|
||||
}
|
||||
|
||||
predicate debug_caseconverted(Guard g1, Guards::Guard g, BasicBlock bb, GuardValue v) {
|
||||
debug_oldconvertCase(g1, _, g, bb, v) and
|
||||
2 <= strictcount(Guard g0 | debug_oldconvertCase(g0, _, g, bb, v))
|
||||
}
|
||||
|
||||
predicate debug_useless(Guards::Guard g, BasicBlock bb, GuardValue v) {
|
||||
debug_oldconvert(g, bb, v) and
|
||||
Guards::InternalUtil::exprHasValue(g, v)
|
||||
}
|
||||
|
||||
predicate debug_compare(int eq, int oldconv, int oldnonconv, int added, int new) {
|
||||
eq =
|
||||
count(Guards::Guard g, BasicBlock bb, GuardValue v |
|
||||
debug_newcontrols(g, bb, v) and debug_oldconvert(g, bb, v)
|
||||
) and
|
||||
oldconv =
|
||||
count(Guards::Guard g, BasicBlock bb, GuardValue v |
|
||||
debug_oldconvert(g, bb, v) and
|
||||
not debug_newcontrols(g, bb, v) and
|
||||
not debug_useless(g, bb, v)
|
||||
) and
|
||||
oldnonconv =
|
||||
count(Guard g, BasicBlock bb, AbstractValue av |
|
||||
g.controlsBasicBlock(bb, av) and
|
||||
not debug_convVals(av, _) and
|
||||
not debug_oldconvertCase(g, av, _, bb, _)
|
||||
// Remaining that are not converted:
|
||||
// av instanceof EmptyCollectionValue
|
||||
) and
|
||||
added =
|
||||
count(Guards::Guard g, BasicBlock bb, GuardValue v |
|
||||
debug_newcontrols(g, bb, v) and
|
||||
not debug_oldconvert(g, bb, v) and
|
||||
not debug_newGv(v)
|
||||
) and
|
||||
new =
|
||||
count(Guards::Guard g, BasicBlock bb, GuardValue v |
|
||||
debug_newcontrols(g, bb, v) and
|
||||
not debug_oldconvert(g, bb, v) and
|
||||
debug_newGv(v)
|
||||
)
|
||||
}
|
||||
|
||||
predicate debug_newGv(GuardValue v) {
|
||||
v.isThrowsException() or
|
||||
v.getDualValue().isThrowsException() or
|
||||
exists(v.getDualValue().asIntValue()) or
|
||||
v.isIntRange(_, _)
|
||||
}
|
||||
|
||||
predicate debug_convVals(AbstractValue av, GuardValue gv) {
|
||||
av.(AbstractValues::BooleanValue).getValue() = gv.asBooleanValue()
|
||||
or
|
||||
av.(AbstractValues::IntegerValue).getValue() = gv.asIntValue()
|
||||
or
|
||||
av.(AbstractValues::NullValue).isNull() and gv.isNullValue()
|
||||
or
|
||||
av.(AbstractValues::NullValue).isNonNull() and gv.isNonNullValue()
|
||||
}
|
||||
|
||||
/** An expression whose value may control the execution of another element. */
|
||||
class Guard extends Expr {
|
||||
Guard() { isGuard(this, _) }
|
||||
|
||||
@@ -26,10 +26,7 @@ private module Cached {
|
||||
TInitializerSplitKind() or
|
||||
TConditionalCompletionSplitKind() or
|
||||
TAssertionSplitKind() or
|
||||
TFinallySplitKind(int nestLevel) { nestLevel = any(Statements::TryStmtTree t).nestLevel() } or
|
||||
TExceptionHandlerSplitKind() or
|
||||
TBooleanSplitKind(BooleanSplitting::BooleanSplitSubKind kind) { kind.startsSplit(_) } or
|
||||
TLoopSplitKind(LoopSplitting::AnalyzableLoopStmt loop)
|
||||
TExceptionHandlerSplitKind()
|
||||
|
||||
cached
|
||||
newtype TSplit =
|
||||
@@ -39,17 +36,7 @@ private module Cached {
|
||||
exists(a.getExpr(i)) and
|
||||
success in [false, true]
|
||||
} or
|
||||
TFinallySplit(FinallySplitting::FinallySplitType type, int nestLevel) {
|
||||
nestLevel = any(Statements::TryStmtTree t).nestLevel() and
|
||||
none()
|
||||
} or
|
||||
TExceptionHandlerSplit(ExceptionClass ec) or
|
||||
TBooleanSplit(BooleanSplitting::BooleanSplitSubKind kind, boolean branch) {
|
||||
kind.startsSplit(_) and
|
||||
branch in [false, true] and
|
||||
none()
|
||||
} or
|
||||
TLoopSplit(LoopSplitting::AnalyzableLoopStmt loop) { none() }
|
||||
TExceptionHandlerSplit(ExceptionClass ec)
|
||||
}
|
||||
|
||||
import Cached
|
||||
@@ -463,241 +450,6 @@ module AssertionSplitting {
|
||||
}
|
||||
}
|
||||
|
||||
module FinallySplitting {
|
||||
/**
|
||||
* The type of a split `finally` node.
|
||||
*
|
||||
* The type represents one of the possible ways of entering a `finally`
|
||||
* block. For example, if a `try` statement ends with a `return` statement,
|
||||
* then the `finally` block must end with a `return` as well (provided that
|
||||
* the `finally` block exits normally).
|
||||
*/
|
||||
class FinallySplitType extends Cfg::SuccessorType {
|
||||
FinallySplitType() { not this instanceof Cfg::ConditionalSuccessor }
|
||||
|
||||
/** Holds if this split type matches entry into a `finally` block with completion `c`. */
|
||||
predicate isSplitForEntryCompletion(Completion c) {
|
||||
if c instanceof NormalCompletion
|
||||
then
|
||||
// If the entry into the `finally` block completes with any normal completion,
|
||||
// it simply means normal execution after the `finally` block
|
||||
this instanceof Cfg::DirectSuccessor
|
||||
else this = c.getAMatchingSuccessorType()
|
||||
}
|
||||
}
|
||||
|
||||
/** A control flow element that belongs to a `finally` block. */
|
||||
private class FinallyAstNode extends AstNode {
|
||||
private Statements::TryStmtTree try;
|
||||
|
||||
FinallyAstNode() { this = try.getAFinallyDescendant() }
|
||||
|
||||
/** Gets the immediate `try` block that this node belongs to. */
|
||||
Statements::TryStmtTree getTryStmt() { result = try }
|
||||
|
||||
/** Holds if this node is the entry node in the `finally` block it belongs to. */
|
||||
predicate isEntryNode() { first(try.(TryStmt).getFinally(), this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A split for elements belonging to a `finally` block, which determines how to
|
||||
* continue execution after leaving the `finally` block. For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* try
|
||||
* {
|
||||
* if (!M())
|
||||
* throw new Exception();
|
||||
* }
|
||||
* finally
|
||||
* {
|
||||
* Log.Write("M failed");
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* all control flow nodes in the `finally` block have two splits: one representing
|
||||
* normal execution of the `try` block (when `M()` returns `true`), and one
|
||||
* representing exceptional execution of the `try` block (when `M()` returns `false`).
|
||||
*/
|
||||
class FinallySplit extends Split, TFinallySplit {
|
||||
private FinallySplitType type;
|
||||
private int nestLevel;
|
||||
|
||||
FinallySplit() { this = TFinallySplit(type, nestLevel) }
|
||||
|
||||
/**
|
||||
* Gets the type of this `finally` split, that is, how to continue execution after the
|
||||
* `finally` block.
|
||||
*/
|
||||
FinallySplitType getType() { result = type }
|
||||
|
||||
/** Gets the `finally` nesting level. */
|
||||
int getNestLevel() { result = nestLevel }
|
||||
|
||||
override string toString() {
|
||||
if type instanceof Cfg::DirectSuccessor
|
||||
then result = ""
|
||||
else
|
||||
if nestLevel > 0
|
||||
then result = "finally(" + nestLevel + "): " + type.toString()
|
||||
else result = "finally: " + type.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private int getListOrder(FinallySplitKind kind) {
|
||||
result = AssertionSplitting::getNextListOrder() + kind.getNestLevel()
|
||||
}
|
||||
|
||||
int getNextListOrder() {
|
||||
result = max([getListOrder(_) + 1, AssertionSplitting::getNextListOrder()])
|
||||
}
|
||||
|
||||
private class FinallySplitKind extends SplitKind, TFinallySplitKind {
|
||||
private int nestLevel;
|
||||
|
||||
FinallySplitKind() { this = TFinallySplitKind(nestLevel) }
|
||||
|
||||
/** Gets the `finally` nesting level. */
|
||||
int getNestLevel() { result = nestLevel }
|
||||
|
||||
override int getListOrder() { result = getListOrder(this) }
|
||||
|
||||
override string toString() { result = "Finally (" + nestLevel + ")" }
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasEntry0(AstNode pred, FinallyAstNode succ, int nestLevel, Completion c) {
|
||||
succ.isEntryNode() and
|
||||
nestLevel = succ.getTryStmt().nestLevel() and
|
||||
succ(pred, succ, c)
|
||||
}
|
||||
|
||||
private class FinallySplitImpl extends SplitImpl instanceof FinallySplit {
|
||||
override FinallySplitKind getKind() { result.getNestLevel() = super.getNestLevel() }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
hasEntry0(pred, succ, super.getNestLevel(), c) and
|
||||
super.getType().isSplitForEntryCompletion(c)
|
||||
}
|
||||
|
||||
override predicate hasEntryScope(CfgScope scope, AstNode first) { none() }
|
||||
|
||||
/**
|
||||
* Holds if this split applies to control flow element `pred`, where `pred`
|
||||
* is a valid predecessor.
|
||||
*/
|
||||
private predicate appliesToPredecessor(AstNode pred) {
|
||||
this.appliesTo(pred) and
|
||||
(succ(pred, _, _) or scopeLast(_, pred, _))
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate exit0(AstNode pred, Statements::TryStmtTree try, int nestLevel, Completion c) {
|
||||
this.appliesToPredecessor(pred) and
|
||||
nestLevel = try.nestLevel() and
|
||||
last(try, pred, c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` may exit this split with completion `c`. The Boolean
|
||||
* `inherited` indicates whether `c` is an inherited completion from a `try`/
|
||||
* `catch` block.
|
||||
*/
|
||||
private predicate exit(AstNode pred, Completion c, boolean inherited) {
|
||||
exists(TryStmt try, FinallySplitType type |
|
||||
this.exit0(pred, try, super.getNestLevel(), c) and
|
||||
type = super.getType()
|
||||
|
|
||||
if last(try.getFinally(), pred, c)
|
||||
then
|
||||
// Finally block can itself exit with completion `c`: either `c` must
|
||||
// match this split, `c` must be an abnormal completion, or this split
|
||||
// does not require another completion to be recovered
|
||||
inherited = false and
|
||||
(
|
||||
type = c.getAMatchingSuccessorType()
|
||||
or
|
||||
not c instanceof NormalCompletion
|
||||
or
|
||||
type instanceof Cfg::DirectSuccessor
|
||||
)
|
||||
else (
|
||||
// Finally block can exit with completion `c` inherited from try/catch
|
||||
// block: must match this split
|
||||
inherited = true and
|
||||
type = c.getAMatchingSuccessorType() and
|
||||
not type instanceof Cfg::DirectSuccessor
|
||||
)
|
||||
)
|
||||
or
|
||||
// If this split is normal, and an outer split can exit based on an inherited
|
||||
// completion, we need to exit this split as well. For example, in
|
||||
//
|
||||
// ```csharp
|
||||
// bool done;
|
||||
// try
|
||||
// {
|
||||
// if (b1) throw new ExceptionA();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (b2) throw new ExceptionB();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// done = true;
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// if the outer split for `done = true` is `ExceptionA` and the inner split
|
||||
// is "normal" (corresponding to `b1 = true` and `b2 = false`), then the inner
|
||||
// split must be able to exit with an `ExceptionA` completion.
|
||||
this.appliesToPredecessor(pred) and
|
||||
exists(FinallySplit outer |
|
||||
outer.getNestLevel() = super.getNestLevel() - 1 and
|
||||
outer.(FinallySplitImpl).exit(pred, c, inherited) and
|
||||
super.getType() instanceof Cfg::DirectSuccessor and
|
||||
inherited = true
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
|
||||
succ(pred, succ, c) and
|
||||
(
|
||||
this.exit(pred, c, _)
|
||||
or
|
||||
this.exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
|
||||
scopeLast(scope, last, c) and
|
||||
(
|
||||
this.exit(last, c, _)
|
||||
or
|
||||
this.exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) {
|
||||
this.appliesSucc(pred, succ, c) and
|
||||
succ =
|
||||
any(FinallyAstNode fcfe |
|
||||
if fcfe.isEntryNode()
|
||||
then
|
||||
// entering a nested `finally` block
|
||||
fcfe.getTryStmt().nestLevel() > super.getNestLevel()
|
||||
else
|
||||
// staying in the same (possibly nested) `finally` block as `pred`
|
||||
fcfe.getTryStmt().nestLevel() >= super.getNestLevel()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module ExceptionHandlerSplitting {
|
||||
private newtype TMatch =
|
||||
TAlways() or
|
||||
@@ -751,12 +503,12 @@ module ExceptionHandlerSplitting {
|
||||
}
|
||||
|
||||
private class ExceptionHandlerSplitKind extends SplitKind, TExceptionHandlerSplitKind {
|
||||
override int getListOrder() { result = FinallySplitting::getNextListOrder() }
|
||||
override int getListOrder() { result = AssertionSplitting::getNextListOrder() }
|
||||
|
||||
override string toString() { result = "ExceptionHandler" }
|
||||
}
|
||||
|
||||
int getNextListOrder() { result = FinallySplitting::getNextListOrder() + 1 }
|
||||
int getNextListOrder() { result = AssertionSplitting::getNextListOrder() + 1 }
|
||||
|
||||
private class ExceptionHandlerSplitImpl extends SplitImpl instanceof ExceptionHandlerSplit {
|
||||
override ExceptionHandlerSplitKind getKind() { any() }
|
||||
@@ -873,452 +625,3 @@ module ExceptionHandlerSplitting {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module BooleanSplitting {
|
||||
private import semmle.code.csharp.controlflow.internal.PreBasicBlocks
|
||||
|
||||
/** A sub-classification of Boolean splits. */
|
||||
abstract class BooleanSplitSubKind extends TBooleanSplitSubKind {
|
||||
/**
|
||||
* Holds if the branch taken by condition `cb1` should be recorded in
|
||||
* this split, and the recorded value determines the branch taken by a
|
||||
* later condition `cb2`, possibly inverted.
|
||||
*
|
||||
* For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* var b = GetB();
|
||||
* if (b)
|
||||
* Console.WriteLine("b is true");
|
||||
* if (!b)
|
||||
* Console.WriteLine("b is false");
|
||||
* ```
|
||||
*
|
||||
* the branch taken in the condition on line 2 can be recorded, and the
|
||||
* recorded value will determine the branch taken in the condition on line 4.
|
||||
*/
|
||||
abstract predicate correlatesConditions(ConditionBlock cb1, ConditionBlock cb2, boolean inverted);
|
||||
|
||||
/** Holds if control flow element `cfe` starts a split of this kind. */
|
||||
predicate startsSplit(AstNode cfe) {
|
||||
this.correlatesConditions(any(ConditionBlock cb | cb.getLastNode() = cfe), _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if basic block `bb` can reach a condition correlated with a
|
||||
* split of this kind.
|
||||
*/
|
||||
abstract predicate canReachCorrelatedCondition(PreBasicBlock bb);
|
||||
|
||||
/** Gets the callable that this Boolean split kind belongs to. */
|
||||
abstract Callable getEnclosingCallable();
|
||||
|
||||
/** Gets a textual representation of this Boolean split kind. */
|
||||
abstract string toString();
|
||||
|
||||
/** Gets the location of this Boolean split kind. */
|
||||
abstract Location getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Boolean split that records the value of a Boolean SSA variable.
|
||||
*
|
||||
* For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* var b = GetB();
|
||||
* if (b)
|
||||
* Console.WriteLine("b is true");
|
||||
* if (!b)
|
||||
* Console.WriteLine("b is false");
|
||||
* ```
|
||||
*
|
||||
* there is a Boolean split on the SSA variable for `b` at line 1.
|
||||
*/
|
||||
class SsaBooleanSplitSubKind extends BooleanSplitSubKind, TSsaBooleanSplitSubKind {
|
||||
private PreSsa::Definition def;
|
||||
|
||||
SsaBooleanSplitSubKind() { this = TSsaBooleanSplitSubKind(def) }
|
||||
|
||||
/**
|
||||
* Holds if condition `cb` is a read of the SSA variable in this split.
|
||||
*/
|
||||
private predicate defCondition(ConditionBlock cb) { cb.getLastNode() = def.getARead() }
|
||||
|
||||
/**
|
||||
* Holds if condition `cb` is a read of the SSA variable in this split,
|
||||
* and `cb` can be reached from `read` without passing through another
|
||||
* condition that reads the same SSA variable.
|
||||
*/
|
||||
private predicate defConditionReachableFromRead(ConditionBlock cb, AssignableRead read) {
|
||||
this.defCondition(cb) and
|
||||
read = cb.getLastNode()
|
||||
or
|
||||
exists(AssignableRead mid | this.defConditionReachableFromRead(cb, mid) |
|
||||
PreSsa::adjacentReadPairSameVar(read, mid) and
|
||||
not this.defCondition(read)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if condition `cb` is a read of the SSA variable in this split,
|
||||
* and `cb` can be reached from the SSA definition without passing through
|
||||
* another condition that reads the same SSA variable.
|
||||
*/
|
||||
private predicate firstDefCondition(ConditionBlock cb) {
|
||||
this.defConditionReachableFromRead(cb, def.getAFirstRead())
|
||||
}
|
||||
|
||||
override predicate correlatesConditions(ConditionBlock cb1, ConditionBlock cb2, boolean inverted) {
|
||||
this.firstDefCondition(cb1) and
|
||||
exists(AssignableRead read1, AssignableRead read2 |
|
||||
read1 = cb1.getLastNode() and
|
||||
PreSsa::adjacentReadPairSameVar+(read1, read2) and
|
||||
read2 = cb2.getLastNode() and
|
||||
inverted = false
|
||||
)
|
||||
}
|
||||
|
||||
override predicate canReachCorrelatedCondition(PreBasicBlock bb) {
|
||||
this.correlatesConditions(_, bb, _) and
|
||||
not def.getBasicBlock() = bb
|
||||
or
|
||||
exists(PreBasicBlock mid | this.canReachCorrelatedCondition(mid) |
|
||||
bb = mid.getAPredecessor() and
|
||||
not def.getBasicBlock() = bb
|
||||
)
|
||||
}
|
||||
|
||||
override Callable getEnclosingCallable() { result = def.getBasicBlock().getEnclosingCallable() }
|
||||
|
||||
override string toString() { result = def.getSourceVariable().toString() }
|
||||
|
||||
override Location getLocation() { result = def.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A split for elements that can reach a condition where this split determines
|
||||
* the Boolean value that the condition evaluates to. For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* if (b)
|
||||
* Console.WriteLine("b is true");
|
||||
* if (!b)
|
||||
* Console.WriteLine("b is false");
|
||||
* ```
|
||||
*
|
||||
* all control flow nodes on line 2 and line 3 have two splits: one representing
|
||||
* that the condition on line 1 took the `true` branch, and one representing that
|
||||
* the condition on line 1 took the `false` branch.
|
||||
*/
|
||||
class BooleanSplit extends Split, TBooleanSplit {
|
||||
private BooleanSplitSubKind kind;
|
||||
private boolean branch;
|
||||
|
||||
BooleanSplit() { this = TBooleanSplit(kind, branch) }
|
||||
|
||||
/** Gets the kind of this Boolean split. */
|
||||
BooleanSplitSubKind getSubKind() { result = kind }
|
||||
|
||||
/** Gets the branch taken in this split. */
|
||||
boolean getBranch() { result = branch }
|
||||
|
||||
override string toString() {
|
||||
exists(int line |
|
||||
line = kind.getLocation().getStartLine() and
|
||||
result = kind.toString() + " (line " + line + "): " + branch.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private int getListOrder(BooleanSplitSubKind kind) {
|
||||
exists(Callable c, int r | c = kind.getEnclosingCallable() |
|
||||
result = r + ExceptionHandlerSplitting::getNextListOrder() - 1 and
|
||||
kind =
|
||||
rank[r](BooleanSplitSubKind kind0, Location l |
|
||||
kind0.getEnclosingCallable() = c and
|
||||
kind0.startsSplit(_) and
|
||||
l = kind0.getLocation()
|
||||
|
|
||||
kind0 order by l.getStartLine(), l.getStartColumn(), kind0.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
int getNextListOrder() {
|
||||
result = max([getListOrder(_) + 1, ExceptionHandlerSplitting::getNextListOrder()])
|
||||
}
|
||||
|
||||
private class BooleanSplitKind extends SplitKind, TBooleanSplitKind {
|
||||
private BooleanSplitSubKind kind;
|
||||
|
||||
BooleanSplitKind() { this = TBooleanSplitKind(kind) }
|
||||
|
||||
/** Gets the sub kind of this Boolean split kind. */
|
||||
BooleanSplitSubKind getSubKind() { result = kind }
|
||||
|
||||
override int getListOrder() { result = getListOrder(kind) }
|
||||
|
||||
override string toString() { result = kind.toString() }
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasEntry0(
|
||||
AstNode pred, AstNode succ, BooleanSplitSubKind kind, boolean b, Completion c
|
||||
) {
|
||||
kind.startsSplit(pred) and
|
||||
succ(pred, succ, c) and
|
||||
b = c.getInnerCompletion().(BooleanCompletion).getValue()
|
||||
}
|
||||
|
||||
private class BooleanSplitImpl extends SplitImpl instanceof BooleanSplit {
|
||||
override BooleanSplitKind getKind() { result.getSubKind() = super.getSubKind() }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
hasEntry0(pred, succ, super.getSubKind(), super.getBranch(), c)
|
||||
}
|
||||
|
||||
override predicate hasEntryScope(CfgScope scope, AstNode first) { none() }
|
||||
|
||||
private ConditionBlock getACorrelatedCondition(boolean inverted) {
|
||||
super.getSubKind().correlatesConditions(_, result, inverted)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this split applies to basic block `bb`, where the the last
|
||||
* element of `bb` can have completion `c`.
|
||||
*/
|
||||
private predicate appliesToBlock(PreBasicBlock bb, Completion c) {
|
||||
this.appliesTo(bb) and
|
||||
exists(AstNode last | last = bb.getLastNode() |
|
||||
(succ(last, _, c) or scopeLast(_, last, c)) and
|
||||
// Respect the value recorded in this split for all correlated conditions
|
||||
forall(boolean inverted | bb = this.getACorrelatedCondition(inverted) |
|
||||
c.getInnerCompletion() instanceof BooleanCompletion
|
||||
implies
|
||||
c.getInnerCompletion().(BooleanCompletion).getValue() =
|
||||
super.getBranch().booleanXor(inverted)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
|
||||
exists(PreBasicBlock bb | this.appliesToBlock(bb, c) |
|
||||
pred = bb.getLastNode() and
|
||||
succ(pred, succ, c) and
|
||||
// Exit this split if we can no longer reach a correlated condition
|
||||
not super.getSubKind().canReachCorrelatedCondition(succ)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
|
||||
exists(PreBasicBlock bb | this.appliesToBlock(bb, c) |
|
||||
last = bb.getLastNode() and
|
||||
scopeLast(scope, last, c)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) {
|
||||
exists(PreBasicBlock bb, Completion c0 | this.appliesToBlock(bb, c0) |
|
||||
pred = bb.getAnElement() and
|
||||
this.appliesSucc(pred, succ, c) and
|
||||
(
|
||||
pred = bb.getLastNode()
|
||||
implies
|
||||
(
|
||||
// We must still be able to reach a correlated condition to stay in this split
|
||||
super.getSubKind().canReachCorrelatedCondition(succ) and
|
||||
c = c0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module LoopSplitting {
|
||||
private import semmle.code.csharp.controlflow.Guards as Guards
|
||||
private import PreBasicBlocks
|
||||
|
||||
/** Holds if `ce` is guarded by a (non-)empty check, as specified by `v`. */
|
||||
private predicate emptinessGuarded(
|
||||
Guards::Guard g, Guards::EnumerableCollectionExpr ce,
|
||||
Guards::AbstractValues::EmptyCollectionValue v
|
||||
) {
|
||||
exists(PreBasicBlock bb | Guards::Internal::preControls(g, bb, v) |
|
||||
PreSsa::adjacentReadPairSameVar(g, ce) and
|
||||
bb.getAnElement() = ce
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A loop where the body is guaranteed to be executed at least once, and hence
|
||||
* can be unrolled in the control flow graph, or where the body is guaranteed
|
||||
* to never be executed, and hence can be removed from the control flow graph.
|
||||
*/
|
||||
abstract class AnalyzableLoopStmt extends LoopStmt {
|
||||
/** Holds if the step `pred --c--> succ` should start the split. */
|
||||
abstract predicate start(AstNode pred, AstNode succ, Completion c);
|
||||
|
||||
/** Holds if the step `pred --c--> succ` should stop the split. */
|
||||
abstract predicate stop(AstNode pred, AstNode succ, Completion c);
|
||||
|
||||
/**
|
||||
* Holds if any step `pred --c--> _` should be pruned from the control flow graph.
|
||||
*/
|
||||
abstract predicate pruneLoopCondition(AstNode pred, ConditionalCompletion c);
|
||||
|
||||
/**
|
||||
* Holds if the body is guaranteed to be executed at least once. If not, the
|
||||
* body is guaranteed to never be executed.
|
||||
*/
|
||||
abstract predicate isUnroll();
|
||||
}
|
||||
|
||||
private class AnalyzableForeachStmt extends AnalyzableLoopStmt, ForeachStmt {
|
||||
Guards::AbstractValues::EmptyCollectionValue v;
|
||||
|
||||
AnalyzableForeachStmt() {
|
||||
/*
|
||||
* We use `unique` to avoid degenerate cases like
|
||||
* ```csharp
|
||||
* if (xs.Length == 0)
|
||||
* return;
|
||||
* if (xs.Length > 0)
|
||||
* return;
|
||||
* foreach (var x in xs)
|
||||
* ....
|
||||
* ```
|
||||
* where the iterator expression `xs` is guarded by both an emptiness check
|
||||
* and a non-emptiness check.
|
||||
*/
|
||||
|
||||
v =
|
||||
unique(Guards::AbstractValues::EmptyCollectionValue v0 |
|
||||
emptinessGuarded(_, this.getIterableExpr(), v0)
|
||||
or
|
||||
this.getIterableExpr() = v0.getAnExpr()
|
||||
|
|
||||
v0
|
||||
)
|
||||
}
|
||||
|
||||
override predicate start(AstNode pred, AstNode succ, Completion c) {
|
||||
last(this.getIterableExpr(), pred, c) and
|
||||
succ = this
|
||||
}
|
||||
|
||||
override predicate stop(AstNode pred, AstNode succ, Completion c) {
|
||||
pred = this and
|
||||
succ(pred, succ, c)
|
||||
}
|
||||
|
||||
override predicate pruneLoopCondition(AstNode pred, ConditionalCompletion c) {
|
||||
pred = this and
|
||||
c = any(EmptinessCompletion ec | if v.isEmpty() then not ec.isEmpty() else ec.isEmpty())
|
||||
}
|
||||
|
||||
override predicate isUnroll() { v.isNonEmpty() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A split for loops where the body is guaranteed to be executed at least once, or
|
||||
* guaranteed to never be executed. For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* void M(string[] args)
|
||||
* {
|
||||
* if (args.Length == 0)
|
||||
* return;
|
||||
* foreach (var arg in args)
|
||||
* System.Console.WriteLine(args);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* the `foreach` loop is guaranteed to be executed at least once, as a result of the
|
||||
* `args.Length == 0` check.
|
||||
*/
|
||||
class LoopSplit extends Split, TLoopSplit {
|
||||
AnalyzableLoopStmt loop;
|
||||
|
||||
LoopSplit() { this = TLoopSplit(loop) }
|
||||
|
||||
override string toString() {
|
||||
if loop.isUnroll()
|
||||
then result = "unroll (line " + loop.getLocation().getStartLine() + ")"
|
||||
else result = "skip (line " + loop.getLocation().getStartLine() + ")"
|
||||
}
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private Callable enclosingCallable(AnalyzableLoopStmt loop) {
|
||||
result = loop.getEnclosingCallable()
|
||||
}
|
||||
|
||||
private int getListOrder(AnalyzableLoopStmt loop) {
|
||||
exists(Callable c, int r | c = enclosingCallable(loop) |
|
||||
result = r + BooleanSplitting::getNextListOrder() - 1 and
|
||||
loop =
|
||||
rank[r](AnalyzableLoopStmt loop0, Location l |
|
||||
enclosingCallable(loop0) = c and
|
||||
l = loop0.getLocation()
|
||||
|
|
||||
loop0 order by l.getStartLine(), l.getStartColumn()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
int getNextListOrder() {
|
||||
result = max([getListOrder(_) + 1, BooleanSplitting::getNextListOrder()])
|
||||
}
|
||||
|
||||
private class LoopSplitKind extends SplitKind, TLoopSplitKind {
|
||||
private AnalyzableLoopStmt loop;
|
||||
|
||||
LoopSplitKind() { this = TLoopSplitKind(loop) }
|
||||
|
||||
override int getListOrder() { result = getListOrder(loop) }
|
||||
|
||||
override string toString() { result = "Unroll" }
|
||||
}
|
||||
|
||||
private class LoopUnrollingSplitImpl extends SplitImpl instanceof LoopSplit {
|
||||
AnalyzableLoopStmt loop;
|
||||
|
||||
LoopUnrollingSplitImpl() { this = TLoopSplit(loop) }
|
||||
|
||||
override LoopSplitKind getKind() { result = TLoopSplitKind(loop) }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
loop.start(pred, succ, c)
|
||||
}
|
||||
|
||||
override predicate hasEntryScope(CfgScope scope, AstNode first) { none() }
|
||||
|
||||
/**
|
||||
* Holds if this split applies to control flow element `pred`, where `pred`
|
||||
* is a valid predecessor.
|
||||
*/
|
||||
private predicate appliesToPredecessor(AstNode pred, Completion c) {
|
||||
this.appliesTo(pred) and
|
||||
(succ(pred, _, c) or scopeLast(_, pred, c)) and
|
||||
not loop.pruneLoopCondition(pred, c)
|
||||
}
|
||||
|
||||
override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
|
||||
this.appliesToPredecessor(pred, c) and
|
||||
loop.stop(pred, succ, c)
|
||||
}
|
||||
|
||||
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
|
||||
this.appliesToPredecessor(last, c) and
|
||||
scopeLast(scope, last, c)
|
||||
}
|
||||
|
||||
override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) {
|
||||
this.appliesToPredecessor(pred, c) and
|
||||
this.appliesSucc(pred, succ, c) and
|
||||
not loop.stop(pred, succ, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,6 @@ import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl as Impl
|
||||
import semmle.code.csharp.controlflow.internal.Splitting as Splitting
|
||||
import Nodes
|
||||
|
||||
query predicate booleanNode(ElementNode e, BooleanSplit split) { split = e.getASplit() }
|
||||
|
||||
class MyFinallySplitControlFlowNode extends ElementNode {
|
||||
MyFinallySplitControlFlowNode() {
|
||||
exists(Splitting::FinallySplitting::FinallySplitType type |
|
||||
type = this.getASplit().(FinallySplit).getType()
|
||||
|
|
||||
not type instanceof DirectSuccessor
|
||||
)
|
||||
}
|
||||
|
||||
Impl::Statements::TryStmtTree getTryStmt() { this.getAstNode() = result.getAFinallyDescendant() }
|
||||
}
|
||||
|
||||
query predicate finallyNode(MyFinallySplitControlFlowNode f, TryStmt try) { try = f.getTryStmt() }
|
||||
|
||||
query predicate entryPoint(Callable c, SourceControlFlowElement cfn) {
|
||||
c.getEntryPoint().getASuccessor().getAstNode() = cfn
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user