diff --git a/rust/ql/lib/codeql/rust/elements/internal/LocalNameBinding.qll b/rust/ql/lib/codeql/rust/elements/internal/LocalNameBinding.qll new file mode 100644 index 000000000000..19412c827e58 --- /dev/null +++ b/rust/ql/lib/codeql/rust/elements/internal/LocalNameBinding.qll @@ -0,0 +1,286 @@ +private import codeql.util.DenseRank +private import codeql.util.Location + +signature module LocalNameBindingInputSig { + /** An AST node. */ + class AstNode { + /** Gets a textual representation of this element. */ + string toString(); + + /** Gets the location of this element. */ + Location getLocation(); + } + + /** Gets the child of AST node `n` at the specified index. */ + AstNode getChild(AstNode n, int index); + + /** + * A conditional where any local declarations in the condition are in scope + * in the then-branch but not the else-branch. + * + * Example: + * + * ```rust + * if let Some(x) = opt { + * // x is in scope here + * } else { + * // x is not in scope here + * } + * ``` + */ + class Conditional extends AstNode { + /** Gets the condition of this conditional. */ + AstNode getCondition(); + + /** Gets the then-branch of this conditional. */ + AstNode getThen(); + + /** Gets the else-branch of this conditional. */ + AstNode getElse(); + } + + /** + * A shadowing declaration where any local declarations in the left-hand side + * are in scope _after_ the declaration and shadow any declarations with the + * same name preceding it. + * + * Example: + * ```rust + * let x = 1; + * let x = x + 1; // this declaration of `x` shadows the previous one, but the `x` in the right-hand side still refers to the first declaration + * println!("{}", x); // this access of `x` refers to the second declaration + * ``` + */ + class ShadowingDecl extends AstNode { + /** Gets the left-hand side of this declaration. */ + AstNode getLhs(); + + /** + * Gets the right-hand side of this declaration. + * + * Any local declared in the left-hand side of this declaration is _not_ in scope + * in the right-hand side. + */ + AstNode getRhs(); + + /** + * Gets the else-branch of this declaration, if any. + * + * Any local declared in the left-hand side of this declaration is _not_ in scope + * in the else-branch. + */ + AstNode getElse(); + } + + /** + * Holds if a local declaration named `name` exists at `definingNode` inside + * the syntactic scope `scope`. + * + * Note that declarations with a `definingNode` in the left-hand side of a + * `ShadowingDecl` `decl` should use `scope = decl`. + */ + predicate declInScope(AstNode definingNode, string name, AstNode scope); + + /** + * Holds if `n` is a node that may access a local named `name`. + */ + predicate accessCand(AstNode n, string name); + + /** + * Holds the access candidate `n` should begin its lookup in `scope` instead + * of its immediately enclosing scope. + * + * For example, the `this` variable in an instance field initializer might need to be resolved + * relative to a constructor body. + * + * If `scope` declares a variable with the name of `ref`, then `scope` is guaranteed to be the + * scope that `ref` ultimately resolves to. This can thus be used to take full control of scope resolution for + * for specific types of references. + */ + default predicate lookupStartsAt(AstNode n, AstNode scope) { none() } +} + +module LocalNameBinding Input> { + private import Input + + final private class AstNodeFinal = AstNode; + + private class Scope extends AstNodeFinal { + Scope() { + declInScope(_, _, this) + or + lookupStartsAt(_, this) + } + } + + private module ChildDenseRankInput implements DenseRankInputSig1 { + // Restrict ranking to relevant nodes for better performance + private class RelevantAstNode extends AstNodeFinal { + RelevantAstNode() { + this instanceof Scope + or + getChild(this, _).(Scope) instanceof ShadowingDecl + or + this = getChild(any(RelevantAstNode parent), _) + } + } + + class C = RelevantAstNode; + + class Ranked = RelevantAstNode; + + /** + * An adjusted version of `ParentChild::getImmediateChild`, which makes the following + * two adjustments: + * + * 1. For conditions like `if cond body`, instead of letting `body` be the second child + * of `if`, we make it the last child of `cond`. This ensures that variables + * introduced in the `cond` scope are available in `body`. + * + * 2. A similar adjustment is made for `while` loops: the body of the loop is made a + * child of the loop condition instead of the loop itself. + */ + int getRank(C parent, Ranked child) { + // parent.getEnclosingCallable().(Function).getName().getText() = "match_pattern14" and + child = + rank[result](RelevantAstNode res, int preOrd, int i | + res = getChild(parent, i) and + preOrd = 0 and + not exists(Conditional cond | res = [cond.getThen(), cond.getElse()]) + or + res = parent.(Conditional).getElse() and + preOrd = -1 and + i = 0 + or + exists(Conditional cond | + parent = cond.getCondition() and + res = cond.getThen() and + preOrd = 1 and + i = 0 + ) + | + res order by preOrd, i + ) + } + } + + private predicate getRankedChild = DenseRank1::denseRank/2; + + private predicate isLetParent(AstNode parent, int i, ShadowingDecl decl) { + decl = getRankedChild(parent, i) + } + + private predicate shouldBeLetChild(AstNode parent, int i, ShadowingDecl decl, AstNode child) { + child = getRankedChild(parent, i) and + ( + isLetParent(parent, i - 1, decl) + or + shouldBeLetChild(parent, i - 1, decl, any(AstNode n | not n instanceof ShadowingDecl)) + ) + } + + private AstNode getAChild(AstNode parent) { + not shouldBeLetChild(_, _, _, result) and + not result = [any(ShadowingDecl decl).getRhs(), any(ShadowingDecl decl).getElse()] and + result = getRankedChild(parent, _) + or + shouldBeLetChild(_, _, parent, result) + or + exists(ShadowingDecl decl | decl = getAChild(parent) | result = [decl.getRhs(), decl.getElse()]) + } + + /** Gets the immediately enclosing variable scope of `n`. */ + private Scope getEnclosingScope(AstNode n) { + n = getAChild(result) + or + exists(AstNode mid | + result = getEnclosingScope(mid) and + n = getAChild(mid) and + not mid instanceof Scope + ) + } + + private class AccessCand extends AstNodeFinal { + string name_; + + AccessCand() { accessCand(this, name_) } + + string getName() { result = name_ } + + Scope getLookupScope() { + lookupStartsAt(this, result) + or + not lookupStartsAt(this, _) and + result = getEnclosingScope(this) + } + } + + pragma[nomagic] + private predicate lookupInScope(string name, Scope lookup, Scope scope) { + exists(AccessCand cand | + cand.getName() = name and + lookup = cand.getLookupScope() and + scope = lookup + ) + or + exists(Scope mid | + lookupInScope(name, lookup, mid) and + not declInScope(_, name, mid) and + scope = getEnclosingScope(mid) + ) + } + + /** A locally declared entity, for example a variable or a parameter. */ + final class Local extends MkLocal { + private AstNode definingNode; + private string name; + + Local() { this = MkLocal(definingNode, name) } + + /** Gets the AST node that defines this local. */ + AstNode getDefiningNode() { result = definingNode } + + /** Gets the name of this local entity. */ + string getName() { result = name } + + /** Gets the location of this local entity. */ + Location getLocation() { result = definingNode.getLocation() } + + /** Gets a textual representation of this local entity. */ + string toString() { result = this.getName() } + + /** Gets an access to this local. */ + LocalAccess getAnAccess() { result.getLocal() = this } + } + + /** A local access. */ + final class LocalAccess extends AstNodeFinal { + private string name; + private Local v; + + LocalAccess() { access(this, name, v) } + + /** Gets the local being accessed. */ + Local getLocal() { result = v } + } + + cached + private module Cached { + cached + newtype TLocal = + MkLocal(AstNode definingNode, string name) { declInScope(definingNode, name, _) } + + cached + predicate access(AccessCand cand, string name, Local v) { + exists(AstNode definingNode, Scope lookup, Scope scope | + cand.getName() = name and + lookup = cand.getLookupScope() and + lookupInScope(name, lookup, scope) and + declInScope(definingNode, name, scope) and + v = MkLocal(definingNode, name) + ) + } + } + + private import Cached +} diff --git a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll index 37a2e4dacc07..bd36bc087c10 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll @@ -5,67 +5,82 @@ private import codeql.rust.elements.internal.generated.ParentChild as ParentChil private import codeql.rust.elements.internal.AstNodeImpl::Impl as AstNodeImpl private import codeql.rust.elements.internal.PathImpl::Impl as PathImpl private import codeql.rust.elements.internal.FormatTemplateVariableAccessImpl::Impl as FormatTemplateVariableAccessImpl -private import codeql.util.DenseRank +private import codeql.rust.elements.internal.LocalNameBinding module Impl { - /** - * A variable scope. Either a block `{ ... }`, the guard/rhs - * of a match arm, or the body of a closure. - */ - abstract class VariableScope extends AstNode { } + private module Input implements LocalNameBindingInputSig { + private import rust as Rust - class BlockExprScope extends VariableScope, BlockExpr { } + class AstNode = Rust::AstNode; - class MatchArmExprScope extends VariableScope { - MatchArmExprScope() { this = any(MatchArm arm).getExpr() } - } + predicate getChild = getImmediateChildAdjRust/2; - class MatchArmGuardScope extends VariableScope { - MatchArmGuardScope() { this = any(MatchArm arm).getGuard() } - } + /** + * A scope for conditions, which may introduce variables using `let` expressions. + * + * Such variables are only available in the body guarded by the condition. + */ + class Conditional extends AstNode { + Conditional() { + this instanceof IfExpr + or + this instanceof WhileExpr + or + this = any(MatchArm ma | ma.hasGuard()) + } - class ClosureBodyScope extends VariableScope { - ClosureBodyScope() { this = any(ClosureExpr ce).getBody() } - } + AstNode getCondition() { + result = this.(IfExpr).getCondition() + or + result = this.(WhileExpr).getCondition() + or + result = this.(MatchArm).getGuard() + } - /** - * A scope for conditions, which may introduce variables using `let` expressions. - * - * Such variables are only available in the body guarded by the condition. - */ - class ConditionScope extends VariableScope { - private AstNode parent; - private AstNode body; - - ConditionScope() { - parent = - any(IfExpr ie | - this = ie.getCondition() and - body = ie.getThen() - ) - or - parent = - any(WhileExpr we | - this = we.getCondition() and - body = we.getLoopBody() - ) + AstNode getThen() { + result = this.(IfExpr).getThen() + or + result = this.(WhileExpr).getLoopBody() + or + result = this.(MatchArm).getExpr() + } + + AstNode getElse() { result = this.(IfExpr).getElse() } + } + + class ShadowingDecl extends AstNode { + ShadowingDecl() { this instanceof LetStmt or this instanceof LetExpr } + + AstNode getLhs() { + result = this.(LetStmt).getPat() + or + result = this.(LetExpr).getPat() + } + + AstNode getRhs() { + result = this.(LetStmt).getInitializer() + or + result = this.(LetExpr).getScrutinee() + } + + AstNode getElse() { result = this.(LetStmt).getLetElse() } + } + + predicate declInScope(AstNode definingNode, string name, AstNode scope) { + variableDeclInScope(definingNode, name, scope) or - parent = - any(MatchArm ma | - this = ma.getGuard() and - body = ma.getExpr() + definingNode = + any(Function f | + f = scope.(BlockExpr).getStmtList().getAStatement() and + name = f.getName().getText() ) } - /** Gets the parent of this condition. */ - AstNode getParent() { result = parent } - - /** - * Gets the body in which variables introduced in this scope are available. - */ - AstNode getBody() { result = body } + predicate accessCand(AstNode n, string name) { name = n.(VariableAccessCand).getName() } } + private import LocalNameBinding + private Pat getAPatAncestor(Pat p) { (p instanceof IdentPat or p instanceof OrPat) and exists(Pat p0 | result = p0.getParentPat() | @@ -127,34 +142,60 @@ module Impl { ) } - /** A variable. */ - class Variable extends MkVariable { - private AstNode definingNode; - private string text; - - Variable() { this = MkVariable(definingNode, text) } - - /** Gets the name of this variable as a string. */ - string getText() { result = text } - - /** Gets the location of this variable. */ - Location getLocation() { result = definingNode.getLocation() } + /** + * Holds if `v` is named `name` and is declared inside variable scope + * `scope`. The pre-order numbering of the binding site of `v`, amongst + * all nodes nested under `scope`, is `ord`. + */ + private predicate variableDeclInScope(AstNode definingNode, string name, AstNode scope) { + exists(Name n | variableDecl(definingNode, n, name) | + exists(SelfParam self | self = scope.(Callable).getSelfParam() and n = self.getName()) + or + exists(Pat pat, Pat pat0 | + pat = getAPatAncestor*(pat0) and + (pat0 = definingNode or pat0.(IdentPat).getName() = n) + | + exists(MatchArm arm | + pat = arm.getPat() and + scope = arm + ) + or + exists(Input::ShadowingDecl let | + let.getLhs() = pat and + scope = let + ) + or + exists(ForExpr fe | + fe.getPat() = pat and + scope = fe.getLoopBody() + ) + or + exists(Param p | p = scope.(Callable).getParamList().getAParamBase() | + pat = p.(Param).getPat() + ) + ) + ) + } - /** Gets a textual representation of this variable. */ - string toString() { result = this.getText() } + /** A variable. */ + class Variable extends Local { + Variable() { not this.getDefiningNode() instanceof Function } /** Gets an access to this variable. */ VariableAccess getAnAccess() { result.getVariable() = this } + /** Gets the name of this variable. */ + string getText() { result = super.getName() } + /** * Get the name of this variable. * * Normally, the name is unique, except when introduced in an or pattern. */ - Name getName() { variableDecl(definingNode, result, text) } + Name getName() { variableDecl(this.getDefiningNode(), result, super.getName()) } /** Gets the block that encloses this variable, if any. */ - BlockExpr getEnclosingBlock() { result = definingNode.getEnclosingBlock() } + BlockExpr getEnclosingBlock() { result = this.getDefiningNode().getEnclosingBlock() } /** Gets the `self` parameter that declares this variable, if any. */ SelfParam getSelfParam() { result.getName() = this.getName() } @@ -173,11 +214,13 @@ module Impl { IdentPat getPat() { result.getName() = this.getName() } /** Gets the enclosing CFG scope for this variable declaration. */ - CfgScope getEnclosingCfgScope() { result = definingNode.getEnclosingCfgScope() } + CfgScope getEnclosingCfgScope() { result = this.getDefiningNode().getEnclosingCfgScope() } + // TODO: elaborate /** Gets the `let` statement that introduces this variable, if any. */ LetStmt getLetStmt() { this.getPat() = result.getPat() } + // TODO: elaborate /** Gets the `let` expression that introduces this variable, if any. */ LetExpr getLetExpr() { this.getPat() = result.getPat() } @@ -196,7 +239,7 @@ module Impl { Cached::ref() and result = this.getSelfParam() or - result.(Param).getPat() = getAVariablePatAncestor(this) + result.(Param).getPat() = getAPatAncestor*(this.getPat()) } /** Hold is this variable is mutable. */ @@ -225,455 +268,68 @@ module Impl { string getName() { result = name_ } } - pragma[nomagic] - private Element getImmediateChildAdj(Element e, int preOrd, int index) { - result = ParentChild::getImmediateChild(e, index) and - preOrd = 0 and - not exists(ConditionScope cs | - e = cs.getParent() and - result = cs.getBody() - ) + private LogicalAndExpr getALetChainAncestor(LetExpr let) { + let = result.getAnOperand() or - result = e.(ConditionScope).getBody() and - preOrd = 1 and - index = 0 + result.getAnOperand() = getALetChainAncestor(let) } - /** - * An adjusted version of `ParentChild::getImmediateChild`, which makes the following - * two adjustments: - * - * 1. For conditions like `if cond body`, instead of letting `body` be the second child - * of `if`, we make it the last child of `cond`. This ensures that variables - * introduced in the `cond` scope are available in `body`. - * - * 2. A similar adjustment is made for `while` loops: the body of the loop is made a - * child of the loop condition instead of the loop itself. - */ - pragma[nomagic] - private Element getImmediateChildAdj(Element e, int index) { - result = - rank[index + 1](Element res, int preOrd, int i | - res = getImmediateChildAdj(e, preOrd, i) - | - res order by preOrd, i - ) + /** Gets the outermost enclosing `|` pattern parent of `p`, if any. */ + private LogicalAndExpr getOutermostLetChainAncestor(LetExpr let) { + result = getALetChainAncestor(let) and + not result.getParentNode() instanceof LogicalAndExpr } - private Element getImmediateParentAdj(Element e) { e = getImmediateChildAdj(result, _) } - - private AstNode getAnAncestorInVariableScope(AstNode n) { + private AstNode getLetChainRootDescendant(LogicalAndExpr lae, string path) { + lae = getOutermostLetChainAncestor(_) and ( - n instanceof Pat or - n instanceof VariableAccessCand or - n instanceof LetStmt or - n = any(LetExpr le).getScrutinee() or - n instanceof VariableScope - ) and - exists(AstNode n0 | - result = getImmediateParentAdj(n0) or - result = n0.(FormatTemplateVariableAccess).getArgument().getParent().getParent() - | - n0 = n + result = lae.getLhs() and path = "0" or - n0 = getAnAncestorInVariableScope(n) and - not n0 instanceof VariableScope + result = lae.getRhs() and path = "1" ) - } - - /** Gets the immediately enclosing variable scope of `n`. */ - private VariableScope getEnclosingScope(AstNode n) { result = getAnAncestorInVariableScope(n) } - - /** - * Get all the pattern ancestors of this variable up to an including the - * root of the pattern. - */ - private Pat getAVariablePatAncestor(Variable v) { - result = v.getPat() or - exists(Pat mid | - mid = getAVariablePatAncestor(v) and - result = mid.getParentPat() - ) - } - - /** - * Holds if a parameter declares the variable `v` inside variable scope `scope`. - */ - private predicate parameterDeclInScope(Variable v, VariableScope scope) { - exists(Callable f | - v.getParameter() = f.getParamList().getAParamBase() and - scope = f.getBody() - ) - } - - /** A subset of `Element`s for which we want to compute pre-order numbers. */ - private class RelevantElement extends Element { - RelevantElement() { - this instanceof VariableScope or - this instanceof VariableAccessCand or - this instanceof LetStmt or - this = any(LetExpr le).getScrutinee() or - getImmediateChildAdj(this, _) instanceof RelevantElement - } - - pragma[nomagic] - private RelevantElement getChild(int index) { result = getImmediateChildAdj(this, index) } - - pragma[nomagic] - private RelevantElement getImmediateChildAdjMin(int index) { - // A child may have multiple positions for different accessors, - // so always use the first - result = this.getChild(index) and - index = min(int i | result = this.getChild(i) | i) - } - - pragma[nomagic] - RelevantElement getImmediateChildAdj(int index) { - result = - rank[index + 1](Element res, int i | res = this.getImmediateChildAdjMin(i) | res order by i) - } - - pragma[nomagic] - RelevantElement getImmediateLastChild() { - exists(int last | - result = this.getImmediateChildAdj(last) and - not exists(this.getImmediateChildAdj(last + 1)) - ) - } - } - - /** - * Gets the pre-order numbering of `n`, where the immediately enclosing - * variable scope of `n` is `scope`. - */ - pragma[nomagic] - private int getPreOrderNumbering(VariableScope scope, RelevantElement n) { - n = scope and - result = 0 - or - exists(RelevantElement parent | - not parent instanceof VariableScope + exists(LogicalAndExpr mid, string prefix | mid = getLetChainRootDescendant(lae, prefix) | + result = mid.getLhs() and path = prefix + ".0" or - parent = scope - | - // first child of a previously numbered node - result = getPreOrderNumbering(scope, parent) + 1 and - n = parent.getImmediateChildAdj(0) - or - // non-first child of a previously numbered node - exists(RelevantElement child, int i | - result = getLastPreOrderNumbering(scope, child) + 1 and - child = parent.getImmediateChildAdj(i) and - n = parent.getImmediateChildAdj(i + 1) - ) + result = mid.getRhs() and path = prefix + ".1" ) } - /** - * Gets the pre-order numbering of the _last_ node nested under `n`, where the - * immediately enclosing variable scope of `n` (and the last node) is `scope`. - */ - pragma[nomagic] - private int getLastPreOrderNumbering(VariableScope scope, RelevantElement n) { - exists(RelevantElement leaf | - result = getPreOrderNumbering(scope, leaf) and - leaf != scope and - ( - not exists(leaf.getImmediateChildAdj(_)) - or - leaf instanceof VariableScope - ) - | - n = leaf - or - n.getImmediateLastChild() = leaf and - not n instanceof VariableScope - ) - or - exists(RelevantElement mid | - mid = n.getImmediateLastChild() and - result = getLastPreOrderNumbering(scope, mid) and - not mid instanceof VariableScope and - not n instanceof VariableScope - ) + private AstNode getLetChainChildAt(LogicalAndExpr lae, string path) { + result = getLetChainRootDescendant(lae, path) and + not result instanceof LogicalAndExpr } - /** - * Holds if `v` is named `name` and is declared inside variable scope - * `scope`. The pre-order numbering of the binding site of `v`, amongst - * all nodes nested under `scope`, is `ord`. - */ - private predicate variableDeclInScope(Variable v, VariableScope scope, string name, int ord) { - name = v.getText() and - ( - parameterDeclInScope(v, scope) and - ord = getPreOrderNumbering(scope, scope) - or - exists(Pat pat | pat = getAVariablePatAncestor(v) | - exists(MatchArm arm | - pat = arm.getPat() and - ord = getPreOrderNumbering(scope, scope) - | - scope = arm.getGuard() - or - not arm.hasGuard() and scope = arm.getExpr() - ) - or - exists(LetStmt let | - let.getPat() = pat and - scope = getEnclosingScope(let) and - // for `let` statements, variables are bound _after_ the statement, i.e. - // not in the RHS - ord = getLastPreOrderNumbering(scope, let) + 1 - ) - or - exists(LetExpr let, Expr scrutinee | - let.getPat() = pat and - scrutinee = let.getScrutinee() and - scope = getEnclosingScope(scrutinee) and - // for `let` expressions, variables are bound _after_ the expression, i.e. - // not in the RHS - ord = getLastPreOrderNumbering(scope, scrutinee) + 1 - ) - or - exists(ForExpr fe | - fe.getPat() = pat and - scope = fe.getLoopBody() and - ord = getPreOrderNumbering(scope, scope) - ) - ) - ) + private AstNode getLetChainChild(LogicalAndExpr lae, int i) { + result = + rank[i + 1](AstNode res, string path | res = getLetChainChildAt(lae, path) | res order by path) } - /** - * Holds if `cand` may access a variable named `name` at pre-order number `ord` - * in the variable scope `scope`. - * - * `nestLevel` is the number of nested scopes that need to be traversed - * to reach `scope` from `cand`. - */ - private predicate variableAccessCandInScope( - VariableAccessCand cand, VariableScope scope, string name, int nestLevel, int ord - ) { - name = cand.getName() and - ( - scope = cand - or - not cand instanceof VariableScope and - scope = getEnclosingScope(cand) - ) and - ord = getPreOrderNumbering(scope, cand) and - nestLevel = 0 + pragma[nomagic] + private AstNode getImmediateChildAdjRust(AstNode parent, int index) { + result = ParentChild::getImmediateChild(parent, index) and + not result = getLetChainRootDescendant(_, _) //and or - exists(VariableScope inner | - variableAccessCandInScope(cand, inner, name, nestLevel - 1, _) and - scope = getEnclosingScope(inner) and - // Use the pre-order number of the inner scope as the number of the access. This allows - // us to collapse multiple accesses in inner scopes to a single entity - ord = getPreOrderNumbering(scope, inner) - ) - } - - private newtype TDefOrAccessCand = - TDefOrAccessCandNestedFunction(Function f, BlockExprScope scope) { - f = scope.getStmtList().getAStatement() - } or - TDefOrAccessCandVariable(Variable v) or - TDefOrAccessCandVariableAccessCand(VariableAccessCand va) - - /** - * A nested function declaration, variable declaration, or variable (or function) - * access candidate. - * - * In order to determine whether a candidate is an actual variable/function access, - * we rank declarations and candidates by their position in the AST. - * - * The ranking must take names into account, but also variable scopes; below a comment - * `rank(scope, name, i)` means that the declaration/access on the given line has rank - * `i` amongst all declarations/accesses inside variable scope `scope`, for name `name`: - * - * ```rust - * fn f() { // scope0 - * let x = 0; // rank(scope0, "x", 0) - * use(x); // rank(scope0, "x", 1) - * let x = // rank(scope0, "x", 3) - * x + 1; // rank(scope0, "x", 2) - * let y = // rank(scope0, "y", 0) - * x; // rank(scope0, "x", 4) - * - * { // scope1 - * use(x); // rank(scope1, "x", 0), rank(scope0, "x", 4) - * use(y); // rank(scope1, "y", 0), rank(scope0, "y", 1) - * let x = 2; // rank(scope1, "x", 1) - * use(x); // rank(scope1, "x", 2), rank(scope0, "x", 4) - * } - * } - * ``` - * - * Function/variable declarations are only ranked in the scope that they bind into, - * while accesses candidates propagate outwards through scopes, as they may access - * declarations from outer scopes. - * - * For an access candidate with ranks `{ rank(scope_i, name, rnk_i) | i in I }` and - * declarations `d in D` with ranks `rnk(scope_d, name, rnk_d)`, the target is - * calculated as - * ``` - * max_{i in I} ( - * max_{d in D | scope_d = scope_i and rnk_d < rnk_i} ( - * d - * ) - * ) - * ``` - * - * i.e., its the nearest declaration before the access in the same (or outer) scope - * as the access. - */ - abstract private class DefOrAccessCand extends TDefOrAccessCand { - abstract string toString(); - - abstract Location getLocation(); - - pragma[nomagic] - abstract predicate rankBy(string name, VariableScope scope, int ord, int kind); - } - - abstract private class NestedFunctionOrVariable extends DefOrAccessCand { } - - private class DefOrAccessCandNestedFunction extends NestedFunctionOrVariable, - TDefOrAccessCandNestedFunction - { - private Function f; - private BlockExprScope scope_; - - DefOrAccessCandNestedFunction() { this = TDefOrAccessCandNestedFunction(f, scope_) } - - override string toString() { result = f.toString() } - - override Location getLocation() { result = f.getLocation() } - - override predicate rankBy(string name, VariableScope scope, int ord, int kind) { - // nested functions behave as if they are defined at the beginning of the scope - name = f.getName().getText() and - scope = scope_ and - ord = 0 and - kind = 0 - } - } - - private class DefOrAccessCandVariable extends NestedFunctionOrVariable, TDefOrAccessCandVariable { - private Variable v; - - DefOrAccessCandVariable() { this = TDefOrAccessCandVariable(v) } - - override string toString() { result = v.toString() } - - override Location getLocation() { result = v.getLocation() } - - override predicate rankBy(string name, VariableScope scope, int ord, int kind) { - variableDeclInScope(v, scope, name, ord) and - kind = 1 - } - } - - private class DefOrAccessCandVariableAccessCand extends DefOrAccessCand, - TDefOrAccessCandVariableAccessCand - { - private VariableAccessCand va; - - DefOrAccessCandVariableAccessCand() { this = TDefOrAccessCandVariableAccessCand(va) } - - override string toString() { result = va.toString() } - - override Location getLocation() { result = va.getLocation() } - - override predicate rankBy(string name, VariableScope scope, int ord, int kind) { - variableAccessCandInScope(va, scope, name, _, ord) and - kind = 2 - } - } - - private module DenseRankInput implements DenseRankInputSig2 { - class C1 = VariableScope; - - class C2 = string; - - class Ranked = DefOrAccessCand; - - int getRank(VariableScope scope, string name, DefOrAccessCand v) { - v = - rank[result](DefOrAccessCand v0, int ord, int kind | - v0.rankBy(name, scope, ord, kind) - | - v0 order by ord, kind - ) - } - } - - /** - * Gets the rank of `v` amongst all other declarations or access candidates - * to a variable named `name` in the variable scope `scope`. - */ - private int rankVariableOrAccess(VariableScope scope, string name, DefOrAccessCand v) { - v = DenseRank2::denseRank(scope, name, result + 1) - } - - /** - * Holds if `v` can reach rank `rnk` in the variable scope `scope`. This is needed to - * take shadowing into account, for example in - * - * ```rust - * let x = 0; // rank 0 - * use(x); // rank 1 - * let x = ""; // rank 2 - * use(x); // rank 3 - * ``` - * - * the declaration at rank 0 can only reach the access at rank 1, while the declaration - * at rank 2 can only reach the access at rank 3. - */ - private predicate variableReachesRank( - VariableScope scope, string name, NestedFunctionOrVariable v, int rnk - ) { - rnk = rankVariableOrAccess(scope, name, v) + result = getLetChainChild(parent, index) or - variableReachesRank(scope, name, v, rnk - 1) and - rnk = rankVariableOrAccess(scope, name, TDefOrAccessCandVariableAccessCand(_)) - } - - private predicate variableReachesCand( - VariableScope scope, string name, NestedFunctionOrVariable v, VariableAccessCand cand, - int nestLevel - ) { - exists(int rnk | - variableReachesRank(scope, name, v, rnk) and - rnk = rankVariableOrAccess(scope, name, TDefOrAccessCandVariableAccessCand(cand)) and - variableAccessCandInScope(cand, scope, name, nestLevel, _) + exists(Format f | + f = result.(FormatTemplateVariableAccess).getArgument().getParent() and + parent = f.getParent() and + index = f.getIndex() ) } - pragma[nomagic] - predicate access(string name, NestedFunctionOrVariable v, VariableAccessCand cand) { - v = - min(NestedFunctionOrVariable v0, int nestLevel | - variableReachesCand(_, name, v0, cand, nestLevel) - | - v0 order by nestLevel - ) - } - /** A variable access. */ - class VariableAccess extends PathExprBase { - private string name; - private Variable v; - - VariableAccess() { variableAccess(name, v, this) } + class VariableAccess extends LocalAccess { + VariableAccess() { this.getLocal() instanceof Variable } /** Gets the variable being accessed. */ - Variable getVariable() { result = v } + Variable getVariable() { result = super.getLocal() } /** Holds if this access is a capture. */ - predicate isCapture() { this.getEnclosingCfgScope() != v.getEnclosingCfgScope() } + predicate isCapture() { + this.getEnclosingCfgScope() != this.getVariable().getEnclosingCfgScope() + } } /** Holds if `e` occurs in the LHS of an assignment operation. */ @@ -682,7 +338,7 @@ module Impl { or exists(Expr mid | assignmentOperationDescendant(ao, mid) and - getImmediateParentAdj(e) = mid and + mid = e.getParentNode() and not mid instanceof DerefExpr and not mid instanceof FieldExpr and not mid instanceof IndexExpr @@ -715,13 +371,11 @@ module Impl { } /** A nested function access. */ - class NestedFunctionAccess extends PathExprBase { + class NestedFunctionAccess extends LocalAccess { private Function f; - NestedFunctionAccess() { nestedFunctionAccess(_, f, this) } - /** Gets the function being accessed. */ - Function getFunction() { result = f } + Function getFunction() { result = super.getLocal().getDefiningNode() } } cached @@ -741,20 +395,6 @@ module Impl { or exists(any(Variable v).getParameter()) } - - cached - newtype TVariable = - MkVariable(AstNode definingNode, string name) { variableDecl(definingNode, _, name) } - - cached - predicate variableAccess(string name, Variable v, VariableAccessCand cand) { - access(name, TDefOrAccessCandVariable(v), cand) - } - - cached - predicate nestedFunctionAccess(string name, Function f, VariableAccessCand cand) { - access(name, TDefOrAccessCandNestedFunction(f, _), cand) - } } private import Cached