From 0d81a6409dd99bbb2ab4ce52d4d4266690968649 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 19 Aug 2022 15:24:28 +0200 Subject: [PATCH 1/8] SSA: Make shared library a parameterized module --- .../ql/consistency-queries/SsaConsistency.ql | 12 +- .../code/csharp/controlflow/BasicBlocks.qll | 6 +- .../code/csharp/dataflow/internal/SsaImpl.qll | 81 +- .../dataflow/internal/SsaImplCommon.qll | 1519 +++++++++-------- .../dataflow/internal/SsaImplSpecific.qll | 19 - .../library-tests/dataflow/ssa/SSAPhiRead.ql | 2 +- 6 files changed, 865 insertions(+), 774 deletions(-) delete mode 100644 csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll diff --git a/csharp/ql/consistency-queries/SsaConsistency.ql b/csharp/ql/consistency-queries/SsaConsistency.ql index ee231828279..71f88bf2ab0 100644 --- a/csharp/ql/consistency-queries/SsaConsistency.ql +++ b/csharp/ql/consistency-queries/SsaConsistency.ql @@ -1,8 +1,8 @@ import csharp -import semmle.code.csharp.dataflow.internal.SsaImplCommon::Consistency +import semmle.code.csharp.dataflow.internal.SsaImpl::Consistency as Consistency import Ssa -class MyRelevantDefinition extends RelevantDefinition, Ssa::Definition { +class MyRelevantDefinition extends Consistency::RelevantDefinition, Ssa::Definition { override predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { @@ -10,6 +10,14 @@ class MyRelevantDefinition extends RelevantDefinition, Ssa::Definition { } } +query predicate nonUniqueDef = Consistency::nonUniqueDef/4; + +query predicate readWithoutDef = Consistency::readWithoutDef/3; + +query predicate deadDef = Consistency::deadDef/2; + +query predicate notDominatedByDef = Consistency::notDominatedByDef/4; + query predicate localDeclWithSsaDef(LocalVariableDeclExpr d) { // Local variables in C# must be initialized before every use, so uninitialized // local variables should not have an SSA definition, as that would imply that diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll index 08e5925ad50..44ff56706a7 100644 --- a/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll +++ b/csharp/ql/lib/semmle/code/csharp/controlflow/BasicBlocks.qll @@ -192,8 +192,7 @@ class BasicBlock extends TBasicBlockStart { * Gets the basic block that immediately dominates this basic block, if any. * * That is, all paths reaching this basic block from some entry point - * basic block must go through the result, which is an immediate basic block - * predecessor of this basic block. + * basic block must go through the result. * * Example: * @@ -207,8 +206,7 @@ class BasicBlock extends TBasicBlockStart { * * The basic block starting on line 2 is an immediate dominator of * the basic block online 4 (all paths from the entry point of `M` - * to `return s.Length;` must go through the null check, and the null check - * is an immediate predecessor of `return s.Length;`). + * to `return s.Length;` must go through the null check. */ BasicBlock getImmediateDominator() { bbIDominates(result, this) } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll index 7528d47db91..bda42600dea 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImpl.qll @@ -3,7 +3,53 @@ */ import csharp -import SsaImplCommon +private import SsaImplCommon as SsaImplCommon +private import AssignableDefinitions + +private module SsaInput implements SsaImplCommon::InputSig { + class BasicBlock = ControlFlow::BasicBlock; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + + class ExitBasicBlock = ControlFlow::BasicBlocks::ExitBlock; + + class SourceVariable = Ssa::SourceVariable; + + /** + * Holds if the `i`th node of basic block `bb` is a (potential) write to source + * variable `v`. The Boolean `certain` indicates whether the write is certain. + * + * This includes implicit writes via calls. + */ + predicate variableWrite(ControlFlow::BasicBlock bb, int i, Ssa::SourceVariable v, boolean certain) { + variableWriteDirect(bb, i, v, certain) + or + variableWriteQualifier(bb, i, v, certain) + or + updatesNamedFieldOrProp(bb, i, _, v, _) and + certain = false + or + updatesCapturedVariable(bb, i, _, v, _, _) and + certain = false + } + + /** + * Holds if the `i`th of basic block `bb` reads source variable `v`. + * + * This includes implicit reads via calls. + */ + predicate variableRead(ControlFlow::BasicBlock bb, int i, Ssa::SourceVariable v, boolean certain) { + variableReadActual(bb, i, v) and + certain = true + or + variableReadPseudo(bb, i, v) and + certain = false + } +} + +import SsaImplCommon::Make /** * Holds if the `i`th node of basic block `bb` reads source variable `v`. @@ -805,24 +851,6 @@ private module CapturedVariableImpl { } } -/** - * Holds if the `i`th node of basic block `bb` is a (potential) write to source - * variable `v`. The Boolean `certain` indicates whether the write is certain. - * - * This includes implicit writes via calls. - */ -predicate variableWrite(ControlFlow::BasicBlock bb, int i, Ssa::SourceVariable v, boolean certain) { - variableWriteDirect(bb, i, v, certain) - or - variableWriteQualifier(bb, i, v, certain) - or - updatesNamedFieldOrProp(bb, i, _, v, _) and - certain = false - or - updatesCapturedVariable(bb, i, _, v, _, _) and - certain = false -} - /** * Liveness analysis to restrict the size of the SSA representation for * captured variables. @@ -1039,19 +1067,6 @@ private predicate variableReadPseudo(ControlFlow::BasicBlock bb, int i, Ssa::Sou capturedReadIn(bb, i, v, _, _, _) } -/** - * Holds if the `i`th of basic block `bb` reads source variable `v`. - * - * This includes implicit reads via calls. - */ -predicate variableRead(ControlFlow::BasicBlock bb, int i, Ssa::SourceVariable v, boolean certain) { - variableReadActual(bb, i, v) and - certain = true - or - variableReadPseudo(bb, i, v) and - certain = false -} - cached private module Cached { cached @@ -1151,7 +1166,7 @@ private module Cached { predicate variableWriteQualifier( ControlFlow::BasicBlock bb, int i, QualifiedFieldOrPropSourceVariable v, boolean certain ) { - variableWrite(bb, i, v.getQualifier(), certain) and + SsaInput::variableWrite(bb, i, v.getQualifier(), certain) and // Eliminate corner case where a call definition can overlap with a // qualifier definition: if method `M` updates field `F`, then a call // to `M` is both an update of `x.M` and `x.M.M`, so the former call diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll index 659940def50..130ab86a64c 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll @@ -3,793 +3,882 @@ * (SSA) form. */ -private import SsaImplSpecific - -private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } - -/** - * Liveness analysis (based on source variables) to restrict the size of the - * SSA representation. - */ -private module Liveness { +/** Provides the input specification of the SSA implementation. */ +signature module InputSig { /** - * A classification of variable references into reads (of a given kind) and - * (certain or uncertain) writes. + * A basic block, that is, a maximal straight-line sequence of control flow nodes + * without branches or joins. */ - private newtype TRefKind = - Read(boolean certain) { certain in [false, true] } or - Write(boolean certain) { certain in [false, true] } - - private class RefKind extends TRefKind { - string toString() { - exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") - or - exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") - } - - int getOrder() { - this = Read(_) and - result = 0 - or - this = Write(_) and - result = 1 - } - } + class BasicBlock; /** - * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. - */ - predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { - exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) - or - exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) - } - - private newtype OrderedRefIndex = - MkOrderedRefIndex(int i, int tag) { - exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) - } - - private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { - ref(bb, i, v, k) and - result = MkOrderedRefIndex(i, ord) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of - * basic block `bb`, which has the given reference kind `k`. + * Gets the basic block that immediately dominates basic block `bb`, if any. * - * Reads are considered before writes when they happen at the same index. + * That is, all paths reaching `bb` from some entry point basic block must go + * through the result. + * + * Example: + * + * ```csharp + * int M(string s) { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The basic block starting on line 2 is an immediate dominator of + * the basic block on line 4 (all paths from the entry point of `M` + * to `return s.Length;` must go through the null check. */ - private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { - refOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedRefIndex res | - res = refOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); - private int maxRefRank(BasicBlock bb, SourceVariable v) { - result = refRank(bb, _, v, _) and - not result + 1 = refRank(bb, _, v, _) - } - - predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { - maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) - } + /** Gets an immediate successor of basic block `bb`, if any. */ + BasicBlock getABasicBlockSuccessor(BasicBlock bb); /** - * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` - * that is either a read or a certain write. + * An exit basic block, that is, a basic block whose last node is + * an exit node. */ - private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { - result = - min(int r, RefKind k | - r = refRank(bb, _, v, k) and - k != Write(false) - | - r - ) - } + class ExitBasicBlock extends BasicBlock; + + /** A variable that can be SSA converted. */ + class SourceVariable; /** - * Holds if source variable `v` is live at the beginning of basic block `bb`. + * Holds if the `i`th node of basic block `bb` is a (potential) write to source + * variable `v`. The Boolean `certain` indicates whether the write is certain. + * + * Examples of uncertain writes are `ref` arguments in C#, where it is the callee + * that may or may not update the argument. */ - predicate liveAtEntry(BasicBlock bb, SourceVariable v) { - // The first read or certain write to `v` inside `bb` is a read - refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) - or - // There is no certain write to `v` inside `bb`, but `v` is live at entry - // to a successor basic block of `bb` - not exists(firstReadOrCertainWrite(bb, v)) and - liveAtExit(bb, v) - } + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain); /** - * Holds if source variable `v` is live at the end of basic block `bb`. + * Holds if the `i`th node of basic block `bb` reads source variable `v`. The + * Boolean `certain` indicates whether the read is certain. + * + * Examples of uncertain reads are pseudo-reads inserted at the end of a C# method + * with a `ref` or `out` parameter, where it is the caller that may or may not read + * the argument. */ - predicate liveAtExit(BasicBlock bb, SourceVariable v) { - liveAtEntry(getABasicBlockSuccessor(bb), v) - } - - /** - * Holds if variable `v` is live in basic block `bb` at index `i`. - * The rank of `i` is `rnk` as defined by `refRank()`. - */ - private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { - exists(RefKind kind | rnk = refRank(bb, i, v, kind) | - rnk = maxRefRank(bb, v) and - liveAtExit(bb, v) - or - ref(bb, i, v, kind) and - kind = Read(_) - or - exists(RefKind nextKind | - liveAtRank(bb, _, v, rnk + 1) and - rnk + 1 = refRank(bb, _, v, nextKind) and - nextKind != Write(true) - ) - ) - } - - /** - * Holds if variable `v` is live after the (certain or uncertain) write at - * index `i` inside basic block `bb`. - */ - predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { - exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) - } + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain); } -private import Liveness - /** - * Holds if `df` is in the dominance frontier of `bb`. + * Provides an SSA implementation. * - * This is equivalent to: + * The SSA construction is pruned based on liveness. That is, SSA definitions are only + * constructed for `Input::variableWrite`s when it is possible to reach an + * `Input::variableRead`, without going through a certain write (the same goes for `phi` + * nodes). Whenever a variable is both read and written at the same index in some basic + * block, the read is assumed to happen before the write. * - * ```ql - * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and - * not bb = getImmediateBasicBlockDominator+(df) + * The result of invoking this parameterized module is not meant to be exposed directly; + * instead, one should define a language-specific layer on top, and make sure to cache + * all exposed predicates marked with + * + * ``` + * NB: If this predicate is exposed, it should be cached. * ``` */ -private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { - bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) - or - exists(BasicBlock prev | inDominanceFrontier(prev, df) | - bb = getImmediateBasicBlockDominator(prev) and - not bb = getImmediateBasicBlockDominator(df) - ) -} +module Make { + private import Input -/** - * Holds if `bb` is in the dominance frontier of a block containing a - * definition of `v`. - */ -pragma[noinline] -private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock defbb, Definition def | - def.definesAt(v, defbb, _) and - inDominanceFrontier(defbb, bb) - ) -} - -cached -newtype TDefinition = - TWriteDef(SourceVariable v, BasicBlock bb, int i) { - variableWrite(bb, i, v, _) and - liveAfterWrite(bb, i, v) - } or - TPhiNode(SourceVariable v, BasicBlock bb) { - inDefDominanceFrontier(bb, v) and - liveAtEntry(bb, v) + private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { + getABasicBlockSuccessor(result) = bb } -private module SsaDefReaches { - newtype TSsaRefKind = - SsaActualRead() or - SsaPhiRead() or - SsaDef() + /** + * Liveness analysis (based on source variables) to restrict the size of the + * SSA representation. + */ + private module Liveness { + /** + * A classification of variable references into reads (of a given kind) and + * (certain or uncertain) writes. + */ + private newtype TRefKind = + Read(boolean certain) { certain in [false, true] } or + Write(boolean certain) { certain in [false, true] } - class SsaRead = SsaActualRead or SsaPhiRead; + private class RefKind extends TRefKind { + string toString() { + exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") + or + exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") + } + + int getOrder() { + this = Read(_) and + result = 0 + or + this = Write(_) and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. + */ + predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { + exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) + or + exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) + } + + private newtype OrderedRefIndex = + MkOrderedRefIndex(int i, int tag) { + exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) + } + + private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { + ref(bb, i, v, k) and + result = MkOrderedRefIndex(i, ord) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of + * basic block `bb`, which has the given reference kind `k`. + * + * Reads are considered before writes when they happen at the same index. + */ + private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { + refOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedRefIndex res | + res = refOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + private int maxRefRank(BasicBlock bb, SourceVariable v) { + result = refRank(bb, _, v, _) and + not result + 1 = refRank(bb, _, v, _) + } + + predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { + maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) + } + + /** + * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` + * that is either a read or a certain write. + */ + private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { + result = + min(int r, RefKind k | + r = refRank(bb, _, v, k) and + k != Write(false) + | + r + ) + } + + /** + * Holds if source variable `v` is live at the beginning of basic block `bb`. + */ + predicate liveAtEntry(BasicBlock bb, SourceVariable v) { + // The first read or certain write to `v` inside `bb` is a read + refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) + or + // There is no certain write to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + not exists(firstReadOrCertainWrite(bb, v)) and + liveAtExit(bb, v) + } + + /** + * Holds if source variable `v` is live at the end of basic block `bb`. + */ + predicate liveAtExit(BasicBlock bb, SourceVariable v) { + liveAtEntry(getABasicBlockSuccessor(bb), v) + } + + /** + * Holds if variable `v` is live in basic block `bb` at index `i`. + * The rank of `i` is `rnk` as defined by `refRank()`. + */ + private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { + exists(RefKind kind | rnk = refRank(bb, i, v, kind) | + rnk = maxRefRank(bb, v) and + liveAtExit(bb, v) + or + ref(bb, i, v, kind) and + kind = Read(_) + or + exists(RefKind nextKind | + liveAtRank(bb, _, v, rnk + 1) and + rnk + 1 = refRank(bb, _, v, nextKind) and + nextKind != Write(true) + ) + ) + } + + /** + * Holds if variable `v` is live after the (certain or uncertain) write at + * index `i` inside basic block `bb`. + */ + predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { + exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) + } + } + + private import Liveness /** - * A classification of SSA variable references into reads and definitions. + * Holds if `df` is in the dominance frontier of `bb`. + * + * This is equivalent to: + * + * ```ql + * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and + * not bb = getImmediateBasicBlockDominator+(df) + * ``` */ - class SsaRefKind extends TSsaRefKind { - string toString() { - this = SsaActualRead() and - result = "SsaActualRead" - or - this = SsaPhiRead() and - result = "SsaPhiRead" - or - this = SsaDef() and - result = "SsaDef" - } - - int getOrder() { - this instanceof SsaRead and - result = 0 - or - this = SsaDef() and - result = 1 - } + private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { + bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) + or + exists(BasicBlock prev | inDominanceFrontier(prev, df) | + bb = getImmediateBasicBlockDominator(prev) and + not bb = getImmediateBasicBlockDominator(df) + ) } /** * Holds if `bb` is in the dominance frontier of a block containing a - * read of `v`. + * definition of `v`. */ - pragma[nomagic] - private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | - lastRefIsRead(readbb, v) - or - phiRead(readbb, v) + pragma[noinline] + private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock defbb, Definition def | + def.definesAt(v, defbb, _) and + inDominanceFrontier(defbb, bb) ) } - /** - * Holds if a phi-read node should be inserted for variable `v` at the beginning - * of basic block `bb`. - * - * Phi-read nodes are like normal phi nodes, but they are inserted based on reads - * instead of writes, and only if the dominance-frontier block does not already - * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is - * an internal implementation detail that is not exposed. - * - * The motivation for adding phi-reads is to improve performance of the use-use - * calculation in cases where there is a large number of reads that can reach the - * same join-point, and from there reach a large number of basic blocks. Example: - * - * ```cs - * if (a) - * use(x); - * else if (b) - * use(x); - * else if (c) - * use(x); - * else if (d) - * use(x); - * // many more ifs ... - * - * // phi-read for `x` inserted here - * - * // program not mentioning `x`, with large basic block graph - * - * use(x); - * ``` - * - * Without phi-reads, the analysis has to replicate reachability for each of - * the guarded uses of `x`. However, with phi-reads, the analysis will limit - * each conditional use of `x` to reach the basic block containing the phi-read - * node for `x`, and only that basic block will have to compute reachability - * through the remainder of the large program. - * - * Like normal reads, each phi-read node `phi-read` can be reached from exactly - * one SSA definition (without passing through another definition): Assume, for - * the sake of contradiction, that there are two reaching definitions `def1` and - * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest - * dominating definition will prevent the other from reaching `phi-read`. So, at - * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. - * Then `def1` must go through one of its dominance-frontier blocks in order to - * reach `phi-read`. However, such a block will always start with a (normal) phi - * node, which contradicts reachability. - * - * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, - * will dominate `phi-read`. Assuming it doesn't means that the path from `def` - * to `phi-read` goes through a dominance-frontier block, and hence a phi node, - * which contradicts reachability. - */ - pragma[nomagic] - predicate phiRead(BasicBlock bb, SourceVariable v) { - inReadDominanceFrontier(bb, v) and - liveAtEntry(bb, v) and - // only if there are no other references to `v` inside `bb` - not ref(bb, _, v, _) and - not exists(Definition def | def.definesAt(v, bb, _)) - } + cached + newtype TDefinition = + TWriteDef(SourceVariable v, BasicBlock bb, int i) { + variableWrite(bb, i, v, _) and + liveAfterWrite(bb, i, v) + } or + TPhiNode(SourceVariable v, BasicBlock bb) { + inDefDominanceFrontier(bb, v) and + liveAtEntry(bb, v) + } - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v`, - * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` - * is `SsaDef()`). - * - * Unlike `Liveness::ref`, this includes `phi` nodes. - */ - pragma[nomagic] - predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - variableRead(bb, i, v, _) and - k = SsaActualRead() - or - phiRead(bb, v) and - i = -1 and - k = SsaPhiRead() - or - any(Definition def).definesAt(v, bb, i) and - k = SsaDef() - } + private module SsaDefReaches { + newtype TSsaRefKind = + SsaActualRead() or + SsaPhiRead() or + SsaDef() - private newtype OrderedSsaRefIndex = - MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + class SsaRead = SsaActualRead or SsaPhiRead; - private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { - ssaRef(bb, i, v, k) and - result = MkOrderedSsaRefIndex(i, k) and - ord = k.getOrder() - } + /** + * A classification of SSA variable references into reads and definitions. + */ + class SsaRefKind extends TSsaRefKind { + string toString() { + this = SsaActualRead() and + result = "SsaActualRead" + or + this = SsaPhiRead() and + result = "SsaPhiRead" + or + this = SsaDef() and + result = "SsaDef" + } - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic - * block `bb`, which has the given reference kind `k`. - * - * For example, if `bb` is a basic block with a phi node for `v` (considered - * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: - * - * ```ql - * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node - * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 - * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 - * ``` - * - * Reads are considered before writes when they happen at the same index. - */ - int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - ssaRefOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedSsaRefIndex res | - res = ssaRefOrd(bb, j, v, _, ord) - | - res order by j, ord + int getOrder() { + this instanceof SsaRead and + result = 0 + or + this = SsaDef() and + result = 1 + } + } + + /** + * Holds if `bb` is in the dominance frontier of a block containing a + * read of `v`. + */ + pragma[nomagic] + private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | + lastRefIsRead(readbb, v) + or + phiRead(readbb, v) ) - } + } - int maxSsaRefRank(BasicBlock bb, SourceVariable v) { - result = ssaRefRank(bb, _, v, _) and - not result + 1 = ssaRefRank(bb, _, v, _) - } + /** + * Holds if a phi-read node should be inserted for variable `v` at the beginning + * of basic block `bb`. + * + * Phi-read nodes are like normal phi nodes, but they are inserted based on reads + * instead of writes, and only if the dominance-frontier block does not already + * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is + * an internal implementation detail that is not exposed. + * + * The motivation for adding phi-reads is to improve performance of the use-use + * calculation in cases where there is a large number of reads that can reach the + * same join-point, and from there reach a large number of basic blocks. Example: + * + * ```cs + * if (a) + * use(x); + * else if (b) + * use(x); + * else if (c) + * use(x); + * else if (d) + * use(x); + * // many more ifs ... + * + * // phi-read for `x` inserted here + * + * // program not mentioning `x`, with large basic block graph + * + * use(x); + * ``` + * + * Without phi-reads, the analysis has to replicate reachability for each of + * the guarded uses of `x`. However, with phi-reads, the analysis will limit + * each conditional use of `x` to reach the basic block containing the phi-read + * node for `x`, and only that basic block will have to compute reachability + * through the remainder of the large program. + * + * Like normal reads, each phi-read node `phi-read` can be reached from exactly + * one SSA definition (without passing through another definition): Assume, for + * the sake of contradiction, that there are two reaching definitions `def1` and + * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest + * dominating definition will prevent the other from reaching `phi-read`. So, at + * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. + * Then `def1` must go through one of its dominance-frontier blocks in order to + * reach `phi-read`. However, such a block will always start with a (normal) phi + * node, which contradicts reachability. + * + * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, + * will dominate `phi-read`. Assuming it doesn't means that the path from `def` + * to `phi-read` goes through a dominance-frontier block, and hence a phi node, + * which contradicts reachability. + */ + pragma[nomagic] + predicate phiRead(BasicBlock bb, SourceVariable v) { + inReadDominanceFrontier(bb, v) and + liveAtEntry(bb, v) and + // only if there are no other references to `v` inside `bb` + not ref(bb, _, v, _) and + not exists(Definition def | def.definesAt(v, bb, _)) + } - /** - * Holds if the SSA definition `def` reaches rank index `rnk` in its own - * basic block `bb`. - */ - predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { - exists(int i | - rnk = ssaRefRank(bb, i, v, SsaDef()) and - def.definesAt(v, bb, i) - ) - or - ssaDefReachesRank(bb, def, rnk - 1, v) and - rnk = ssaRefRank(bb, _, v, any(SsaRead k)) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches index `i` in the same - * basic block `bb`, without crossing another SSA definition of `v`. - */ - predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { - exists(int rnk | - ssaDefReachesRank(bb, def, rnk, v) and - rnk = ssaRefRank(bb, i, v, any(SsaRead k)) - ) - } - - /** - * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. - */ - int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { - v = def.getSourceVariable() and - result = ssaRefRank(bb, i, v, k) and - ( - ssaDefReachesRead(_, def, bb, i) + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v`, + * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` + * is `SsaDef()`). + * + * Unlike `Liveness::ref`, this includes `phi` nodes. + */ + pragma[nomagic] + predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + variableRead(bb, i, v, _) and + k = SsaActualRead() or - def.definesAt(_, bb, i) - ) + phiRead(bb, v) and + i = -1 and + k = SsaPhiRead() + or + any(Definition def).definesAt(v, bb, i) and + k = SsaDef() + } + + private newtype OrderedSsaRefIndex = + MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + + private OrderedSsaRefIndex ssaRefOrd( + BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord + ) { + ssaRef(bb, i, v, k) and + result = MkOrderedSsaRefIndex(i, k) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic + * block `bb`, which has the given reference kind `k`. + * + * For example, if `bb` is a basic block with a phi node for `v` (considered + * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: + * + * ```ql + * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node + * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 + * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 + * ``` + * + * Reads are considered before writes when they happen at the same index. + */ + int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + ssaRefOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedSsaRefIndex res | + res = ssaRefOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + int maxSsaRefRank(BasicBlock bb, SourceVariable v) { + result = ssaRefRank(bb, _, v, _) and + not result + 1 = ssaRefRank(bb, _, v, _) + } + + /** + * Holds if the SSA definition `def` reaches rank index `rnk` in its own + * basic block `bb`. + */ + predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { + exists(int i | + rnk = ssaRefRank(bb, i, v, SsaDef()) and + def.definesAt(v, bb, i) + ) + or + ssaDefReachesRank(bb, def, rnk - 1, v) and + rnk = ssaRefRank(bb, _, v, any(SsaRead k)) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches index `i` in the same + * basic block `bb`, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { + exists(int rnk | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, any(SsaRead k)) + ) + } + + /** + * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. + */ + int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { + v = def.getSourceVariable() and + result = ssaRefRank(bb, i, v, k) and + ( + ssaDefReachesRead(_, def, bb, i) + or + def.definesAt(_, bb, i) + ) + } + + /** + * Holds if the reference to `def` at index `i` in basic block `bb` is the + * last reference to `v` inside `bb`. + */ + pragma[noinline] + predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) + } + + predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { + exists(ssaDefRank(def, v, bb, _, k)) + } + + pragma[noinline] + private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { + ssaDefReachesEndOfBlock(bb, def, _) and + not defOccursInBlock(_, bb, def.getSourceVariable(), _) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ + * predecessor of `bb2`, and the underlying variable for `def` is neither read + * nor written in any block on the path between `bb1` and `bb2`. + * + * Phi reads are considered as normal reads for this predicate. + */ + pragma[nomagic] + private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + defOccursInBlock(def, bb1, _, _) and + bb2 = getABasicBlockSuccessor(bb1) + or + exists(BasicBlock mid | + varBlockReachesInclPhiRead(def, bb1, mid) and + ssaDefReachesThroughBlock(def, mid) and + bb2 = getABasicBlockSuccessor(mid) + ) + } + + pragma[nomagic] + private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(def, bb1, bb2) and + defOccursInBlock(def, bb2, v, SsaPhiRead()) + } + + pragma[nomagic] + private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and + ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) + or + exists(BasicBlock mid | + varBlockReachesExclPhiRead(def, mid, bb2) and + phiReadStep(def, _, bb1, mid) + ) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * the underlying variable `v` of `def` is accessed in basic block `bb2` + * (either a read or a write), `bb2` is a transitive successor of `bb1`, and + * `v` is neither read nor written in any block on the path between `bb1` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesExclPhiRead(def, bb1, bb2) and + not defOccursInBlock(def, bb1, _, SsaPhiRead()) + } + + pragma[nomagic] + predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { + varBlockReaches(def, bb1, bb2) and + ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + } + + /** + * Holds if `def` is accessed in basic block `bb` (either a read or a write), + * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, + * and `v` is neither read nor written in any block on the path between `bb` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReachesExit(Definition def, BasicBlock bb) { + exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | + not defOccursInBlock(def, bb2, _, _) and + not ssaDefReachesEndOfBlock(bb2, def, _) + ) + or + exists(BasicBlock mid | + varBlockReachesExit(def, mid) and + phiReadStep(def, _, bb, mid) + ) + } + } + + predicate phiReadExposedForTesting = phiRead/2; + + private import SsaDefReaches + + pragma[nomagic] + predicate liveThrough(BasicBlock bb, SourceVariable v) { + liveAtExit(bb, v) and + not ssaRef(bb, _, v, SsaDef()) } /** - * Holds if the reference to `def` at index `i` in basic block `bb` is the - * last reference to `v` inside `bb`. - */ - pragma[noinline] - predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) - } - - predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { - exists(ssaDefRank(def, v, bb, _, k)) - } - - pragma[noinline] - private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { - ssaDefReachesEndOfBlock(bb, def, _) and - not defOccursInBlock(_, bb, def.getSourceVariable(), _) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ - * predecessor of `bb2`, and the underlying variable for `def` is neither read - * nor written in any block on the path between `bb1` and `bb2`. + * NB: If this predicate is exposed, it should be cached. * - * Phi reads are considered as normal reads for this predicate. + * Holds if the SSA definition of `v` at `def` reaches the end of basic + * block `bb`, at which point it is still live, without crossing another + * SSA definition of `v`. */ pragma[nomagic] - private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - defOccursInBlock(def, bb1, _, _) and - bb2 = getABasicBlockSuccessor(bb1) - or - exists(BasicBlock mid | - varBlockReachesInclPhiRead(def, bb1, mid) and - ssaDefReachesThroughBlock(def, mid) and - bb2 = getABasicBlockSuccessor(mid) + predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { + exists(int last | + last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and + ssaDefReachesRank(bb, def, last, v) and + liveAtExit(bb, v) ) - } - - pragma[nomagic] - private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(def, bb1, bb2) and - defOccursInBlock(def, bb2, v, SsaPhiRead()) - } - - pragma[nomagic] - private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and - ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) or - exists(BasicBlock mid | - varBlockReachesExclPhiRead(def, mid, bb2) and - phiReadStep(def, _, bb1, mid) + // The construction of SSA form ensures that each read of a variable is + // dominated by its definition. An SSA definition therefore reaches a + // control flow node if it is the _closest_ SSA definition that dominates + // the node. If two definitions dominate a node then one must dominate the + // other, so therefore the definition of _closest_ is given by the dominator + // tree. Thus, reaching definitions can be calculated in terms of dominance. + ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and + liveThrough(bb, pragma[only_bind_into](v)) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. + */ + pragma[nomagic] + predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { + exists(SourceVariable v, BasicBlock bbDef | + phi.definesAt(v, bbDef, _) and + getABasicBlockPredecessor(bbDef) = bb and + ssaDefReachesEndOfBlock(bb, inp, v) ) } /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * the underlying variable `v` of `def` is accessed in basic block `bb2` - * (either a read or a write), `bb2` is a transitive successor of `bb1`, and - * `v` is neither read nor written in any block on the path between `bb1` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in + * basic block `bb`, without crossing another SSA definition of `v`. The read + * is of kind `rk`. */ pragma[nomagic] - predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesExclPhiRead(def, bb1, bb2) and - not defOccursInBlock(def, bb1, _, SsaPhiRead()) - } - - pragma[nomagic] - predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { - varBlockReaches(def, bb1, bb2) and - ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { + ssaDefReachesReadWithinBlock(v, def, bb, i) + or + ssaRef(bb, i, v, any(SsaRead k)) and + ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and + not ssaDefReachesReadWithinBlock(v, _, bb, i) } /** - * Holds if `def` is accessed in basic block `bb` (either a read or a write), - * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, - * and `v` is neither read nor written in any block on the path between `bb` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read + * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a + * path between them without any read of `def`. */ pragma[nomagic] - predicate varBlockReachesExit(Definition def, BasicBlock bb) { - exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | - not defOccursInBlock(def, bb2, _, _) and - not ssaDefReachesEndOfBlock(bb2, def, _) + predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + exists(int rnk | + rnk = ssaDefRank(def, _, bb1, i1, _) and + rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and + variableRead(bb1, i2, _, _) and + bb2 = bb1 ) or - exists(BasicBlock mid | - varBlockReachesExit(def, mid) and - phiReadStep(def, _, bb, mid) - ) - } -} - -predicate phiReadExposedForTesting = phiRead/2; - -private import SsaDefReaches - -pragma[nomagic] -predicate liveThrough(BasicBlock bb, SourceVariable v) { - liveAtExit(bb, v) and - not ssaRef(bb, _, v, SsaDef()) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches the end of basic - * block `bb`, at which point it is still live, without crossing another - * SSA definition of `v`. - */ -pragma[nomagic] -predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { - exists(int last | - last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and - ssaDefReachesRank(bb, def, last, v) and - liveAtExit(bb, v) - ) - or - // The construction of SSA form ensures that each read of a variable is - // dominated by its definition. An SSA definition therefore reaches a - // control flow node if it is the _closest_ SSA definition that dominates - // the node. If two definitions dominate a node then one must dominate the - // other, so therefore the definition of _closest_ is given by the dominator - // tree. Thus, reaching definitions can be calculated in terms of dominance. - ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and - liveThrough(bb, pragma[only_bind_into](v)) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. - */ -pragma[nomagic] -predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { - exists(SourceVariable v, BasicBlock bbDef | - phi.definesAt(v, bbDef, _) and - getABasicBlockPredecessor(bbDef) = bb and - ssaDefReachesEndOfBlock(bb, inp, v) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in - * basic block `bb`, without crossing another SSA definition of `v`. The read - * is of kind `rk`. - */ -pragma[nomagic] -predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { - ssaDefReachesReadWithinBlock(v, def, bb, i) - or - ssaRef(bb, i, v, any(SsaRead k)) and - ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and - not ssaDefReachesReadWithinBlock(v, _, bb, i) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read - * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a - * path between them without any read of `def`. - */ -pragma[nomagic] -predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - exists(int rnk | - rnk = ssaDefRank(def, _, bb1, i1, _) and - rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and - variableRead(bb1, i2, _, _) and - bb2 = bb1 - ) - or - lastSsaRef(def, _, bb1, i1) and - defAdjacentRead(def, bb1, bb2, i2) -} - -pragma[noinline] -private predicate adjacentDefRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v -) { - adjacentDefRead(def, bb1, i1, bb2, i2) and - v = def.getSourceVariable() -} - -private predicate adjacentDefReachesRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | - ssaRef(bb1, i1, v, SsaDef()) - or - variableRead(bb1, i1, v, true) - ) - or - exists(BasicBlock bb3, int i3 | - adjacentDefReachesRead(def, bb1, i1, bb3, i3) and - variableRead(bb3, i3, _, false) and - adjacentDefRead(def, bb3, i3, bb2, i2) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `adjacentDefRead`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, true) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA definition - * `def`. The reference is last because it can reach another write `next`, - * without passing through another read or write. - */ -pragma[nomagic] -predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { - exists(SourceVariable v | - // Next reference to `v` inside `bb` is a write - exists(int rnk, int j | - rnk = ssaDefRank(def, v, bb, i, _) and - next.definesAt(v, bb, j) and - rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) - ) - or - // Can reach a write using one or more steps - lastSsaRef(def, v, bb, i) and - exists(BasicBlock bb2 | - varBlockReaches(def, bb, bb2) and - 1 = ssaDefRank(next, v, bb2, _, SsaDef()) - ) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an immediately preceding definition of uncertain definition - * `def`. Since `def` is uncertain, the value from the preceding definition might - * still be valid. - */ -pragma[nomagic] -predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { - lastRefRedef(inp, _, _, def) -} - -private predicate adjacentDefReachesUncertainRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, false) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { - lastRefRedef(def, bb, i, next) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRefRedef(def, bb0, i0, next) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA - * definition `def`. - * - * That is, the node can reach the end of the enclosing callable, or another - * SSA definition for the underlying source variable, without passing through - * another read. - */ -pragma[nomagic] -predicate lastRef(Definition def, BasicBlock bb, int i) { - // Can reach another definition - lastRefRedef(def, bb, i, _) - or - exists(SourceVariable v | lastSsaRef(def, v, bb, i) | - // Can reach exit directly - bb instanceof ExitBasicBlock - or - // Can reach a block using one or more steps, where `def` is no longer live - varBlockReachesExit(def, bb) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { - lastRef(def, bb, i) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRef(def, bb0, i0) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** A static single assignment (SSA) definition. */ -class Definition extends TDefinition { - /** Gets the source variable underlying this SSA definition. */ - SourceVariable getSourceVariable() { this.definesAt(result, _, _) } - - /** - * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. - * Phi nodes are considered to be at index `-1`, while normal variable writes - * are at the index of the control flow node they wrap. - */ - final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { - this = TWriteDef(v, bb, i) - or - this = TPhiNode(v, bb) and i = -1 + lastSsaRef(def, _, bb1, i1) and + defAdjacentRead(def, bb1, bb2, i2) } - /** Gets the basic block to which this SSA definition belongs. */ - final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } - - /** Gets a textual representation of this SSA definition. */ - string toString() { none() } -} - -/** An SSA definition that corresponds to a write. */ -class WriteDefinition extends Definition, TWriteDef { - private SourceVariable v; - private BasicBlock bb; - private int i; - - WriteDefinition() { this = TWriteDef(v, bb, i) } - - override string toString() { result = "WriteDef" } -} - -/** A phi node. */ -class PhiNode extends Definition, TPhiNode { - override string toString() { result = "Phi" } -} - -/** - * An SSA definition that represents an uncertain update of the underlying - * source variable. - */ -class UncertainWriteDefinition extends WriteDefinition { - UncertainWriteDefinition() { - exists(SourceVariable v, BasicBlock bb, int i | - this.definesAt(v, bb, i) and - variableWrite(bb, i, v, false) - ) - } -} - -/** Provides a set of consistency queries. */ -module Consistency { - abstract class RelevantDefinition extends Definition { - abstract predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ); + pragma[noinline] + private predicate adjacentDefRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v + ) { + adjacentDefRead(def, bb1, i1, bb2, i2) and + v = def.getSourceVariable() } - query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefReachesRead(v, def, bb, i) and - not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) - } - - query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { - variableRead(bb, i, v, _) and - not ssaDefReachesRead(v, _, bb, i) - } - - query predicate deadDef(RelevantDefinition def, SourceVariable v) { - v = def.getSourceVariable() and - not ssaDefReachesRead(_, def, _, _) and - not phiHasInputFromBlock(_, def, _) and - not uncertainWriteDefinitionInput(_, def) - } - - query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | - ssaDefReachesReadWithinBlock(v, def, bb, i) and - (bb != bbDef or i < iDef) + private predicate adjacentDefReachesRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | + ssaRef(bb1, i1, v, SsaDef()) or - ssaDefReachesRead(v, def, bb, i) and - not ssaDefReachesReadWithinBlock(v, def, bb, i) and - not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + variableRead(bb1, i1, v, true) + ) + or + exists(BasicBlock bb3, int i3 | + adjacentDefReachesRead(def, bb1, i1, bb3, i3) and + variableRead(bb3, i3, _, false) and + adjacentDefRead(def, bb3, i3, bb2, i2) ) } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `adjacentDefRead`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate adjacentDefNoUncertainReads( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, true) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA definition + * `def`. The reference is last because it can reach another write `next`, + * without passing through another read or write. + */ + pragma[nomagic] + predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { + exists(SourceVariable v | + // Next reference to `v` inside `bb` is a write + exists(int rnk, int j | + rnk = ssaDefRank(def, v, bb, i, _) and + next.definesAt(v, bb, j) and + rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) + ) + or + // Can reach a write using one or more steps + lastSsaRef(def, v, bb, i) and + exists(BasicBlock bb2 | + varBlockReaches(def, bb, bb2) and + 1 = ssaDefRank(next, v, bb2, _, SsaDef()) + ) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an immediately preceding definition of uncertain definition + * `def`. Since `def` is uncertain, the value from the preceding definition might + * still be valid. + */ + pragma[nomagic] + predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { + lastRefRedef(inp, _, _, def) + } + + private predicate adjacentDefReachesUncertainRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, false) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { + lastRefRedef(def, bb, i, next) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRefRedef(def, bb0, i0, next) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA + * definition `def`. + * + * That is, the node can reach the end of the enclosing callable, or another + * SSA definition for the underlying source variable, without passing through + * another read. + */ + pragma[nomagic] + predicate lastRef(Definition def, BasicBlock bb, int i) { + // Can reach another definition + lastRefRedef(def, bb, i, _) + or + exists(SourceVariable v | lastSsaRef(def, v, bb, i) | + // Can reach exit directly + bb instanceof ExitBasicBlock + or + // Can reach a block using one or more steps, where `def` is no longer live + varBlockReachesExit(def, bb) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { + lastRef(def, bb, i) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRef(def, bb0, i0) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** A static single assignment (SSA) definition. */ + class Definition extends TDefinition { + /** Gets the source variable underlying this SSA definition. */ + SourceVariable getSourceVariable() { this.definesAt(result, _, _) } + + /** + * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. + * Phi nodes are considered to be at index `-1`, while normal variable writes + * are at the index of the control flow node they wrap. + */ + final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { + this = TWriteDef(v, bb, i) + or + this = TPhiNode(v, bb) and i = -1 + } + + /** Gets the basic block to which this SSA definition belongs. */ + final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } + + /** Gets a textual representation of this SSA definition. */ + string toString() { none() } + } + + /** An SSA definition that corresponds to a write. */ + class WriteDefinition extends Definition, TWriteDef { + private SourceVariable v; + private BasicBlock bb; + private int i; + + WriteDefinition() { this = TWriteDef(v, bb, i) } + + override string toString() { result = "WriteDef" } + } + + /** A phi node. */ + class PhiNode extends Definition, TPhiNode { + override string toString() { result = "Phi" } + } + + /** + * An SSA definition that represents an uncertain update of the underlying + * source variable. + */ + class UncertainWriteDefinition extends WriteDefinition { + UncertainWriteDefinition() { + exists(SourceVariable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + variableWrite(bb, i, v, false) + ) + } + } + + /** Provides a set of consistency queries. */ + // TODO: Make these `query` predicates once class signatures are supported + // (`SourceVariable` and `BasicBlock` must have `toString`) + module Consistency { + abstract class RelevantDefinition extends Definition { + abstract predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ); + } + + predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefReachesRead(v, def, bb, i) and + not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) + } + + predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { + variableRead(bb, i, v, _) and + not ssaDefReachesRead(v, _, bb, i) + } + + predicate deadDef(RelevantDefinition def, SourceVariable v) { + v = def.getSourceVariable() and + not ssaDefReachesRead(_, def, _, _) and + not phiHasInputFromBlock(_, def, _) and + not uncertainWriteDefinitionInput(_, def) + } + + predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | + ssaDefReachesReadWithinBlock(v, def, bb, i) and + (bb != bbDef or i < iDef) + or + ssaDefReachesRead(v, def, bb, i) and + not ssaDefReachesReadWithinBlock(v, def, bb, i) and + not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + ) + } + } } diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll deleted file mode 100644 index a929e23a942..00000000000 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll +++ /dev/null @@ -1,19 +0,0 @@ -/** Provides the C# specific parameters for `SsaImplCommon.qll`. */ - -private import csharp -private import AssignableDefinitions -private import SsaImpl as SsaImpl - -class BasicBlock = ControlFlow::BasicBlock; - -BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } - -BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } - -class ExitBasicBlock = ControlFlow::BasicBlocks::ExitBlock; - -class SourceVariable = Ssa::SourceVariable; - -predicate variableWrite = SsaImpl::variableWrite/4; - -predicate variableRead = SsaImpl::variableRead/4; diff --git a/csharp/ql/test/library-tests/dataflow/ssa/SSAPhiRead.ql b/csharp/ql/test/library-tests/dataflow/ssa/SSAPhiRead.ql index abc40f41e8d..f9603dc1da2 100644 --- a/csharp/ql/test/library-tests/dataflow/ssa/SSAPhiRead.ql +++ b/csharp/ql/test/library-tests/dataflow/ssa/SSAPhiRead.ql @@ -1,5 +1,5 @@ import csharp -import semmle.code.csharp.dataflow.internal.SsaImplCommon +import semmle.code.csharp.dataflow.internal.SsaImpl from Ssa::SourceVariable v, ControlFlow::BasicBlock bb where phiReadExposedForTesting(bb, v) From f55300121789763ceb0dd35ed7f04f3a7079d412 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 29 Aug 2022 10:53:54 +0200 Subject: [PATCH 2/8] C#: Update CIL SSA library to use parameterized module --- config/identical-files.json | 3 +- csharp/ql/lib/semmle/code/cil/Ssa.qll | 13 +- .../lib/semmle/code/cil/internal/SsaImpl.qll | 36 +- .../code/cil/internal/SsaImplCommon.qll | 795 ------------------ .../code/cil/internal/SsaImplSpecific.qll | 30 - 5 files changed, 41 insertions(+), 836 deletions(-) delete mode 100644 csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll delete mode 100644 csharp/ql/lib/semmle/code/cil/internal/SsaImplSpecific.qll diff --git a/config/identical-files.json b/config/identical-files.json index be53da39482..8789b860427 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -464,7 +464,6 @@ "csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll", "csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll", - "csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll", "swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll" @@ -602,4 +601,4 @@ "javascript/ql/lib/semmle/javascript/security/IncompleteMultiCharacterSanitizationQuery.qll", "ruby/ql/lib/codeql/ruby/security/IncompleteMultiCharacterSanitizationQuery.qll" ] -} +} \ No newline at end of file diff --git a/csharp/ql/lib/semmle/code/cil/Ssa.qll b/csharp/ql/lib/semmle/code/cil/Ssa.qll index 229925f6d08..50338d3284d 100644 --- a/csharp/ql/lib/semmle/code/cil/Ssa.qll +++ b/csharp/ql/lib/semmle/code/cil/Ssa.qll @@ -8,13 +8,12 @@ private import CIL * Provides classes for working with static single assignment (SSA) form. */ module Ssa { - private import internal.SsaImplCommon as SsaImpl - private import internal.SsaImpl + private import internal.SsaImpl as SsaImpl /** An SSA definition. */ class Definition extends SsaImpl::Definition { /** Gets a read of this SSA definition. */ - final ReadAccess getARead() { result = getARead(this) } + final ReadAccess getARead() { result = SsaImpl::getARead(this) } /** Gets the underlying variable update, if any. */ final VariableUpdate getVariableUpdate() { @@ -25,11 +24,11 @@ module Ssa { } /** Gets a first read of this SSA definition. */ - final ReadAccess getAFirstRead() { result = getAFirstRead(this) } + final ReadAccess getAFirstRead() { result = SsaImpl::getAFirstRead(this) } /** Holds if `first` and `second` are adjacent reads of this SSA definition. */ final predicate hasAdjacentReads(ReadAccess first, ReadAccess second) { - hasAdjacentReads(this, first, second) + SsaImpl::hasAdjacentReads(this, first, second) } private Definition getAPhiInput() { result = this.(PhiNode).getAnInput() } @@ -52,7 +51,7 @@ module Ssa { final override Location getLocation() { result = this.getBasicBlock().getLocation() } /** Gets an input to this phi node. */ - final Definition getAnInput() { result = getAPhiInput(this) } + final Definition getAnInput() { result = SsaImpl::getAPhiInput(this) } /** * Holds if if `def` is an input to this phi node, and a reference to `def` at @@ -60,7 +59,7 @@ module Ssa { * other references. */ final predicate hasLastInputRef(Definition def, BasicBlock bb, int i) { - hasLastInputRef(this, def, bb, i) + SsaImpl::hasLastInputRef(this, def, bb, i) } } } diff --git a/csharp/ql/lib/semmle/code/cil/internal/SsaImpl.qll b/csharp/ql/lib/semmle/code/cil/internal/SsaImpl.qll index 593c877b9af..f90df6aa857 100644 --- a/csharp/ql/lib/semmle/code/cil/internal/SsaImpl.qll +++ b/csharp/ql/lib/semmle/code/cil/internal/SsaImpl.qll @@ -1,8 +1,40 @@ -private import semmle.code.cil.CIL -private import SsaImplCommon +private import cil +private import semmle.code.csharp.dataflow.internal.SsaImplCommon as SsaImplCommon + +private module SsaInput implements SsaImplCommon::InputSig { + class BasicBlock = CIL::BasicBlock; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + + class ExitBasicBlock = CIL::ExitBasicBlock; + + class SourceVariable = CIL::StackVariable; + + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + forceCachingInSameStage() and + exists(CIL::VariableUpdate vu | + vu.updatesAt(bb, i) and + v = vu.getVariable() and + certain = true + ) + } + + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(CIL::ReadAccess ra | bb.getNode(i) = ra | + ra.getTarget() = v and + certain = true + ) + } +} + +import SsaImplCommon::Make cached private module Cached { + private import CIL + cached predicate forceCachingInSameStage() { any() } diff --git a/csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll b/csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll deleted file mode 100644 index 659940def50..00000000000 --- a/csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll +++ /dev/null @@ -1,795 +0,0 @@ -/** - * Provides a language-independent implementation of static single assignment - * (SSA) form. - */ - -private import SsaImplSpecific - -private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } - -/** - * Liveness analysis (based on source variables) to restrict the size of the - * SSA representation. - */ -private module Liveness { - /** - * A classification of variable references into reads (of a given kind) and - * (certain or uncertain) writes. - */ - private newtype TRefKind = - Read(boolean certain) { certain in [false, true] } or - Write(boolean certain) { certain in [false, true] } - - private class RefKind extends TRefKind { - string toString() { - exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") - or - exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") - } - - int getOrder() { - this = Read(_) and - result = 0 - or - this = Write(_) and - result = 1 - } - } - - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. - */ - predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { - exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) - or - exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) - } - - private newtype OrderedRefIndex = - MkOrderedRefIndex(int i, int tag) { - exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) - } - - private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { - ref(bb, i, v, k) and - result = MkOrderedRefIndex(i, ord) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of - * basic block `bb`, which has the given reference kind `k`. - * - * Reads are considered before writes when they happen at the same index. - */ - private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { - refOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedRefIndex res | - res = refOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } - - private int maxRefRank(BasicBlock bb, SourceVariable v) { - result = refRank(bb, _, v, _) and - not result + 1 = refRank(bb, _, v, _) - } - - predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { - maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) - } - - /** - * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` - * that is either a read or a certain write. - */ - private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { - result = - min(int r, RefKind k | - r = refRank(bb, _, v, k) and - k != Write(false) - | - r - ) - } - - /** - * Holds if source variable `v` is live at the beginning of basic block `bb`. - */ - predicate liveAtEntry(BasicBlock bb, SourceVariable v) { - // The first read or certain write to `v` inside `bb` is a read - refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) - or - // There is no certain write to `v` inside `bb`, but `v` is live at entry - // to a successor basic block of `bb` - not exists(firstReadOrCertainWrite(bb, v)) and - liveAtExit(bb, v) - } - - /** - * Holds if source variable `v` is live at the end of basic block `bb`. - */ - predicate liveAtExit(BasicBlock bb, SourceVariable v) { - liveAtEntry(getABasicBlockSuccessor(bb), v) - } - - /** - * Holds if variable `v` is live in basic block `bb` at index `i`. - * The rank of `i` is `rnk` as defined by `refRank()`. - */ - private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { - exists(RefKind kind | rnk = refRank(bb, i, v, kind) | - rnk = maxRefRank(bb, v) and - liveAtExit(bb, v) - or - ref(bb, i, v, kind) and - kind = Read(_) - or - exists(RefKind nextKind | - liveAtRank(bb, _, v, rnk + 1) and - rnk + 1 = refRank(bb, _, v, nextKind) and - nextKind != Write(true) - ) - ) - } - - /** - * Holds if variable `v` is live after the (certain or uncertain) write at - * index `i` inside basic block `bb`. - */ - predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { - exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) - } -} - -private import Liveness - -/** - * Holds if `df` is in the dominance frontier of `bb`. - * - * This is equivalent to: - * - * ```ql - * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and - * not bb = getImmediateBasicBlockDominator+(df) - * ``` - */ -private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { - bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) - or - exists(BasicBlock prev | inDominanceFrontier(prev, df) | - bb = getImmediateBasicBlockDominator(prev) and - not bb = getImmediateBasicBlockDominator(df) - ) -} - -/** - * Holds if `bb` is in the dominance frontier of a block containing a - * definition of `v`. - */ -pragma[noinline] -private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock defbb, Definition def | - def.definesAt(v, defbb, _) and - inDominanceFrontier(defbb, bb) - ) -} - -cached -newtype TDefinition = - TWriteDef(SourceVariable v, BasicBlock bb, int i) { - variableWrite(bb, i, v, _) and - liveAfterWrite(bb, i, v) - } or - TPhiNode(SourceVariable v, BasicBlock bb) { - inDefDominanceFrontier(bb, v) and - liveAtEntry(bb, v) - } - -private module SsaDefReaches { - newtype TSsaRefKind = - SsaActualRead() or - SsaPhiRead() or - SsaDef() - - class SsaRead = SsaActualRead or SsaPhiRead; - - /** - * A classification of SSA variable references into reads and definitions. - */ - class SsaRefKind extends TSsaRefKind { - string toString() { - this = SsaActualRead() and - result = "SsaActualRead" - or - this = SsaPhiRead() and - result = "SsaPhiRead" - or - this = SsaDef() and - result = "SsaDef" - } - - int getOrder() { - this instanceof SsaRead and - result = 0 - or - this = SsaDef() and - result = 1 - } - } - - /** - * Holds if `bb` is in the dominance frontier of a block containing a - * read of `v`. - */ - pragma[nomagic] - private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | - lastRefIsRead(readbb, v) - or - phiRead(readbb, v) - ) - } - - /** - * Holds if a phi-read node should be inserted for variable `v` at the beginning - * of basic block `bb`. - * - * Phi-read nodes are like normal phi nodes, but they are inserted based on reads - * instead of writes, and only if the dominance-frontier block does not already - * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is - * an internal implementation detail that is not exposed. - * - * The motivation for adding phi-reads is to improve performance of the use-use - * calculation in cases where there is a large number of reads that can reach the - * same join-point, and from there reach a large number of basic blocks. Example: - * - * ```cs - * if (a) - * use(x); - * else if (b) - * use(x); - * else if (c) - * use(x); - * else if (d) - * use(x); - * // many more ifs ... - * - * // phi-read for `x` inserted here - * - * // program not mentioning `x`, with large basic block graph - * - * use(x); - * ``` - * - * Without phi-reads, the analysis has to replicate reachability for each of - * the guarded uses of `x`. However, with phi-reads, the analysis will limit - * each conditional use of `x` to reach the basic block containing the phi-read - * node for `x`, and only that basic block will have to compute reachability - * through the remainder of the large program. - * - * Like normal reads, each phi-read node `phi-read` can be reached from exactly - * one SSA definition (without passing through another definition): Assume, for - * the sake of contradiction, that there are two reaching definitions `def1` and - * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest - * dominating definition will prevent the other from reaching `phi-read`. So, at - * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. - * Then `def1` must go through one of its dominance-frontier blocks in order to - * reach `phi-read`. However, such a block will always start with a (normal) phi - * node, which contradicts reachability. - * - * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, - * will dominate `phi-read`. Assuming it doesn't means that the path from `def` - * to `phi-read` goes through a dominance-frontier block, and hence a phi node, - * which contradicts reachability. - */ - pragma[nomagic] - predicate phiRead(BasicBlock bb, SourceVariable v) { - inReadDominanceFrontier(bb, v) and - liveAtEntry(bb, v) and - // only if there are no other references to `v` inside `bb` - not ref(bb, _, v, _) and - not exists(Definition def | def.definesAt(v, bb, _)) - } - - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v`, - * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` - * is `SsaDef()`). - * - * Unlike `Liveness::ref`, this includes `phi` nodes. - */ - pragma[nomagic] - predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - variableRead(bb, i, v, _) and - k = SsaActualRead() - or - phiRead(bb, v) and - i = -1 and - k = SsaPhiRead() - or - any(Definition def).definesAt(v, bb, i) and - k = SsaDef() - } - - private newtype OrderedSsaRefIndex = - MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } - - private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { - ssaRef(bb, i, v, k) and - result = MkOrderedSsaRefIndex(i, k) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic - * block `bb`, which has the given reference kind `k`. - * - * For example, if `bb` is a basic block with a phi node for `v` (considered - * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: - * - * ```ql - * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node - * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 - * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 - * ``` - * - * Reads are considered before writes when they happen at the same index. - */ - int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - ssaRefOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedSsaRefIndex res | - res = ssaRefOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } - - int maxSsaRefRank(BasicBlock bb, SourceVariable v) { - result = ssaRefRank(bb, _, v, _) and - not result + 1 = ssaRefRank(bb, _, v, _) - } - - /** - * Holds if the SSA definition `def` reaches rank index `rnk` in its own - * basic block `bb`. - */ - predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { - exists(int i | - rnk = ssaRefRank(bb, i, v, SsaDef()) and - def.definesAt(v, bb, i) - ) - or - ssaDefReachesRank(bb, def, rnk - 1, v) and - rnk = ssaRefRank(bb, _, v, any(SsaRead k)) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches index `i` in the same - * basic block `bb`, without crossing another SSA definition of `v`. - */ - predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { - exists(int rnk | - ssaDefReachesRank(bb, def, rnk, v) and - rnk = ssaRefRank(bb, i, v, any(SsaRead k)) - ) - } - - /** - * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. - */ - int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { - v = def.getSourceVariable() and - result = ssaRefRank(bb, i, v, k) and - ( - ssaDefReachesRead(_, def, bb, i) - or - def.definesAt(_, bb, i) - ) - } - - /** - * Holds if the reference to `def` at index `i` in basic block `bb` is the - * last reference to `v` inside `bb`. - */ - pragma[noinline] - predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) - } - - predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { - exists(ssaDefRank(def, v, bb, _, k)) - } - - pragma[noinline] - private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { - ssaDefReachesEndOfBlock(bb, def, _) and - not defOccursInBlock(_, bb, def.getSourceVariable(), _) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ - * predecessor of `bb2`, and the underlying variable for `def` is neither read - * nor written in any block on the path between `bb1` and `bb2`. - * - * Phi reads are considered as normal reads for this predicate. - */ - pragma[nomagic] - private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - defOccursInBlock(def, bb1, _, _) and - bb2 = getABasicBlockSuccessor(bb1) - or - exists(BasicBlock mid | - varBlockReachesInclPhiRead(def, bb1, mid) and - ssaDefReachesThroughBlock(def, mid) and - bb2 = getABasicBlockSuccessor(mid) - ) - } - - pragma[nomagic] - private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(def, bb1, bb2) and - defOccursInBlock(def, bb2, v, SsaPhiRead()) - } - - pragma[nomagic] - private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and - ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) - or - exists(BasicBlock mid | - varBlockReachesExclPhiRead(def, mid, bb2) and - phiReadStep(def, _, bb1, mid) - ) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * the underlying variable `v` of `def` is accessed in basic block `bb2` - * (either a read or a write), `bb2` is a transitive successor of `bb1`, and - * `v` is neither read nor written in any block on the path between `bb1` - * and `bb2`. - */ - pragma[nomagic] - predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesExclPhiRead(def, bb1, bb2) and - not defOccursInBlock(def, bb1, _, SsaPhiRead()) - } - - pragma[nomagic] - predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { - varBlockReaches(def, bb1, bb2) and - ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 - } - - /** - * Holds if `def` is accessed in basic block `bb` (either a read or a write), - * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, - * and `v` is neither read nor written in any block on the path between `bb` - * and `bb2`. - */ - pragma[nomagic] - predicate varBlockReachesExit(Definition def, BasicBlock bb) { - exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | - not defOccursInBlock(def, bb2, _, _) and - not ssaDefReachesEndOfBlock(bb2, def, _) - ) - or - exists(BasicBlock mid | - varBlockReachesExit(def, mid) and - phiReadStep(def, _, bb, mid) - ) - } -} - -predicate phiReadExposedForTesting = phiRead/2; - -private import SsaDefReaches - -pragma[nomagic] -predicate liveThrough(BasicBlock bb, SourceVariable v) { - liveAtExit(bb, v) and - not ssaRef(bb, _, v, SsaDef()) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches the end of basic - * block `bb`, at which point it is still live, without crossing another - * SSA definition of `v`. - */ -pragma[nomagic] -predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { - exists(int last | - last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and - ssaDefReachesRank(bb, def, last, v) and - liveAtExit(bb, v) - ) - or - // The construction of SSA form ensures that each read of a variable is - // dominated by its definition. An SSA definition therefore reaches a - // control flow node if it is the _closest_ SSA definition that dominates - // the node. If two definitions dominate a node then one must dominate the - // other, so therefore the definition of _closest_ is given by the dominator - // tree. Thus, reaching definitions can be calculated in terms of dominance. - ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and - liveThrough(bb, pragma[only_bind_into](v)) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. - */ -pragma[nomagic] -predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { - exists(SourceVariable v, BasicBlock bbDef | - phi.definesAt(v, bbDef, _) and - getABasicBlockPredecessor(bbDef) = bb and - ssaDefReachesEndOfBlock(bb, inp, v) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in - * basic block `bb`, without crossing another SSA definition of `v`. The read - * is of kind `rk`. - */ -pragma[nomagic] -predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { - ssaDefReachesReadWithinBlock(v, def, bb, i) - or - ssaRef(bb, i, v, any(SsaRead k)) and - ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and - not ssaDefReachesReadWithinBlock(v, _, bb, i) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read - * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a - * path between them without any read of `def`. - */ -pragma[nomagic] -predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - exists(int rnk | - rnk = ssaDefRank(def, _, bb1, i1, _) and - rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and - variableRead(bb1, i2, _, _) and - bb2 = bb1 - ) - or - lastSsaRef(def, _, bb1, i1) and - defAdjacentRead(def, bb1, bb2, i2) -} - -pragma[noinline] -private predicate adjacentDefRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v -) { - adjacentDefRead(def, bb1, i1, bb2, i2) and - v = def.getSourceVariable() -} - -private predicate adjacentDefReachesRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | - ssaRef(bb1, i1, v, SsaDef()) - or - variableRead(bb1, i1, v, true) - ) - or - exists(BasicBlock bb3, int i3 | - adjacentDefReachesRead(def, bb1, i1, bb3, i3) and - variableRead(bb3, i3, _, false) and - adjacentDefRead(def, bb3, i3, bb2, i2) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `adjacentDefRead`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, true) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA definition - * `def`. The reference is last because it can reach another write `next`, - * without passing through another read or write. - */ -pragma[nomagic] -predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { - exists(SourceVariable v | - // Next reference to `v` inside `bb` is a write - exists(int rnk, int j | - rnk = ssaDefRank(def, v, bb, i, _) and - next.definesAt(v, bb, j) and - rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) - ) - or - // Can reach a write using one or more steps - lastSsaRef(def, v, bb, i) and - exists(BasicBlock bb2 | - varBlockReaches(def, bb, bb2) and - 1 = ssaDefRank(next, v, bb2, _, SsaDef()) - ) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an immediately preceding definition of uncertain definition - * `def`. Since `def` is uncertain, the value from the preceding definition might - * still be valid. - */ -pragma[nomagic] -predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { - lastRefRedef(inp, _, _, def) -} - -private predicate adjacentDefReachesUncertainRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, false) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { - lastRefRedef(def, bb, i, next) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRefRedef(def, bb0, i0, next) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA - * definition `def`. - * - * That is, the node can reach the end of the enclosing callable, or another - * SSA definition for the underlying source variable, without passing through - * another read. - */ -pragma[nomagic] -predicate lastRef(Definition def, BasicBlock bb, int i) { - // Can reach another definition - lastRefRedef(def, bb, i, _) - or - exists(SourceVariable v | lastSsaRef(def, v, bb, i) | - // Can reach exit directly - bb instanceof ExitBasicBlock - or - // Can reach a block using one or more steps, where `def` is no longer live - varBlockReachesExit(def, bb) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { - lastRef(def, bb, i) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRef(def, bb0, i0) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** A static single assignment (SSA) definition. */ -class Definition extends TDefinition { - /** Gets the source variable underlying this SSA definition. */ - SourceVariable getSourceVariable() { this.definesAt(result, _, _) } - - /** - * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. - * Phi nodes are considered to be at index `-1`, while normal variable writes - * are at the index of the control flow node they wrap. - */ - final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { - this = TWriteDef(v, bb, i) - or - this = TPhiNode(v, bb) and i = -1 - } - - /** Gets the basic block to which this SSA definition belongs. */ - final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } - - /** Gets a textual representation of this SSA definition. */ - string toString() { none() } -} - -/** An SSA definition that corresponds to a write. */ -class WriteDefinition extends Definition, TWriteDef { - private SourceVariable v; - private BasicBlock bb; - private int i; - - WriteDefinition() { this = TWriteDef(v, bb, i) } - - override string toString() { result = "WriteDef" } -} - -/** A phi node. */ -class PhiNode extends Definition, TPhiNode { - override string toString() { result = "Phi" } -} - -/** - * An SSA definition that represents an uncertain update of the underlying - * source variable. - */ -class UncertainWriteDefinition extends WriteDefinition { - UncertainWriteDefinition() { - exists(SourceVariable v, BasicBlock bb, int i | - this.definesAt(v, bb, i) and - variableWrite(bb, i, v, false) - ) - } -} - -/** Provides a set of consistency queries. */ -module Consistency { - abstract class RelevantDefinition extends Definition { - abstract predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ); - } - - query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefReachesRead(v, def, bb, i) and - not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) - } - - query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { - variableRead(bb, i, v, _) and - not ssaDefReachesRead(v, _, bb, i) - } - - query predicate deadDef(RelevantDefinition def, SourceVariable v) { - v = def.getSourceVariable() and - not ssaDefReachesRead(_, def, _, _) and - not phiHasInputFromBlock(_, def, _) and - not uncertainWriteDefinitionInput(_, def) - } - - query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | - ssaDefReachesReadWithinBlock(v, def, bb, i) and - (bb != bbDef or i < iDef) - or - ssaDefReachesRead(v, def, bb, i) and - not ssaDefReachesReadWithinBlock(v, def, bb, i) and - not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) - ) - } -} diff --git a/csharp/ql/lib/semmle/code/cil/internal/SsaImplSpecific.qll b/csharp/ql/lib/semmle/code/cil/internal/SsaImplSpecific.qll deleted file mode 100644 index 94b4812d996..00000000000 --- a/csharp/ql/lib/semmle/code/cil/internal/SsaImplSpecific.qll +++ /dev/null @@ -1,30 +0,0 @@ -/** Provides the CIL specific parameters for `SsaImplCommon.qll`. */ - -private import cil -private import SsaImpl - -class BasicBlock = CIL::BasicBlock; - -BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } - -BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } - -class ExitBasicBlock = CIL::ExitBasicBlock; - -class SourceVariable = CIL::StackVariable; - -predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { - forceCachingInSameStage() and - exists(CIL::VariableUpdate vu | - vu.updatesAt(bb, i) and - v = vu.getVariable() and - certain = true - ) -} - -predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { - exists(CIL::ReadAccess ra | bb.getNode(i) = ra | - ra.getTarget() = v and - certain = true - ) -} From 8725bf06202d560a523e56a3b5779a7fef16ac72 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 29 Aug 2022 12:49:56 +0200 Subject: [PATCH 3/8] C#: Update Base SSA library to use parameterized module --- config/identical-files.json | 1 - .../code/csharp/dataflow/internal/BaseSSA.qll | 63 +- .../internal/basessa/SsaImplCommon.qll | 795 ------------------ .../internal/basessa/SsaImplSpecific.qll | 51 -- 4 files changed, 59 insertions(+), 851 deletions(-) delete mode 100644 csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll delete mode 100644 csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplSpecific.qll diff --git a/config/identical-files.json b/config/identical-files.json index 8789b860427..05129081bbe 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -463,7 +463,6 @@ "SSA C#": [ "csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll", "csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll", - "csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll", "swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll" diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/BaseSSA.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/BaseSSA.qll index 281523a2fbd..87b5c759724 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/BaseSSA.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/BaseSSA.qll @@ -4,19 +4,74 @@ import csharp * Provides a simple SSA implementation for local scope variables. */ module BaseSsa { - import basessa.SsaImplSpecific - private import basessa.SsaImplCommon as SsaImpl + private import AssignableDefinitions + private import semmle.code.csharp.dataflow.internal.SsaImplCommon as SsaImplCommon + + /** + * Holds if the `i`th node of basic block `bb` is assignable definition `def`, + * targeting local scope variable `v`. + */ + private predicate definitionAt( + AssignableDefinition def, ControlFlow::BasicBlock bb, int i, SsaInput::SourceVariable v + ) { + bb.getNode(i) = def.getAControlFlowNode() and + v = def.getTarget() and + // In cases like `(x, x) = (0, 1)`, we discard the first (dead) definition of `x` + not exists(TupleAssignmentDefinition first, TupleAssignmentDefinition second | first = def | + second.getAssignment() = first.getAssignment() and + second.getEvaluationOrder() > first.getEvaluationOrder() and + second.getTarget() = v + ) + } + + private module SsaInput implements SsaImplCommon::InputSig { + class BasicBlock = ControlFlow::BasicBlock; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { + result = bb.getImmediateDominator() + } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + + class ExitBasicBlock = ControlFlow::BasicBlocks::ExitBlock; + + pragma[noinline] + private Callable getAnAssigningCallable(LocalScopeVariable v) { + result = any(AssignableDefinition def | def.getTarget() = v).getEnclosingCallable() + } + + class SourceVariable extends LocalScopeVariable { + SourceVariable() { not getAnAssigningCallable(this) != getAnAssigningCallable(this) } + } + + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(AssignableDefinition def | + definitionAt(def, bb, i, v) and + if def.isCertain() then certain = true else certain = false + ) + } + + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(AssignableRead read | + read.getAControlFlowNode() = bb.getNode(i) and + read.getTarget() = v and + certain = true + ) + } + } + + private module SsaImpl = SsaImplCommon::Make; class Definition extends SsaImpl::Definition { final AssignableRead getARead() { - exists(BasicBlock bb, int i | + exists(ControlFlow::BasicBlock bb, int i | SsaImpl::ssaDefReachesRead(_, this, bb, i) and result.getAControlFlowNode() = bb.getNode(i) ) } final AssignableDefinition getDefinition() { - exists(BasicBlock bb, int i, SourceVariable v | + exists(ControlFlow::BasicBlock bb, int i, SsaInput::SourceVariable v | this.definesAt(v, bb, i) and definitionAt(result, bb, i, v) ) diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll deleted file mode 100644 index 659940def50..00000000000 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll +++ /dev/null @@ -1,795 +0,0 @@ -/** - * Provides a language-independent implementation of static single assignment - * (SSA) form. - */ - -private import SsaImplSpecific - -private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } - -/** - * Liveness analysis (based on source variables) to restrict the size of the - * SSA representation. - */ -private module Liveness { - /** - * A classification of variable references into reads (of a given kind) and - * (certain or uncertain) writes. - */ - private newtype TRefKind = - Read(boolean certain) { certain in [false, true] } or - Write(boolean certain) { certain in [false, true] } - - private class RefKind extends TRefKind { - string toString() { - exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") - or - exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") - } - - int getOrder() { - this = Read(_) and - result = 0 - or - this = Write(_) and - result = 1 - } - } - - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. - */ - predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { - exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) - or - exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) - } - - private newtype OrderedRefIndex = - MkOrderedRefIndex(int i, int tag) { - exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) - } - - private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { - ref(bb, i, v, k) and - result = MkOrderedRefIndex(i, ord) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of - * basic block `bb`, which has the given reference kind `k`. - * - * Reads are considered before writes when they happen at the same index. - */ - private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { - refOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedRefIndex res | - res = refOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } - - private int maxRefRank(BasicBlock bb, SourceVariable v) { - result = refRank(bb, _, v, _) and - not result + 1 = refRank(bb, _, v, _) - } - - predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { - maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) - } - - /** - * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` - * that is either a read or a certain write. - */ - private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { - result = - min(int r, RefKind k | - r = refRank(bb, _, v, k) and - k != Write(false) - | - r - ) - } - - /** - * Holds if source variable `v` is live at the beginning of basic block `bb`. - */ - predicate liveAtEntry(BasicBlock bb, SourceVariable v) { - // The first read or certain write to `v` inside `bb` is a read - refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) - or - // There is no certain write to `v` inside `bb`, but `v` is live at entry - // to a successor basic block of `bb` - not exists(firstReadOrCertainWrite(bb, v)) and - liveAtExit(bb, v) - } - - /** - * Holds if source variable `v` is live at the end of basic block `bb`. - */ - predicate liveAtExit(BasicBlock bb, SourceVariable v) { - liveAtEntry(getABasicBlockSuccessor(bb), v) - } - - /** - * Holds if variable `v` is live in basic block `bb` at index `i`. - * The rank of `i` is `rnk` as defined by `refRank()`. - */ - private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { - exists(RefKind kind | rnk = refRank(bb, i, v, kind) | - rnk = maxRefRank(bb, v) and - liveAtExit(bb, v) - or - ref(bb, i, v, kind) and - kind = Read(_) - or - exists(RefKind nextKind | - liveAtRank(bb, _, v, rnk + 1) and - rnk + 1 = refRank(bb, _, v, nextKind) and - nextKind != Write(true) - ) - ) - } - - /** - * Holds if variable `v` is live after the (certain or uncertain) write at - * index `i` inside basic block `bb`. - */ - predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { - exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) - } -} - -private import Liveness - -/** - * Holds if `df` is in the dominance frontier of `bb`. - * - * This is equivalent to: - * - * ```ql - * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and - * not bb = getImmediateBasicBlockDominator+(df) - * ``` - */ -private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { - bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) - or - exists(BasicBlock prev | inDominanceFrontier(prev, df) | - bb = getImmediateBasicBlockDominator(prev) and - not bb = getImmediateBasicBlockDominator(df) - ) -} - -/** - * Holds if `bb` is in the dominance frontier of a block containing a - * definition of `v`. - */ -pragma[noinline] -private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock defbb, Definition def | - def.definesAt(v, defbb, _) and - inDominanceFrontier(defbb, bb) - ) -} - -cached -newtype TDefinition = - TWriteDef(SourceVariable v, BasicBlock bb, int i) { - variableWrite(bb, i, v, _) and - liveAfterWrite(bb, i, v) - } or - TPhiNode(SourceVariable v, BasicBlock bb) { - inDefDominanceFrontier(bb, v) and - liveAtEntry(bb, v) - } - -private module SsaDefReaches { - newtype TSsaRefKind = - SsaActualRead() or - SsaPhiRead() or - SsaDef() - - class SsaRead = SsaActualRead or SsaPhiRead; - - /** - * A classification of SSA variable references into reads and definitions. - */ - class SsaRefKind extends TSsaRefKind { - string toString() { - this = SsaActualRead() and - result = "SsaActualRead" - or - this = SsaPhiRead() and - result = "SsaPhiRead" - or - this = SsaDef() and - result = "SsaDef" - } - - int getOrder() { - this instanceof SsaRead and - result = 0 - or - this = SsaDef() and - result = 1 - } - } - - /** - * Holds if `bb` is in the dominance frontier of a block containing a - * read of `v`. - */ - pragma[nomagic] - private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | - lastRefIsRead(readbb, v) - or - phiRead(readbb, v) - ) - } - - /** - * Holds if a phi-read node should be inserted for variable `v` at the beginning - * of basic block `bb`. - * - * Phi-read nodes are like normal phi nodes, but they are inserted based on reads - * instead of writes, and only if the dominance-frontier block does not already - * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is - * an internal implementation detail that is not exposed. - * - * The motivation for adding phi-reads is to improve performance of the use-use - * calculation in cases where there is a large number of reads that can reach the - * same join-point, and from there reach a large number of basic blocks. Example: - * - * ```cs - * if (a) - * use(x); - * else if (b) - * use(x); - * else if (c) - * use(x); - * else if (d) - * use(x); - * // many more ifs ... - * - * // phi-read for `x` inserted here - * - * // program not mentioning `x`, with large basic block graph - * - * use(x); - * ``` - * - * Without phi-reads, the analysis has to replicate reachability for each of - * the guarded uses of `x`. However, with phi-reads, the analysis will limit - * each conditional use of `x` to reach the basic block containing the phi-read - * node for `x`, and only that basic block will have to compute reachability - * through the remainder of the large program. - * - * Like normal reads, each phi-read node `phi-read` can be reached from exactly - * one SSA definition (without passing through another definition): Assume, for - * the sake of contradiction, that there are two reaching definitions `def1` and - * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest - * dominating definition will prevent the other from reaching `phi-read`. So, at - * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. - * Then `def1` must go through one of its dominance-frontier blocks in order to - * reach `phi-read`. However, such a block will always start with a (normal) phi - * node, which contradicts reachability. - * - * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, - * will dominate `phi-read`. Assuming it doesn't means that the path from `def` - * to `phi-read` goes through a dominance-frontier block, and hence a phi node, - * which contradicts reachability. - */ - pragma[nomagic] - predicate phiRead(BasicBlock bb, SourceVariable v) { - inReadDominanceFrontier(bb, v) and - liveAtEntry(bb, v) and - // only if there are no other references to `v` inside `bb` - not ref(bb, _, v, _) and - not exists(Definition def | def.definesAt(v, bb, _)) - } - - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v`, - * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` - * is `SsaDef()`). - * - * Unlike `Liveness::ref`, this includes `phi` nodes. - */ - pragma[nomagic] - predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - variableRead(bb, i, v, _) and - k = SsaActualRead() - or - phiRead(bb, v) and - i = -1 and - k = SsaPhiRead() - or - any(Definition def).definesAt(v, bb, i) and - k = SsaDef() - } - - private newtype OrderedSsaRefIndex = - MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } - - private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { - ssaRef(bb, i, v, k) and - result = MkOrderedSsaRefIndex(i, k) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic - * block `bb`, which has the given reference kind `k`. - * - * For example, if `bb` is a basic block with a phi node for `v` (considered - * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: - * - * ```ql - * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node - * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 - * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 - * ``` - * - * Reads are considered before writes when they happen at the same index. - */ - int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - ssaRefOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedSsaRefIndex res | - res = ssaRefOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } - - int maxSsaRefRank(BasicBlock bb, SourceVariable v) { - result = ssaRefRank(bb, _, v, _) and - not result + 1 = ssaRefRank(bb, _, v, _) - } - - /** - * Holds if the SSA definition `def` reaches rank index `rnk` in its own - * basic block `bb`. - */ - predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { - exists(int i | - rnk = ssaRefRank(bb, i, v, SsaDef()) and - def.definesAt(v, bb, i) - ) - or - ssaDefReachesRank(bb, def, rnk - 1, v) and - rnk = ssaRefRank(bb, _, v, any(SsaRead k)) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches index `i` in the same - * basic block `bb`, without crossing another SSA definition of `v`. - */ - predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { - exists(int rnk | - ssaDefReachesRank(bb, def, rnk, v) and - rnk = ssaRefRank(bb, i, v, any(SsaRead k)) - ) - } - - /** - * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. - */ - int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { - v = def.getSourceVariable() and - result = ssaRefRank(bb, i, v, k) and - ( - ssaDefReachesRead(_, def, bb, i) - or - def.definesAt(_, bb, i) - ) - } - - /** - * Holds if the reference to `def` at index `i` in basic block `bb` is the - * last reference to `v` inside `bb`. - */ - pragma[noinline] - predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) - } - - predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { - exists(ssaDefRank(def, v, bb, _, k)) - } - - pragma[noinline] - private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { - ssaDefReachesEndOfBlock(bb, def, _) and - not defOccursInBlock(_, bb, def.getSourceVariable(), _) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ - * predecessor of `bb2`, and the underlying variable for `def` is neither read - * nor written in any block on the path between `bb1` and `bb2`. - * - * Phi reads are considered as normal reads for this predicate. - */ - pragma[nomagic] - private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - defOccursInBlock(def, bb1, _, _) and - bb2 = getABasicBlockSuccessor(bb1) - or - exists(BasicBlock mid | - varBlockReachesInclPhiRead(def, bb1, mid) and - ssaDefReachesThroughBlock(def, mid) and - bb2 = getABasicBlockSuccessor(mid) - ) - } - - pragma[nomagic] - private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(def, bb1, bb2) and - defOccursInBlock(def, bb2, v, SsaPhiRead()) - } - - pragma[nomagic] - private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and - ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) - or - exists(BasicBlock mid | - varBlockReachesExclPhiRead(def, mid, bb2) and - phiReadStep(def, _, bb1, mid) - ) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * the underlying variable `v` of `def` is accessed in basic block `bb2` - * (either a read or a write), `bb2` is a transitive successor of `bb1`, and - * `v` is neither read nor written in any block on the path between `bb1` - * and `bb2`. - */ - pragma[nomagic] - predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesExclPhiRead(def, bb1, bb2) and - not defOccursInBlock(def, bb1, _, SsaPhiRead()) - } - - pragma[nomagic] - predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { - varBlockReaches(def, bb1, bb2) and - ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 - } - - /** - * Holds if `def` is accessed in basic block `bb` (either a read or a write), - * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, - * and `v` is neither read nor written in any block on the path between `bb` - * and `bb2`. - */ - pragma[nomagic] - predicate varBlockReachesExit(Definition def, BasicBlock bb) { - exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | - not defOccursInBlock(def, bb2, _, _) and - not ssaDefReachesEndOfBlock(bb2, def, _) - ) - or - exists(BasicBlock mid | - varBlockReachesExit(def, mid) and - phiReadStep(def, _, bb, mid) - ) - } -} - -predicate phiReadExposedForTesting = phiRead/2; - -private import SsaDefReaches - -pragma[nomagic] -predicate liveThrough(BasicBlock bb, SourceVariable v) { - liveAtExit(bb, v) and - not ssaRef(bb, _, v, SsaDef()) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches the end of basic - * block `bb`, at which point it is still live, without crossing another - * SSA definition of `v`. - */ -pragma[nomagic] -predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { - exists(int last | - last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and - ssaDefReachesRank(bb, def, last, v) and - liveAtExit(bb, v) - ) - or - // The construction of SSA form ensures that each read of a variable is - // dominated by its definition. An SSA definition therefore reaches a - // control flow node if it is the _closest_ SSA definition that dominates - // the node. If two definitions dominate a node then one must dominate the - // other, so therefore the definition of _closest_ is given by the dominator - // tree. Thus, reaching definitions can be calculated in terms of dominance. - ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and - liveThrough(bb, pragma[only_bind_into](v)) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. - */ -pragma[nomagic] -predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { - exists(SourceVariable v, BasicBlock bbDef | - phi.definesAt(v, bbDef, _) and - getABasicBlockPredecessor(bbDef) = bb and - ssaDefReachesEndOfBlock(bb, inp, v) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in - * basic block `bb`, without crossing another SSA definition of `v`. The read - * is of kind `rk`. - */ -pragma[nomagic] -predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { - ssaDefReachesReadWithinBlock(v, def, bb, i) - or - ssaRef(bb, i, v, any(SsaRead k)) and - ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and - not ssaDefReachesReadWithinBlock(v, _, bb, i) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read - * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a - * path between them without any read of `def`. - */ -pragma[nomagic] -predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - exists(int rnk | - rnk = ssaDefRank(def, _, bb1, i1, _) and - rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and - variableRead(bb1, i2, _, _) and - bb2 = bb1 - ) - or - lastSsaRef(def, _, bb1, i1) and - defAdjacentRead(def, bb1, bb2, i2) -} - -pragma[noinline] -private predicate adjacentDefRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v -) { - adjacentDefRead(def, bb1, i1, bb2, i2) and - v = def.getSourceVariable() -} - -private predicate adjacentDefReachesRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | - ssaRef(bb1, i1, v, SsaDef()) - or - variableRead(bb1, i1, v, true) - ) - or - exists(BasicBlock bb3, int i3 | - adjacentDefReachesRead(def, bb1, i1, bb3, i3) and - variableRead(bb3, i3, _, false) and - adjacentDefRead(def, bb3, i3, bb2, i2) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `adjacentDefRead`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, true) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA definition - * `def`. The reference is last because it can reach another write `next`, - * without passing through another read or write. - */ -pragma[nomagic] -predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { - exists(SourceVariable v | - // Next reference to `v` inside `bb` is a write - exists(int rnk, int j | - rnk = ssaDefRank(def, v, bb, i, _) and - next.definesAt(v, bb, j) and - rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) - ) - or - // Can reach a write using one or more steps - lastSsaRef(def, v, bb, i) and - exists(BasicBlock bb2 | - varBlockReaches(def, bb, bb2) and - 1 = ssaDefRank(next, v, bb2, _, SsaDef()) - ) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an immediately preceding definition of uncertain definition - * `def`. Since `def` is uncertain, the value from the preceding definition might - * still be valid. - */ -pragma[nomagic] -predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { - lastRefRedef(inp, _, _, def) -} - -private predicate adjacentDefReachesUncertainRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, false) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { - lastRefRedef(def, bb, i, next) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRefRedef(def, bb0, i0, next) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA - * definition `def`. - * - * That is, the node can reach the end of the enclosing callable, or another - * SSA definition for the underlying source variable, without passing through - * another read. - */ -pragma[nomagic] -predicate lastRef(Definition def, BasicBlock bb, int i) { - // Can reach another definition - lastRefRedef(def, bb, i, _) - or - exists(SourceVariable v | lastSsaRef(def, v, bb, i) | - // Can reach exit directly - bb instanceof ExitBasicBlock - or - // Can reach a block using one or more steps, where `def` is no longer live - varBlockReachesExit(def, bb) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { - lastRef(def, bb, i) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRef(def, bb0, i0) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** A static single assignment (SSA) definition. */ -class Definition extends TDefinition { - /** Gets the source variable underlying this SSA definition. */ - SourceVariable getSourceVariable() { this.definesAt(result, _, _) } - - /** - * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. - * Phi nodes are considered to be at index `-1`, while normal variable writes - * are at the index of the control flow node they wrap. - */ - final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { - this = TWriteDef(v, bb, i) - or - this = TPhiNode(v, bb) and i = -1 - } - - /** Gets the basic block to which this SSA definition belongs. */ - final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } - - /** Gets a textual representation of this SSA definition. */ - string toString() { none() } -} - -/** An SSA definition that corresponds to a write. */ -class WriteDefinition extends Definition, TWriteDef { - private SourceVariable v; - private BasicBlock bb; - private int i; - - WriteDefinition() { this = TWriteDef(v, bb, i) } - - override string toString() { result = "WriteDef" } -} - -/** A phi node. */ -class PhiNode extends Definition, TPhiNode { - override string toString() { result = "Phi" } -} - -/** - * An SSA definition that represents an uncertain update of the underlying - * source variable. - */ -class UncertainWriteDefinition extends WriteDefinition { - UncertainWriteDefinition() { - exists(SourceVariable v, BasicBlock bb, int i | - this.definesAt(v, bb, i) and - variableWrite(bb, i, v, false) - ) - } -} - -/** Provides a set of consistency queries. */ -module Consistency { - abstract class RelevantDefinition extends Definition { - abstract predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ); - } - - query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefReachesRead(v, def, bb, i) and - not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) - } - - query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { - variableRead(bb, i, v, _) and - not ssaDefReachesRead(v, _, bb, i) - } - - query predicate deadDef(RelevantDefinition def, SourceVariable v) { - v = def.getSourceVariable() and - not ssaDefReachesRead(_, def, _, _) and - not phiHasInputFromBlock(_, def, _) and - not uncertainWriteDefinitionInput(_, def) - } - - query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | - ssaDefReachesReadWithinBlock(v, def, bb, i) and - (bb != bbDef or i < iDef) - or - ssaDefReachesRead(v, def, bb, i) and - not ssaDefReachesReadWithinBlock(v, def, bb, i) and - not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) - ) - } -} diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplSpecific.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplSpecific.qll deleted file mode 100644 index f095926e2a4..00000000000 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplSpecific.qll +++ /dev/null @@ -1,51 +0,0 @@ -/** Provides the C# specific parameters for `SsaImplCommon.qll`. */ - -private import csharp -private import AssignableDefinitions - -class BasicBlock = ControlFlow::BasicBlock; - -BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } - -BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } - -class ExitBasicBlock = ControlFlow::BasicBlocks::ExitBlock; - -pragma[noinline] -private Callable getAnAssigningCallable(LocalScopeVariable v) { - result = any(AssignableDefinition def | def.getTarget() = v).getEnclosingCallable() -} - -class SourceVariable extends LocalScopeVariable { - SourceVariable() { not getAnAssigningCallable(this) != getAnAssigningCallable(this) } -} - -/** - * Holds if the `i`th node of basic block `bb` is assignable definition `def`, - * targeting local scope variable `v`. - */ -predicate definitionAt(AssignableDefinition def, BasicBlock bb, int i, SourceVariable v) { - bb.getNode(i) = def.getAControlFlowNode() and - v = def.getTarget() and - // In cases like `(x, x) = (0, 1)`, we discard the first (dead) definition of `x` - not exists(TupleAssignmentDefinition first, TupleAssignmentDefinition second | first = def | - second.getAssignment() = first.getAssignment() and - second.getEvaluationOrder() > first.getEvaluationOrder() and - second.getTarget() = v - ) -} - -predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { - exists(AssignableDefinition def | - definitionAt(def, bb, i, v) and - if def.isCertain() then certain = true else certain = false - ) -} - -predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { - exists(AssignableRead read | - read.getAControlFlowNode() = bb.getNode(i) and - read.getTarget() = v and - certain = true - ) -} From 2681b88035176e56f874431043180f2cf4603ffb Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 29 Aug 2022 13:04:06 +0200 Subject: [PATCH 4/8] C#: Update Pre SSA library to use parameterized module --- config/identical-files.json | 1 - .../csharp/controlflow/internal/PreSsa.qll | 150 +++- .../internal/pressa/SsaImplCommon.qll | 795 ------------------ .../internal/pressa/SsaImplSpecific.qll | 130 --- .../dataflow/ssa/PreSsaConsistency.ql | 13 +- 5 files changed, 150 insertions(+), 939 deletions(-) delete mode 100644 csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll delete mode 100644 csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll diff --git a/config/identical-files.json b/config/identical-files.json index 05129081bbe..8282600733e 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -462,7 +462,6 @@ ], "SSA C#": [ "csharp/ql/lib/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll", - "csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll", "cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll", "swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll" diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreSsa.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreSsa.qll index 5ac313651d7..15cfd083e16 100644 --- a/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreSsa.qll +++ b/csharp/ql/lib/semmle/code/csharp/controlflow/internal/PreSsa.qll @@ -6,26 +6,158 @@ import csharp * scope variables. */ module PreSsa { - import pressa.SsaImplSpecific - private import pressa.SsaImplCommon as SsaImpl + private import AssignableDefinitions + private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl + private import semmle.code.csharp.controlflow.internal.PreBasicBlocks as PreBasicBlocks + private import semmle.code.csharp.dataflow.internal.SsaImplCommon as SsaImplCommon + + private predicate definitionAt( + AssignableDefinition def, SsaInput::BasicBlock bb, int i, SsaInput::SourceVariable v + ) { + bb.getElement(i) = def.getExpr() and + v = def.getTarget() and + // In cases like `(x, x) = (0, 1)`, we discard the first (dead) definition of `x` + not exists(TupleAssignmentDefinition first, TupleAssignmentDefinition second | first = def | + second.getAssignment() = first.getAssignment() and + second.getEvaluationOrder() > first.getEvaluationOrder() and + second.getTarget() = v + ) + or + def.(ImplicitParameterDefinition).getParameter() = v and + exists(Callable c | v = c.getAParameter() | + scopeFirst(c, bb) and + i = -1 + ) + } + + predicate implicitEntryDef(Callable c, SsaInput::BasicBlock bb, SsaInput::SourceVariable v) { + not v instanceof LocalScopeVariable and + c = v.getACallable() and + scopeFirst(c, bb) + } + + module SsaInput implements SsaImplCommon::InputSig { + class BasicBlock = PreBasicBlocks::PreBasicBlock; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result.immediatelyDominates(bb) } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + + class ExitBasicBlock extends BasicBlock { + ExitBasicBlock() { scopeLast(_, this.getLastElement(), _) } + } + + /** Holds if `a` is assigned in non-constructor callable `c`. */ + pragma[nomagic] + private predicate assignableDefinition(Assignable a, Callable c) { + exists(AssignableDefinition def | def.getTarget() = a | + c = def.getEnclosingCallable() and + not c instanceof Constructor + ) + } + + /** Holds if `a` is accessed in callable `c`. */ + pragma[nomagic] + private predicate assignableAccess(Assignable a, Callable c) { + exists(AssignableAccess aa | aa.getTarget() = a | c = aa.getEnclosingCallable()) + } + + pragma[nomagic] + private predicate assignableNoCapturing(Assignable a, Callable c) { + assignableAccess(a, c) and + /* + * The code below is equivalent to + * ```ql + * not exists(Callable other | assignableDefinition(a, other) | other != c) + * ``` + * but it avoids a Cartesian product in the compiler generated antijoin + * predicate. + */ + + ( + not assignableDefinition(a, _) + or + c = unique(Callable c0 | assignableDefinition(a, c0) | c0) + ) + } + + pragma[noinline] + private predicate assignableNoComplexQualifiers(Assignable a) { + forall(QualifiableExpr qe | qe.(AssignableAccess).getTarget() = a | qe.targetIsThisInstance()) + } + + /** + * A simple assignable. Either a local scope variable or a field/property + * that behaves like a local scope variable. + */ + class SourceVariable extends Assignable { + private Callable c; + + SourceVariable() { + ( + this instanceof LocalScopeVariable + or + this = any(Field f | not f.isVolatile()) + or + this = any(TrivialProperty tp | not tp.isOverridableOrImplementable()) + ) and + assignableNoCapturing(this, c) and + assignableNoComplexQualifiers(this) + } + + /** Gets a callable in which this simple assignable can be analyzed. */ + Callable getACallable() { result = c } + } + + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(AssignableDefinition def | + definitionAt(def, bb, i, v) and + if def.getTargetAccess().isRefArgument() then certain = false else certain = true + ) + or + exists(Callable c | + implicitEntryDef(c, bb, v) and + i = -1 and + certain = true + ) + } + + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(AssignableRead read | + read = bb.getElement(i) and + read.getTarget() = v and + certain = true + ) + or + v = + any(LocalScopeVariable lsv | + lsv.getCallable() = bb.(ExitBasicBlock).getEnclosingCallable() and + i = bb.length() and + (lsv.isRef() or v.(Parameter).isOut()) and + certain = false + ) + } + } + + private module SsaImpl = SsaImplCommon::Make; class Definition extends SsaImpl::Definition { final AssignableRead getARead() { - exists(BasicBlock bb, int i | + exists(SsaInput::BasicBlock bb, int i | SsaImpl::ssaDefReachesRead(_, this, bb, i) and result = bb.getElement(i) ) } final AssignableDefinition getDefinition() { - exists(BasicBlock bb, int i, SourceVariable v | + exists(SsaInput::BasicBlock bb, int i, SsaInput::SourceVariable v | this.definesAt(v, bb, i) and definitionAt(result, bb, i, v) ) } final AssignableRead getAFirstRead() { - exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + exists(SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2 | this.definesAt(_, bb1, i1) and SsaImpl::adjacentDefRead(this, bb1, i1, bb2, i2) and result = bb2.getElement(i2) @@ -42,14 +174,14 @@ module PreSsa { not result instanceof PhiNode } - final predicate isLiveAtEndOfBlock(BasicBlock bb) { + final predicate isLiveAtEndOfBlock(SsaInput::BasicBlock bb) { SsaImpl::ssaDefReachesEndOfBlock(bb, this, _) } Location getLocation() { result = this.getDefinition().getLocation() or - exists(Callable c, BasicBlock bb, SourceVariable v | + exists(Callable c, SsaInput::BasicBlock bb, SsaInput::SourceVariable v | this.definesAt(v, bb, -1) and implicitEntryDef(c, bb, v) and result = c.getLocation() @@ -64,9 +196,9 @@ module PreSsa { } predicate adjacentReadPairSameVar(AssignableRead read1, AssignableRead read2) { - exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + exists(SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2 | read1 = bb1.getElement(i1) and - variableRead(bb1, i1, _, true) and + SsaInput::variableRead(bb1, i1, _, true) and SsaImpl::adjacentDefRead(_, bb1, i1, bb2, i2) and read2 = bb2.getElement(i2) ) diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll deleted file mode 100644 index 659940def50..00000000000 --- a/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll +++ /dev/null @@ -1,795 +0,0 @@ -/** - * Provides a language-independent implementation of static single assignment - * (SSA) form. - */ - -private import SsaImplSpecific - -private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } - -/** - * Liveness analysis (based on source variables) to restrict the size of the - * SSA representation. - */ -private module Liveness { - /** - * A classification of variable references into reads (of a given kind) and - * (certain or uncertain) writes. - */ - private newtype TRefKind = - Read(boolean certain) { certain in [false, true] } or - Write(boolean certain) { certain in [false, true] } - - private class RefKind extends TRefKind { - string toString() { - exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") - or - exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") - } - - int getOrder() { - this = Read(_) and - result = 0 - or - this = Write(_) and - result = 1 - } - } - - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. - */ - predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { - exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) - or - exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) - } - - private newtype OrderedRefIndex = - MkOrderedRefIndex(int i, int tag) { - exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) - } - - private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { - ref(bb, i, v, k) and - result = MkOrderedRefIndex(i, ord) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of - * basic block `bb`, which has the given reference kind `k`. - * - * Reads are considered before writes when they happen at the same index. - */ - private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { - refOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedRefIndex res | - res = refOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } - - private int maxRefRank(BasicBlock bb, SourceVariable v) { - result = refRank(bb, _, v, _) and - not result + 1 = refRank(bb, _, v, _) - } - - predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { - maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) - } - - /** - * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` - * that is either a read or a certain write. - */ - private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { - result = - min(int r, RefKind k | - r = refRank(bb, _, v, k) and - k != Write(false) - | - r - ) - } - - /** - * Holds if source variable `v` is live at the beginning of basic block `bb`. - */ - predicate liveAtEntry(BasicBlock bb, SourceVariable v) { - // The first read or certain write to `v` inside `bb` is a read - refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) - or - // There is no certain write to `v` inside `bb`, but `v` is live at entry - // to a successor basic block of `bb` - not exists(firstReadOrCertainWrite(bb, v)) and - liveAtExit(bb, v) - } - - /** - * Holds if source variable `v` is live at the end of basic block `bb`. - */ - predicate liveAtExit(BasicBlock bb, SourceVariable v) { - liveAtEntry(getABasicBlockSuccessor(bb), v) - } - - /** - * Holds if variable `v` is live in basic block `bb` at index `i`. - * The rank of `i` is `rnk` as defined by `refRank()`. - */ - private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { - exists(RefKind kind | rnk = refRank(bb, i, v, kind) | - rnk = maxRefRank(bb, v) and - liveAtExit(bb, v) - or - ref(bb, i, v, kind) and - kind = Read(_) - or - exists(RefKind nextKind | - liveAtRank(bb, _, v, rnk + 1) and - rnk + 1 = refRank(bb, _, v, nextKind) and - nextKind != Write(true) - ) - ) - } - - /** - * Holds if variable `v` is live after the (certain or uncertain) write at - * index `i` inside basic block `bb`. - */ - predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { - exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) - } -} - -private import Liveness - -/** - * Holds if `df` is in the dominance frontier of `bb`. - * - * This is equivalent to: - * - * ```ql - * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and - * not bb = getImmediateBasicBlockDominator+(df) - * ``` - */ -private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { - bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) - or - exists(BasicBlock prev | inDominanceFrontier(prev, df) | - bb = getImmediateBasicBlockDominator(prev) and - not bb = getImmediateBasicBlockDominator(df) - ) -} - -/** - * Holds if `bb` is in the dominance frontier of a block containing a - * definition of `v`. - */ -pragma[noinline] -private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock defbb, Definition def | - def.definesAt(v, defbb, _) and - inDominanceFrontier(defbb, bb) - ) -} - -cached -newtype TDefinition = - TWriteDef(SourceVariable v, BasicBlock bb, int i) { - variableWrite(bb, i, v, _) and - liveAfterWrite(bb, i, v) - } or - TPhiNode(SourceVariable v, BasicBlock bb) { - inDefDominanceFrontier(bb, v) and - liveAtEntry(bb, v) - } - -private module SsaDefReaches { - newtype TSsaRefKind = - SsaActualRead() or - SsaPhiRead() or - SsaDef() - - class SsaRead = SsaActualRead or SsaPhiRead; - - /** - * A classification of SSA variable references into reads and definitions. - */ - class SsaRefKind extends TSsaRefKind { - string toString() { - this = SsaActualRead() and - result = "SsaActualRead" - or - this = SsaPhiRead() and - result = "SsaPhiRead" - or - this = SsaDef() and - result = "SsaDef" - } - - int getOrder() { - this instanceof SsaRead and - result = 0 - or - this = SsaDef() and - result = 1 - } - } - - /** - * Holds if `bb` is in the dominance frontier of a block containing a - * read of `v`. - */ - pragma[nomagic] - private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | - lastRefIsRead(readbb, v) - or - phiRead(readbb, v) - ) - } - - /** - * Holds if a phi-read node should be inserted for variable `v` at the beginning - * of basic block `bb`. - * - * Phi-read nodes are like normal phi nodes, but they are inserted based on reads - * instead of writes, and only if the dominance-frontier block does not already - * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is - * an internal implementation detail that is not exposed. - * - * The motivation for adding phi-reads is to improve performance of the use-use - * calculation in cases where there is a large number of reads that can reach the - * same join-point, and from there reach a large number of basic blocks. Example: - * - * ```cs - * if (a) - * use(x); - * else if (b) - * use(x); - * else if (c) - * use(x); - * else if (d) - * use(x); - * // many more ifs ... - * - * // phi-read for `x` inserted here - * - * // program not mentioning `x`, with large basic block graph - * - * use(x); - * ``` - * - * Without phi-reads, the analysis has to replicate reachability for each of - * the guarded uses of `x`. However, with phi-reads, the analysis will limit - * each conditional use of `x` to reach the basic block containing the phi-read - * node for `x`, and only that basic block will have to compute reachability - * through the remainder of the large program. - * - * Like normal reads, each phi-read node `phi-read` can be reached from exactly - * one SSA definition (without passing through another definition): Assume, for - * the sake of contradiction, that there are two reaching definitions `def1` and - * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest - * dominating definition will prevent the other from reaching `phi-read`. So, at - * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. - * Then `def1` must go through one of its dominance-frontier blocks in order to - * reach `phi-read`. However, such a block will always start with a (normal) phi - * node, which contradicts reachability. - * - * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, - * will dominate `phi-read`. Assuming it doesn't means that the path from `def` - * to `phi-read` goes through a dominance-frontier block, and hence a phi node, - * which contradicts reachability. - */ - pragma[nomagic] - predicate phiRead(BasicBlock bb, SourceVariable v) { - inReadDominanceFrontier(bb, v) and - liveAtEntry(bb, v) and - // only if there are no other references to `v` inside `bb` - not ref(bb, _, v, _) and - not exists(Definition def | def.definesAt(v, bb, _)) - } - - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v`, - * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` - * is `SsaDef()`). - * - * Unlike `Liveness::ref`, this includes `phi` nodes. - */ - pragma[nomagic] - predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - variableRead(bb, i, v, _) and - k = SsaActualRead() - or - phiRead(bb, v) and - i = -1 and - k = SsaPhiRead() - or - any(Definition def).definesAt(v, bb, i) and - k = SsaDef() - } - - private newtype OrderedSsaRefIndex = - MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } - - private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { - ssaRef(bb, i, v, k) and - result = MkOrderedSsaRefIndex(i, k) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic - * block `bb`, which has the given reference kind `k`. - * - * For example, if `bb` is a basic block with a phi node for `v` (considered - * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: - * - * ```ql - * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node - * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 - * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 - * ``` - * - * Reads are considered before writes when they happen at the same index. - */ - int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - ssaRefOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedSsaRefIndex res | - res = ssaRefOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } - - int maxSsaRefRank(BasicBlock bb, SourceVariable v) { - result = ssaRefRank(bb, _, v, _) and - not result + 1 = ssaRefRank(bb, _, v, _) - } - - /** - * Holds if the SSA definition `def` reaches rank index `rnk` in its own - * basic block `bb`. - */ - predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { - exists(int i | - rnk = ssaRefRank(bb, i, v, SsaDef()) and - def.definesAt(v, bb, i) - ) - or - ssaDefReachesRank(bb, def, rnk - 1, v) and - rnk = ssaRefRank(bb, _, v, any(SsaRead k)) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches index `i` in the same - * basic block `bb`, without crossing another SSA definition of `v`. - */ - predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { - exists(int rnk | - ssaDefReachesRank(bb, def, rnk, v) and - rnk = ssaRefRank(bb, i, v, any(SsaRead k)) - ) - } - - /** - * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. - */ - int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { - v = def.getSourceVariable() and - result = ssaRefRank(bb, i, v, k) and - ( - ssaDefReachesRead(_, def, bb, i) - or - def.definesAt(_, bb, i) - ) - } - - /** - * Holds if the reference to `def` at index `i` in basic block `bb` is the - * last reference to `v` inside `bb`. - */ - pragma[noinline] - predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) - } - - predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { - exists(ssaDefRank(def, v, bb, _, k)) - } - - pragma[noinline] - private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { - ssaDefReachesEndOfBlock(bb, def, _) and - not defOccursInBlock(_, bb, def.getSourceVariable(), _) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ - * predecessor of `bb2`, and the underlying variable for `def` is neither read - * nor written in any block on the path between `bb1` and `bb2`. - * - * Phi reads are considered as normal reads for this predicate. - */ - pragma[nomagic] - private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - defOccursInBlock(def, bb1, _, _) and - bb2 = getABasicBlockSuccessor(bb1) - or - exists(BasicBlock mid | - varBlockReachesInclPhiRead(def, bb1, mid) and - ssaDefReachesThroughBlock(def, mid) and - bb2 = getABasicBlockSuccessor(mid) - ) - } - - pragma[nomagic] - private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(def, bb1, bb2) and - defOccursInBlock(def, bb2, v, SsaPhiRead()) - } - - pragma[nomagic] - private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and - ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) - or - exists(BasicBlock mid | - varBlockReachesExclPhiRead(def, mid, bb2) and - phiReadStep(def, _, bb1, mid) - ) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * the underlying variable `v` of `def` is accessed in basic block `bb2` - * (either a read or a write), `bb2` is a transitive successor of `bb1`, and - * `v` is neither read nor written in any block on the path between `bb1` - * and `bb2`. - */ - pragma[nomagic] - predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesExclPhiRead(def, bb1, bb2) and - not defOccursInBlock(def, bb1, _, SsaPhiRead()) - } - - pragma[nomagic] - predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { - varBlockReaches(def, bb1, bb2) and - ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 - } - - /** - * Holds if `def` is accessed in basic block `bb` (either a read or a write), - * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, - * and `v` is neither read nor written in any block on the path between `bb` - * and `bb2`. - */ - pragma[nomagic] - predicate varBlockReachesExit(Definition def, BasicBlock bb) { - exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | - not defOccursInBlock(def, bb2, _, _) and - not ssaDefReachesEndOfBlock(bb2, def, _) - ) - or - exists(BasicBlock mid | - varBlockReachesExit(def, mid) and - phiReadStep(def, _, bb, mid) - ) - } -} - -predicate phiReadExposedForTesting = phiRead/2; - -private import SsaDefReaches - -pragma[nomagic] -predicate liveThrough(BasicBlock bb, SourceVariable v) { - liveAtExit(bb, v) and - not ssaRef(bb, _, v, SsaDef()) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches the end of basic - * block `bb`, at which point it is still live, without crossing another - * SSA definition of `v`. - */ -pragma[nomagic] -predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { - exists(int last | - last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and - ssaDefReachesRank(bb, def, last, v) and - liveAtExit(bb, v) - ) - or - // The construction of SSA form ensures that each read of a variable is - // dominated by its definition. An SSA definition therefore reaches a - // control flow node if it is the _closest_ SSA definition that dominates - // the node. If two definitions dominate a node then one must dominate the - // other, so therefore the definition of _closest_ is given by the dominator - // tree. Thus, reaching definitions can be calculated in terms of dominance. - ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and - liveThrough(bb, pragma[only_bind_into](v)) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. - */ -pragma[nomagic] -predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { - exists(SourceVariable v, BasicBlock bbDef | - phi.definesAt(v, bbDef, _) and - getABasicBlockPredecessor(bbDef) = bb and - ssaDefReachesEndOfBlock(bb, inp, v) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in - * basic block `bb`, without crossing another SSA definition of `v`. The read - * is of kind `rk`. - */ -pragma[nomagic] -predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { - ssaDefReachesReadWithinBlock(v, def, bb, i) - or - ssaRef(bb, i, v, any(SsaRead k)) and - ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and - not ssaDefReachesReadWithinBlock(v, _, bb, i) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read - * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a - * path between them without any read of `def`. - */ -pragma[nomagic] -predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - exists(int rnk | - rnk = ssaDefRank(def, _, bb1, i1, _) and - rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and - variableRead(bb1, i2, _, _) and - bb2 = bb1 - ) - or - lastSsaRef(def, _, bb1, i1) and - defAdjacentRead(def, bb1, bb2, i2) -} - -pragma[noinline] -private predicate adjacentDefRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v -) { - adjacentDefRead(def, bb1, i1, bb2, i2) and - v = def.getSourceVariable() -} - -private predicate adjacentDefReachesRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | - ssaRef(bb1, i1, v, SsaDef()) - or - variableRead(bb1, i1, v, true) - ) - or - exists(BasicBlock bb3, int i3 | - adjacentDefReachesRead(def, bb1, i1, bb3, i3) and - variableRead(bb3, i3, _, false) and - adjacentDefRead(def, bb3, i3, bb2, i2) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `adjacentDefRead`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, true) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA definition - * `def`. The reference is last because it can reach another write `next`, - * without passing through another read or write. - */ -pragma[nomagic] -predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { - exists(SourceVariable v | - // Next reference to `v` inside `bb` is a write - exists(int rnk, int j | - rnk = ssaDefRank(def, v, bb, i, _) and - next.definesAt(v, bb, j) and - rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) - ) - or - // Can reach a write using one or more steps - lastSsaRef(def, v, bb, i) and - exists(BasicBlock bb2 | - varBlockReaches(def, bb, bb2) and - 1 = ssaDefRank(next, v, bb2, _, SsaDef()) - ) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an immediately preceding definition of uncertain definition - * `def`. Since `def` is uncertain, the value from the preceding definition might - * still be valid. - */ -pragma[nomagic] -predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { - lastRefRedef(inp, _, _, def) -} - -private predicate adjacentDefReachesUncertainRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, false) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { - lastRefRedef(def, bb, i, next) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRefRedef(def, bb0, i0, next) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA - * definition `def`. - * - * That is, the node can reach the end of the enclosing callable, or another - * SSA definition for the underlying source variable, without passing through - * another read. - */ -pragma[nomagic] -predicate lastRef(Definition def, BasicBlock bb, int i) { - // Can reach another definition - lastRefRedef(def, bb, i, _) - or - exists(SourceVariable v | lastSsaRef(def, v, bb, i) | - // Can reach exit directly - bb instanceof ExitBasicBlock - or - // Can reach a block using one or more steps, where `def` is no longer live - varBlockReachesExit(def, bb) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { - lastRef(def, bb, i) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRef(def, bb0, i0) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** A static single assignment (SSA) definition. */ -class Definition extends TDefinition { - /** Gets the source variable underlying this SSA definition. */ - SourceVariable getSourceVariable() { this.definesAt(result, _, _) } - - /** - * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. - * Phi nodes are considered to be at index `-1`, while normal variable writes - * are at the index of the control flow node they wrap. - */ - final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { - this = TWriteDef(v, bb, i) - or - this = TPhiNode(v, bb) and i = -1 - } - - /** Gets the basic block to which this SSA definition belongs. */ - final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } - - /** Gets a textual representation of this SSA definition. */ - string toString() { none() } -} - -/** An SSA definition that corresponds to a write. */ -class WriteDefinition extends Definition, TWriteDef { - private SourceVariable v; - private BasicBlock bb; - private int i; - - WriteDefinition() { this = TWriteDef(v, bb, i) } - - override string toString() { result = "WriteDef" } -} - -/** A phi node. */ -class PhiNode extends Definition, TPhiNode { - override string toString() { result = "Phi" } -} - -/** - * An SSA definition that represents an uncertain update of the underlying - * source variable. - */ -class UncertainWriteDefinition extends WriteDefinition { - UncertainWriteDefinition() { - exists(SourceVariable v, BasicBlock bb, int i | - this.definesAt(v, bb, i) and - variableWrite(bb, i, v, false) - ) - } -} - -/** Provides a set of consistency queries. */ -module Consistency { - abstract class RelevantDefinition extends Definition { - abstract predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ); - } - - query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefReachesRead(v, def, bb, i) and - not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) - } - - query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { - variableRead(bb, i, v, _) and - not ssaDefReachesRead(v, _, bb, i) - } - - query predicate deadDef(RelevantDefinition def, SourceVariable v) { - v = def.getSourceVariable() and - not ssaDefReachesRead(_, def, _, _) and - not phiHasInputFromBlock(_, def, _) and - not uncertainWriteDefinitionInput(_, def) - } - - query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | - ssaDefReachesReadWithinBlock(v, def, bb, i) and - (bb != bbDef or i < iDef) - or - ssaDefReachesRead(v, def, bb, i) and - not ssaDefReachesReadWithinBlock(v, def, bb, i) and - not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) - ) - } -} diff --git a/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll b/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll deleted file mode 100644 index ad64c38973a..00000000000 --- a/csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll +++ /dev/null @@ -1,130 +0,0 @@ -/** Provides the C# specific parameters for `SsaImplCommon.qll`. */ - -private import csharp -private import AssignableDefinitions -private import semmle.code.csharp.controlflow.internal.PreBasicBlocks as PreBasicBlocks -private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl - -class BasicBlock = PreBasicBlocks::PreBasicBlock; - -BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result.immediatelyDominates(bb) } - -BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } - -class ExitBasicBlock extends BasicBlock { - ExitBasicBlock() { scopeLast(_, this.getLastElement(), _) } -} - -/** Holds if `a` is assigned in non-constructor callable `c`. */ -pragma[nomagic] -private predicate assignableDefinition(Assignable a, Callable c) { - exists(AssignableDefinition def | def.getTarget() = a | - c = def.getEnclosingCallable() and - not c instanceof Constructor - ) -} - -/** Holds if `a` is accessed in callable `c`. */ -pragma[nomagic] -private predicate assignableAccess(Assignable a, Callable c) { - exists(AssignableAccess aa | aa.getTarget() = a | c = aa.getEnclosingCallable()) -} - -pragma[nomagic] -private predicate assignableNoCapturing(Assignable a, Callable c) { - assignableAccess(a, c) and - /* - * The code below is equivalent to - * ```ql - * not exists(Callable other | assignableDefinition(a, other) | other != c) - * ``` - * but it avoids a Cartesian product in the compiler generated antijoin - * predicate. - */ - - ( - not assignableDefinition(a, _) - or - c = unique(Callable c0 | assignableDefinition(a, c0) | c0) - ) -} - -pragma[noinline] -private predicate assignableNoComplexQualifiers(Assignable a) { - forall(QualifiableExpr qe | qe.(AssignableAccess).getTarget() = a | qe.targetIsThisInstance()) -} - -/** - * A simple assignable. Either a local scope variable or a field/property - * that behaves like a local scope variable. - */ -class SourceVariable extends Assignable { - private Callable c; - - SourceVariable() { - ( - this instanceof LocalScopeVariable - or - this = any(Field f | not f.isVolatile()) - or - this = any(TrivialProperty tp | not tp.isOverridableOrImplementable()) - ) and - assignableNoCapturing(this, c) and - assignableNoComplexQualifiers(this) - } - - /** Gets a callable in which this simple assignable can be analyzed. */ - Callable getACallable() { result = c } -} - -predicate definitionAt(AssignableDefinition def, BasicBlock bb, int i, SourceVariable v) { - bb.getElement(i) = def.getExpr() and - v = def.getTarget() and - // In cases like `(x, x) = (0, 1)`, we discard the first (dead) definition of `x` - not exists(TupleAssignmentDefinition first, TupleAssignmentDefinition second | first = def | - second.getAssignment() = first.getAssignment() and - second.getEvaluationOrder() > first.getEvaluationOrder() and - second.getTarget() = v - ) - or - def.(ImplicitParameterDefinition).getParameter() = v and - exists(Callable c | v = c.getAParameter() | - scopeFirst(c, bb) and - i = -1 - ) -} - -predicate implicitEntryDef(Callable c, BasicBlock bb, SourceVariable v) { - not v instanceof LocalScopeVariable and - c = v.getACallable() and - scopeFirst(c, bb) -} - -predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { - exists(AssignableDefinition def | - definitionAt(def, bb, i, v) and - if def.getTargetAccess().isRefArgument() then certain = false else certain = true - ) - or - exists(Callable c | - implicitEntryDef(c, bb, v) and - i = -1 and - certain = true - ) -} - -predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { - exists(AssignableRead read | - read = bb.getElement(i) and - read.getTarget() = v and - certain = true - ) - or - v = - any(LocalScopeVariable lsv | - lsv.getCallable() = bb.(ExitBasicBlock).getEnclosingCallable() and - i = bb.length() and - (lsv.isRef() or v.(Parameter).isOut()) and - certain = false - ) -} diff --git a/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql b/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql index 849500904a0..5ee4c6e27a9 100644 --- a/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql +++ b/csharp/ql/test/library-tests/dataflow/ssa/PreSsaConsistency.ql @@ -7,7 +7,9 @@ class CallableWithSplitting extends Callable { CallableWithSplitting() { this = any(SplitControlFlowElement e).getEnclosingCallable() } } -query predicate defReadInconsistency(AssignableRead ar, Expr e, PreSsa::SourceVariable v, boolean b) { +query predicate defReadInconsistency( + AssignableRead ar, Expr e, PreSsa::SsaInput::SourceVariable v, boolean b +) { // Exclude definitions in callables with CFG splitting, as SSA definitions may be // very different from pre-SSA definitions not ar.getEnclosingCallable() instanceof CallableWithSplitting and @@ -36,7 +38,8 @@ query predicate defReadInconsistency(AssignableRead ar, Expr e, PreSsa::SourceVa } query predicate readReadInconsistency( - LocalScopeVariableRead read1, LocalScopeVariableRead read2, PreSsa::SourceVariable v, boolean b + LocalScopeVariableRead read1, LocalScopeVariableRead read2, PreSsa::SsaInput::SourceVariable v, + boolean b ) { // Exclude definitions in callables with CFG splitting, as SSA definitions may be // very different from pre-SSA definitions @@ -50,7 +53,7 @@ query predicate readReadInconsistency( b = false and v = read1.getTarget() and SsaImpl::adjacentReadPairSameVar(_, read1.getAControlFlowNode(), read2.getAControlFlowNode()) and - read1.getTarget() instanceof PreSsa::SourceVariable and + read1.getTarget() instanceof PreSsa::SsaInput::SourceVariable and not PreSsa::adjacentReadPairSameVar(read1, read2) and // Exclude split CFG elements because SSA may be more precise than pre-SSA // in those cases @@ -59,7 +62,9 @@ query predicate readReadInconsistency( ) } -query predicate phiInconsistency(ControlFlowElement cfe, Expr e, PreSsa::SourceVariable v, boolean b) { +query predicate phiInconsistency( + ControlFlowElement cfe, Expr e, PreSsa::SsaInput::SourceVariable v, boolean b +) { // Exclude definitions in callables with CFG splitting, as SSA definitions may be // very different from pre-SSA definitions not cfe.getEnclosingCallable() instanceof CallableWithSplitting and From 760c7beb94344ad2bfae10f31e61ddba9d55910b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 29 Aug 2022 13:10:45 +0200 Subject: [PATCH 5/8] SSA: Sync files --- .../ir/dataflow/internal/SsaImplCommon.qll | 1519 +++++++++-------- .../ruby/dataflow/internal/SsaImplCommon.qll | 1519 +++++++++-------- .../swift/dataflow/internal/SsaImplCommon.qll | 1519 +++++++++-------- 3 files changed, 2412 insertions(+), 2145 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll index 659940def50..3c706849415 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll @@ -3,793 +3,882 @@ * (SSA) form. */ -private import SsaImplSpecific - -private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } - -/** - * Liveness analysis (based on source variables) to restrict the size of the - * SSA representation. - */ -private module Liveness { +/** Provides the input specification of the SSA implementation. */ +signature module SsaInputSig { /** - * A classification of variable references into reads (of a given kind) and - * (certain or uncertain) writes. + * A basic block, that is, a maximal straight-line sequence of control flow nodes + * without branches or joins. */ - private newtype TRefKind = - Read(boolean certain) { certain in [false, true] } or - Write(boolean certain) { certain in [false, true] } - - private class RefKind extends TRefKind { - string toString() { - exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") - or - exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") - } - - int getOrder() { - this = Read(_) and - result = 0 - or - this = Write(_) and - result = 1 - } - } + class BasicBlock; /** - * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. - */ - predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { - exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) - or - exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) - } - - private newtype OrderedRefIndex = - MkOrderedRefIndex(int i, int tag) { - exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) - } - - private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { - ref(bb, i, v, k) and - result = MkOrderedRefIndex(i, ord) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of - * basic block `bb`, which has the given reference kind `k`. + * Gets the basic block that immediately dominates basic block `bb`, if any. * - * Reads are considered before writes when they happen at the same index. + * That is, all paths reaching `bb` from some entry point basic block must go + * through the result. + * + * Example: + * + * ```csharp + * int M(string s) { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The basic block starting on line 2 is an immediate dominator of + * the basic block online 4 (all paths from the entry point of `M` + * to `return s.Length;` must go through the null check. */ - private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { - refOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedRefIndex res | - res = refOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); - private int maxRefRank(BasicBlock bb, SourceVariable v) { - result = refRank(bb, _, v, _) and - not result + 1 = refRank(bb, _, v, _) - } - - predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { - maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) - } + /** Gets an immediate successor of basic block `bb`, if any. */ + BasicBlock getABasicBlockSuccessor(BasicBlock bb); /** - * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` - * that is either a read or a certain write. + * An exit basic block, that is, a basic block whose last node is + * an exit node. */ - private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { - result = - min(int r, RefKind k | - r = refRank(bb, _, v, k) and - k != Write(false) - | - r - ) - } + class ExitBasicBlock extends BasicBlock; + + /** A variable that can be SSA converted. */ + class SourceVariable; /** - * Holds if source variable `v` is live at the beginning of basic block `bb`. + * Holds if the `i`th node of basic block `bb` is a (potential) write to source + * variable `v`. The Boolean `certain` indicates whether the write is certain. + * + * Examples of uncertain writes are `ref` arguments in C#, where it is the callee + * that may or may not update the argument. */ - predicate liveAtEntry(BasicBlock bb, SourceVariable v) { - // The first read or certain write to `v` inside `bb` is a read - refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) - or - // There is no certain write to `v` inside `bb`, but `v` is live at entry - // to a successor basic block of `bb` - not exists(firstReadOrCertainWrite(bb, v)) and - liveAtExit(bb, v) - } + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain); /** - * Holds if source variable `v` is live at the end of basic block `bb`. + * Holds if the `i`th node of basic block `bb` reads source variable `v`. The + * Boolean `certain` indicates whether the read is certain. + * + * Examples of uncertain reads are pseudo-reads inserted at the end of a C# method + * with a `ref` or `out` parameter, where it is the caller that may or may not read + * the argument. */ - predicate liveAtExit(BasicBlock bb, SourceVariable v) { - liveAtEntry(getABasicBlockSuccessor(bb), v) - } - - /** - * Holds if variable `v` is live in basic block `bb` at index `i`. - * The rank of `i` is `rnk` as defined by `refRank()`. - */ - private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { - exists(RefKind kind | rnk = refRank(bb, i, v, kind) | - rnk = maxRefRank(bb, v) and - liveAtExit(bb, v) - or - ref(bb, i, v, kind) and - kind = Read(_) - or - exists(RefKind nextKind | - liveAtRank(bb, _, v, rnk + 1) and - rnk + 1 = refRank(bb, _, v, nextKind) and - nextKind != Write(true) - ) - ) - } - - /** - * Holds if variable `v` is live after the (certain or uncertain) write at - * index `i` inside basic block `bb`. - */ - predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { - exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) - } + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain); } -private import Liveness - /** - * Holds if `df` is in the dominance frontier of `bb`. + * Provides an SSA implementation. * - * This is equivalent to: + * The SSA construction is pruned based on liveness. That is, SSA definitions are only + * constructed for `Input::variableWrite`s when it is possible to reach an + * `Input::variableRead`, without going through a certain write (the same goes for `phi` + * nodes). Whenever a variable is both read and written at the same index in some basic + * block, the read is assumed to happen before the write. * - * ```ql - * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and - * not bb = getImmediateBasicBlockDominator+(df) + * The result of invoking this parameterized module is not meant to be exposed directly; + * instead, one should define a language-specific layer on top, and make sure to cache + * all exposed predicates marked with + * + * ``` + * NB: If this predicate is exposed, it should be cached. * ``` */ -private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { - bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) - or - exists(BasicBlock prev | inDominanceFrontier(prev, df) | - bb = getImmediateBasicBlockDominator(prev) and - not bb = getImmediateBasicBlockDominator(df) - ) -} +module Make { + private import Input -/** - * Holds if `bb` is in the dominance frontier of a block containing a - * definition of `v`. - */ -pragma[noinline] -private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock defbb, Definition def | - def.definesAt(v, defbb, _) and - inDominanceFrontier(defbb, bb) - ) -} - -cached -newtype TDefinition = - TWriteDef(SourceVariable v, BasicBlock bb, int i) { - variableWrite(bb, i, v, _) and - liveAfterWrite(bb, i, v) - } or - TPhiNode(SourceVariable v, BasicBlock bb) { - inDefDominanceFrontier(bb, v) and - liveAtEntry(bb, v) + private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { + getABasicBlockSuccessor(result) = bb } -private module SsaDefReaches { - newtype TSsaRefKind = - SsaActualRead() or - SsaPhiRead() or - SsaDef() + /** + * Liveness analysis (based on source variables) to restrict the size of the + * SSA representation. + */ + private module Liveness { + /** + * A classification of variable references into reads (of a given kind) and + * (certain or uncertain) writes. + */ + private newtype TRefKind = + Read(boolean certain) { certain in [false, true] } or + Write(boolean certain) { certain in [false, true] } - class SsaRead = SsaActualRead or SsaPhiRead; + private class RefKind extends TRefKind { + string toString() { + exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") + or + exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") + } + + int getOrder() { + this = Read(_) and + result = 0 + or + this = Write(_) and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. + */ + predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { + exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) + or + exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) + } + + private newtype OrderedRefIndex = + MkOrderedRefIndex(int i, int tag) { + exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) + } + + private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { + ref(bb, i, v, k) and + result = MkOrderedRefIndex(i, ord) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of + * basic block `bb`, which has the given reference kind `k`. + * + * Reads are considered before writes when they happen at the same index. + */ + private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { + refOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedRefIndex res | + res = refOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + private int maxRefRank(BasicBlock bb, SourceVariable v) { + result = refRank(bb, _, v, _) and + not result + 1 = refRank(bb, _, v, _) + } + + predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { + maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) + } + + /** + * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` + * that is either a read or a certain write. + */ + private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { + result = + min(int r, RefKind k | + r = refRank(bb, _, v, k) and + k != Write(false) + | + r + ) + } + + /** + * Holds if source variable `v` is live at the beginning of basic block `bb`. + */ + predicate liveAtEntry(BasicBlock bb, SourceVariable v) { + // The first read or certain write to `v` inside `bb` is a read + refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) + or + // There is no certain write to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + not exists(firstReadOrCertainWrite(bb, v)) and + liveAtExit(bb, v) + } + + /** + * Holds if source variable `v` is live at the end of basic block `bb`. + */ + predicate liveAtExit(BasicBlock bb, SourceVariable v) { + liveAtEntry(getABasicBlockSuccessor(bb), v) + } + + /** + * Holds if variable `v` is live in basic block `bb` at index `i`. + * The rank of `i` is `rnk` as defined by `refRank()`. + */ + private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { + exists(RefKind kind | rnk = refRank(bb, i, v, kind) | + rnk = maxRefRank(bb, v) and + liveAtExit(bb, v) + or + ref(bb, i, v, kind) and + kind = Read(_) + or + exists(RefKind nextKind | + liveAtRank(bb, _, v, rnk + 1) and + rnk + 1 = refRank(bb, _, v, nextKind) and + nextKind != Write(true) + ) + ) + } + + /** + * Holds if variable `v` is live after the (certain or uncertain) write at + * index `i` inside basic block `bb`. + */ + predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { + exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) + } + } + + private import Liveness /** - * A classification of SSA variable references into reads and definitions. + * Holds if `df` is in the dominance frontier of `bb`. + * + * This is equivalent to: + * + * ```ql + * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and + * not bb = getImmediateBasicBlockDominator+(df) + * ``` */ - class SsaRefKind extends TSsaRefKind { - string toString() { - this = SsaActualRead() and - result = "SsaActualRead" - or - this = SsaPhiRead() and - result = "SsaPhiRead" - or - this = SsaDef() and - result = "SsaDef" - } - - int getOrder() { - this instanceof SsaRead and - result = 0 - or - this = SsaDef() and - result = 1 - } + private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { + bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) + or + exists(BasicBlock prev | inDominanceFrontier(prev, df) | + bb = getImmediateBasicBlockDominator(prev) and + not bb = getImmediateBasicBlockDominator(df) + ) } /** * Holds if `bb` is in the dominance frontier of a block containing a - * read of `v`. + * definition of `v`. */ - pragma[nomagic] - private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | - lastRefIsRead(readbb, v) - or - phiRead(readbb, v) + pragma[noinline] + private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock defbb, Definition def | + def.definesAt(v, defbb, _) and + inDominanceFrontier(defbb, bb) ) } - /** - * Holds if a phi-read node should be inserted for variable `v` at the beginning - * of basic block `bb`. - * - * Phi-read nodes are like normal phi nodes, but they are inserted based on reads - * instead of writes, and only if the dominance-frontier block does not already - * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is - * an internal implementation detail that is not exposed. - * - * The motivation for adding phi-reads is to improve performance of the use-use - * calculation in cases where there is a large number of reads that can reach the - * same join-point, and from there reach a large number of basic blocks. Example: - * - * ```cs - * if (a) - * use(x); - * else if (b) - * use(x); - * else if (c) - * use(x); - * else if (d) - * use(x); - * // many more ifs ... - * - * // phi-read for `x` inserted here - * - * // program not mentioning `x`, with large basic block graph - * - * use(x); - * ``` - * - * Without phi-reads, the analysis has to replicate reachability for each of - * the guarded uses of `x`. However, with phi-reads, the analysis will limit - * each conditional use of `x` to reach the basic block containing the phi-read - * node for `x`, and only that basic block will have to compute reachability - * through the remainder of the large program. - * - * Like normal reads, each phi-read node `phi-read` can be reached from exactly - * one SSA definition (without passing through another definition): Assume, for - * the sake of contradiction, that there are two reaching definitions `def1` and - * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest - * dominating definition will prevent the other from reaching `phi-read`. So, at - * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. - * Then `def1` must go through one of its dominance-frontier blocks in order to - * reach `phi-read`. However, such a block will always start with a (normal) phi - * node, which contradicts reachability. - * - * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, - * will dominate `phi-read`. Assuming it doesn't means that the path from `def` - * to `phi-read` goes through a dominance-frontier block, and hence a phi node, - * which contradicts reachability. - */ - pragma[nomagic] - predicate phiRead(BasicBlock bb, SourceVariable v) { - inReadDominanceFrontier(bb, v) and - liveAtEntry(bb, v) and - // only if there are no other references to `v` inside `bb` - not ref(bb, _, v, _) and - not exists(Definition def | def.definesAt(v, bb, _)) - } + cached + newtype TDefinition = + TWriteDef(SourceVariable v, BasicBlock bb, int i) { + variableWrite(bb, i, v, _) and + liveAfterWrite(bb, i, v) + } or + TPhiNode(SourceVariable v, BasicBlock bb) { + inDefDominanceFrontier(bb, v) and + liveAtEntry(bb, v) + } - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v`, - * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` - * is `SsaDef()`). - * - * Unlike `Liveness::ref`, this includes `phi` nodes. - */ - pragma[nomagic] - predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - variableRead(bb, i, v, _) and - k = SsaActualRead() - or - phiRead(bb, v) and - i = -1 and - k = SsaPhiRead() - or - any(Definition def).definesAt(v, bb, i) and - k = SsaDef() - } + private module SsaDefReaches { + newtype TSsaRefKind = + SsaActualRead() or + SsaPhiRead() or + SsaDef() - private newtype OrderedSsaRefIndex = - MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + class SsaRead = SsaActualRead or SsaPhiRead; - private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { - ssaRef(bb, i, v, k) and - result = MkOrderedSsaRefIndex(i, k) and - ord = k.getOrder() - } + /** + * A classification of SSA variable references into reads and definitions. + */ + class SsaRefKind extends TSsaRefKind { + string toString() { + this = SsaActualRead() and + result = "SsaActualRead" + or + this = SsaPhiRead() and + result = "SsaPhiRead" + or + this = SsaDef() and + result = "SsaDef" + } - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic - * block `bb`, which has the given reference kind `k`. - * - * For example, if `bb` is a basic block with a phi node for `v` (considered - * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: - * - * ```ql - * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node - * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 - * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 - * ``` - * - * Reads are considered before writes when they happen at the same index. - */ - int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - ssaRefOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedSsaRefIndex res | - res = ssaRefOrd(bb, j, v, _, ord) - | - res order by j, ord + int getOrder() { + this instanceof SsaRead and + result = 0 + or + this = SsaDef() and + result = 1 + } + } + + /** + * Holds if `bb` is in the dominance frontier of a block containing a + * read of `v`. + */ + pragma[nomagic] + private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | + lastRefIsRead(readbb, v) + or + phiRead(readbb, v) ) - } + } - int maxSsaRefRank(BasicBlock bb, SourceVariable v) { - result = ssaRefRank(bb, _, v, _) and - not result + 1 = ssaRefRank(bb, _, v, _) - } + /** + * Holds if a phi-read node should be inserted for variable `v` at the beginning + * of basic block `bb`. + * + * Phi-read nodes are like normal phi nodes, but they are inserted based on reads + * instead of writes, and only if the dominance-frontier block does not already + * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is + * an internal implementation detail that is not exposed. + * + * The motivation for adding phi-reads is to improve performance of the use-use + * calculation in cases where there is a large number of reads that can reach the + * same join-point, and from there reach a large number of basic blocks. Example: + * + * ```cs + * if (a) + * use(x); + * else if (b) + * use(x); + * else if (c) + * use(x); + * else if (d) + * use(x); + * // many more ifs ... + * + * // phi-read for `x` inserted here + * + * // program not mentioning `x`, with large basic block graph + * + * use(x); + * ``` + * + * Without phi-reads, the analysis has to replicate reachability for each of + * the guarded uses of `x`. However, with phi-reads, the analysis will limit + * each conditional use of `x` to reach the basic block containing the phi-read + * node for `x`, and only that basic block will have to compute reachability + * through the remainder of the large program. + * + * Like normal reads, each phi-read node `phi-read` can be reached from exactly + * one SSA definition (without passing through another definition): Assume, for + * the sake of contradiction, that there are two reaching definitions `def1` and + * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest + * dominating definition will prevent the other from reaching `phi-read`. So, at + * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. + * Then `def1` must go through one of its dominance-frontier blocks in order to + * reach `phi-read`. However, such a block will always start with a (normal) phi + * node, which contradicts reachability. + * + * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, + * will dominate `phi-read`. Assuming it doesn't means that the path from `def` + * to `phi-read` goes through a dominance-frontier block, and hence a phi node, + * which contradicts reachability. + */ + pragma[nomagic] + predicate phiRead(BasicBlock bb, SourceVariable v) { + inReadDominanceFrontier(bb, v) and + liveAtEntry(bb, v) and + // only if there are no other references to `v` inside `bb` + not ref(bb, _, v, _) and + not exists(Definition def | def.definesAt(v, bb, _)) + } - /** - * Holds if the SSA definition `def` reaches rank index `rnk` in its own - * basic block `bb`. - */ - predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { - exists(int i | - rnk = ssaRefRank(bb, i, v, SsaDef()) and - def.definesAt(v, bb, i) - ) - or - ssaDefReachesRank(bb, def, rnk - 1, v) and - rnk = ssaRefRank(bb, _, v, any(SsaRead k)) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches index `i` in the same - * basic block `bb`, without crossing another SSA definition of `v`. - */ - predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { - exists(int rnk | - ssaDefReachesRank(bb, def, rnk, v) and - rnk = ssaRefRank(bb, i, v, any(SsaRead k)) - ) - } - - /** - * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. - */ - int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { - v = def.getSourceVariable() and - result = ssaRefRank(bb, i, v, k) and - ( - ssaDefReachesRead(_, def, bb, i) + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v`, + * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` + * is `SsaDef()`). + * + * Unlike `Liveness::ref`, this includes `phi` nodes. + */ + pragma[nomagic] + predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + variableRead(bb, i, v, _) and + k = SsaActualRead() or - def.definesAt(_, bb, i) - ) + phiRead(bb, v) and + i = -1 and + k = SsaPhiRead() + or + any(Definition def).definesAt(v, bb, i) and + k = SsaDef() + } + + private newtype OrderedSsaRefIndex = + MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + + private OrderedSsaRefIndex ssaRefOrd( + BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord + ) { + ssaRef(bb, i, v, k) and + result = MkOrderedSsaRefIndex(i, k) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic + * block `bb`, which has the given reference kind `k`. + * + * For example, if `bb` is a basic block with a phi node for `v` (considered + * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: + * + * ```ql + * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node + * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 + * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 + * ``` + * + * Reads are considered before writes when they happen at the same index. + */ + int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + ssaRefOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedSsaRefIndex res | + res = ssaRefOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + int maxSsaRefRank(BasicBlock bb, SourceVariable v) { + result = ssaRefRank(bb, _, v, _) and + not result + 1 = ssaRefRank(bb, _, v, _) + } + + /** + * Holds if the SSA definition `def` reaches rank index `rnk` in its own + * basic block `bb`. + */ + predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { + exists(int i | + rnk = ssaRefRank(bb, i, v, SsaDef()) and + def.definesAt(v, bb, i) + ) + or + ssaDefReachesRank(bb, def, rnk - 1, v) and + rnk = ssaRefRank(bb, _, v, any(SsaRead k)) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches index `i` in the same + * basic block `bb`, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { + exists(int rnk | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, any(SsaRead k)) + ) + } + + /** + * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. + */ + int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { + v = def.getSourceVariable() and + result = ssaRefRank(bb, i, v, k) and + ( + ssaDefReachesRead(_, def, bb, i) + or + def.definesAt(_, bb, i) + ) + } + + /** + * Holds if the reference to `def` at index `i` in basic block `bb` is the + * last reference to `v` inside `bb`. + */ + pragma[noinline] + predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) + } + + predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { + exists(ssaDefRank(def, v, bb, _, k)) + } + + pragma[noinline] + private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { + ssaDefReachesEndOfBlock(bb, def, _) and + not defOccursInBlock(_, bb, def.getSourceVariable(), _) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ + * predecessor of `bb2`, and the underlying variable for `def` is neither read + * nor written in any block on the path between `bb1` and `bb2`. + * + * Phi reads are considered as normal reads for this predicate. + */ + pragma[nomagic] + private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + defOccursInBlock(def, bb1, _, _) and + bb2 = getABasicBlockSuccessor(bb1) + or + exists(BasicBlock mid | + varBlockReachesInclPhiRead(def, bb1, mid) and + ssaDefReachesThroughBlock(def, mid) and + bb2 = getABasicBlockSuccessor(mid) + ) + } + + pragma[nomagic] + private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(def, bb1, bb2) and + defOccursInBlock(def, bb2, v, SsaPhiRead()) + } + + pragma[nomagic] + private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and + ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) + or + exists(BasicBlock mid | + varBlockReachesExclPhiRead(def, mid, bb2) and + phiReadStep(def, _, bb1, mid) + ) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * the underlying variable `v` of `def` is accessed in basic block `bb2` + * (either a read or a write), `bb2` is a transitive successor of `bb1`, and + * `v` is neither read nor written in any block on the path between `bb1` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesExclPhiRead(def, bb1, bb2) and + not defOccursInBlock(def, bb1, _, SsaPhiRead()) + } + + pragma[nomagic] + predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { + varBlockReaches(def, bb1, bb2) and + ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + } + + /** + * Holds if `def` is accessed in basic block `bb` (either a read or a write), + * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, + * and `v` is neither read nor written in any block on the path between `bb` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReachesExit(Definition def, BasicBlock bb) { + exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | + not defOccursInBlock(def, bb2, _, _) and + not ssaDefReachesEndOfBlock(bb2, def, _) + ) + or + exists(BasicBlock mid | + varBlockReachesExit(def, mid) and + phiReadStep(def, _, bb, mid) + ) + } + } + + predicate phiReadExposedForTesting = phiRead/2; + + private import SsaDefReaches + + pragma[nomagic] + predicate liveThrough(BasicBlock bb, SourceVariable v) { + liveAtExit(bb, v) and + not ssaRef(bb, _, v, SsaDef()) } /** - * Holds if the reference to `def` at index `i` in basic block `bb` is the - * last reference to `v` inside `bb`. - */ - pragma[noinline] - predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) - } - - predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { - exists(ssaDefRank(def, v, bb, _, k)) - } - - pragma[noinline] - private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { - ssaDefReachesEndOfBlock(bb, def, _) and - not defOccursInBlock(_, bb, def.getSourceVariable(), _) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ - * predecessor of `bb2`, and the underlying variable for `def` is neither read - * nor written in any block on the path between `bb1` and `bb2`. + * NB: If this predicate is exposed, it should be cached. * - * Phi reads are considered as normal reads for this predicate. + * Holds if the SSA definition of `v` at `def` reaches the end of basic + * block `bb`, at which point it is still live, without crossing another + * SSA definition of `v`. */ pragma[nomagic] - private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - defOccursInBlock(def, bb1, _, _) and - bb2 = getABasicBlockSuccessor(bb1) - or - exists(BasicBlock mid | - varBlockReachesInclPhiRead(def, bb1, mid) and - ssaDefReachesThroughBlock(def, mid) and - bb2 = getABasicBlockSuccessor(mid) + predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { + exists(int last | + last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and + ssaDefReachesRank(bb, def, last, v) and + liveAtExit(bb, v) ) - } - - pragma[nomagic] - private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(def, bb1, bb2) and - defOccursInBlock(def, bb2, v, SsaPhiRead()) - } - - pragma[nomagic] - private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and - ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) or - exists(BasicBlock mid | - varBlockReachesExclPhiRead(def, mid, bb2) and - phiReadStep(def, _, bb1, mid) + // The construction of SSA form ensures that each read of a variable is + // dominated by its definition. An SSA definition therefore reaches a + // control flow node if it is the _closest_ SSA definition that dominates + // the node. If two definitions dominate a node then one must dominate the + // other, so therefore the definition of _closest_ is given by the dominator + // tree. Thus, reaching definitions can be calculated in terms of dominance. + ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and + liveThrough(bb, pragma[only_bind_into](v)) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. + */ + pragma[nomagic] + predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { + exists(SourceVariable v, BasicBlock bbDef | + phi.definesAt(v, bbDef, _) and + getABasicBlockPredecessor(bbDef) = bb and + ssaDefReachesEndOfBlock(bb, inp, v) ) } /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * the underlying variable `v` of `def` is accessed in basic block `bb2` - * (either a read or a write), `bb2` is a transitive successor of `bb1`, and - * `v` is neither read nor written in any block on the path between `bb1` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in + * basic block `bb`, without crossing another SSA definition of `v`. The read + * is of kind `rk`. */ pragma[nomagic] - predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesExclPhiRead(def, bb1, bb2) and - not defOccursInBlock(def, bb1, _, SsaPhiRead()) - } - - pragma[nomagic] - predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { - varBlockReaches(def, bb1, bb2) and - ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { + ssaDefReachesReadWithinBlock(v, def, bb, i) + or + ssaRef(bb, i, v, any(SsaRead k)) and + ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and + not ssaDefReachesReadWithinBlock(v, _, bb, i) } /** - * Holds if `def` is accessed in basic block `bb` (either a read or a write), - * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, - * and `v` is neither read nor written in any block on the path between `bb` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read + * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a + * path between them without any read of `def`. */ pragma[nomagic] - predicate varBlockReachesExit(Definition def, BasicBlock bb) { - exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | - not defOccursInBlock(def, bb2, _, _) and - not ssaDefReachesEndOfBlock(bb2, def, _) + predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + exists(int rnk | + rnk = ssaDefRank(def, _, bb1, i1, _) and + rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and + variableRead(bb1, i2, _, _) and + bb2 = bb1 ) or - exists(BasicBlock mid | - varBlockReachesExit(def, mid) and - phiReadStep(def, _, bb, mid) - ) - } -} - -predicate phiReadExposedForTesting = phiRead/2; - -private import SsaDefReaches - -pragma[nomagic] -predicate liveThrough(BasicBlock bb, SourceVariable v) { - liveAtExit(bb, v) and - not ssaRef(bb, _, v, SsaDef()) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches the end of basic - * block `bb`, at which point it is still live, without crossing another - * SSA definition of `v`. - */ -pragma[nomagic] -predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { - exists(int last | - last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and - ssaDefReachesRank(bb, def, last, v) and - liveAtExit(bb, v) - ) - or - // The construction of SSA form ensures that each read of a variable is - // dominated by its definition. An SSA definition therefore reaches a - // control flow node if it is the _closest_ SSA definition that dominates - // the node. If two definitions dominate a node then one must dominate the - // other, so therefore the definition of _closest_ is given by the dominator - // tree. Thus, reaching definitions can be calculated in terms of dominance. - ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and - liveThrough(bb, pragma[only_bind_into](v)) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. - */ -pragma[nomagic] -predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { - exists(SourceVariable v, BasicBlock bbDef | - phi.definesAt(v, bbDef, _) and - getABasicBlockPredecessor(bbDef) = bb and - ssaDefReachesEndOfBlock(bb, inp, v) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in - * basic block `bb`, without crossing another SSA definition of `v`. The read - * is of kind `rk`. - */ -pragma[nomagic] -predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { - ssaDefReachesReadWithinBlock(v, def, bb, i) - or - ssaRef(bb, i, v, any(SsaRead k)) and - ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and - not ssaDefReachesReadWithinBlock(v, _, bb, i) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read - * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a - * path between them without any read of `def`. - */ -pragma[nomagic] -predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - exists(int rnk | - rnk = ssaDefRank(def, _, bb1, i1, _) and - rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and - variableRead(bb1, i2, _, _) and - bb2 = bb1 - ) - or - lastSsaRef(def, _, bb1, i1) and - defAdjacentRead(def, bb1, bb2, i2) -} - -pragma[noinline] -private predicate adjacentDefRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v -) { - adjacentDefRead(def, bb1, i1, bb2, i2) and - v = def.getSourceVariable() -} - -private predicate adjacentDefReachesRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | - ssaRef(bb1, i1, v, SsaDef()) - or - variableRead(bb1, i1, v, true) - ) - or - exists(BasicBlock bb3, int i3 | - adjacentDefReachesRead(def, bb1, i1, bb3, i3) and - variableRead(bb3, i3, _, false) and - adjacentDefRead(def, bb3, i3, bb2, i2) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `adjacentDefRead`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, true) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA definition - * `def`. The reference is last because it can reach another write `next`, - * without passing through another read or write. - */ -pragma[nomagic] -predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { - exists(SourceVariable v | - // Next reference to `v` inside `bb` is a write - exists(int rnk, int j | - rnk = ssaDefRank(def, v, bb, i, _) and - next.definesAt(v, bb, j) and - rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) - ) - or - // Can reach a write using one or more steps - lastSsaRef(def, v, bb, i) and - exists(BasicBlock bb2 | - varBlockReaches(def, bb, bb2) and - 1 = ssaDefRank(next, v, bb2, _, SsaDef()) - ) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an immediately preceding definition of uncertain definition - * `def`. Since `def` is uncertain, the value from the preceding definition might - * still be valid. - */ -pragma[nomagic] -predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { - lastRefRedef(inp, _, _, def) -} - -private predicate adjacentDefReachesUncertainRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, false) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { - lastRefRedef(def, bb, i, next) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRefRedef(def, bb0, i0, next) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA - * definition `def`. - * - * That is, the node can reach the end of the enclosing callable, or another - * SSA definition for the underlying source variable, without passing through - * another read. - */ -pragma[nomagic] -predicate lastRef(Definition def, BasicBlock bb, int i) { - // Can reach another definition - lastRefRedef(def, bb, i, _) - or - exists(SourceVariable v | lastSsaRef(def, v, bb, i) | - // Can reach exit directly - bb instanceof ExitBasicBlock - or - // Can reach a block using one or more steps, where `def` is no longer live - varBlockReachesExit(def, bb) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { - lastRef(def, bb, i) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRef(def, bb0, i0) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** A static single assignment (SSA) definition. */ -class Definition extends TDefinition { - /** Gets the source variable underlying this SSA definition. */ - SourceVariable getSourceVariable() { this.definesAt(result, _, _) } - - /** - * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. - * Phi nodes are considered to be at index `-1`, while normal variable writes - * are at the index of the control flow node they wrap. - */ - final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { - this = TWriteDef(v, bb, i) - or - this = TPhiNode(v, bb) and i = -1 + lastSsaRef(def, _, bb1, i1) and + defAdjacentRead(def, bb1, bb2, i2) } - /** Gets the basic block to which this SSA definition belongs. */ - final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } - - /** Gets a textual representation of this SSA definition. */ - string toString() { none() } -} - -/** An SSA definition that corresponds to a write. */ -class WriteDefinition extends Definition, TWriteDef { - private SourceVariable v; - private BasicBlock bb; - private int i; - - WriteDefinition() { this = TWriteDef(v, bb, i) } - - override string toString() { result = "WriteDef" } -} - -/** A phi node. */ -class PhiNode extends Definition, TPhiNode { - override string toString() { result = "Phi" } -} - -/** - * An SSA definition that represents an uncertain update of the underlying - * source variable. - */ -class UncertainWriteDefinition extends WriteDefinition { - UncertainWriteDefinition() { - exists(SourceVariable v, BasicBlock bb, int i | - this.definesAt(v, bb, i) and - variableWrite(bb, i, v, false) - ) - } -} - -/** Provides a set of consistency queries. */ -module Consistency { - abstract class RelevantDefinition extends Definition { - abstract predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ); + pragma[noinline] + private predicate adjacentDefRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v + ) { + adjacentDefRead(def, bb1, i1, bb2, i2) and + v = def.getSourceVariable() } - query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefReachesRead(v, def, bb, i) and - not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) - } - - query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { - variableRead(bb, i, v, _) and - not ssaDefReachesRead(v, _, bb, i) - } - - query predicate deadDef(RelevantDefinition def, SourceVariable v) { - v = def.getSourceVariable() and - not ssaDefReachesRead(_, def, _, _) and - not phiHasInputFromBlock(_, def, _) and - not uncertainWriteDefinitionInput(_, def) - } - - query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | - ssaDefReachesReadWithinBlock(v, def, bb, i) and - (bb != bbDef or i < iDef) + private predicate adjacentDefReachesRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | + ssaRef(bb1, i1, v, SsaDef()) or - ssaDefReachesRead(v, def, bb, i) and - not ssaDefReachesReadWithinBlock(v, def, bb, i) and - not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + variableRead(bb1, i1, v, true) + ) + or + exists(BasicBlock bb3, int i3 | + adjacentDefReachesRead(def, bb1, i1, bb3, i3) and + variableRead(bb3, i3, _, false) and + adjacentDefRead(def, bb3, i3, bb2, i2) ) } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `adjacentDefRead`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate adjacentDefNoUncertainReads( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, true) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA definition + * `def`. The reference is last because it can reach another write `next`, + * without passing through another read or write. + */ + pragma[nomagic] + predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { + exists(SourceVariable v | + // Next reference to `v` inside `bb` is a write + exists(int rnk, int j | + rnk = ssaDefRank(def, v, bb, i, _) and + next.definesAt(v, bb, j) and + rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) + ) + or + // Can reach a write using one or more steps + lastSsaRef(def, v, bb, i) and + exists(BasicBlock bb2 | + varBlockReaches(def, bb, bb2) and + 1 = ssaDefRank(next, v, bb2, _, SsaDef()) + ) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an immediately preceding definition of uncertain definition + * `def`. Since `def` is uncertain, the value from the preceding definition might + * still be valid. + */ + pragma[nomagic] + predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { + lastRefRedef(inp, _, _, def) + } + + private predicate adjacentDefReachesUncertainRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, false) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { + lastRefRedef(def, bb, i, next) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRefRedef(def, bb0, i0, next) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA + * definition `def`. + * + * That is, the node can reach the end of the enclosing callable, or another + * SSA definition for the underlying source variable, without passing through + * another read. + */ + pragma[nomagic] + predicate lastRef(Definition def, BasicBlock bb, int i) { + // Can reach another definition + lastRefRedef(def, bb, i, _) + or + exists(SourceVariable v | lastSsaRef(def, v, bb, i) | + // Can reach exit directly + bb instanceof ExitBasicBlock + or + // Can reach a block using one or more steps, where `def` is no longer live + varBlockReachesExit(def, bb) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { + lastRef(def, bb, i) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRef(def, bb0, i0) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** A static single assignment (SSA) definition. */ + class Definition extends TDefinition { + /** Gets the source variable underlying this SSA definition. */ + SourceVariable getSourceVariable() { this.definesAt(result, _, _) } + + /** + * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. + * Phi nodes are considered to be at index `-1`, while normal variable writes + * are at the index of the control flow node they wrap. + */ + final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { + this = TWriteDef(v, bb, i) + or + this = TPhiNode(v, bb) and i = -1 + } + + /** Gets the basic block to which this SSA definition belongs. */ + final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } + + /** Gets a textual representation of this SSA definition. */ + string toString() { none() } + } + + /** An SSA definition that corresponds to a write. */ + class WriteDefinition extends Definition, TWriteDef { + private SourceVariable v; + private BasicBlock bb; + private int i; + + WriteDefinition() { this = TWriteDef(v, bb, i) } + + override string toString() { result = "WriteDef" } + } + + /** A phi node. */ + class PhiNode extends Definition, TPhiNode { + override string toString() { result = "Phi" } + } + + /** + * An SSA definition that represents an uncertain update of the underlying + * source variable. + */ + class UncertainWriteDefinition extends WriteDefinition { + UncertainWriteDefinition() { + exists(SourceVariable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + variableWrite(bb, i, v, false) + ) + } + } + + /** Provides a set of consistency queries. */ + // TODO: Make these `query` predicates once class signatures are supported + // (`SourceVariable` and `BasicBlock` must have `toString`) + module Consistency { + abstract class RelevantDefinition extends Definition { + abstract predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ); + } + + predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefReachesRead(v, def, bb, i) and + not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) + } + + predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { + variableRead(bb, i, v, _) and + not ssaDefReachesRead(v, _, bb, i) + } + + predicate deadDef(RelevantDefinition def, SourceVariable v) { + v = def.getSourceVariable() and + not ssaDefReachesRead(_, def, _, _) and + not phiHasInputFromBlock(_, def, _) and + not uncertainWriteDefinitionInput(_, def) + } + + predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | + ssaDefReachesReadWithinBlock(v, def, bb, i) and + (bb != bbDef or i < iDef) + or + ssaDefReachesRead(v, def, bb, i) and + not ssaDefReachesReadWithinBlock(v, def, bb, i) and + not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + ) + } + } } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll index 659940def50..3c706849415 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll @@ -3,793 +3,882 @@ * (SSA) form. */ -private import SsaImplSpecific - -private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } - -/** - * Liveness analysis (based on source variables) to restrict the size of the - * SSA representation. - */ -private module Liveness { +/** Provides the input specification of the SSA implementation. */ +signature module SsaInputSig { /** - * A classification of variable references into reads (of a given kind) and - * (certain or uncertain) writes. + * A basic block, that is, a maximal straight-line sequence of control flow nodes + * without branches or joins. */ - private newtype TRefKind = - Read(boolean certain) { certain in [false, true] } or - Write(boolean certain) { certain in [false, true] } - - private class RefKind extends TRefKind { - string toString() { - exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") - or - exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") - } - - int getOrder() { - this = Read(_) and - result = 0 - or - this = Write(_) and - result = 1 - } - } + class BasicBlock; /** - * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. - */ - predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { - exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) - or - exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) - } - - private newtype OrderedRefIndex = - MkOrderedRefIndex(int i, int tag) { - exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) - } - - private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { - ref(bb, i, v, k) and - result = MkOrderedRefIndex(i, ord) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of - * basic block `bb`, which has the given reference kind `k`. + * Gets the basic block that immediately dominates basic block `bb`, if any. * - * Reads are considered before writes when they happen at the same index. + * That is, all paths reaching `bb` from some entry point basic block must go + * through the result. + * + * Example: + * + * ```csharp + * int M(string s) { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The basic block starting on line 2 is an immediate dominator of + * the basic block online 4 (all paths from the entry point of `M` + * to `return s.Length;` must go through the null check. */ - private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { - refOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedRefIndex res | - res = refOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); - private int maxRefRank(BasicBlock bb, SourceVariable v) { - result = refRank(bb, _, v, _) and - not result + 1 = refRank(bb, _, v, _) - } - - predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { - maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) - } + /** Gets an immediate successor of basic block `bb`, if any. */ + BasicBlock getABasicBlockSuccessor(BasicBlock bb); /** - * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` - * that is either a read or a certain write. + * An exit basic block, that is, a basic block whose last node is + * an exit node. */ - private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { - result = - min(int r, RefKind k | - r = refRank(bb, _, v, k) and - k != Write(false) - | - r - ) - } + class ExitBasicBlock extends BasicBlock; + + /** A variable that can be SSA converted. */ + class SourceVariable; /** - * Holds if source variable `v` is live at the beginning of basic block `bb`. + * Holds if the `i`th node of basic block `bb` is a (potential) write to source + * variable `v`. The Boolean `certain` indicates whether the write is certain. + * + * Examples of uncertain writes are `ref` arguments in C#, where it is the callee + * that may or may not update the argument. */ - predicate liveAtEntry(BasicBlock bb, SourceVariable v) { - // The first read or certain write to `v` inside `bb` is a read - refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) - or - // There is no certain write to `v` inside `bb`, but `v` is live at entry - // to a successor basic block of `bb` - not exists(firstReadOrCertainWrite(bb, v)) and - liveAtExit(bb, v) - } + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain); /** - * Holds if source variable `v` is live at the end of basic block `bb`. + * Holds if the `i`th node of basic block `bb` reads source variable `v`. The + * Boolean `certain` indicates whether the read is certain. + * + * Examples of uncertain reads are pseudo-reads inserted at the end of a C# method + * with a `ref` or `out` parameter, where it is the caller that may or may not read + * the argument. */ - predicate liveAtExit(BasicBlock bb, SourceVariable v) { - liveAtEntry(getABasicBlockSuccessor(bb), v) - } - - /** - * Holds if variable `v` is live in basic block `bb` at index `i`. - * The rank of `i` is `rnk` as defined by `refRank()`. - */ - private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { - exists(RefKind kind | rnk = refRank(bb, i, v, kind) | - rnk = maxRefRank(bb, v) and - liveAtExit(bb, v) - or - ref(bb, i, v, kind) and - kind = Read(_) - or - exists(RefKind nextKind | - liveAtRank(bb, _, v, rnk + 1) and - rnk + 1 = refRank(bb, _, v, nextKind) and - nextKind != Write(true) - ) - ) - } - - /** - * Holds if variable `v` is live after the (certain or uncertain) write at - * index `i` inside basic block `bb`. - */ - predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { - exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) - } + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain); } -private import Liveness - /** - * Holds if `df` is in the dominance frontier of `bb`. + * Provides an SSA implementation. * - * This is equivalent to: + * The SSA construction is pruned based on liveness. That is, SSA definitions are only + * constructed for `Input::variableWrite`s when it is possible to reach an + * `Input::variableRead`, without going through a certain write (the same goes for `phi` + * nodes). Whenever a variable is both read and written at the same index in some basic + * block, the read is assumed to happen before the write. * - * ```ql - * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and - * not bb = getImmediateBasicBlockDominator+(df) + * The result of invoking this parameterized module is not meant to be exposed directly; + * instead, one should define a language-specific layer on top, and make sure to cache + * all exposed predicates marked with + * + * ``` + * NB: If this predicate is exposed, it should be cached. * ``` */ -private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { - bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) - or - exists(BasicBlock prev | inDominanceFrontier(prev, df) | - bb = getImmediateBasicBlockDominator(prev) and - not bb = getImmediateBasicBlockDominator(df) - ) -} +module Make { + private import Input -/** - * Holds if `bb` is in the dominance frontier of a block containing a - * definition of `v`. - */ -pragma[noinline] -private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock defbb, Definition def | - def.definesAt(v, defbb, _) and - inDominanceFrontier(defbb, bb) - ) -} - -cached -newtype TDefinition = - TWriteDef(SourceVariable v, BasicBlock bb, int i) { - variableWrite(bb, i, v, _) and - liveAfterWrite(bb, i, v) - } or - TPhiNode(SourceVariable v, BasicBlock bb) { - inDefDominanceFrontier(bb, v) and - liveAtEntry(bb, v) + private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { + getABasicBlockSuccessor(result) = bb } -private module SsaDefReaches { - newtype TSsaRefKind = - SsaActualRead() or - SsaPhiRead() or - SsaDef() + /** + * Liveness analysis (based on source variables) to restrict the size of the + * SSA representation. + */ + private module Liveness { + /** + * A classification of variable references into reads (of a given kind) and + * (certain or uncertain) writes. + */ + private newtype TRefKind = + Read(boolean certain) { certain in [false, true] } or + Write(boolean certain) { certain in [false, true] } - class SsaRead = SsaActualRead or SsaPhiRead; + private class RefKind extends TRefKind { + string toString() { + exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") + or + exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") + } + + int getOrder() { + this = Read(_) and + result = 0 + or + this = Write(_) and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. + */ + predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { + exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) + or + exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) + } + + private newtype OrderedRefIndex = + MkOrderedRefIndex(int i, int tag) { + exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) + } + + private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { + ref(bb, i, v, k) and + result = MkOrderedRefIndex(i, ord) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of + * basic block `bb`, which has the given reference kind `k`. + * + * Reads are considered before writes when they happen at the same index. + */ + private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { + refOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedRefIndex res | + res = refOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + private int maxRefRank(BasicBlock bb, SourceVariable v) { + result = refRank(bb, _, v, _) and + not result + 1 = refRank(bb, _, v, _) + } + + predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { + maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) + } + + /** + * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` + * that is either a read or a certain write. + */ + private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { + result = + min(int r, RefKind k | + r = refRank(bb, _, v, k) and + k != Write(false) + | + r + ) + } + + /** + * Holds if source variable `v` is live at the beginning of basic block `bb`. + */ + predicate liveAtEntry(BasicBlock bb, SourceVariable v) { + // The first read or certain write to `v` inside `bb` is a read + refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) + or + // There is no certain write to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + not exists(firstReadOrCertainWrite(bb, v)) and + liveAtExit(bb, v) + } + + /** + * Holds if source variable `v` is live at the end of basic block `bb`. + */ + predicate liveAtExit(BasicBlock bb, SourceVariable v) { + liveAtEntry(getABasicBlockSuccessor(bb), v) + } + + /** + * Holds if variable `v` is live in basic block `bb` at index `i`. + * The rank of `i` is `rnk` as defined by `refRank()`. + */ + private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { + exists(RefKind kind | rnk = refRank(bb, i, v, kind) | + rnk = maxRefRank(bb, v) and + liveAtExit(bb, v) + or + ref(bb, i, v, kind) and + kind = Read(_) + or + exists(RefKind nextKind | + liveAtRank(bb, _, v, rnk + 1) and + rnk + 1 = refRank(bb, _, v, nextKind) and + nextKind != Write(true) + ) + ) + } + + /** + * Holds if variable `v` is live after the (certain or uncertain) write at + * index `i` inside basic block `bb`. + */ + predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { + exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) + } + } + + private import Liveness /** - * A classification of SSA variable references into reads and definitions. + * Holds if `df` is in the dominance frontier of `bb`. + * + * This is equivalent to: + * + * ```ql + * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and + * not bb = getImmediateBasicBlockDominator+(df) + * ``` */ - class SsaRefKind extends TSsaRefKind { - string toString() { - this = SsaActualRead() and - result = "SsaActualRead" - or - this = SsaPhiRead() and - result = "SsaPhiRead" - or - this = SsaDef() and - result = "SsaDef" - } - - int getOrder() { - this instanceof SsaRead and - result = 0 - or - this = SsaDef() and - result = 1 - } + private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { + bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) + or + exists(BasicBlock prev | inDominanceFrontier(prev, df) | + bb = getImmediateBasicBlockDominator(prev) and + not bb = getImmediateBasicBlockDominator(df) + ) } /** * Holds if `bb` is in the dominance frontier of a block containing a - * read of `v`. + * definition of `v`. */ - pragma[nomagic] - private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | - lastRefIsRead(readbb, v) - or - phiRead(readbb, v) + pragma[noinline] + private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock defbb, Definition def | + def.definesAt(v, defbb, _) and + inDominanceFrontier(defbb, bb) ) } - /** - * Holds if a phi-read node should be inserted for variable `v` at the beginning - * of basic block `bb`. - * - * Phi-read nodes are like normal phi nodes, but they are inserted based on reads - * instead of writes, and only if the dominance-frontier block does not already - * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is - * an internal implementation detail that is not exposed. - * - * The motivation for adding phi-reads is to improve performance of the use-use - * calculation in cases where there is a large number of reads that can reach the - * same join-point, and from there reach a large number of basic blocks. Example: - * - * ```cs - * if (a) - * use(x); - * else if (b) - * use(x); - * else if (c) - * use(x); - * else if (d) - * use(x); - * // many more ifs ... - * - * // phi-read for `x` inserted here - * - * // program not mentioning `x`, with large basic block graph - * - * use(x); - * ``` - * - * Without phi-reads, the analysis has to replicate reachability for each of - * the guarded uses of `x`. However, with phi-reads, the analysis will limit - * each conditional use of `x` to reach the basic block containing the phi-read - * node for `x`, and only that basic block will have to compute reachability - * through the remainder of the large program. - * - * Like normal reads, each phi-read node `phi-read` can be reached from exactly - * one SSA definition (without passing through another definition): Assume, for - * the sake of contradiction, that there are two reaching definitions `def1` and - * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest - * dominating definition will prevent the other from reaching `phi-read`. So, at - * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. - * Then `def1` must go through one of its dominance-frontier blocks in order to - * reach `phi-read`. However, such a block will always start with a (normal) phi - * node, which contradicts reachability. - * - * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, - * will dominate `phi-read`. Assuming it doesn't means that the path from `def` - * to `phi-read` goes through a dominance-frontier block, and hence a phi node, - * which contradicts reachability. - */ - pragma[nomagic] - predicate phiRead(BasicBlock bb, SourceVariable v) { - inReadDominanceFrontier(bb, v) and - liveAtEntry(bb, v) and - // only if there are no other references to `v` inside `bb` - not ref(bb, _, v, _) and - not exists(Definition def | def.definesAt(v, bb, _)) - } + cached + newtype TDefinition = + TWriteDef(SourceVariable v, BasicBlock bb, int i) { + variableWrite(bb, i, v, _) and + liveAfterWrite(bb, i, v) + } or + TPhiNode(SourceVariable v, BasicBlock bb) { + inDefDominanceFrontier(bb, v) and + liveAtEntry(bb, v) + } - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v`, - * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` - * is `SsaDef()`). - * - * Unlike `Liveness::ref`, this includes `phi` nodes. - */ - pragma[nomagic] - predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - variableRead(bb, i, v, _) and - k = SsaActualRead() - or - phiRead(bb, v) and - i = -1 and - k = SsaPhiRead() - or - any(Definition def).definesAt(v, bb, i) and - k = SsaDef() - } + private module SsaDefReaches { + newtype TSsaRefKind = + SsaActualRead() or + SsaPhiRead() or + SsaDef() - private newtype OrderedSsaRefIndex = - MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + class SsaRead = SsaActualRead or SsaPhiRead; - private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { - ssaRef(bb, i, v, k) and - result = MkOrderedSsaRefIndex(i, k) and - ord = k.getOrder() - } + /** + * A classification of SSA variable references into reads and definitions. + */ + class SsaRefKind extends TSsaRefKind { + string toString() { + this = SsaActualRead() and + result = "SsaActualRead" + or + this = SsaPhiRead() and + result = "SsaPhiRead" + or + this = SsaDef() and + result = "SsaDef" + } - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic - * block `bb`, which has the given reference kind `k`. - * - * For example, if `bb` is a basic block with a phi node for `v` (considered - * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: - * - * ```ql - * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node - * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 - * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 - * ``` - * - * Reads are considered before writes when they happen at the same index. - */ - int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - ssaRefOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedSsaRefIndex res | - res = ssaRefOrd(bb, j, v, _, ord) - | - res order by j, ord + int getOrder() { + this instanceof SsaRead and + result = 0 + or + this = SsaDef() and + result = 1 + } + } + + /** + * Holds if `bb` is in the dominance frontier of a block containing a + * read of `v`. + */ + pragma[nomagic] + private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | + lastRefIsRead(readbb, v) + or + phiRead(readbb, v) ) - } + } - int maxSsaRefRank(BasicBlock bb, SourceVariable v) { - result = ssaRefRank(bb, _, v, _) and - not result + 1 = ssaRefRank(bb, _, v, _) - } + /** + * Holds if a phi-read node should be inserted for variable `v` at the beginning + * of basic block `bb`. + * + * Phi-read nodes are like normal phi nodes, but they are inserted based on reads + * instead of writes, and only if the dominance-frontier block does not already + * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is + * an internal implementation detail that is not exposed. + * + * The motivation for adding phi-reads is to improve performance of the use-use + * calculation in cases where there is a large number of reads that can reach the + * same join-point, and from there reach a large number of basic blocks. Example: + * + * ```cs + * if (a) + * use(x); + * else if (b) + * use(x); + * else if (c) + * use(x); + * else if (d) + * use(x); + * // many more ifs ... + * + * // phi-read for `x` inserted here + * + * // program not mentioning `x`, with large basic block graph + * + * use(x); + * ``` + * + * Without phi-reads, the analysis has to replicate reachability for each of + * the guarded uses of `x`. However, with phi-reads, the analysis will limit + * each conditional use of `x` to reach the basic block containing the phi-read + * node for `x`, and only that basic block will have to compute reachability + * through the remainder of the large program. + * + * Like normal reads, each phi-read node `phi-read` can be reached from exactly + * one SSA definition (without passing through another definition): Assume, for + * the sake of contradiction, that there are two reaching definitions `def1` and + * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest + * dominating definition will prevent the other from reaching `phi-read`. So, at + * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. + * Then `def1` must go through one of its dominance-frontier blocks in order to + * reach `phi-read`. However, such a block will always start with a (normal) phi + * node, which contradicts reachability. + * + * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, + * will dominate `phi-read`. Assuming it doesn't means that the path from `def` + * to `phi-read` goes through a dominance-frontier block, and hence a phi node, + * which contradicts reachability. + */ + pragma[nomagic] + predicate phiRead(BasicBlock bb, SourceVariable v) { + inReadDominanceFrontier(bb, v) and + liveAtEntry(bb, v) and + // only if there are no other references to `v` inside `bb` + not ref(bb, _, v, _) and + not exists(Definition def | def.definesAt(v, bb, _)) + } - /** - * Holds if the SSA definition `def` reaches rank index `rnk` in its own - * basic block `bb`. - */ - predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { - exists(int i | - rnk = ssaRefRank(bb, i, v, SsaDef()) and - def.definesAt(v, bb, i) - ) - or - ssaDefReachesRank(bb, def, rnk - 1, v) and - rnk = ssaRefRank(bb, _, v, any(SsaRead k)) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches index `i` in the same - * basic block `bb`, without crossing another SSA definition of `v`. - */ - predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { - exists(int rnk | - ssaDefReachesRank(bb, def, rnk, v) and - rnk = ssaRefRank(bb, i, v, any(SsaRead k)) - ) - } - - /** - * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. - */ - int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { - v = def.getSourceVariable() and - result = ssaRefRank(bb, i, v, k) and - ( - ssaDefReachesRead(_, def, bb, i) + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v`, + * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` + * is `SsaDef()`). + * + * Unlike `Liveness::ref`, this includes `phi` nodes. + */ + pragma[nomagic] + predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + variableRead(bb, i, v, _) and + k = SsaActualRead() or - def.definesAt(_, bb, i) - ) + phiRead(bb, v) and + i = -1 and + k = SsaPhiRead() + or + any(Definition def).definesAt(v, bb, i) and + k = SsaDef() + } + + private newtype OrderedSsaRefIndex = + MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + + private OrderedSsaRefIndex ssaRefOrd( + BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord + ) { + ssaRef(bb, i, v, k) and + result = MkOrderedSsaRefIndex(i, k) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic + * block `bb`, which has the given reference kind `k`. + * + * For example, if `bb` is a basic block with a phi node for `v` (considered + * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: + * + * ```ql + * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node + * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 + * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 + * ``` + * + * Reads are considered before writes when they happen at the same index. + */ + int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + ssaRefOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedSsaRefIndex res | + res = ssaRefOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + int maxSsaRefRank(BasicBlock bb, SourceVariable v) { + result = ssaRefRank(bb, _, v, _) and + not result + 1 = ssaRefRank(bb, _, v, _) + } + + /** + * Holds if the SSA definition `def` reaches rank index `rnk` in its own + * basic block `bb`. + */ + predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { + exists(int i | + rnk = ssaRefRank(bb, i, v, SsaDef()) and + def.definesAt(v, bb, i) + ) + or + ssaDefReachesRank(bb, def, rnk - 1, v) and + rnk = ssaRefRank(bb, _, v, any(SsaRead k)) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches index `i` in the same + * basic block `bb`, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { + exists(int rnk | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, any(SsaRead k)) + ) + } + + /** + * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. + */ + int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { + v = def.getSourceVariable() and + result = ssaRefRank(bb, i, v, k) and + ( + ssaDefReachesRead(_, def, bb, i) + or + def.definesAt(_, bb, i) + ) + } + + /** + * Holds if the reference to `def` at index `i` in basic block `bb` is the + * last reference to `v` inside `bb`. + */ + pragma[noinline] + predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) + } + + predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { + exists(ssaDefRank(def, v, bb, _, k)) + } + + pragma[noinline] + private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { + ssaDefReachesEndOfBlock(bb, def, _) and + not defOccursInBlock(_, bb, def.getSourceVariable(), _) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ + * predecessor of `bb2`, and the underlying variable for `def` is neither read + * nor written in any block on the path between `bb1` and `bb2`. + * + * Phi reads are considered as normal reads for this predicate. + */ + pragma[nomagic] + private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + defOccursInBlock(def, bb1, _, _) and + bb2 = getABasicBlockSuccessor(bb1) + or + exists(BasicBlock mid | + varBlockReachesInclPhiRead(def, bb1, mid) and + ssaDefReachesThroughBlock(def, mid) and + bb2 = getABasicBlockSuccessor(mid) + ) + } + + pragma[nomagic] + private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(def, bb1, bb2) and + defOccursInBlock(def, bb2, v, SsaPhiRead()) + } + + pragma[nomagic] + private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and + ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) + or + exists(BasicBlock mid | + varBlockReachesExclPhiRead(def, mid, bb2) and + phiReadStep(def, _, bb1, mid) + ) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * the underlying variable `v` of `def` is accessed in basic block `bb2` + * (either a read or a write), `bb2` is a transitive successor of `bb1`, and + * `v` is neither read nor written in any block on the path between `bb1` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesExclPhiRead(def, bb1, bb2) and + not defOccursInBlock(def, bb1, _, SsaPhiRead()) + } + + pragma[nomagic] + predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { + varBlockReaches(def, bb1, bb2) and + ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + } + + /** + * Holds if `def` is accessed in basic block `bb` (either a read or a write), + * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, + * and `v` is neither read nor written in any block on the path between `bb` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReachesExit(Definition def, BasicBlock bb) { + exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | + not defOccursInBlock(def, bb2, _, _) and + not ssaDefReachesEndOfBlock(bb2, def, _) + ) + or + exists(BasicBlock mid | + varBlockReachesExit(def, mid) and + phiReadStep(def, _, bb, mid) + ) + } + } + + predicate phiReadExposedForTesting = phiRead/2; + + private import SsaDefReaches + + pragma[nomagic] + predicate liveThrough(BasicBlock bb, SourceVariable v) { + liveAtExit(bb, v) and + not ssaRef(bb, _, v, SsaDef()) } /** - * Holds if the reference to `def` at index `i` in basic block `bb` is the - * last reference to `v` inside `bb`. - */ - pragma[noinline] - predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) - } - - predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { - exists(ssaDefRank(def, v, bb, _, k)) - } - - pragma[noinline] - private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { - ssaDefReachesEndOfBlock(bb, def, _) and - not defOccursInBlock(_, bb, def.getSourceVariable(), _) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ - * predecessor of `bb2`, and the underlying variable for `def` is neither read - * nor written in any block on the path between `bb1` and `bb2`. + * NB: If this predicate is exposed, it should be cached. * - * Phi reads are considered as normal reads for this predicate. + * Holds if the SSA definition of `v` at `def` reaches the end of basic + * block `bb`, at which point it is still live, without crossing another + * SSA definition of `v`. */ pragma[nomagic] - private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - defOccursInBlock(def, bb1, _, _) and - bb2 = getABasicBlockSuccessor(bb1) - or - exists(BasicBlock mid | - varBlockReachesInclPhiRead(def, bb1, mid) and - ssaDefReachesThroughBlock(def, mid) and - bb2 = getABasicBlockSuccessor(mid) + predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { + exists(int last | + last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and + ssaDefReachesRank(bb, def, last, v) and + liveAtExit(bb, v) ) - } - - pragma[nomagic] - private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(def, bb1, bb2) and - defOccursInBlock(def, bb2, v, SsaPhiRead()) - } - - pragma[nomagic] - private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and - ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) or - exists(BasicBlock mid | - varBlockReachesExclPhiRead(def, mid, bb2) and - phiReadStep(def, _, bb1, mid) + // The construction of SSA form ensures that each read of a variable is + // dominated by its definition. An SSA definition therefore reaches a + // control flow node if it is the _closest_ SSA definition that dominates + // the node. If two definitions dominate a node then one must dominate the + // other, so therefore the definition of _closest_ is given by the dominator + // tree. Thus, reaching definitions can be calculated in terms of dominance. + ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and + liveThrough(bb, pragma[only_bind_into](v)) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. + */ + pragma[nomagic] + predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { + exists(SourceVariable v, BasicBlock bbDef | + phi.definesAt(v, bbDef, _) and + getABasicBlockPredecessor(bbDef) = bb and + ssaDefReachesEndOfBlock(bb, inp, v) ) } /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * the underlying variable `v` of `def` is accessed in basic block `bb2` - * (either a read or a write), `bb2` is a transitive successor of `bb1`, and - * `v` is neither read nor written in any block on the path between `bb1` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in + * basic block `bb`, without crossing another SSA definition of `v`. The read + * is of kind `rk`. */ pragma[nomagic] - predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesExclPhiRead(def, bb1, bb2) and - not defOccursInBlock(def, bb1, _, SsaPhiRead()) - } - - pragma[nomagic] - predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { - varBlockReaches(def, bb1, bb2) and - ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { + ssaDefReachesReadWithinBlock(v, def, bb, i) + or + ssaRef(bb, i, v, any(SsaRead k)) and + ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and + not ssaDefReachesReadWithinBlock(v, _, bb, i) } /** - * Holds if `def` is accessed in basic block `bb` (either a read or a write), - * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, - * and `v` is neither read nor written in any block on the path between `bb` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read + * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a + * path between them without any read of `def`. */ pragma[nomagic] - predicate varBlockReachesExit(Definition def, BasicBlock bb) { - exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | - not defOccursInBlock(def, bb2, _, _) and - not ssaDefReachesEndOfBlock(bb2, def, _) + predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + exists(int rnk | + rnk = ssaDefRank(def, _, bb1, i1, _) and + rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and + variableRead(bb1, i2, _, _) and + bb2 = bb1 ) or - exists(BasicBlock mid | - varBlockReachesExit(def, mid) and - phiReadStep(def, _, bb, mid) - ) - } -} - -predicate phiReadExposedForTesting = phiRead/2; - -private import SsaDefReaches - -pragma[nomagic] -predicate liveThrough(BasicBlock bb, SourceVariable v) { - liveAtExit(bb, v) and - not ssaRef(bb, _, v, SsaDef()) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches the end of basic - * block `bb`, at which point it is still live, without crossing another - * SSA definition of `v`. - */ -pragma[nomagic] -predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { - exists(int last | - last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and - ssaDefReachesRank(bb, def, last, v) and - liveAtExit(bb, v) - ) - or - // The construction of SSA form ensures that each read of a variable is - // dominated by its definition. An SSA definition therefore reaches a - // control flow node if it is the _closest_ SSA definition that dominates - // the node. If two definitions dominate a node then one must dominate the - // other, so therefore the definition of _closest_ is given by the dominator - // tree. Thus, reaching definitions can be calculated in terms of dominance. - ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and - liveThrough(bb, pragma[only_bind_into](v)) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. - */ -pragma[nomagic] -predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { - exists(SourceVariable v, BasicBlock bbDef | - phi.definesAt(v, bbDef, _) and - getABasicBlockPredecessor(bbDef) = bb and - ssaDefReachesEndOfBlock(bb, inp, v) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in - * basic block `bb`, without crossing another SSA definition of `v`. The read - * is of kind `rk`. - */ -pragma[nomagic] -predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { - ssaDefReachesReadWithinBlock(v, def, bb, i) - or - ssaRef(bb, i, v, any(SsaRead k)) and - ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and - not ssaDefReachesReadWithinBlock(v, _, bb, i) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read - * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a - * path between them without any read of `def`. - */ -pragma[nomagic] -predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - exists(int rnk | - rnk = ssaDefRank(def, _, bb1, i1, _) and - rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and - variableRead(bb1, i2, _, _) and - bb2 = bb1 - ) - or - lastSsaRef(def, _, bb1, i1) and - defAdjacentRead(def, bb1, bb2, i2) -} - -pragma[noinline] -private predicate adjacentDefRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v -) { - adjacentDefRead(def, bb1, i1, bb2, i2) and - v = def.getSourceVariable() -} - -private predicate adjacentDefReachesRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | - ssaRef(bb1, i1, v, SsaDef()) - or - variableRead(bb1, i1, v, true) - ) - or - exists(BasicBlock bb3, int i3 | - adjacentDefReachesRead(def, bb1, i1, bb3, i3) and - variableRead(bb3, i3, _, false) and - adjacentDefRead(def, bb3, i3, bb2, i2) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `adjacentDefRead`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, true) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA definition - * `def`. The reference is last because it can reach another write `next`, - * without passing through another read or write. - */ -pragma[nomagic] -predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { - exists(SourceVariable v | - // Next reference to `v` inside `bb` is a write - exists(int rnk, int j | - rnk = ssaDefRank(def, v, bb, i, _) and - next.definesAt(v, bb, j) and - rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) - ) - or - // Can reach a write using one or more steps - lastSsaRef(def, v, bb, i) and - exists(BasicBlock bb2 | - varBlockReaches(def, bb, bb2) and - 1 = ssaDefRank(next, v, bb2, _, SsaDef()) - ) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an immediately preceding definition of uncertain definition - * `def`. Since `def` is uncertain, the value from the preceding definition might - * still be valid. - */ -pragma[nomagic] -predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { - lastRefRedef(inp, _, _, def) -} - -private predicate adjacentDefReachesUncertainRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, false) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { - lastRefRedef(def, bb, i, next) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRefRedef(def, bb0, i0, next) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA - * definition `def`. - * - * That is, the node can reach the end of the enclosing callable, or another - * SSA definition for the underlying source variable, without passing through - * another read. - */ -pragma[nomagic] -predicate lastRef(Definition def, BasicBlock bb, int i) { - // Can reach another definition - lastRefRedef(def, bb, i, _) - or - exists(SourceVariable v | lastSsaRef(def, v, bb, i) | - // Can reach exit directly - bb instanceof ExitBasicBlock - or - // Can reach a block using one or more steps, where `def` is no longer live - varBlockReachesExit(def, bb) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { - lastRef(def, bb, i) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRef(def, bb0, i0) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** A static single assignment (SSA) definition. */ -class Definition extends TDefinition { - /** Gets the source variable underlying this SSA definition. */ - SourceVariable getSourceVariable() { this.definesAt(result, _, _) } - - /** - * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. - * Phi nodes are considered to be at index `-1`, while normal variable writes - * are at the index of the control flow node they wrap. - */ - final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { - this = TWriteDef(v, bb, i) - or - this = TPhiNode(v, bb) and i = -1 + lastSsaRef(def, _, bb1, i1) and + defAdjacentRead(def, bb1, bb2, i2) } - /** Gets the basic block to which this SSA definition belongs. */ - final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } - - /** Gets a textual representation of this SSA definition. */ - string toString() { none() } -} - -/** An SSA definition that corresponds to a write. */ -class WriteDefinition extends Definition, TWriteDef { - private SourceVariable v; - private BasicBlock bb; - private int i; - - WriteDefinition() { this = TWriteDef(v, bb, i) } - - override string toString() { result = "WriteDef" } -} - -/** A phi node. */ -class PhiNode extends Definition, TPhiNode { - override string toString() { result = "Phi" } -} - -/** - * An SSA definition that represents an uncertain update of the underlying - * source variable. - */ -class UncertainWriteDefinition extends WriteDefinition { - UncertainWriteDefinition() { - exists(SourceVariable v, BasicBlock bb, int i | - this.definesAt(v, bb, i) and - variableWrite(bb, i, v, false) - ) - } -} - -/** Provides a set of consistency queries. */ -module Consistency { - abstract class RelevantDefinition extends Definition { - abstract predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ); + pragma[noinline] + private predicate adjacentDefRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v + ) { + adjacentDefRead(def, bb1, i1, bb2, i2) and + v = def.getSourceVariable() } - query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefReachesRead(v, def, bb, i) and - not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) - } - - query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { - variableRead(bb, i, v, _) and - not ssaDefReachesRead(v, _, bb, i) - } - - query predicate deadDef(RelevantDefinition def, SourceVariable v) { - v = def.getSourceVariable() and - not ssaDefReachesRead(_, def, _, _) and - not phiHasInputFromBlock(_, def, _) and - not uncertainWriteDefinitionInput(_, def) - } - - query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | - ssaDefReachesReadWithinBlock(v, def, bb, i) and - (bb != bbDef or i < iDef) + private predicate adjacentDefReachesRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | + ssaRef(bb1, i1, v, SsaDef()) or - ssaDefReachesRead(v, def, bb, i) and - not ssaDefReachesReadWithinBlock(v, def, bb, i) and - not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + variableRead(bb1, i1, v, true) + ) + or + exists(BasicBlock bb3, int i3 | + adjacentDefReachesRead(def, bb1, i1, bb3, i3) and + variableRead(bb3, i3, _, false) and + adjacentDefRead(def, bb3, i3, bb2, i2) ) } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `adjacentDefRead`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate adjacentDefNoUncertainReads( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, true) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA definition + * `def`. The reference is last because it can reach another write `next`, + * without passing through another read or write. + */ + pragma[nomagic] + predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { + exists(SourceVariable v | + // Next reference to `v` inside `bb` is a write + exists(int rnk, int j | + rnk = ssaDefRank(def, v, bb, i, _) and + next.definesAt(v, bb, j) and + rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) + ) + or + // Can reach a write using one or more steps + lastSsaRef(def, v, bb, i) and + exists(BasicBlock bb2 | + varBlockReaches(def, bb, bb2) and + 1 = ssaDefRank(next, v, bb2, _, SsaDef()) + ) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an immediately preceding definition of uncertain definition + * `def`. Since `def` is uncertain, the value from the preceding definition might + * still be valid. + */ + pragma[nomagic] + predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { + lastRefRedef(inp, _, _, def) + } + + private predicate adjacentDefReachesUncertainRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, false) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { + lastRefRedef(def, bb, i, next) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRefRedef(def, bb0, i0, next) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA + * definition `def`. + * + * That is, the node can reach the end of the enclosing callable, or another + * SSA definition for the underlying source variable, without passing through + * another read. + */ + pragma[nomagic] + predicate lastRef(Definition def, BasicBlock bb, int i) { + // Can reach another definition + lastRefRedef(def, bb, i, _) + or + exists(SourceVariable v | lastSsaRef(def, v, bb, i) | + // Can reach exit directly + bb instanceof ExitBasicBlock + or + // Can reach a block using one or more steps, where `def` is no longer live + varBlockReachesExit(def, bb) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { + lastRef(def, bb, i) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRef(def, bb0, i0) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** A static single assignment (SSA) definition. */ + class Definition extends TDefinition { + /** Gets the source variable underlying this SSA definition. */ + SourceVariable getSourceVariable() { this.definesAt(result, _, _) } + + /** + * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. + * Phi nodes are considered to be at index `-1`, while normal variable writes + * are at the index of the control flow node they wrap. + */ + final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { + this = TWriteDef(v, bb, i) + or + this = TPhiNode(v, bb) and i = -1 + } + + /** Gets the basic block to which this SSA definition belongs. */ + final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } + + /** Gets a textual representation of this SSA definition. */ + string toString() { none() } + } + + /** An SSA definition that corresponds to a write. */ + class WriteDefinition extends Definition, TWriteDef { + private SourceVariable v; + private BasicBlock bb; + private int i; + + WriteDefinition() { this = TWriteDef(v, bb, i) } + + override string toString() { result = "WriteDef" } + } + + /** A phi node. */ + class PhiNode extends Definition, TPhiNode { + override string toString() { result = "Phi" } + } + + /** + * An SSA definition that represents an uncertain update of the underlying + * source variable. + */ + class UncertainWriteDefinition extends WriteDefinition { + UncertainWriteDefinition() { + exists(SourceVariable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + variableWrite(bb, i, v, false) + ) + } + } + + /** Provides a set of consistency queries. */ + // TODO: Make these `query` predicates once class signatures are supported + // (`SourceVariable` and `BasicBlock` must have `toString`) + module Consistency { + abstract class RelevantDefinition extends Definition { + abstract predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ); + } + + predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefReachesRead(v, def, bb, i) and + not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) + } + + predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { + variableRead(bb, i, v, _) and + not ssaDefReachesRead(v, _, bb, i) + } + + predicate deadDef(RelevantDefinition def, SourceVariable v) { + v = def.getSourceVariable() and + not ssaDefReachesRead(_, def, _, _) and + not phiHasInputFromBlock(_, def, _) and + not uncertainWriteDefinitionInput(_, def) + } + + predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | + ssaDefReachesReadWithinBlock(v, def, bb, i) and + (bb != bbDef or i < iDef) + or + ssaDefReachesRead(v, def, bb, i) and + not ssaDefReachesReadWithinBlock(v, def, bb, i) and + not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + ) + } + } } diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll b/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll index 659940def50..3c706849415 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll @@ -3,793 +3,882 @@ * (SSA) form. */ -private import SsaImplSpecific - -private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb } - -/** - * Liveness analysis (based on source variables) to restrict the size of the - * SSA representation. - */ -private module Liveness { +/** Provides the input specification of the SSA implementation. */ +signature module SsaInputSig { /** - * A classification of variable references into reads (of a given kind) and - * (certain or uncertain) writes. + * A basic block, that is, a maximal straight-line sequence of control flow nodes + * without branches or joins. */ - private newtype TRefKind = - Read(boolean certain) { certain in [false, true] } or - Write(boolean certain) { certain in [false, true] } - - private class RefKind extends TRefKind { - string toString() { - exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") - or - exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") - } - - int getOrder() { - this = Read(_) and - result = 0 - or - this = Write(_) and - result = 1 - } - } + class BasicBlock; /** - * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. - */ - predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { - exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) - or - exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) - } - - private newtype OrderedRefIndex = - MkOrderedRefIndex(int i, int tag) { - exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) - } - - private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { - ref(bb, i, v, k) and - result = MkOrderedRefIndex(i, ord) and - ord = k.getOrder() - } - - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of - * basic block `bb`, which has the given reference kind `k`. + * Gets the basic block that immediately dominates basic block `bb`, if any. * - * Reads are considered before writes when they happen at the same index. + * That is, all paths reaching `bb` from some entry point basic block must go + * through the result. + * + * Example: + * + * ```csharp + * int M(string s) { + * if (s == null) + * throw new ArgumentNullException(nameof(s)); + * return s.Length; + * } + * ``` + * + * The basic block starting on line 2 is an immediate dominator of + * the basic block online 4 (all paths from the entry point of `M` + * to `return s.Length;` must go through the null check. */ - private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { - refOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedRefIndex res | - res = refOrd(bb, j, v, _, ord) - | - res order by j, ord - ) - } + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); - private int maxRefRank(BasicBlock bb, SourceVariable v) { - result = refRank(bb, _, v, _) and - not result + 1 = refRank(bb, _, v, _) - } - - predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { - maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) - } + /** Gets an immediate successor of basic block `bb`, if any. */ + BasicBlock getABasicBlockSuccessor(BasicBlock bb); /** - * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` - * that is either a read or a certain write. + * An exit basic block, that is, a basic block whose last node is + * an exit node. */ - private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { - result = - min(int r, RefKind k | - r = refRank(bb, _, v, k) and - k != Write(false) - | - r - ) - } + class ExitBasicBlock extends BasicBlock; + + /** A variable that can be SSA converted. */ + class SourceVariable; /** - * Holds if source variable `v` is live at the beginning of basic block `bb`. + * Holds if the `i`th node of basic block `bb` is a (potential) write to source + * variable `v`. The Boolean `certain` indicates whether the write is certain. + * + * Examples of uncertain writes are `ref` arguments in C#, where it is the callee + * that may or may not update the argument. */ - predicate liveAtEntry(BasicBlock bb, SourceVariable v) { - // The first read or certain write to `v` inside `bb` is a read - refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) - or - // There is no certain write to `v` inside `bb`, but `v` is live at entry - // to a successor basic block of `bb` - not exists(firstReadOrCertainWrite(bb, v)) and - liveAtExit(bb, v) - } + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain); /** - * Holds if source variable `v` is live at the end of basic block `bb`. + * Holds if the `i`th node of basic block `bb` reads source variable `v`. The + * Boolean `certain` indicates whether the read is certain. + * + * Examples of uncertain reads are pseudo-reads inserted at the end of a C# method + * with a `ref` or `out` parameter, where it is the caller that may or may not read + * the argument. */ - predicate liveAtExit(BasicBlock bb, SourceVariable v) { - liveAtEntry(getABasicBlockSuccessor(bb), v) - } - - /** - * Holds if variable `v` is live in basic block `bb` at index `i`. - * The rank of `i` is `rnk` as defined by `refRank()`. - */ - private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { - exists(RefKind kind | rnk = refRank(bb, i, v, kind) | - rnk = maxRefRank(bb, v) and - liveAtExit(bb, v) - or - ref(bb, i, v, kind) and - kind = Read(_) - or - exists(RefKind nextKind | - liveAtRank(bb, _, v, rnk + 1) and - rnk + 1 = refRank(bb, _, v, nextKind) and - nextKind != Write(true) - ) - ) - } - - /** - * Holds if variable `v` is live after the (certain or uncertain) write at - * index `i` inside basic block `bb`. - */ - predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { - exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) - } + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain); } -private import Liveness - /** - * Holds if `df` is in the dominance frontier of `bb`. + * Provides an SSA implementation. * - * This is equivalent to: + * The SSA construction is pruned based on liveness. That is, SSA definitions are only + * constructed for `Input::variableWrite`s when it is possible to reach an + * `Input::variableRead`, without going through a certain write (the same goes for `phi` + * nodes). Whenever a variable is both read and written at the same index in some basic + * block, the read is assumed to happen before the write. * - * ```ql - * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and - * not bb = getImmediateBasicBlockDominator+(df) + * The result of invoking this parameterized module is not meant to be exposed directly; + * instead, one should define a language-specific layer on top, and make sure to cache + * all exposed predicates marked with + * + * ``` + * NB: If this predicate is exposed, it should be cached. * ``` */ -private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { - bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) - or - exists(BasicBlock prev | inDominanceFrontier(prev, df) | - bb = getImmediateBasicBlockDominator(prev) and - not bb = getImmediateBasicBlockDominator(df) - ) -} +module Make { + private import Input -/** - * Holds if `bb` is in the dominance frontier of a block containing a - * definition of `v`. - */ -pragma[noinline] -private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock defbb, Definition def | - def.definesAt(v, defbb, _) and - inDominanceFrontier(defbb, bb) - ) -} - -cached -newtype TDefinition = - TWriteDef(SourceVariable v, BasicBlock bb, int i) { - variableWrite(bb, i, v, _) and - liveAfterWrite(bb, i, v) - } or - TPhiNode(SourceVariable v, BasicBlock bb) { - inDefDominanceFrontier(bb, v) and - liveAtEntry(bb, v) + private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { + getABasicBlockSuccessor(result) = bb } -private module SsaDefReaches { - newtype TSsaRefKind = - SsaActualRead() or - SsaPhiRead() or - SsaDef() + /** + * Liveness analysis (based on source variables) to restrict the size of the + * SSA representation. + */ + private module Liveness { + /** + * A classification of variable references into reads (of a given kind) and + * (certain or uncertain) writes. + */ + private newtype TRefKind = + Read(boolean certain) { certain in [false, true] } or + Write(boolean certain) { certain in [false, true] } - class SsaRead = SsaActualRead or SsaPhiRead; + private class RefKind extends TRefKind { + string toString() { + exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")") + or + exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")") + } + + int getOrder() { + this = Read(_) and + result = 0 + or + this = Write(_) and + result = 1 + } + } + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`. + */ + predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) { + exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain)) + or + exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain)) + } + + private newtype OrderedRefIndex = + MkOrderedRefIndex(int i, int tag) { + exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder()) + } + + private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) { + ref(bb, i, v, k) and + result = MkOrderedRefIndex(i, ord) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of + * basic block `bb`, which has the given reference kind `k`. + * + * Reads are considered before writes when they happen at the same index. + */ + private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) { + refOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedRefIndex res | + res = refOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + private int maxRefRank(BasicBlock bb, SourceVariable v) { + result = refRank(bb, _, v, _) and + not result + 1 = refRank(bb, _, v, _) + } + + predicate lastRefIsRead(BasicBlock bb, SourceVariable v) { + maxRefRank(bb, v) = refRank(bb, _, v, Read(_)) + } + + /** + * Gets the (1-based) rank of the first reference to `v` inside basic block `bb` + * that is either a read or a certain write. + */ + private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) { + result = + min(int r, RefKind k | + r = refRank(bb, _, v, k) and + k != Write(false) + | + r + ) + } + + /** + * Holds if source variable `v` is live at the beginning of basic block `bb`. + */ + predicate liveAtEntry(BasicBlock bb, SourceVariable v) { + // The first read or certain write to `v` inside `bb` is a read + refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v) + or + // There is no certain write to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + not exists(firstReadOrCertainWrite(bb, v)) and + liveAtExit(bb, v) + } + + /** + * Holds if source variable `v` is live at the end of basic block `bb`. + */ + predicate liveAtExit(BasicBlock bb, SourceVariable v) { + liveAtEntry(getABasicBlockSuccessor(bb), v) + } + + /** + * Holds if variable `v` is live in basic block `bb` at index `i`. + * The rank of `i` is `rnk` as defined by `refRank()`. + */ + private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) { + exists(RefKind kind | rnk = refRank(bb, i, v, kind) | + rnk = maxRefRank(bb, v) and + liveAtExit(bb, v) + or + ref(bb, i, v, kind) and + kind = Read(_) + or + exists(RefKind nextKind | + liveAtRank(bb, _, v, rnk + 1) and + rnk + 1 = refRank(bb, _, v, nextKind) and + nextKind != Write(true) + ) + ) + } + + /** + * Holds if variable `v` is live after the (certain or uncertain) write at + * index `i` inside basic block `bb`. + */ + predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) { + exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk)) + } + } + + private import Liveness /** - * A classification of SSA variable references into reads and definitions. + * Holds if `df` is in the dominance frontier of `bb`. + * + * This is equivalent to: + * + * ```ql + * bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and + * not bb = getImmediateBasicBlockDominator+(df) + * ``` */ - class SsaRefKind extends TSsaRefKind { - string toString() { - this = SsaActualRead() and - result = "SsaActualRead" - or - this = SsaPhiRead() and - result = "SsaPhiRead" - or - this = SsaDef() and - result = "SsaDef" - } - - int getOrder() { - this instanceof SsaRead and - result = 0 - or - this = SsaDef() and - result = 1 - } + private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) { + bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df) + or + exists(BasicBlock prev | inDominanceFrontier(prev, df) | + bb = getImmediateBasicBlockDominator(prev) and + not bb = getImmediateBasicBlockDominator(df) + ) } /** * Holds if `bb` is in the dominance frontier of a block containing a - * read of `v`. + * definition of `v`. */ - pragma[nomagic] - private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { - exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | - lastRefIsRead(readbb, v) - or - phiRead(readbb, v) + pragma[noinline] + private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock defbb, Definition def | + def.definesAt(v, defbb, _) and + inDominanceFrontier(defbb, bb) ) } - /** - * Holds if a phi-read node should be inserted for variable `v` at the beginning - * of basic block `bb`. - * - * Phi-read nodes are like normal phi nodes, but they are inserted based on reads - * instead of writes, and only if the dominance-frontier block does not already - * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is - * an internal implementation detail that is not exposed. - * - * The motivation for adding phi-reads is to improve performance of the use-use - * calculation in cases where there is a large number of reads that can reach the - * same join-point, and from there reach a large number of basic blocks. Example: - * - * ```cs - * if (a) - * use(x); - * else if (b) - * use(x); - * else if (c) - * use(x); - * else if (d) - * use(x); - * // many more ifs ... - * - * // phi-read for `x` inserted here - * - * // program not mentioning `x`, with large basic block graph - * - * use(x); - * ``` - * - * Without phi-reads, the analysis has to replicate reachability for each of - * the guarded uses of `x`. However, with phi-reads, the analysis will limit - * each conditional use of `x` to reach the basic block containing the phi-read - * node for `x`, and only that basic block will have to compute reachability - * through the remainder of the large program. - * - * Like normal reads, each phi-read node `phi-read` can be reached from exactly - * one SSA definition (without passing through another definition): Assume, for - * the sake of contradiction, that there are two reaching definitions `def1` and - * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest - * dominating definition will prevent the other from reaching `phi-read`. So, at - * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. - * Then `def1` must go through one of its dominance-frontier blocks in order to - * reach `phi-read`. However, such a block will always start with a (normal) phi - * node, which contradicts reachability. - * - * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, - * will dominate `phi-read`. Assuming it doesn't means that the path from `def` - * to `phi-read` goes through a dominance-frontier block, and hence a phi node, - * which contradicts reachability. - */ - pragma[nomagic] - predicate phiRead(BasicBlock bb, SourceVariable v) { - inReadDominanceFrontier(bb, v) and - liveAtEntry(bb, v) and - // only if there are no other references to `v` inside `bb` - not ref(bb, _, v, _) and - not exists(Definition def | def.definesAt(v, bb, _)) - } + cached + newtype TDefinition = + TWriteDef(SourceVariable v, BasicBlock bb, int i) { + variableWrite(bb, i, v, _) and + liveAfterWrite(bb, i, v) + } or + TPhiNode(SourceVariable v, BasicBlock bb) { + inDefDominanceFrontier(bb, v) and + liveAtEntry(bb, v) + } - /** - * Holds if the `i`th node of basic block `bb` is a reference to `v`, - * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` - * is `SsaDef()`). - * - * Unlike `Liveness::ref`, this includes `phi` nodes. - */ - pragma[nomagic] - predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - variableRead(bb, i, v, _) and - k = SsaActualRead() - or - phiRead(bb, v) and - i = -1 and - k = SsaPhiRead() - or - any(Definition def).definesAt(v, bb, i) and - k = SsaDef() - } + private module SsaDefReaches { + newtype TSsaRefKind = + SsaActualRead() or + SsaPhiRead() or + SsaDef() - private newtype OrderedSsaRefIndex = - MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + class SsaRead = SsaActualRead or SsaPhiRead; - private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) { - ssaRef(bb, i, v, k) and - result = MkOrderedSsaRefIndex(i, k) and - ord = k.getOrder() - } + /** + * A classification of SSA variable references into reads and definitions. + */ + class SsaRefKind extends TSsaRefKind { + string toString() { + this = SsaActualRead() and + result = "SsaActualRead" + or + this = SsaPhiRead() and + result = "SsaPhiRead" + or + this = SsaDef() and + result = "SsaDef" + } - /** - * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic - * block `bb`, which has the given reference kind `k`. - * - * For example, if `bb` is a basic block with a phi node for `v` (considered - * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: - * - * ```ql - * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node - * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 - * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 - * ``` - * - * Reads are considered before writes when they happen at the same index. - */ - int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { - ssaRefOrd(bb, i, v, k, _) = - rank[result](int j, int ord, OrderedSsaRefIndex res | - res = ssaRefOrd(bb, j, v, _, ord) - | - res order by j, ord + int getOrder() { + this instanceof SsaRead and + result = 0 + or + this = SsaDef() and + result = 1 + } + } + + /** + * Holds if `bb` is in the dominance frontier of a block containing a + * read of `v`. + */ + pragma[nomagic] + private predicate inReadDominanceFrontier(BasicBlock bb, SourceVariable v) { + exists(BasicBlock readbb | inDominanceFrontier(readbb, bb) | + lastRefIsRead(readbb, v) + or + phiRead(readbb, v) ) - } + } - int maxSsaRefRank(BasicBlock bb, SourceVariable v) { - result = ssaRefRank(bb, _, v, _) and - not result + 1 = ssaRefRank(bb, _, v, _) - } + /** + * Holds if a phi-read node should be inserted for variable `v` at the beginning + * of basic block `bb`. + * + * Phi-read nodes are like normal phi nodes, but they are inserted based on reads + * instead of writes, and only if the dominance-frontier block does not already + * contain a reference (read or write) to `v`. Unlike normal phi nodes, this is + * an internal implementation detail that is not exposed. + * + * The motivation for adding phi-reads is to improve performance of the use-use + * calculation in cases where there is a large number of reads that can reach the + * same join-point, and from there reach a large number of basic blocks. Example: + * + * ```cs + * if (a) + * use(x); + * else if (b) + * use(x); + * else if (c) + * use(x); + * else if (d) + * use(x); + * // many more ifs ... + * + * // phi-read for `x` inserted here + * + * // program not mentioning `x`, with large basic block graph + * + * use(x); + * ``` + * + * Without phi-reads, the analysis has to replicate reachability for each of + * the guarded uses of `x`. However, with phi-reads, the analysis will limit + * each conditional use of `x` to reach the basic block containing the phi-read + * node for `x`, and only that basic block will have to compute reachability + * through the remainder of the large program. + * + * Like normal reads, each phi-read node `phi-read` can be reached from exactly + * one SSA definition (without passing through another definition): Assume, for + * the sake of contradiction, that there are two reaching definitions `def1` and + * `def2`. Now, if both `def1` and `def2` dominate `phi-read`, then the nearest + * dominating definition will prevent the other from reaching `phi-read`. So, at + * least one of `def1` and `def2` cannot dominate `phi-read`; assume it is `def1`. + * Then `def1` must go through one of its dominance-frontier blocks in order to + * reach `phi-read`. However, such a block will always start with a (normal) phi + * node, which contradicts reachability. + * + * Also, like normal reads, the unique SSA definition `def` that reaches `phi-read`, + * will dominate `phi-read`. Assuming it doesn't means that the path from `def` + * to `phi-read` goes through a dominance-frontier block, and hence a phi node, + * which contradicts reachability. + */ + pragma[nomagic] + predicate phiRead(BasicBlock bb, SourceVariable v) { + inReadDominanceFrontier(bb, v) and + liveAtEntry(bb, v) and + // only if there are no other references to `v` inside `bb` + not ref(bb, _, v, _) and + not exists(Definition def | def.definesAt(v, bb, _)) + } - /** - * Holds if the SSA definition `def` reaches rank index `rnk` in its own - * basic block `bb`. - */ - predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { - exists(int i | - rnk = ssaRefRank(bb, i, v, SsaDef()) and - def.definesAt(v, bb, i) - ) - or - ssaDefReachesRank(bb, def, rnk - 1, v) and - rnk = ssaRefRank(bb, _, v, any(SsaRead k)) - } - - /** - * Holds if the SSA definition of `v` at `def` reaches index `i` in the same - * basic block `bb`, without crossing another SSA definition of `v`. - */ - predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { - exists(int rnk | - ssaDefReachesRank(bb, def, rnk, v) and - rnk = ssaRefRank(bb, i, v, any(SsaRead k)) - ) - } - - /** - * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. - */ - int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { - v = def.getSourceVariable() and - result = ssaRefRank(bb, i, v, k) and - ( - ssaDefReachesRead(_, def, bb, i) + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v`, + * either a read (when `k` is `SsaRead()`) or an SSA definition (when `k` + * is `SsaDef()`). + * + * Unlike `Liveness::ref`, this includes `phi` nodes. + */ + pragma[nomagic] + predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + variableRead(bb, i, v, _) and + k = SsaActualRead() or - def.definesAt(_, bb, i) - ) + phiRead(bb, v) and + i = -1 and + k = SsaPhiRead() + or + any(Definition def).definesAt(v, bb, i) and + k = SsaDef() + } + + private newtype OrderedSsaRefIndex = + MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) } + + private OrderedSsaRefIndex ssaRefOrd( + BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord + ) { + ssaRef(bb, i, v, k) and + result = MkOrderedSsaRefIndex(i, k) and + ord = k.getOrder() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic + * block `bb`, which has the given reference kind `k`. + * + * For example, if `bb` is a basic block with a phi node for `v` (considered + * to be at index -1), reads `v` at node 2, and defines it at node 5, we have: + * + * ```ql + * ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node + * ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2 + * ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5 + * ``` + * + * Reads are considered before writes when they happen at the same index. + */ + int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) { + ssaRefOrd(bb, i, v, k, _) = + rank[result](int j, int ord, OrderedSsaRefIndex res | + res = ssaRefOrd(bb, j, v, _, ord) + | + res order by j, ord + ) + } + + int maxSsaRefRank(BasicBlock bb, SourceVariable v) { + result = ssaRefRank(bb, _, v, _) and + not result + 1 = ssaRefRank(bb, _, v, _) + } + + /** + * Holds if the SSA definition `def` reaches rank index `rnk` in its own + * basic block `bb`. + */ + predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) { + exists(int i | + rnk = ssaRefRank(bb, i, v, SsaDef()) and + def.definesAt(v, bb, i) + ) + or + ssaDefReachesRank(bb, def, rnk - 1, v) and + rnk = ssaRefRank(bb, _, v, any(SsaRead k)) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches index `i` in the same + * basic block `bb`, without crossing another SSA definition of `v`. + */ + predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) { + exists(int rnk | + ssaDefReachesRank(bb, def, rnk, v) and + rnk = ssaRefRank(bb, i, v, any(SsaRead k)) + ) + } + + /** + * Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`. + */ + int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) { + v = def.getSourceVariable() and + result = ssaRefRank(bb, i, v, k) and + ( + ssaDefReachesRead(_, def, bb, i) + or + def.definesAt(_, bb, i) + ) + } + + /** + * Holds if the reference to `def` at index `i` in basic block `bb` is the + * last reference to `v` inside `bb`. + */ + pragma[noinline] + predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) + } + + predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { + exists(ssaDefRank(def, v, bb, _, k)) + } + + pragma[noinline] + private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { + ssaDefReachesEndOfBlock(bb, def, _) and + not defOccursInBlock(_, bb, def.getSourceVariable(), _) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ + * predecessor of `bb2`, and the underlying variable for `def` is neither read + * nor written in any block on the path between `bb1` and `bb2`. + * + * Phi reads are considered as normal reads for this predicate. + */ + pragma[nomagic] + private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + defOccursInBlock(def, bb1, _, _) and + bb2 = getABasicBlockSuccessor(bb1) + or + exists(BasicBlock mid | + varBlockReachesInclPhiRead(def, bb1, mid) and + ssaDefReachesThroughBlock(def, mid) and + bb2 = getABasicBlockSuccessor(mid) + ) + } + + pragma[nomagic] + private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(def, bb1, bb2) and + defOccursInBlock(def, bb2, v, SsaPhiRead()) + } + + pragma[nomagic] + private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and + ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) + or + exists(BasicBlock mid | + varBlockReachesExclPhiRead(def, mid, bb2) and + phiReadStep(def, _, bb1, mid) + ) + } + + /** + * Holds if `def` is accessed in basic block `bb1` (either a read or a write), + * the underlying variable `v` of `def` is accessed in basic block `bb2` + * (either a read or a write), `bb2` is a transitive successor of `bb1`, and + * `v` is neither read nor written in any block on the path between `bb1` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { + varBlockReachesExclPhiRead(def, bb1, bb2) and + not defOccursInBlock(def, bb1, _, SsaPhiRead()) + } + + pragma[nomagic] + predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { + varBlockReaches(def, bb1, bb2) and + ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + } + + /** + * Holds if `def` is accessed in basic block `bb` (either a read or a write), + * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, + * and `v` is neither read nor written in any block on the path between `bb` + * and `bb2`. + */ + pragma[nomagic] + predicate varBlockReachesExit(Definition def, BasicBlock bb) { + exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | + not defOccursInBlock(def, bb2, _, _) and + not ssaDefReachesEndOfBlock(bb2, def, _) + ) + or + exists(BasicBlock mid | + varBlockReachesExit(def, mid) and + phiReadStep(def, _, bb, mid) + ) + } + } + + predicate phiReadExposedForTesting = phiRead/2; + + private import SsaDefReaches + + pragma[nomagic] + predicate liveThrough(BasicBlock bb, SourceVariable v) { + liveAtExit(bb, v) and + not ssaRef(bb, _, v, SsaDef()) } /** - * Holds if the reference to `def` at index `i` in basic block `bb` is the - * last reference to `v` inside `bb`. - */ - pragma[noinline] - predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) - } - - predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v, SsaRefKind k) { - exists(ssaDefRank(def, v, bb, _, k)) - } - - pragma[noinline] - private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) { - ssaDefReachesEndOfBlock(bb, def, _) and - not defOccursInBlock(_, bb, def.getSourceVariable(), _) - } - - /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * `bb2` is a transitive successor of `bb1`, `def` is live at the end of _some_ - * predecessor of `bb2`, and the underlying variable for `def` is neither read - * nor written in any block on the path between `bb1` and `bb2`. + * NB: If this predicate is exposed, it should be cached. * - * Phi reads are considered as normal reads for this predicate. + * Holds if the SSA definition of `v` at `def` reaches the end of basic + * block `bb`, at which point it is still live, without crossing another + * SSA definition of `v`. */ pragma[nomagic] - private predicate varBlockReachesInclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - defOccursInBlock(def, bb1, _, _) and - bb2 = getABasicBlockSuccessor(bb1) - or - exists(BasicBlock mid | - varBlockReachesInclPhiRead(def, bb1, mid) and - ssaDefReachesThroughBlock(def, mid) and - bb2 = getABasicBlockSuccessor(mid) + predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { + exists(int last | + last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and + ssaDefReachesRank(bb, def, last, v) and + liveAtExit(bb, v) ) - } - - pragma[nomagic] - private predicate phiReadStep(Definition def, SourceVariable v, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(def, bb1, bb2) and - defOccursInBlock(def, bb2, v, SsaPhiRead()) - } - - pragma[nomagic] - private predicate varBlockReachesExclPhiRead(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesInclPhiRead(pragma[only_bind_into](def), bb1, pragma[only_bind_into](bb2)) and - ssaRef(bb2, _, def.getSourceVariable(), [SsaActualRead().(TSsaRefKind), SsaDef()]) or - exists(BasicBlock mid | - varBlockReachesExclPhiRead(def, mid, bb2) and - phiReadStep(def, _, bb1, mid) + // The construction of SSA form ensures that each read of a variable is + // dominated by its definition. An SSA definition therefore reaches a + // control flow node if it is the _closest_ SSA definition that dominates + // the node. If two definitions dominate a node then one must dominate the + // other, so therefore the definition of _closest_ is given by the dominator + // tree. Thus, reaching definitions can be calculated in terms of dominance. + ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and + liveThrough(bb, pragma[only_bind_into](v)) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. + */ + pragma[nomagic] + predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { + exists(SourceVariable v, BasicBlock bbDef | + phi.definesAt(v, bbDef, _) and + getABasicBlockPredecessor(bbDef) = bb and + ssaDefReachesEndOfBlock(bb, inp, v) ) } /** - * Holds if `def` is accessed in basic block `bb1` (either a read or a write), - * the underlying variable `v` of `def` is accessed in basic block `bb2` - * (either a read or a write), `bb2` is a transitive successor of `bb1`, and - * `v` is neither read nor written in any block on the path between `bb1` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in + * basic block `bb`, without crossing another SSA definition of `v`. The read + * is of kind `rk`. */ pragma[nomagic] - predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) { - varBlockReachesExclPhiRead(def, bb1, bb2) and - not defOccursInBlock(def, bb1, _, SsaPhiRead()) - } - - pragma[nomagic] - predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) { - varBlockReaches(def, bb1, bb2) and - ssaRefRank(bb2, i2, def.getSourceVariable(), SsaActualRead()) = 1 + predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { + ssaDefReachesReadWithinBlock(v, def, bb, i) + or + ssaRef(bb, i, v, any(SsaRead k)) and + ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and + not ssaDefReachesReadWithinBlock(v, _, bb, i) } /** - * Holds if `def` is accessed in basic block `bb` (either a read or a write), - * `bb1` can reach a transitive successor `bb2` where `def` is no longer live, - * and `v` is neither read nor written in any block on the path between `bb` - * and `bb2`. + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read + * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a + * path between them without any read of `def`. */ pragma[nomagic] - predicate varBlockReachesExit(Definition def, BasicBlock bb) { - exists(BasicBlock bb2 | varBlockReachesInclPhiRead(def, bb, bb2) | - not defOccursInBlock(def, bb2, _, _) and - not ssaDefReachesEndOfBlock(bb2, def, _) + predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { + exists(int rnk | + rnk = ssaDefRank(def, _, bb1, i1, _) and + rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and + variableRead(bb1, i2, _, _) and + bb2 = bb1 ) or - exists(BasicBlock mid | - varBlockReachesExit(def, mid) and - phiReadStep(def, _, bb, mid) - ) - } -} - -predicate phiReadExposedForTesting = phiRead/2; - -private import SsaDefReaches - -pragma[nomagic] -predicate liveThrough(BasicBlock bb, SourceVariable v) { - liveAtExit(bb, v) and - not ssaRef(bb, _, v, SsaDef()) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches the end of basic - * block `bb`, at which point it is still live, without crossing another - * SSA definition of `v`. - */ -pragma[nomagic] -predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) { - exists(int last | - last = maxSsaRefRank(pragma[only_bind_into](bb), pragma[only_bind_into](v)) and - ssaDefReachesRank(bb, def, last, v) and - liveAtExit(bb, v) - ) - or - // The construction of SSA form ensures that each read of a variable is - // dominated by its definition. An SSA definition therefore reaches a - // control flow node if it is the _closest_ SSA definition that dominates - // the node. If two definitions dominate a node then one must dominate the - // other, so therefore the definition of _closest_ is given by the dominator - // tree. Thus, reaching definitions can be calculated in terms of dominance. - ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and - liveThrough(bb, pragma[only_bind_into](v)) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`. - */ -pragma[nomagic] -predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) { - exists(SourceVariable v, BasicBlock bbDef | - phi.definesAt(v, bbDef, _) and - getABasicBlockPredecessor(bbDef) = bb and - ssaDefReachesEndOfBlock(bb, inp, v) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the SSA definition of `v` at `def` reaches a read at index `i` in - * basic block `bb`, without crossing another SSA definition of `v`. The read - * is of kind `rk`. - */ -pragma[nomagic] -predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) { - ssaDefReachesReadWithinBlock(v, def, bb, i) - or - ssaRef(bb, i, v, any(SsaRead k)) and - ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and - not ssaDefReachesReadWithinBlock(v, _, bb, i) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read - * or a write), `def` is read at index `i2` in basic block `bb2`, and there is a - * path between them without any read of `def`. - */ -pragma[nomagic] -predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - exists(int rnk | - rnk = ssaDefRank(def, _, bb1, i1, _) and - rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaActualRead()) and - variableRead(bb1, i2, _, _) and - bb2 = bb1 - ) - or - lastSsaRef(def, _, bb1, i1) and - defAdjacentRead(def, bb1, bb2, i2) -} - -pragma[noinline] -private predicate adjacentDefRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v -) { - adjacentDefRead(def, bb1, i1, bb2, i2) and - v = def.getSourceVariable() -} - -private predicate adjacentDefReachesRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | - ssaRef(bb1, i1, v, SsaDef()) - or - variableRead(bb1, i1, v, true) - ) - or - exists(BasicBlock bb3, int i3 | - adjacentDefReachesRead(def, bb1, i1, bb3, i3) and - variableRead(bb3, i3, _, false) and - adjacentDefRead(def, bb3, i3, bb2, i2) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `adjacentDefRead`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, true) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA definition - * `def`. The reference is last because it can reach another write `next`, - * without passing through another read or write. - */ -pragma[nomagic] -predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { - exists(SourceVariable v | - // Next reference to `v` inside `bb` is a write - exists(int rnk, int j | - rnk = ssaDefRank(def, v, bb, i, _) and - next.definesAt(v, bb, j) and - rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) - ) - or - // Can reach a write using one or more steps - lastSsaRef(def, v, bb, i) and - exists(BasicBlock bb2 | - varBlockReaches(def, bb, bb2) and - 1 = ssaDefRank(next, v, bb2, _, SsaDef()) - ) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if `inp` is an immediately preceding definition of uncertain definition - * `def`. Since `def` is uncertain, the value from the preceding definition might - * still be valid. - */ -pragma[nomagic] -predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { - lastRefRedef(inp, _, _, def) -} - -private predicate adjacentDefReachesUncertainRead( - Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 -) { - adjacentDefReachesRead(def, bb1, i1, bb2, i2) and - variableRead(bb2, i2, _, false) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { - lastRefRedef(def, bb, i, next) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRefRedef(def, bb0, i0, next) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Holds if the node at index `i` in `bb` is a last reference to SSA - * definition `def`. - * - * That is, the node can reach the end of the enclosing callable, or another - * SSA definition for the underlying source variable, without passing through - * another read. - */ -pragma[nomagic] -predicate lastRef(Definition def, BasicBlock bb, int i) { - // Can reach another definition - lastRefRedef(def, bb, i, _) - or - exists(SourceVariable v | lastSsaRef(def, v, bb, i) | - // Can reach exit directly - bb instanceof ExitBasicBlock - or - // Can reach a block using one or more steps, where `def` is no longer live - varBlockReachesExit(def, bb) - ) -} - -/** - * NB: If this predicate is exposed, it should be cached. - * - * Same as `lastRefRedef`, but ignores uncertain reads. - */ -pragma[nomagic] -predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { - lastRef(def, bb, i) and - not variableRead(bb, i, def.getSourceVariable(), false) - or - exists(BasicBlock bb0, int i0 | - lastRef(def, bb0, i0) and - adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) - ) -} - -/** A static single assignment (SSA) definition. */ -class Definition extends TDefinition { - /** Gets the source variable underlying this SSA definition. */ - SourceVariable getSourceVariable() { this.definesAt(result, _, _) } - - /** - * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. - * Phi nodes are considered to be at index `-1`, while normal variable writes - * are at the index of the control flow node they wrap. - */ - final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { - this = TWriteDef(v, bb, i) - or - this = TPhiNode(v, bb) and i = -1 + lastSsaRef(def, _, bb1, i1) and + defAdjacentRead(def, bb1, bb2, i2) } - /** Gets the basic block to which this SSA definition belongs. */ - final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } - - /** Gets a textual representation of this SSA definition. */ - string toString() { none() } -} - -/** An SSA definition that corresponds to a write. */ -class WriteDefinition extends Definition, TWriteDef { - private SourceVariable v; - private BasicBlock bb; - private int i; - - WriteDefinition() { this = TWriteDef(v, bb, i) } - - override string toString() { result = "WriteDef" } -} - -/** A phi node. */ -class PhiNode extends Definition, TPhiNode { - override string toString() { result = "Phi" } -} - -/** - * An SSA definition that represents an uncertain update of the underlying - * source variable. - */ -class UncertainWriteDefinition extends WriteDefinition { - UncertainWriteDefinition() { - exists(SourceVariable v, BasicBlock bb, int i | - this.definesAt(v, bb, i) and - variableWrite(bb, i, v, false) - ) - } -} - -/** Provides a set of consistency queries. */ -module Consistency { - abstract class RelevantDefinition extends Definition { - abstract predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ); + pragma[noinline] + private predicate adjacentDefRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v + ) { + adjacentDefRead(def, bb1, i1, bb2, i2) and + v = def.getSourceVariable() } - query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - ssaDefReachesRead(v, def, bb, i) and - not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) - } - - query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { - variableRead(bb, i, v, _) and - not ssaDefReachesRead(v, _, bb, i) - } - - query predicate deadDef(RelevantDefinition def, SourceVariable v) { - v = def.getSourceVariable() and - not ssaDefReachesRead(_, def, _, _) and - not phiHasInputFromBlock(_, def, _) and - not uncertainWriteDefinitionInput(_, def) - } - - query predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { - exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | - ssaDefReachesReadWithinBlock(v, def, bb, i) and - (bb != bbDef or i < iDef) + private predicate adjacentDefReachesRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) | + ssaRef(bb1, i1, v, SsaDef()) or - ssaDefReachesRead(v, def, bb, i) and - not ssaDefReachesReadWithinBlock(v, def, bb, i) and - not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + variableRead(bb1, i1, v, true) + ) + or + exists(BasicBlock bb3, int i3 | + adjacentDefReachesRead(def, bb1, i1, bb3, i3) and + variableRead(bb3, i3, _, false) and + adjacentDefRead(def, bb3, i3, bb2, i2) ) } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `adjacentDefRead`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate adjacentDefNoUncertainReads( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, true) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA definition + * `def`. The reference is last because it can reach another write `next`, + * without passing through another read or write. + */ + pragma[nomagic] + predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) { + exists(SourceVariable v | + // Next reference to `v` inside `bb` is a write + exists(int rnk, int j | + rnk = ssaDefRank(def, v, bb, i, _) and + next.definesAt(v, bb, j) and + rnk + 1 = ssaRefRank(bb, j, v, SsaDef()) + ) + or + // Can reach a write using one or more steps + lastSsaRef(def, v, bb, i) and + exists(BasicBlock bb2 | + varBlockReaches(def, bb, bb2) and + 1 = ssaDefRank(next, v, bb2, _, SsaDef()) + ) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if `inp` is an immediately preceding definition of uncertain definition + * `def`. Since `def` is uncertain, the value from the preceding definition might + * still be valid. + */ + pragma[nomagic] + predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) { + lastRefRedef(inp, _, _, def) + } + + private predicate adjacentDefReachesUncertainRead( + Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2 + ) { + adjacentDefReachesRead(def, bb1, i1, bb2, i2) and + variableRead(bb2, i2, _, false) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) { + lastRefRedef(def, bb, i, next) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRefRedef(def, bb0, i0, next) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Holds if the node at index `i` in `bb` is a last reference to SSA + * definition `def`. + * + * That is, the node can reach the end of the enclosing callable, or another + * SSA definition for the underlying source variable, without passing through + * another read. + */ + pragma[nomagic] + predicate lastRef(Definition def, BasicBlock bb, int i) { + // Can reach another definition + lastRefRedef(def, bb, i, _) + or + exists(SourceVariable v | lastSsaRef(def, v, bb, i) | + // Can reach exit directly + bb instanceof ExitBasicBlock + or + // Can reach a block using one or more steps, where `def` is no longer live + varBlockReachesExit(def, bb) + ) + } + + /** + * NB: If this predicate is exposed, it should be cached. + * + * Same as `lastRefRedef`, but ignores uncertain reads. + */ + pragma[nomagic] + predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) { + lastRef(def, bb, i) and + not variableRead(bb, i, def.getSourceVariable(), false) + or + exists(BasicBlock bb0, int i0 | + lastRef(def, bb0, i0) and + adjacentDefReachesUncertainRead(def, bb, i, bb0, i0) + ) + } + + /** A static single assignment (SSA) definition. */ + class Definition extends TDefinition { + /** Gets the source variable underlying this SSA definition. */ + SourceVariable getSourceVariable() { this.definesAt(result, _, _) } + + /** + * Holds if this SSA definition defines `v` at index `i` in basic block `bb`. + * Phi nodes are considered to be at index `-1`, while normal variable writes + * are at the index of the control flow node they wrap. + */ + final predicate definesAt(SourceVariable v, BasicBlock bb, int i) { + this = TWriteDef(v, bb, i) + or + this = TPhiNode(v, bb) and i = -1 + } + + /** Gets the basic block to which this SSA definition belongs. */ + final BasicBlock getBasicBlock() { this.definesAt(_, result, _) } + + /** Gets a textual representation of this SSA definition. */ + string toString() { none() } + } + + /** An SSA definition that corresponds to a write. */ + class WriteDefinition extends Definition, TWriteDef { + private SourceVariable v; + private BasicBlock bb; + private int i; + + WriteDefinition() { this = TWriteDef(v, bb, i) } + + override string toString() { result = "WriteDef" } + } + + /** A phi node. */ + class PhiNode extends Definition, TPhiNode { + override string toString() { result = "Phi" } + } + + /** + * An SSA definition that represents an uncertain update of the underlying + * source variable. + */ + class UncertainWriteDefinition extends WriteDefinition { + UncertainWriteDefinition() { + exists(SourceVariable v, BasicBlock bb, int i | + this.definesAt(v, bb, i) and + variableWrite(bb, i, v, false) + ) + } + } + + /** Provides a set of consistency queries. */ + // TODO: Make these `query` predicates once class signatures are supported + // (`SourceVariable` and `BasicBlock` must have `toString`) + module Consistency { + abstract class RelevantDefinition extends Definition { + abstract predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ); + } + + predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + ssaDefReachesRead(v, def, bb, i) and + not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i))) + } + + predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) { + variableRead(bb, i, v, _) and + not ssaDefReachesRead(v, _, bb, i) + } + + predicate deadDef(RelevantDefinition def, SourceVariable v) { + v = def.getSourceVariable() and + not ssaDefReachesRead(_, def, _, _) and + not phiHasInputFromBlock(_, def, _) and + not uncertainWriteDefinitionInput(_, def) + } + + predicate notDominatedByDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) { + exists(BasicBlock bbDef, int iDef | def.definesAt(v, bbDef, iDef) | + ssaDefReachesReadWithinBlock(v, def, bb, i) and + (bb != bbDef or i < iDef) + or + ssaDefReachesRead(v, def, bb, i) and + not ssaDefReachesReadWithinBlock(v, def, bb, i) and + not def.definesAt(v, getImmediateBasicBlockDominator*(bb), _) + ) + } + } } From 61b67640f4b19a518633de19e390dba21ed7cb41 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 29 Aug 2022 13:25:56 +0200 Subject: [PATCH 6/8] Ruby: Adapt to parameterized SSA implementation --- ruby/ql/consistency-queries/SsaConsistency.ql | 12 +- ruby/ql/lib/codeql/ruby/dataflow/SSA.qll | 15 +- .../codeql/ruby/dataflow/internal/SsaImpl.qll | 158 ++++++++++++------ .../ruby/dataflow/internal/SsaImplCommon.qll | 6 +- .../dataflow/internal/SsaImplSpecific.qll | 45 ----- 5 files changed, 123 insertions(+), 113 deletions(-) delete mode 100644 ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll diff --git a/ruby/ql/consistency-queries/SsaConsistency.ql b/ruby/ql/consistency-queries/SsaConsistency.ql index b1725ef3e85..54c1b149ab2 100644 --- a/ruby/ql/consistency-queries/SsaConsistency.ql +++ b/ruby/ql/consistency-queries/SsaConsistency.ql @@ -1,10 +1,18 @@ import codeql.ruby.dataflow.SSA -import codeql.ruby.dataflow.internal.SsaImplCommon::Consistency +import codeql.ruby.dataflow.internal.SsaImpl::Consistency as Consistency -class MyRelevantDefinition extends RelevantDefinition, Ssa::Definition { +class MyRelevantDefinition extends Consistency::RelevantDefinition, Ssa::Definition { override predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) } } + +query predicate nonUniqueDef = Consistency::nonUniqueDef/4; + +query predicate readWithoutDef = Consistency::readWithoutDef/3; + +query predicate deadDef = Consistency::deadDef/2; + +query predicate notDominatedByDef = Consistency::notDominatedByDef/4; diff --git a/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll index b8880b39829..efae79e8d29 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/SSA.qll @@ -9,12 +9,11 @@ module Ssa { private import codeql.Locations private import codeql.ruby.CFG private import codeql.ruby.ast.Variable - private import internal.SsaImplCommon as SsaImplCommon private import internal.SsaImpl as SsaImpl private import CfgNodes::ExprNodes /** A static single assignment (SSA) definition. */ - class Definition extends SsaImplCommon::Definition { + class Definition extends SsaImpl::Definition { /** * Gets the control flow node of this SSA definition, if any. Phi nodes are * examples of SSA definitions without a control flow node, as they are @@ -190,7 +189,7 @@ module Ssa { * puts x * ``` */ - class WriteDefinition extends Definition, SsaImplCommon::WriteDefinition { + class WriteDefinition extends Definition, SsaImpl::WriteDefinition { private VariableWriteAccess write; WriteDefinition() { @@ -223,7 +222,7 @@ module Ssa { /** * An SSA definition that corresponds to the value of `self` upon entry to a method, class or module. */ - class SelfDefinition extends Definition, SsaImplCommon::WriteDefinition { + class SelfDefinition extends Definition, SsaImpl::WriteDefinition { private SelfVariable v; SelfDefinition() { @@ -254,7 +253,7 @@ module Ssa { * since the assignment to `x` is conditional, an unitialized definition for * `x` is inserted at the start of `m`. */ - class UninitializedDefinition extends Definition, SsaImplCommon::WriteDefinition { + class UninitializedDefinition extends Definition, SsaImpl::WriteDefinition { UninitializedDefinition() { exists(BasicBlock bb, int i, Variable v | this.definesAt(v, bb, i) and @@ -283,7 +282,7 @@ module Ssa { * * an entry definition for `y` is inserted at the start of the `do` block. */ - class CapturedEntryDefinition extends Definition, SsaImplCommon::WriteDefinition { + class CapturedEntryDefinition extends Definition, SsaImpl::WriteDefinition { CapturedEntryDefinition() { exists(BasicBlock bb, int i, Variable v | this.definesAt(v, bb, i) and @@ -312,7 +311,7 @@ module Ssa { * * a definition for `y` is inserted at the call to `times`. */ - class CapturedCallDefinition extends Definition, SsaImplCommon::UncertainWriteDefinition { + class CapturedCallDefinition extends Definition, SsaImpl::UncertainWriteDefinition { CapturedCallDefinition() { exists(Variable v, BasicBlock bb, int i | this.definesAt(v, bb, i) and @@ -343,7 +342,7 @@ module Ssa { * * a phi node for `x` is inserted just before the call `puts x`. */ - class PhiNode extends Definition, SsaImplCommon::PhiNode { + class PhiNode extends Definition, SsaImpl::PhiNode { /** * Gets an input of this phi node. * diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll index ac0e9f79838..50f0b38d28c 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qll @@ -1,19 +1,72 @@ -private import SsaImplCommon -private import SsaImplSpecific as SsaImplSpecific +private import SsaImplCommon as SsaImplCommon private import codeql.ruby.AST -private import codeql.ruby.CFG +private import codeql.ruby.CFG as CFG private import codeql.ruby.ast.Variable -private import CfgNodes::ExprNodes +private import CFG::CfgNodes::ExprNodes + +private module SsaInput implements SsaImplCommon::InputSig { + private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks + + class BasicBlock = BasicBlocks::BasicBlock; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + + class ExitBasicBlock = BasicBlocks::ExitBasicBlock; + + class SourceVariable = LocalVariable; + + /** + * Holds if the statement at index `i` of basic block `bb` contains a write to variable `v`. + * `certain` is true if the write definitely occurs. + */ + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + ( + exists(Scope scope | scope = v.(SelfVariable).getDeclaringScope() | + // We consider the `self` variable to have a single write at the entry to a method block... + scope = bb.(BasicBlocks::EntryBasicBlock).getScope() and + i = 0 + or + // ...or a class or module block. + bb.getNode(i).getNode() = scope.(ModuleBase).getAControlFlowEntryNode() + ) + or + uninitializedWrite(bb, i, v) + or + capturedEntryWrite(bb, i, v) + or + variableWriteActual(bb, i, v, _) + ) and + certain = true + or + capturedCallWrite(_, bb, i, v) and + certain = false + } + + predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) { + variableReadActual(bb, i, v) and + certain = true + or + capturedCallRead(_, bb, i, v) and + certain = false + or + capturedExitRead(bb, i, v) and + certain = false + } +} + +import SsaImplCommon::Make /** Holds if `v` is uninitialized at index `i` in entry block `bb`. */ -predicate uninitializedWrite(EntryBasicBlock bb, int i, LocalVariable v) { +predicate uninitializedWrite(CFG::EntryBasicBlock bb, int i, LocalVariable v) { v.getDeclaringScope() = bb.getScope() and i = -1 } /** Holds if `bb` contains a caputured read of variable `v`. */ pragma[noinline] -private predicate hasCapturedVariableRead(BasicBlock bb, LocalVariable v) { +private predicate hasCapturedVariableRead(CFG::BasicBlock bb, LocalVariable v) { exists(LocalVariableReadAccess read | read = bb.getANode().getNode() and read.isCapturedAccess() and @@ -23,7 +76,7 @@ private predicate hasCapturedVariableRead(BasicBlock bb, LocalVariable v) { /** Holds if `bb` contains a caputured write to variable `v`. */ pragma[noinline] -private predicate writesCapturedVariable(BasicBlock bb, LocalVariable v) { +private predicate writesCapturedVariable(CFG::BasicBlock bb, LocalVariable v) { exists(LocalVariableWriteAccess write | write = bb.getANode().getNode() and write.isCapturedAccess() and @@ -35,7 +88,7 @@ private predicate writesCapturedVariable(BasicBlock bb, LocalVariable v) { * Holds if a pseudo read of captured variable `v` should be inserted * at index `i` in exit block `bb`. */ -private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVariable v) { +private predicate capturedExitRead(CFG::AnnotatedExitBasicBlock bb, int i, LocalVariable v) { bb.isNormal() and writesCapturedVariable(bb.getAPredecessor*(), v) and i = bb.length() @@ -46,7 +99,7 @@ private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVaria * or inside a (transitively) nested scope of `scope`. */ pragma[noinline] -private predicate hasCapturedRead(Variable v, CfgScope scope) { +private predicate hasCapturedRead(Variable v, CFG::CfgScope scope) { any(LocalVariableReadAccess read | read.getVariable() = v and scope = read.getCfgScope().getOuterCfgScope*() ).isCapturedAccess() @@ -57,13 +110,15 @@ private predicate hasCapturedRead(Variable v, CfgScope scope) { * outer scope of `scope`. */ pragma[noinline] -private predicate variableWriteInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) { - SsaImplSpecific::variableWrite(bb, _, v, _) and +private predicate variableWriteInOuterScope(CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope) { + SsaInput::variableWrite(bb, _, v, _) and scope.getOuterCfgScope() = bb.getScope() } pragma[noinline] -private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable v, CfgScope scope) { +private predicate hasVariableWriteWithCapturedRead( + CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope +) { hasCapturedRead(v, scope) and variableWriteInOuterScope(bb, v, scope) } @@ -72,10 +127,8 @@ private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable * Holds if the call `call` at index `i` in basic block `bb` may reach * a callable that reads captured variable `v`. */ -private predicate capturedCallRead( - CfgNodes::ExprNodes::CallCfgNode call, BasicBlock bb, int i, LocalVariable v -) { - exists(CfgScope scope | +private predicate capturedCallRead(CallCfgNode call, CFG::BasicBlock bb, int i, LocalVariable v) { + exists(CFG::CfgScope scope | hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and call = bb.getNode(i) | @@ -88,30 +141,19 @@ private predicate capturedCallRead( } /** Holds if `v` is read at index `i` in basic block `bb`. */ -private predicate variableReadActual(BasicBlock bb, int i, LocalVariable v) { +private predicate variableReadActual(CFG::BasicBlock bb, int i, LocalVariable v) { exists(VariableReadAccess read | read.getVariable() = v and read = bb.getNode(i).getNode() ) } -predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) { - variableReadActual(bb, i, v) and - certain = true - or - capturedCallRead(_, bb, i, v) and - certain = false - or - capturedExitRead(bb, i, v) and - certain = false -} - /** * Holds if captured variable `v` is written directly inside `scope`, * or inside a (transitively) nested scope of `scope`. */ pragma[noinline] -private predicate hasCapturedWrite(Variable v, CfgScope scope) { +private predicate hasCapturedWrite(Variable v, CFG::CfgScope scope) { any(LocalVariableWriteAccess write | write.getVariable() = v and scope = write.getCfgScope().getOuterCfgScope*() ).isCapturedAccess() @@ -122,13 +164,17 @@ private predicate hasCapturedWrite(Variable v, CfgScope scope) { * outer scope of `scope`. */ pragma[noinline] -private predicate variableReadActualInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) { +private predicate variableReadActualInOuterScope( + CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope +) { variableReadActual(bb, _, v) and bb.getScope() = scope.getOuterCfgScope() } pragma[noinline] -private predicate hasVariableReadWithCapturedWrite(BasicBlock bb, LocalVariable v, CfgScope scope) { +private predicate hasVariableReadWithCapturedWrite( + CFG::BasicBlock bb, LocalVariable v, CFG::CfgScope scope +) { hasCapturedWrite(v, scope) and variableReadActualInOuterScope(bb, v, scope) } @@ -140,7 +186,7 @@ private module Cached { * `i` in entry block `bb`. */ cached - predicate capturedEntryWrite(EntryBasicBlock bb, int i, LocalVariable v) { + predicate capturedEntryWrite(CFG::EntryBasicBlock bb, int i, LocalVariable v) { hasCapturedVariableRead(bb.getASuccessor*(), v) and i = -1 } @@ -150,10 +196,8 @@ private module Cached { * that writes captured variable `v`. */ cached - predicate capturedCallWrite( - CfgNodes::ExprNodes::CallCfgNode call, BasicBlock bb, int i, LocalVariable v - ) { - exists(CfgScope scope | + predicate capturedCallWrite(CallCfgNode call, CFG::BasicBlock bb, int i, LocalVariable v) { + exists(CFG::CfgScope scope | hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and call = bb.getNode(i) | @@ -170,7 +214,9 @@ private module Cached { * AST write access is `write`. */ cached - predicate variableWriteActual(BasicBlock bb, int i, LocalVariable v, VariableWriteAccess write) { + predicate variableWriteActual( + CFG::BasicBlock bb, int i, LocalVariable v, VariableWriteAccess write + ) { exists(AstNode n | write.getVariable() = v and n = bb.getNode(i).getNode() @@ -184,7 +230,7 @@ private module Cached { cached VariableReadAccessCfgNode getARead(Definition def) { - exists(LocalVariable v, BasicBlock bb, int i | + exists(LocalVariable v, CFG::BasicBlock bb, int i | ssaDefReachesRead(v, def, bb, i) and variableReadActual(bb, i, v) and result = bb.getNode(i) @@ -193,9 +239,9 @@ private module Cached { pragma[noinline] private predicate defReachesCallReadInOuterScope( - Definition def, CfgNodes::ExprNodes::CallCfgNode call, LocalVariable v, CfgScope scope + Definition def, CallCfgNode call, LocalVariable v, CFG::CfgScope scope ) { - exists(BasicBlock bb, int i | + exists(CFG::BasicBlock bb, int i | ssaDefReachesRead(v, def, bb, i) and capturedCallRead(call, bb, i, v) and scope.getOuterCfgScope() = bb.getScope() @@ -203,8 +249,8 @@ private module Cached { } pragma[noinline] - private predicate hasCapturedEntryWrite(Definition entry, LocalVariable v, CfgScope scope) { - exists(BasicBlock bb, int i | + private predicate hasCapturedEntryWrite(Definition entry, LocalVariable v, CFG::CfgScope scope) { + exists(CFG::BasicBlock bb, int i | capturedEntryWrite(bb, i, v) and entry.definesAt(v, bb, i) and bb.getScope().getOuterCfgScope*() = scope @@ -221,8 +267,8 @@ private module Cached { * ``` */ cached - predicate captureFlowIn(CfgNodes::ExprNodes::CallCfgNode call, Definition def, Definition entry) { - exists(LocalVariable v, CfgScope scope | + predicate captureFlowIn(CallCfgNode call, Definition def, Definition entry) { + exists(LocalVariable v, CFG::CfgScope scope | defReachesCallReadInOuterScope(def, call, v, scope) and hasCapturedEntryWrite(entry, v, scope) | @@ -237,8 +283,10 @@ private module Cached { private import codeql.ruby.dataflow.SSA pragma[noinline] - private predicate defReachesExitReadInInnerScope(Definition def, LocalVariable v, CfgScope scope) { - exists(BasicBlock bb, int i | + private predicate defReachesExitReadInInnerScope( + Definition def, LocalVariable v, CFG::CfgScope scope + ) { + exists(CFG::BasicBlock bb, int i | ssaDefReachesRead(v, def, bb, i) and capturedExitRead(bb, i, v) and scope = bb.getScope().getOuterCfgScope*() @@ -247,9 +295,9 @@ private module Cached { pragma[noinline] private predicate hasCapturedExitRead( - Definition exit, CfgNodes::ExprNodes::CallCfgNode call, LocalVariable v, CfgScope scope + Definition exit, CallCfgNode call, LocalVariable v, CFG::CfgScope scope ) { - exists(BasicBlock bb, int i | + exists(CFG::BasicBlock bb, int i | capturedCallWrite(call, bb, i, v) and exit.definesAt(v, bb, i) and bb.getScope() = scope.getOuterCfgScope() @@ -267,8 +315,8 @@ private module Cached { * ``` */ cached - predicate captureFlowOut(CfgNodes::ExprNodes::CallCfgNode call, Definition def, Definition exit) { - exists(LocalVariable v, CfgScope scope | + predicate captureFlowOut(CallCfgNode call, Definition def, Definition exit) { + exists(LocalVariable v, CFG::CfgScope scope | defReachesExitReadInInnerScope(def, v, scope) and hasCapturedExitRead(exit, call, v, _) | @@ -281,7 +329,7 @@ private module Cached { } cached - Definition phiHasInputFromBlock(PhiNode phi, BasicBlock bb) { + Definition phiHasInputFromBlock(PhiNode phi, CFG::BasicBlock bb) { phiHasInputFromBlock(phi, result, bb) } @@ -291,7 +339,7 @@ private module Cached { */ cached predicate firstRead(Definition def, VariableReadAccessCfgNode read) { - exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + exists(CFG::BasicBlock bb1, int i1, CFG::BasicBlock bb2, int i2 | def.definesAt(_, bb1, i1) and adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and read = bb2.getNode(i2) @@ -307,7 +355,7 @@ private module Cached { predicate adjacentReadPair( Definition def, VariableReadAccessCfgNode read1, VariableReadAccessCfgNode read2 ) { - exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + exists(CFG::BasicBlock bb1, int i1, CFG::BasicBlock bb2, int i2 | read1 = bb1.getNode(i1) and variableReadActual(bb1, i1, _) and adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and @@ -322,7 +370,7 @@ private module Cached { */ cached predicate lastRead(Definition def, VariableReadAccessCfgNode read) { - exists(BasicBlock bb, int i | + exists(CFG::BasicBlock bb, int i | lastRefNoUncertainReads(def, bb, i) and variableReadActual(bb, i, _) and read = bb.getNode(i) @@ -337,7 +385,7 @@ private module Cached { * The reference is either a read of `def` or `def` itself. */ cached - predicate lastRefBeforeRedef(Definition def, BasicBlock bb, int i, Definition next) { + predicate lastRefBeforeRedef(Definition def, CFG::BasicBlock bb, int i, Definition next) { lastRefRedefNoUncertainReads(def, bb, i, next) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll index 3c706849415..130ab86a64c 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll @@ -4,7 +4,7 @@ */ /** Provides the input specification of the SSA implementation. */ -signature module SsaInputSig { +signature module InputSig { /** * A basic block, that is, a maximal straight-line sequence of control flow nodes * without branches or joins. @@ -28,7 +28,7 @@ signature module SsaInputSig { * ``` * * The basic block starting on line 2 is an immediate dominator of - * the basic block online 4 (all paths from the entry point of `M` + * the basic block on line 4 (all paths from the entry point of `M` * to `return s.Length;` must go through the null check. */ BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); @@ -82,7 +82,7 @@ signature module SsaInputSig { * NB: If this predicate is exposed, it should be cached. * ``` */ -module Make { +module Make { private import Input private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll deleted file mode 100644 index 0e8af2a3f35..00000000000 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplSpecific.qll +++ /dev/null @@ -1,45 +0,0 @@ -/** Provides the Ruby specific parameters for `SsaImplCommon.qll`. */ - -private import SsaImpl as SsaImpl -private import codeql.ruby.AST -private import codeql.ruby.controlflow.BasicBlocks as BasicBlocks -private import codeql.ruby.controlflow.ControlFlowGraph - -class BasicBlock = BasicBlocks::BasicBlock; - -BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } - -BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } - -class ExitBasicBlock = BasicBlocks::ExitBasicBlock; - -class SourceVariable = LocalVariable; - -/** - * Holds if the statement at index `i` of basic block `bb` contains a write to variable `v`. - * `certain` is true if the write definitely occurs. - */ -predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { - ( - exists(Scope scope | scope = v.(SelfVariable).getDeclaringScope() | - // We consider the `self` variable to have a single write at the entry to a method block... - scope = bb.(BasicBlocks::EntryBasicBlock).getScope() and - i = 0 - or - // ...or a class or module block. - bb.getNode(i).getNode() = scope.(ModuleBase).getAControlFlowEntryNode() - ) - or - SsaImpl::uninitializedWrite(bb, i, v) - or - SsaImpl::capturedEntryWrite(bb, i, v) - or - SsaImpl::variableWriteActual(bb, i, v, _) - ) and - certain = true - or - SsaImpl::capturedCallWrite(_, bb, i, v) and - certain = false -} - -predicate variableRead = SsaImpl::variableRead/4; From 643efb3d6be743846f924a0da7c0df71ce6b8a38 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 29 Aug 2022 13:48:34 +0200 Subject: [PATCH 7/8] Swift: Adapt to parameterized SSA implementation --- swift/ql/lib/codeql/swift/dataflow/Ssa.qll | 120 +++++++++++++++--- .../swift/dataflow/internal/SsaImplCommon.qll | 6 +- .../dataflow/internal/SsaImplSpecific.qll | 74 ----------- 3 files changed, 103 insertions(+), 97 deletions(-) delete mode 100644 swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll diff --git a/swift/ql/lib/codeql/swift/dataflow/Ssa.qll b/swift/ql/lib/codeql/swift/dataflow/Ssa.qll index a185b0e8d25..7ddb561a0e2 100644 --- a/swift/ql/lib/codeql/swift/dataflow/Ssa.qll +++ b/swift/ql/lib/codeql/swift/dataflow/Ssa.qll @@ -2,55 +2,131 @@ cached module Ssa { private import swift private import internal.SsaImplCommon as SsaImplCommon - private import internal.SsaImplSpecific as SsaImplSpecific private import codeql.swift.controlflow.CfgNodes private import codeql.swift.controlflow.ControlFlowGraph - private import codeql.swift.controlflow.BasicBlocks + private import codeql.swift.controlflow.BasicBlocks as BasicBlocks + + private module SsaInput implements SsaImplCommon::InputSig { + private import internal.DataFlowPrivate + private import codeql.swift.controlflow.ControlFlowGraph + private import codeql.swift.controlflow.CfgNodes + + class BasicBlock = BasicBlocks::BasicBlock; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { + result = bb.getImmediateDominator() + } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + + class ExitBasicBlock = BasicBlocks::ExitBasicBlock; + + class SourceVariable = VarDecl; + + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(AssignExpr assign | + bb.getNode(i).getNode().asAstNode() = assign and + assign.getDest() = v.getAnAccess() and + certain = true + ) + or + exists(PatternBindingDecl decl, Pattern pattern | + bb.getNode(i).getNode().asAstNode() = pattern and + decl.getAPattern() = pattern and + v.getParentPattern() = pattern and + certain = true + ) + or + v instanceof ParamDecl and + bb.getNode(i).getNode().asAstNode() = v and + certain = true + or + // Mark the subexpression as a write of the local variable declared in the `TapExpr`. + exists(TapExpr tap | + v = tap.getVar() and + bb.getNode(i).getNode().asAstNode() = tap.getSubExpr() and + certain = true + ) + } + + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(DeclRefExpr ref | + not isLValue(ref) and + bb.getNode(i).getNode().asAstNode() = ref and + v = ref.getDecl() and + certain = true + ) + or + exists(InOutExpr expr | + bb.getNode(i).getNode().asAstNode() = expr and + expr.getSubExpr() = v.getAnAccess() and + certain = true + ) + or + exists(ExitNode exit, AbstractFunctionDecl func | + func.getAParam() = v or func.getSelfParam() = v + | + bb.getNode(i) = exit and + modifiableParam(v) and + bb.getScope() = func and + certain = true + ) + or + // Mark the `TapExpr` as a read of the of the local variable. + exists(TapExpr tap | + v = tap.getVar() and + bb.getNode(i).getNode().asAstNode() = tap and + certain = true + ) + } + } + + private module SsaImpl = SsaImplCommon::Make; cached - class Definition extends SsaImplCommon::Definition { + class Definition extends SsaImpl::Definition { cached Location getLocation() { none() } cached ControlFlowNode getARead() { - exists(VarDecl v, BasicBlock bb, int i | - SsaImplCommon::ssaDefReachesRead(v, this, bb, i) and - SsaImplSpecific::variableRead(bb, i, v, true) and + exists(VarDecl v, SsaInput::BasicBlock bb, int i | + SsaImpl::ssaDefReachesRead(v, this, bb, i) and + SsaInput::variableRead(bb, i, v, true) and result = bb.getNode(i) ) } cached ControlFlowNode getAFirstRead() { - exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + exists(SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2 | this.definesAt(_, bb1, i1) and - SsaImplCommon::adjacentDefNoUncertainReads(this, bb1, i1, bb2, i2) and + SsaImpl::adjacentDefNoUncertainReads(this, bb1, i1, bb2, i2) and result = bb2.getNode(i2) ) } cached predicate adjacentReadPair(ControlFlowNode read1, ControlFlowNode read2) { - exists(BasicBlock bb1, int i1, BasicBlock bb2, int i2 | + exists(SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2 | read1 = bb1.getNode(i1) and - SsaImplSpecific::variableRead(bb1, i1, _, true) and - SsaImplCommon::adjacentDefNoUncertainReads(this, bb1, i1, bb2, i2) and + SsaInput::variableRead(bb1, i1, _, true) and + SsaImpl::adjacentDefNoUncertainReads(this, bb1, i1, bb2, i2) and read2 = bb2.getNode(i2) ) } cached - predicate lastRefRedef(BasicBlock bb, int i, Definition next) { - SsaImplCommon::lastRefRedef(this, bb, i, next) + predicate lastRefRedef(SsaInput::BasicBlock bb, int i, Definition next) { + SsaImpl::lastRefRedef(this, bb, i, next) } } cached - class WriteDefinition extends Definition, SsaImplCommon::WriteDefinition { + class WriteDefinition extends Definition, SsaImpl::WriteDefinition { cached override Location getLocation() { - exists(BasicBlock bb, int i | + exists(SsaInput::BasicBlock bb, int i | this.definesAt(_, bb, i) and result = bb.getNode(i).getLocation() ) @@ -63,14 +139,16 @@ module Ssa { cached predicate assigns(CfgNode value) { exists( - AssignExpr a, BasicBlock bb, int i // TODO: use CFG node for assignment expr + AssignExpr a, SsaInput::BasicBlock bb, int i // TODO: use CFG node for assignment expr | this.definesAt(_, bb, i) and a = bb.getNode(i).getNode().asAstNode() and value.getNode().asAstNode() = a.getSource() ) or - exists(VarDecl var, BasicBlock bb, int blockIndex, PatternBindingDecl pbd, Expr init | + exists( + VarDecl var, SsaInput::BasicBlock bb, int blockIndex, PatternBindingDecl pbd, Expr init + | this.definesAt(var, bb, blockIndex) and pbd.getAPattern() = bb.getNode(blockIndex).getNode().asAstNode() and init = var.getParentInitializer() @@ -84,17 +162,19 @@ module Ssa { } cached - class PhiDefinition extends Definition, SsaImplCommon::PhiNode { + class PhiDefinition extends Definition, SsaImpl::PhiNode { cached override Location getLocation() { - exists(BasicBlock bb, int i | + exists(SsaInput::BasicBlock bb, int i | this.definesAt(_, bb, i) and result = bb.getLocation() ) } cached - Definition getPhiInput(BasicBlock bb) { SsaImplCommon::phiHasInputFromBlock(this, result, bb) } + Definition getPhiInput(SsaInput::BasicBlock bb) { + SsaImpl::phiHasInputFromBlock(this, result, bb) + } cached Definition getAPhiInput() { result = this.getPhiInput(_) } diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll b/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll index 3c706849415..130ab86a64c 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplCommon.qll @@ -4,7 +4,7 @@ */ /** Provides the input specification of the SSA implementation. */ -signature module SsaInputSig { +signature module InputSig { /** * A basic block, that is, a maximal straight-line sequence of control flow nodes * without branches or joins. @@ -28,7 +28,7 @@ signature module SsaInputSig { * ``` * * The basic block starting on line 2 is an immediate dominator of - * the basic block online 4 (all paths from the entry point of `M` + * the basic block on line 4 (all paths from the entry point of `M` * to `return s.Length;` must go through the null check. */ BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); @@ -82,7 +82,7 @@ signature module SsaInputSig { * NB: If this predicate is exposed, it should be cached. * ``` */ -module Make { +module Make { private import Input private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll b/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll deleted file mode 100644 index f2b8a2ae9e9..00000000000 --- a/swift/ql/lib/codeql/swift/dataflow/internal/SsaImplSpecific.qll +++ /dev/null @@ -1,74 +0,0 @@ -/** Provides the Swift specific parameters for `SsaImplCommon.qll`. */ - -private import swift -private import codeql.swift.controlflow.BasicBlocks as BasicBlocks -private import codeql.swift.controlflow.ControlFlowGraph -private import codeql.swift.controlflow.CfgNodes -private import DataFlowPrivate - -class BasicBlock = BasicBlocks::BasicBlock; - -BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result = bb.getImmediateDominator() } - -BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } - -class ExitBasicBlock = BasicBlocks::ExitBasicBlock; - -class SourceVariable = VarDecl; - -predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { - exists(AssignExpr assign | - bb.getNode(i).getNode().asAstNode() = assign and - assign.getDest() = v.getAnAccess() and - certain = true - ) - or - exists(PatternBindingDecl decl, Pattern pattern | - bb.getNode(i).getNode().asAstNode() = pattern and - decl.getAPattern() = pattern and - v.getParentPattern() = pattern and - certain = true - ) - or - v instanceof ParamDecl and - bb.getNode(i).getNode().asAstNode() = v and - certain = true - or - // Mark the subexpression as a write of the local variable declared in the `TapExpr`. - exists(TapExpr tap | - v = tap.getVar() and - bb.getNode(i).getNode().asAstNode() = tap.getSubExpr() and - certain = true - ) -} - -predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { - exists(DeclRefExpr ref | - not isLValue(ref) and - bb.getNode(i).getNode().asAstNode() = ref and - v = ref.getDecl() and - certain = true - ) - or - exists(InOutExpr expr | - bb.getNode(i).getNode().asAstNode() = expr and - expr.getSubExpr() = v.getAnAccess() and - certain = true - ) - or - exists(ExitNode exit, AbstractFunctionDecl func | - func.getAParam() = v or func.getSelfParam() = v - | - bb.getNode(i) = exit and - modifiableParam(v) and - bb.getScope() = func and - certain = true - ) - or - // Mark the `TapExpr` as a read of the of the local variable. - exists(TapExpr tap | - v = tap.getVar() and - bb.getNode(i).getNode().asAstNode() = tap and - certain = true - ) -} From 7a2d43432e36529417d895abf0e32827e32edf53 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 29 Aug 2022 13:57:39 +0200 Subject: [PATCH 8/8] C++: Adapt to parameterized SSA implementation --- .../ir/dataflow/internal/SsaImplCommon.qll | 6 +- .../ir/dataflow/internal/SsaImplSpecific.qll | 18 ---- .../cpp/ir/dataflow/internal/SsaInternals.qll | 93 +++++++++++-------- 3 files changed, 59 insertions(+), 58 deletions(-) delete mode 100644 cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplSpecific.qll diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll index 3c706849415..130ab86a64c 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll @@ -4,7 +4,7 @@ */ /** Provides the input specification of the SSA implementation. */ -signature module SsaInputSig { +signature module InputSig { /** * A basic block, that is, a maximal straight-line sequence of control flow nodes * without branches or joins. @@ -28,7 +28,7 @@ signature module SsaInputSig { * ``` * * The basic block starting on line 2 is an immediate dominator of - * the basic block online 4 (all paths from the entry point of `M` + * the basic block on line 4 (all paths from the entry point of `M` * to `return s.Length;` must go through the null check. */ BasicBlock getImmediateBasicBlockDominator(BasicBlock bb); @@ -82,7 +82,7 @@ signature module SsaInputSig { * NB: If this predicate is exposed, it should be cached. * ``` */ -module Make { +module Make { private import Input private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplSpecific.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplSpecific.qll deleted file mode 100644 index 20f9d1894b1..00000000000 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplSpecific.qll +++ /dev/null @@ -1,18 +0,0 @@ -private import semmle.code.cpp.ir.IR -private import SsaInternals as Ssa - -class BasicBlock = IRBlock; - -class SourceVariable = Ssa::SourceVariable; - -BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result.immediatelyDominates(bb) } - -BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } - -class ExitBasicBlock extends IRBlock { - ExitBasicBlock() { this.getLastInstruction() instanceof ExitFunctionInstruction } -} - -predicate variableWrite = Ssa::variableWrite/4; - -predicate variableRead = Ssa::variableRead/4; diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll index 7aefc3893f2..bd3f56dcfea 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll @@ -1,10 +1,10 @@ -import SsaImplCommon private import cpp as Cpp private import semmle.code.cpp.ir.IR private import DataFlowUtil private import DataFlowImplCommon as DataFlowImplCommon private import semmle.code.cpp.models.interfaces.Allocation as Alloc private import semmle.code.cpp.models.interfaces.DataFlow as DataFlow +private import SsaImplCommon as SsaImplCommon private module SourceVariables { private newtype TSourceVariable = @@ -38,8 +38,6 @@ private module SourceVariables { } } -import SourceVariables - cached private newtype TDefOrUse = TExplicitDef(Instruction store) { explicitWrite(_, store, _) } or @@ -86,7 +84,7 @@ abstract class Def extends DefOrUse { Instruction getInstruction() { result = store } /** Gets the variable that is defined by this definition. */ - abstract SourceVariable getSourceVariable(); + abstract SourceVariables::SourceVariable getSourceVariable(); /** Holds if this definition is guaranteed to happen. */ abstract predicate isCertain(); @@ -103,10 +101,10 @@ abstract class Def extends DefOrUse { private class ExplicitDef extends Def, TExplicitDef { ExplicitDef() { this = TExplicitDef(store) } - override SourceVariable getSourceVariable() { + override SourceVariables::SourceVariable getSourceVariable() { exists(VariableInstruction var | explicitWrite(_, this.getInstruction(), var) and - result.(SourceIRVariable).getIRVariable() = var.getIRVariable() + result.(SourceVariables::SourceIRVariable).getIRVariable() = var.getIRVariable() ) } @@ -116,11 +114,11 @@ private class ExplicitDef extends Def, TExplicitDef { private class ParameterDef extends Def, TInitializeParam { ParameterDef() { this = TInitializeParam(store) } - override SourceVariable getSourceVariable() { - result.(SourceIRVariable).getIRVariable() = + override SourceVariables::SourceVariable getSourceVariable() { + result.(SourceVariables::SourceIRVariable).getIRVariable() = store.(InitializeParameterInstruction).getIRVariable() or - result.(SourceIRVariableIndirection).getUnderlyingIRVariable() = + result.(SourceVariables::SourceIRVariableIndirection).getUnderlyingIRVariable() = store.(InitializeIndirectionInstruction).getIRVariable() } @@ -138,7 +136,7 @@ abstract class Use extends DefOrUse { override string toString() { result = "Use" } /** Gets the variable that is used by this use. */ - abstract SourceVariable getSourceVariable(); + abstract SourceVariables::SourceVariable getSourceVariable(); override IRBlock getBlock() { result = use.getUse().getBlock() } @@ -148,12 +146,14 @@ abstract class Use extends DefOrUse { private class ExplicitUse extends Use, TExplicitUse { ExplicitUse() { this = TExplicitUse(use) } - override SourceVariable getSourceVariable() { + override SourceVariables::SourceVariable getSourceVariable() { exists(VariableInstruction var | use.getDef() = var and if use.getUse() instanceof ReadSideEffectInstruction - then result.(SourceIRVariableIndirection).getUnderlyingIRVariable() = var.getIRVariable() - else result.(SourceIRVariable).getIRVariable() = var.getIRVariable() + then + result.(SourceVariables::SourceIRVariableIndirection).getUnderlyingIRVariable() = + var.getIRVariable() + else result.(SourceVariables::SourceIRVariable).getIRVariable() = var.getIRVariable() ) } } @@ -161,10 +161,11 @@ private class ExplicitUse extends Use, TExplicitUse { private class ReturnParameterIndirection extends Use, TReturnParamIndirection { ReturnParameterIndirection() { this = TReturnParamIndirection(use) } - override SourceVariable getSourceVariable() { + override SourceVariables::SourceVariable getSourceVariable() { exists(ReturnIndirectionInstruction ret | returnParameterIndirection(use, ret) and - result.(SourceIRVariableIndirection).getUnderlyingIRVariable() = ret.getIRVariable() + result.(SourceVariables::SourceIRVariableIndirection).getUnderlyingIRVariable() = + ret.getIRVariable() ) } } @@ -610,27 +611,45 @@ private module Cached { import Cached -/** - * Holds if the `i`'th write in block `bb` writes to the variable `v`. - * `certain` is `true` if the write is guaranteed to overwrite the entire variable. - */ -predicate variableWrite(IRBlock bb, int i, SourceVariable v, boolean certain) { - DataFlowImplCommon::forceCachingInSameStage() and - exists(Def def | - def.hasIndexInBlock(bb, i) and - v = def.getSourceVariable() and - (if def.isCertain() then certain = true else certain = false) - ) +private module SsaInput implements SsaImplCommon::InputSig { + private import semmle.code.cpp.ir.IR + + class BasicBlock = IRBlock; + + class SourceVariable = SourceVariables::SourceVariable; + + BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result.immediatelyDominates(bb) } + + BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() } + + class ExitBasicBlock extends IRBlock { + ExitBasicBlock() { this.getLastInstruction() instanceof ExitFunctionInstruction } + } + + /** + * Holds if the `i`'th write in block `bb` writes to the variable `v`. + * `certain` is `true` if the write is guaranteed to overwrite the entire variable. + */ + predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { + DataFlowImplCommon::forceCachingInSameStage() and + exists(Def def | + def.hasIndexInBlock(bb, i) and + v = def.getSourceVariable() and + (if def.isCertain() then certain = true else certain = false) + ) + } + + /** + * Holds if the `i`'th read in block `bb` reads to the variable `v`. + * `certain` is `true` if the read is guaranteed. For C++, this is always the case. + */ + predicate variableRead(BasicBlock bb, int i, SourceVariable v, boolean certain) { + exists(Use use | + use.hasIndexInBlock(bb, i) and + v = use.getSourceVariable() and + certain = true + ) + } } -/** - * Holds if the `i`'th read in block `bb` reads to the variable `v`. - * `certain` is `true` if the read is guaranteed. For C++, this is always the case. - */ -predicate variableRead(IRBlock bb, int i, SourceVariable v, boolean certain) { - exists(Use use | - use.hasIndexInBlock(bb, i) and - v = use.getSourceVariable() and - certain = true - ) -} +import SsaImplCommon::Make