Swift: Add control-flow library.

This commit is contained in:
Mathias Vorreiter Pedersen 2022-05-23 12:49:27 +01:00
Родитель 26f0d3ac43
Коммит 9f8fbd7aa7
10 изменённых файлов: 3568 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,255 @@
/** Provides classes representing basic blocks. */
private import swift
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import CfgNodes
private import SuccessorTypes
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*/
class BasicBlock extends TBasicBlockStart {
/** Gets the scope of this basic block. */
CfgScope getScope() { result = this.getAPredecessor().getScope() }
/** Gets an immediate successor of this basic block, if any. */
BasicBlock getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate successor of this basic block of a given type, if any. */
BasicBlock getASuccessor(SuccessorType t) {
result.getFirstNode() = this.getLastNode().getASuccessor(t)
}
/** Gets an immediate predecessor of this basic block, if any. */
BasicBlock getAPredecessor() { result.getASuccessor() = this }
/** Gets an immediate predecessor of this basic block of a given type, if any. */
BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
ControlFlowNode getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
/** Gets a control flow node in this basic block. */
ControlFlowNode getANode() { result = this.getNode(_) }
/** Gets the first control flow node in this basic block. */
ControlFlowNode getFirstNode() { this = TBasicBlockStart(result) }
/** Gets the last control flow node in this basic block. */
ControlFlowNode getLastNode() { result = this.getNode(this.length() - 1) }
/** Gets the length of this basic block. */
int length() { result = strictcount(this.getANode()) }
predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
predicate dominates(BasicBlock bb) {
bb = this or
this.strictlyDominates(bb)
}
predicate inDominanceFrontier(BasicBlock df) {
this.dominatesPredecessor(df) and
not this.strictlyDominates(df)
}
/**
* Holds if this basic block dominates a predecessor of `df`.
*/
private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
predicate postDominates(BasicBlock bb) {
this.strictlyPostDominates(bb) or
this = bb
}
/** Holds if this basic block is in a loop in the control flow graph. */
predicate inLoop() { this.getASuccessor+() = this }
/** Gets a textual representation of this basic block. */
string toString() { result = this.getFirstNode().toString() }
/** Gets the location of this basic block. */
Location getLocation() { result = this.getFirstNode().getLocation() }
}
cached
private module Cached {
/** Internal representation of basic blocks. */
cached
newtype TBasicBlock = TBasicBlockStart(ControlFlowNode cfn) { startsBB(cfn) }
/** Holds if `cfn` starts a new basic block. */
private predicate startsBB(ControlFlowNode cfn) {
not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
or
cfn.isJoin()
or
cfn.getAPredecessor().isBranch()
}
/**
* Holds if `succ` is a control flow successor of `pred` within
* the same basic block.
*/
private predicate intraBBSucc(ControlFlowNode pred, ControlFlowNode succ) {
succ = pred.getASuccessor() and
not startsBB(succ)
}
/**
* Holds if `cfn` is the `i`th node in basic block `bb`.
*
* In other words, `i` is the shortest distance from a node `bb`
* that starts a basic block to `cfn` along the `intraBBSucc` relation.
*/
cached
predicate bbIndex(ControlFlowNode bbStart, ControlFlowNode cfn, int i) =
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
/**
* Holds if the first node of basic block `succ` is a control flow
* successor of the last node of basic block `pred`.
*/
private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
/** Holds if `dom` is an immediate dominator of `bb`. */
cached
predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
idominance(entryBB/1, succBB/2)(_, dom, bb)
/** Holds if `pred` is a basic block predecessor of `succ`. */
private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
/** Holds if `bb` is an exit basic block that represents normal exit. */
private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() }
/** Holds if `dom` is an immediate post-dominator of `bb`. */
cached
predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
idominance(normalExitBB/1, predBB/2)(_, dom, bb)
/**
* Gets the `i`th predecessor of join block `jb`, with respect to some
* arbitrary order.
*/
cached
JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) {
result =
rank[i + 1](JoinBlockPredecessor jbp |
jbp = jb.getAPredecessor()
|
jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp)
)
}
}
private import Cached
/** Holds if `bb` is an entry basic block. */
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode }
/**
* An entry basic block, that is, a basic block whose first node is
* an entry node.
*/
class EntryBasicBlock extends BasicBlock {
EntryBasicBlock() { entryBB(this) }
override CfgScope getScope() { this.getFirstNode() = TEntryNode(result) }
}
/**
* An annotated exit basic block, that is, a basic block whose last node is
* an annotated exit node.
*/
class AnnotatedExitBasicBlock extends BasicBlock {
private boolean normal;
AnnotatedExitBasicBlock() {
exists(AnnotatedExitNode n |
n = this.getANode() and
if n.isNormal() then normal = true else normal = false
)
}
/** Holds if this block represent a normal exit. */
final predicate isNormal() { normal = true }
}
/**
* An exit basic block, that is, a basic block whose last node is
* an exit node.
*/
class ExitBasicBlock extends BasicBlock {
ExitBasicBlock() { this.getLastNode() instanceof ExitNode }
}
private module JoinBlockPredecessors {
private predicate id(AstNode x, AstNode y) { x = y }
private predicate idOf(AstNode x, int y) = equivalenceRelation(id/2)(x, y)
int getId(JoinBlockPredecessor jbp) {
idOf(jbp.getFirstNode().(AstCfgNode).getNode(), result)
or
idOf(jbp.(EntryBasicBlock).getScope(), result)
}
string getSplitString(JoinBlockPredecessor jbp) {
result = jbp.getFirstNode().(AstCfgNode).getSplitsString()
or
not exists(jbp.getFirstNode().(AstCfgNode).getSplitsString()) and
result = ""
}
}
/** A basic block with more than one predecessor. */
class JoinBlock extends BasicBlock {
JoinBlock() { this.getFirstNode().isJoin() }
/**
* Gets the `i`th predecessor of this join block, with respect to some
* arbitrary order.
*/
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) }
}
/** A basic block that is an immediate predecessor of a join block. */
class JoinBlockPredecessor extends BasicBlock {
JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
}
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
class ConditionBlock extends BasicBlock {
ConditionBlock() { this.getLastNode().isCondition() }
/**
* Holds if basic block `succ` is immediately controlled by this basic
* block with conditional value `s`. That is, `succ` is an immediate
* 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, SuccessorType s) {
succ = this.getASuccessor(s) and
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred))
}
/**
* Holds if basic block `controlled` is controlled by this basic block with
* conditional value `s`. That is, `controlled` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
predicate controls(BasicBlock controlled, BooleanSuccessor s) {
exists(BasicBlock succ | this.immediatelyControls(succ, s) | succ.dominates(controlled))
}
}

Просмотреть файл

@ -0,0 +1,103 @@
/** Provides classes representing nodes in a control flow graph. */
private import swift
private import BasicBlocks
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import internal.Splitting
/** An entry node for a given scope. */
class EntryNode extends ControlFlowNode, TEntryNode {
private CfgScope scope;
EntryNode() { this = TEntryNode(scope) }
final override EntryBasicBlock getBasicBlock() { result = ControlFlowNode.super.getBasicBlock() }
final override Location getLocation() { result = scope.getLocation() }
final override string toString() { result = "enter " + scope }
}
/** An exit node for a given scope, annotated with the type of exit. */
class AnnotatedExitNode extends ControlFlowNode, TAnnotatedExitNode {
private CfgScope scope;
private boolean normal;
AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
/** Holds if this node represent a normal exit. */
final predicate isNormal() { normal = true }
final override AnnotatedExitBasicBlock getBasicBlock() {
result = ControlFlowNode.super.getBasicBlock()
}
final override Location getLocation() { result = scope.getLocation() }
final override string toString() {
exists(string s |
normal = true and s = "normal"
or
normal = false and s = "abnormal"
|
result = "exit " + scope + " (" + s + ")"
)
}
}
/** An exit node for a given scope. */
class ExitNode extends ControlFlowNode, TExitNode {
private CfgScope scope;
ExitNode() { this = TExitNode(scope) }
final override Location getLocation() { result = scope.getLocation() }
final override string toString() { result = "exit " + scope }
}
/**
* A node for an AST node.
*
* Each AST node maps to zero or more `AstCfgNode`s: zero when the node is unreachable
* (dead) code or not important for control flow, and multiple when there are different
* splits for the AST node.
*/
class AstCfgNode extends ControlFlowNode, TElementNode {
private Splits splits;
private AstNode n;
AstCfgNode() { this = TElementNode(_, n, splits) }
final override AstNode getNode() { result = n }
override Location getLocation() { result = n.getLocation() }
final override string toString() {
exists(string s | s = n.(AstNode).toString() |
result = "[" + this.getSplitsString() + "] " + s
or
not exists(this.getSplitsString()) and result = s
)
}
/** Gets a comma-separated list of strings for each split in this node, if any. */
final string getSplitsString() {
result = splits.toString() and
result != ""
}
/** Gets a split for this control flow node, if any. */
final Split getASplit() { result = splits.getASplit() }
}
/** A control-flow node that wraps an AST expression. */
class ExprCfgNode extends AstCfgNode {
Expr e;
ExprCfgNode() { e = this.getNode() }
/** Gets the underlying expression. */
Expr getExpr() { result = e }
}

Просмотреть файл

@ -0,0 +1,137 @@
/** Provides classes representing the control flow graph. */
private import swift
private import BasicBlocks
private import SuccessorTypes
private import internal.ControlFlowGraphImpl
private import internal.Completion
private import internal.Scope
/** An AST node with an associated control-flow graph. */
class CfgScope extends Scope instanceof CfgScope::Range_ {
/** Gets the CFG scope that this scope is nested under, if any. */
final CfgScope getOuterCfgScope() {
exists(AstNode parent |
parent = getParent(this) and
result = getCfgScope(parent)
)
}
}
/**
* A control flow node.
*
* A control flow node is a node in the control flow graph (CFG). There is a
* many-to-one relationship between CFG nodes and AST nodes.
*
* Only nodes that can be reached from an entry point are included in the CFG.
*/
class ControlFlowNode extends TCfgNode {
/** Gets a textual representation of this control flow node. */
string toString() { none() }
/** Gets the AST node that this node corresponds to, if any. */
AstNode getNode() { none() }
/** Gets the location of this control flow node. */
Location getLocation() { none() }
/** Gets the file of this control flow node. */
final File getFile() { result = this.getLocation().getFile() }
/** Holds if this control flow node has conditional successors. */
final predicate isCondition() { exists(this.getASuccessor(any(BooleanSuccessor bs))) }
/** Gets the scope of this node. */
final CfgScope getScope() { result = this.getBasicBlock().getScope() }
/** Gets the basic block that this control flow node belongs to. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** Gets a successor node of a given type, if any. */
final ControlFlowNode getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
/** Gets an immediate successor, if any. */
final ControlFlowNode getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate predecessor node of a given flow type, if any. */
final ControlFlowNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets an immediate predecessor, if any. */
final ControlFlowNode getAPredecessor() { result = this.getAPredecessor(_) }
/** Holds if this node has more than one predecessor. */
final predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
/** Holds if this node has more than one successor. */
final predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
}
/** The type of a control flow successor. */
class SuccessorType extends TSuccessorType {
/** Gets a textual representation of successor type. */
string toString() { none() }
}
/** Provides different types of control flow successor types. */
module SuccessorTypes {
/** A normal control flow successor. */
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
final override string toString() { result = "successor" }
}
/** A conditional control flow successor. */
abstract class ConditionalSuccessor extends SuccessorType {
boolean value;
bindingset[value]
ConditionalSuccessor() { any() }
/** Gets the Boolean value of this successor. */
final boolean getValue() { result = value }
override string toString() { result = this.getValue().toString() }
}
/** A Boolean control flow successor. */
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor {
BooleanSuccessor() { this = TBooleanSuccessor(value) }
}
class BreakSuccessor extends SuccessorType, TBreakSuccessor {
final override string toString() { result = "break" }
}
class ContinueSuccessor extends SuccessorType, TContinueSuccessor {
final override string toString() { result = "continue" }
}
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
final override string toString() { result = "return" }
}
class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor {
MatchingSuccessor() { this = TMatchingSuccessor(value) }
/** Holds if this is a match successor. */
predicate isMatch() { value = true }
override string toString() { if this.isMatch() then result = "match" else result = "no-match" }
}
class FallthroughSuccessor extends SuccessorType, TFallthroughSuccessor {
final override string toString() { result = "fallthrough" }
}
class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor {
EmptinessSuccessor() { this = TEmptinessSuccessor(value) }
predicate isEmpty() { value = true }
override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" }
}
class ExceptionSuccessor extends SuccessorType, TExceptionSuccessor {
override string toString() { result = "exception" }
}
}

Просмотреть файл

@ -0,0 +1,479 @@
/**
* Provides classes representing control flow completions.
*
* A completion represents how a statement or expression terminates.
*/
private import swift
private import codeql.swift.controlflow.ControlFlowGraph
private import ControlFlowGraphImpl
private import SuccessorTypes
private newtype TCompletion =
TSimpleCompletion() or
TBooleanCompletion(boolean b) { b in [false, true] } or
TBreakCompletion(Stmt target) { target = any(BreakStmt stmt).getTarget() } or
TContinueCompletion(Stmts::Loops::LoopStmt target) { target = any(ContinueStmt stmt).getTarget() } or
TReturnCompletion() or
TMatchingCompletion(boolean isMatch) { isMatch in [false, true] } or
TFallthroughCompletion(CaseStmt dest) { dest = any(FallthroughStmt stmt).getFallthroughDest() } or
TEmptinessCompletion(boolean isEmpty) { isEmpty in [false, true] } or
TThrowCompletion()
/** Holds if `c` is a valid completion after evaluating `stmt`. */
pragma[noinline]
private predicate completionIsValidForStmt(Stmt stmt, Completion c) {
c = TBreakCompletion(stmt.(BreakStmt).getTarget())
or
c = TContinueCompletion(stmt.(ContinueStmt).getTarget())
or
(
// Exclude return completions from returns that are implicit generated by an autoclosure expression.
stmt instanceof ReturnStmt and not stmt = any(AutoClosureExpr closure).getReturn()
or
// `FailStmt` is only emitted in place of `return nil` in failable initializers.
stmt instanceof FailStmt
) and
c = TReturnCompletion()
or
c = TFallthroughCompletion(stmt.(FallthroughStmt).getFallthroughDest())
}
/** A completion of a statement or an expression. */
abstract class Completion extends TCompletion {
private predicate isValidForSpecific(AstNode n) {
completionIsValidForStmt(n, this)
or
mustHaveBooleanCompletion(n) and
(
exists(boolean value | isBooleanConstant(n, value) | this = TBooleanCompletion(value))
or
not isBooleanConstant(n, _) and
this = TBooleanCompletion(_)
)
or
mustHaveMatchingCompletion(n) and
(
exists(boolean value | isMatchingConstant(n, value) | this = TMatchingCompletion(value))
or
not isMatchingConstant(n, _) and
this = TMatchingCompletion(_)
)
or
mustHaveThrowCompletion(n, this)
}
/** Holds if this completion is valid for node `n`. */
predicate isValidFor(AstNode n) {
this.isValidForSpecific(n)
or
mayHaveThrowCompletion(n, this)
or
not any(Completion c).isValidForSpecific(n) and
this = TSimpleCompletion()
}
/**
* Holds if this completion will continue the loop `stmt` when it is the completion
* of a loop body.
*/
predicate continuesLoop(Stmts::Loops::LoopStmt stmt) { none() }
/** Gets a successor type that matches this completion. */
abstract SuccessorType getAMatchingSuccessorType();
/** Gets a textual representation of this completion. */
abstract string toString();
}
/** Holds if node `n` has the Boolean constant value `value`. */
private predicate isBooleanConstant(AstNode n, boolean value) {
mustHaveBooleanCompletion(n) and
value = n.(BooleanLiteralExpr).getValue()
or
// Boolean consants hidden inside conversions are also
// constants that resolve to the same value.
isBooleanConstant(n.getResolveStep(), value)
}
/**
* Holds if a normal completion of `n` must be a Boolean completion.
*/
private predicate mustHaveBooleanCompletion(AstNode n) { inBooleanContext(n) }
/**
* Holds if `n` is used in a Boolean context. That is, the value
* that `n` evaluates to determines a true/false branch successor.
*/
private predicate inBooleanContext(AstNode n) {
n = any(ConditionElement condElem).getFullyUnresolved()
or
n = any(StmtCondition stmtCond).getFullyUnresolved()
or
exists(RepeatWhileStmt repeat | n = repeat.getCondition().getFullyConverted())
or
exists(LogicalAndExpr parent |
n = parent.getLeftOperand().getFullyConverted()
or
inBooleanContext(parent) and
n = parent.getRightOperand().getFullyConverted()
)
or
exists(LogicalOrExpr parent |
n = parent.getLeftOperand().getFullyConverted()
or
inBooleanContext(parent) and
n = parent.getRightOperand().getFullyConverted()
)
or
n = any(NotExpr parent | inBooleanContext(parent)).getOperand().getFullyConverted()
or
exists(IfExpr ifExpr |
ifExpr.getCondition().getFullyConverted() = n
or
inBooleanContext(ifExpr) and
n = ifExpr.getBranch(_).getFullyConverted()
)
or
exists(ForEachStmt foreach | n = foreach.getWhere().getFullyConverted())
or
exists(Exprs::Conversions::ConversionOrIdentityTree parent |
inBooleanContext(parent) and
parent.convertsFrom(n)
)
}
/**
* Holds if a normal completion of `ast` must be a matching completion. Thats is,
* whether `ast` determines a match in a `switch` or `catch` statement.
*/
private predicate mustHaveMatchingCompletion(AstNode ast) {
switchMatching(_, _, ast) or catchMatching(_, _, ast)
}
/**
* Holds if `ast` must have a matching completion, and `e` is the fully converted
* expression that is being matched in some `switch` statement.
*/
private predicate mustHaveMatchingCompletion(Expr e, AstNode ast) {
exists(SwitchStmt switch |
switchMatching(switch, _, ast) and
e = switch.getExpr().getFullyConverted()
)
}
/**
* Holds if `ast` is a (sub-)pattern (or a guard of a top-level pattern) inside
* case `c`, belonging to `switch` statement `switch`.
*/
private predicate switchMatching(SwitchStmt switch, CaseStmt c, AstNode ast) {
switchMatchingCaseLabelItem(switch, c, ast)
or
switchMatchingPattern(switch, c, ast)
}
/**
* Holds if `cli` a top-level pattern of case `c`, belonging
* to `switch` statement `switch`.
*/
private predicate switchMatchingCaseLabelItem(SwitchStmt switch, CaseStmt c, CaseLabelItem cli) {
switch.getACase() = c and
c.getALabel() = cli
}
/**
* Holds if `pattern` is a sub-pattern of the pattern in case
* statement `c` of the `switch` statement `switch`.
*/
private predicate switchMatchingPattern(SwitchStmt s, CaseStmt c, Pattern pattern) {
s.getACase() = c and
exists(CaseLabelItem cli | switchMatching(s, c, cli) |
cli.getPattern() = pattern
or
isSubPattern+(cli.getPattern(), pattern)
)
}
/**
* Holds if `ast` is a (sub-)pattern (or a guard of a top-level pattern) inside
* case `c`, belonging to `catch` statement `s`.
*/
predicate catchMatching(DoCatchStmt s, CaseStmt c, AstNode ast) {
catchMatchingCaseLabelItem(s, c, ast)
or
catchMatchingPattern(s, c, ast)
}
/**
* Holds if `cli` a top-level pattern of case `c`, belonging to `catch` statement `s`.
*/
predicate catchMatchingCaseLabelItem(DoCatchStmt s, CaseStmt c, CaseLabelItem cli) {
s.getACatch() = c and
c.getALabel() = cli
}
/**
* Holds if `pattern` is a sub-pattern of the pattern in case
* statement `c` of the `catch` statement `s`.
*/
predicate catchMatchingPattern(DoCatchStmt s, CaseStmt c, Pattern pattern) {
s.getACatch() = c and
exists(CaseLabelItem cli | catchMatching(s, c, cli) |
cli.getPattern() = pattern
or
isSubPattern+(cli.getPattern(), pattern)
)
}
/** Holds if `sub` is a subpattern of `p`. */
private predicate isSubPattern(Pattern p, Pattern sub) {
sub = p.(BindingPattern).getSubPattern().getFullyUnresolved()
or
sub = p.(EnumElementPattern).getSubPattern().getFullyUnresolved()
or
sub = p.(IsPattern).getSubPattern().getFullyUnresolved()
or
sub = p.(OptionalSomePattern).getFullyUnresolved()
or
sub = p.(ParenPattern).getResolveStep()
or
sub = p.(TuplePattern).getAnElement().getFullyUnresolved()
or
sub = p.(TypedPattern).getSubPattern().getFullyUnresolved()
}
/** Gets the value of `e` if it is a constant value, disregarding conversions. */
private string getExprValue(Expr e) {
result = e.(IntegerLiteralExpr).getStringValue()
or
result = e.(BooleanLiteralExpr).getValue().toString()
or
result = e.(StringLiteralExpr).getValue()
}
/** Gets the value of `e` if it is a constant value, taking conversions into account. */
private string getConvertedExprValue(Expr e) { result = getExprValue(e.getUnconverted()) }
/** Gets the value of `p` if it is a constant value. */
private string getPatternValue(Pattern p) {
result = p.(BoolPattern).getValue().toString()
or
result = getConvertedExprValue(p.(ExprPattern).getSubExpr())
}
/** Holds if `p` always matches. */
private predicate isIrrefutableMatch(Pattern p) {
(p instanceof NamedPattern or p instanceof AnyPattern)
or
isIrrefutableMatch(p.(BindingPattern).getSubPattern().getFullyUnresolved())
or
isIrrefutableMatch(p.(TypedPattern).getSubPattern().getFullyUnresolved())
or
// A pattern hidden underneath a conversion is also an irrefutable pattern.
isIrrefutableMatch(p.getResolveStep())
}
/**
* Holds if the top-level pattern `cli` constantly matches (`value = true`) or
* constantly non-matches (`value = false`).
*/
private predicate isMatchingConstant(CaseLabelItem cli, boolean value) {
// If the pattern always matches
isIrrefutableMatch(cli.getPattern()) and
(
// And there is no guard
not cli.hasGuard()
or
// Or the guard is always true
getConvertedExprValue(cli.getGuard().getFullyConverted()) = "true"
) and
// Then the pattern always matches
value = true
or
// If the pattern is always false
getConvertedExprValue(cli.getGuard().getFullyConverted()) = "false" and
// Then the pattern never matches
value = false
or
// Check if the expression being matched on always matched the pattern
exists(Expr e, string exprValue, string patternValue |
mustHaveMatchingCompletion(e, cli) and
exprValue = getConvertedExprValue(e) and
patternValue = getPatternValue(cli.getPattern())
|
exprValue = patternValue and
getConvertedExprValue(cli.getGuard()) = "true" and
value = true
or
exprValue != patternValue and
value = false
or
getConvertedExprValue(cli.getGuard()) = "false" and
value = false
)
}
private predicate mustHaveThrowCompletion(ThrowStmt throw, ThrowCompletion c) { any() }
private predicate mayHaveThrowCompletion(ApplyExpr n, ThrowCompletion c) {
// TODO: We should really just use the function type here, I think (and then check
// if the type has a `throws`).
exists(n.getStaticTarget())
}
/**
* A completion that represents normal evaluation of a statement or an
* expression.
*/
abstract class NormalCompletion extends Completion {
override predicate continuesLoop(Stmts::Loops::LoopStmt stmt) { any() }
}
/** A simple (normal) completion. */
class SimpleCompletion extends NormalCompletion, TSimpleCompletion {
override NormalSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "simple" }
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a break from a loop.
*/
class BreakCompletion extends TBreakCompletion, Completion {
Stmt target;
BreakCompletion() { this = TBreakCompletion(target) }
override BreakSuccessor getAMatchingSuccessorType() { any() }
Stmt getTarget() { result = target }
override string toString() { result = "break" }
}
/**
* A completion that represents evaluation of a statement or an
* expression resulting in a continue from a loop.
*/
class ContinueCompletion extends TContinueCompletion, Completion {
Stmts::Loops::LoopStmt target;
ContinueCompletion() { this = TContinueCompletion(target) }
override ContinueSuccessor getAMatchingSuccessorType() { any() }
override predicate continuesLoop(Stmts::Loops::LoopStmt stmt) { target = stmt }
Stmts::Loops::LoopStmt getTarget() { result = target }
override string toString() { result = "continue" }
}
/**
* A completion that represents a return.
*/
class ReturnCompletion extends TReturnCompletion, Completion {
override ReturnSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "return" }
}
/**
* A completion that represents evaluation of an expression, whose value determines
* the successor. Either a Boolean completion (`BooleanCompletion`), or a matching
* completion (`MatchingCompletion`).
*/
abstract class ConditionalCompletion extends NormalCompletion {
boolean value;
bindingset[value]
ConditionalCompletion() { any() }
/** Gets the Boolean value of this conditional completion. */
final boolean getValue() { result = value }
/** Gets the dual completion. */
abstract ConditionalCompletion getDual();
}
/**
* A completion that represents evaluation of an expression
* with a Boolean value.
*/
class BooleanCompletion extends ConditionalCompletion, TBooleanCompletion {
BooleanCompletion() { this = TBooleanCompletion(value) }
/** Gets the dual Boolean completion. */
override BooleanCompletion getDual() { result = TBooleanCompletion(value.booleanNot()) }
override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { result = value.toString() }
}
/** A Boolean `true` completion. */
class TrueCompletion extends BooleanCompletion {
TrueCompletion() { this.getValue() = true }
}
/** A Boolean `false` completion. */
class FalseCompletion extends BooleanCompletion {
FalseCompletion() { this.getValue() = false }
}
/**
* A completion that represents matching, for example a `case` statement in a
* `switch` statement.
*/
class MatchingCompletion extends ConditionalCompletion, TMatchingCompletion {
MatchingCompletion() { this = TMatchingCompletion(value) }
/** Holds if there is a match. */
predicate isMatch() { value = true }
/** Holds if there is not a match. */
predicate isNonMatch() { value = false }
override MatchingCompletion getDual() { result = TMatchingCompletion(value.booleanNot()) }
override MatchingSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { if this.isMatch() then result = "match" else result = "no-match" }
}
class FallthroughCompletion extends Completion, TFallthroughCompletion {
CaseStmt dest;
FallthroughCompletion() { this = TFallthroughCompletion(dest) }
override FallthroughSuccessor getAMatchingSuccessorType() { any() }
CaseStmt getDestination() { result = dest }
override string toString() { result = "fallthrough" }
}
/**
* A completion that represents evaluation of an emptiness test, for example
* a test in a `foreach` statement.
*/
class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
EmptinessCompletion() { this = TEmptinessCompletion(value) }
/** Holds if the emptiness test evaluates to `true`. */
predicate isEmpty() { value = true }
override EmptinessCompletion getDual() { result = TEmptinessCompletion(value.booleanNot()) }
override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" }
}
/** A completion that represents an evaluation that has thrown an exception. */
class ThrowCompletion extends TThrowCompletion, Completion {
override ExceptionSuccessor getAMatchingSuccessorType() { any() }
override string toString() { result = "throw" }
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,68 @@
private import swift as s
private import ControlFlowGraphImpl as Impl
private import Completion as Comp
private import codeql.swift.controlflow.ControlFlowGraph as CFG
private import Splitting as Splitting
/** The base class for `ControlFlowTree`. */
class ControlFlowTreeBase extends s::AstNode { }
class ControlFlowElement = s::AstNode;
class Completion = Comp::Completion;
/**
* Hold if `c` represents normal evaluation of a statement or an
* expression.
*/
predicate completionIsNormal(Completion c) { c instanceof Comp::NormalCompletion }
/**
* Hold if `c` represents simple (normal) evaluation of a statement or an
* expression.
*/
predicate completionIsSimple(Completion c) { c instanceof Comp::SimpleCompletion }
/** Holds if `c` is a valid completion for `e`. */
predicate completionIsValidFor(Completion c, ControlFlowElement e) { c.isValidFor(e) }
class CfgScope = CFG::CfgScope;
predicate getCfgScope = Impl::getCfgScope/1;
/** Holds if `first` is first executed when entering `scope`. */
predicate scopeFirst(CfgScope scope, ControlFlowElement first) {
scope.(Impl::CfgScope::Range_).entry(first)
}
/** Holds if `scope` is exited when `last` finishes with completion `c`. */
predicate scopeLast(CfgScope scope, ControlFlowElement last, Completion c) {
scope.(Impl::CfgScope::Range_).exit(last, c)
}
/** The maximum number of splits allowed for a given node. */
int maxSplits() { result = 5 }
class SplitKindBase = Splitting::TSplitKind;
class Split = Splitting::Split;
class SuccessorType = CFG::SuccessorType;
/** Gets a successor type that matches completion `c`. */
SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() }
/**
* Hold if `c` represents simple (normal) evaluation of a statement or an
* expression.
*/
predicate successorTypeIsSimple(SuccessorType t) {
t instanceof CFG::SuccessorTypes::NormalSuccessor
}
/** Holds if `t` is an abnormal exit type out of a CFG scope. */
predicate isAbnormalExitType(SuccessorType t) { none() } // TODO
class Location = s::Location;
class Node = CFG::ControlFlowNode;

Просмотреть файл

@ -0,0 +1,15 @@
/**
* @name Print CFG
* @description Produces a representation of a file's Control Flow Graph.
* This query is used by the VS Code extension.
* @id swift/print-cfg
* @kind graph
* @tags ide-contextual-queries/print-cfg
*/
private import codeql.swift.controlflow.ControlFlowGraph
private import codeql.swift.controlflow.internal.ControlFlowGraphImpl::TestOutput
class MyRelevantNode extends RelevantNode {
MyRelevantNode() { any() }
}

Просмотреть файл

@ -0,0 +1,249 @@
private import swift
module CallableBase {
class TypeRange = @abstract_function_decl;
class Range extends Scope::Range, TypeRange { }
}
module Callable {
class TypeRange = CallableBase::TypeRange;
class Range extends Scope::Range, TypeRange { }
}
private module Scope {
class TypeRange = Callable::TypeRange;
class Range extends AstNode, TypeRange {
Range getOuterScope() { result = scopeOf(this) }
}
}
class Scope extends AstNode instanceof Scope::Range {
/** Gets the scope in which this scope is nested, if any. */
Scope getOuterScope() { result = super.getOuterScope() }
}
cached
private module Cached {
private AstNode getChild(AstNode ast) {
result = ast.(TopLevelCodeDecl).getBody()
or
result = ast.(AnyTryExpr).getSubExpr()
or
result = ast.(ClosureExpr).getBody()
or
result = ast.(ExplicitCastExpr).getSubExpr()
or
result = ast.(ForceValueExpr).getSubExpr()
or
result = ast.(IdentityExpr).getSubExpr()
or
result = ast.(ImplicitConversionExpr).getSubExpr()
or
result = ast.(InOutExpr).getSubExpr()
or
result = ast.(MemberRefExpr).getBaseExpr()
or
result = ast.(SelfApplyExpr).getBaseExpr()
or
result = ast.(VarargExpansionExpr).getSubExpr()
or
result = ast.(BindingPattern).getSubPattern()
or
result = ast.(EnumElementPattern).getSubPattern()
or
result = ast.(ExprPattern).getSubExpr()
or
result = ast.(IsPattern).getSubPattern()
or
result = ast.(OptionalSomePattern).getSubPattern()
or
result = ast.(ParenPattern).getSubPattern()
or
result = ast.(TypedPattern).getSubPattern()
or
result = ast.(DeferStmt).getBody()
or
result = ast.(DoStmt).getBody()
or
result = ast.(ReturnStmt).getResult()
or
result = ast.(ThrowStmt).getSubExpr()
}
cached
AstNode getChild(AstNode ast, int index) {
exists(AbstractFunctionDecl afd | afd = ast |
index = -1 and
result = afd.getBody()
or
result = afd.getParam(index)
)
or
result = ast.(EnumCaseDecl).getElement(index)
or
result = ast.(EnumElementDecl).getParam(index)
or
exists(PatternBindingDecl pbd, int i | pbd = ast |
index = 2 * i and
result = pbd.getPattern(i)
or
index = 2 * i + 1 and
result = pbd.getInit(i)
)
or
exists(ApplyExpr apply | apply = ast |
index = -1 and
result = apply.getFunction()
or
result = apply.getArgument(index).getExpr()
)
or
result = ast.(ArrayExpr).getElement(index)
or
// `x` is evaluated before `y` in `x = y`.
exists(AssignExpr assign | assign = ast |
index = 0 and
result = assign.getDest()
or
index = 1 and
result = assign.getSource()
)
or
exists(BinaryExpr binary | binary = ast |
index = 0 and
result = binary.getLeftOperand()
or
index = 1 and
result = binary.getRightOperand()
)
or
// TODO: There are two other getters. Should they be included?
exists(InterpolatedStringLiteralExpr interpolated | interpolated = ast |
index = 0 and
result = interpolated.getInterpolationExpr()
or
index = 1 and
result = interpolated.getAppendingExpr()
)
or
exists(KeyPathExpr kp | kp = ast |
index = 0 and
result = kp.getParsedRoot()
or
index = 1 and
result = kp.getParsedPath()
)
or
exists(SubscriptExpr sub | sub = ast |
index = -1 and
result = sub.getBaseExpr()
or
result = sub.getArgument(index).getExpr()
)
or
exists(TapExpr tap | tap = ast |
index = 0 and
result = tap.getVar()
or
index = 1 and
result = tap.getSubExpr()
or
index = 2 and
result = tap.getBody()
)
or
result = ast.(TupleExpr).getElement(index)
or
result = ast.(TuplePattern).getElement(index)
or
result = ast.(BraceStmt).getElement(index)
or
exists(CaseStmt case | case = ast |
index = -1 and
result = case.getBody()
or
result = case.getLabel(index)
or
index >= case.getNumberOfLabels() and
result = case.getVariable(case.getNumberOfLabels() - index)
)
or
exists(DoCatchStmt catch | catch = ast |
index = -1 and
result = catch.getBody()
or
result = catch.getCatch(index)
)
or
exists(ForEachStmt forEach | forEach = ast |
index = 0 and
result = forEach.getWhere()
or
index = 1 and
result = forEach.getBody()
)
or
exists(GuardStmt guard | guard = ast |
index = 0 and
result = guard.getBody()
or
index = 1 and
result = guard.getCondition()
)
or
result = ast.(StmtCondition).getElement(index).getUnderlyingCondition()
or
exists(IfStmt ifStmt | ifStmt = ast |
index = 0 and
result = ifStmt.getCondition().getFullyUnresolved()
or
index = 1 and
result = ifStmt.getThen()
or
index = 2 and
result = ifStmt.getElse()
)
or
exists(RepeatWhileStmt repeat | repeat = ast |
index = 0 and
result = repeat.getCondition()
or
index = 1 and
result = repeat.getBody()
)
or
exists(SwitchStmt switch | switch = ast |
index = -1 and
result = switch.getExpr()
or
result = switch.getCase(index)
)
or
exists(WhileStmt while | while = ast |
index = 0 and
result = while.getCondition().getFullyUnresolved()
or
index = 1 and
result = while.getBody()
)
or
index = 0 and
result = getChild(ast)
}
cached
AstNode getParent(AstNode ast) { getChild(result, _) = ast }
}
/** Gets the enclosing scope of a node */
cached
AstNode scopeOf(AstNode n) {
exists(AstNode p | p = getParent(n) |
if p instanceof Scope then p = result else result = scopeOf(p)
)
}
import Cached

Просмотреть файл

@ -0,0 +1,91 @@
/**
* Provides classes and predicates relevant for splitting the control flow graph.
*/
private import swift
private import Completion
private import ControlFlowGraphImpl
private import codeql.swift.controlflow.ControlFlowGraph
cached
private module Cached {
cached
newtype TSplitKind = TConditionalCompletionSplitKind() { forceCachingInSameStage() }
cached
newtype TSplit = TConditionalCompletionSplit(ConditionalCompletion c)
}
import Cached
/** A split for a control flow node. */
class Split extends TSplit {
/** Gets a textual representation of this split. */
string toString() { none() }
}
private module ConditionalCompletionSplitting {
/** A split for conditional completions. */
class ConditionalCompletionSplit extends Split, TConditionalCompletionSplit {
ConditionalCompletion completion;
ConditionalCompletionSplit() { this = TConditionalCompletionSplit(completion) }
override string toString() { result = completion.toString() }
}
private class ConditionalCompletionSplitKind extends SplitKind, TConditionalCompletionSplitKind {
override int getListOrder() { result = 0 }
override predicate isEnabled(AstNode n) { this.appliesTo(n) }
override string toString() { result = "ConditionalCompletion" }
}
private class ConditionalCompletionSplitImpl extends SplitImpl, ConditionalCompletionSplit {
override ConditionalCompletionSplitKind getKind() { any() }
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
succ(pred, succ, c) and
last(succ, _, completion) and
(
last(succ.(NotExpr).getOperand().getFullyConverted(), pred, c) and
completion.(BooleanCompletion).getDual() = c
or
last(succ.(LogicalAndExpr).getAnOperand().getFullyConverted(), pred, c) and
completion = c
or
last(succ.(LogicalOrExpr).getAnOperand().getFullyConverted(), pred, c) and
completion = c
or
succ =
any(IfExpr ce |
last(ce.getBranch(_).getFullyConverted(), pred, c) and
completion = c
)
or
exists(Expr e |
succ.(Exprs::Conversions::ConversionOrIdentityTree).convertsFrom(e) and
last(e, pred, c) and
completion = c
)
)
}
override predicate hasEntryScope(CfgScope scope, AstNode succ) { none() }
override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
this.appliesTo(pred) and
succ(pred, succ, c) and
if c instanceof ConditionalCompletion then completion = c else any()
}
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
this.appliesTo(last) and
succExit(scope, last, c) and
if c instanceof ConditionalCompletion then completion = c else any()
}
override predicate hasSuccessor(AstNode pred, AstNode succ, Completion c) { none() }
}
}