Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/LocalNameBinding.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
private import codeql.util.DenseRank
private import codeql.util.Location

signature module LocalNameBindingInputSig<LocationSig Location> {
/** 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.

Check warning

Code scanning / CodeQL

Predicate QLDoc style Warning

The QLDoc for a predicate with a result should start with 'Gets'.
*/

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for parent, but the QLDoc mentions body, and while
default predicate lookupStartsAt(AstNode n, AstNode scope) { none() }
}

module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location> 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.
*/
Comment on lines +132 to +142
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<ChildDenseRankInput>::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`. */

Check notice

Code scanning / CodeQL

Field only used in CharPred Note

Field is only used in CharPred.
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
}
Loading
Loading