зеркало из https://github.com/github/codeql.git
Swift: Add control-flow library.
This commit is contained in:
Родитель
26f0d3ac43
Коммит
9f8fbd7aa7
|
@ -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() }
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче