Skip to content
Open
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
1 change: 0 additions & 1 deletion rust/ql/.generated.list

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion rust/ql/.gitattributes

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 4 additions & 10 deletions rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ private import codeql.rust.controlflow.ControlFlowGraph
*/
module Impl {
private import rust
private import codeql.rust.elements.internal.ElementImpl::Impl as ElementImpl
private import codeql.rust.elements.internal.generated.ParentChild
private import codeql.rust.controlflow.ControlFlowGraph
private import codeql.rust.elements.internal.MacroCallImpl::Impl as MacroCallImpl

/**
* Gets the immediate parent of a non-`AstNode` element `e`.
Expand Down Expand Up @@ -71,21 +71,15 @@ module Impl {
}

/** Holds if this node is inside a macro expansion. */
predicate isInMacroExpansion() { MacroCallImpl::isInMacroExpansion(_, this) }
predicate isInMacroExpansion() { ElementImpl::MacroExpansion::isInMacroExpansion(this) }

/**
* Holds if this node exists only as the result of a macro expansion.
*
* This is the same as `isInMacroExpansion()`, but excludes AST nodes corresponding
* to macro arguments.
* to macro arguments, including attribute macro targets.
*/
pragma[nomagic]
predicate isFromMacroExpansion() {
exists(AstNode root |
MacroCallImpl::isInMacroExpansion(root, this) and
not this = root.(MacroCall).getATokenTreeNode()
)
}
predicate isFromMacroExpansion() { ElementImpl::MacroExpansion::isFromMacroExpansion(this) }

/**
* Gets a control flow node for this AST node, if any.
Expand Down
111 changes: 111 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/ElementImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,118 @@ private import codeql.rust.elements.internal.generated.Element
* be referenced directly.
*/
module Impl {
private import rust
private import codeql.rust.elements.internal.generated.ParentChild
private import codeql.rust.elements.internal.generated.Synth
private import codeql.rust.elements.internal.generated.Raw
private import codeql.rust.elements.internal.LocationImpl

/**
* Provides logic for classifying elements with respect to macro expansions.
*/
cached
module MacroExpansion {
/**
* Holds if `e` is superseded by an attribute macro expansion. That is, `e` is
* a transitive child of an item with an attribute macro expansion.
*
* Since this predicate is referenced in the charpred of `Element`, we need to
* use the parent-child relation on raw elements to avoid non-monotonicity.
*/
private predicate supersededByAttributeMacroExpansionRaw(Raw::Item item, Raw::Element e) {
exists(item.getAttributeMacroExpansion()) and
e = Raw::getImmediateChild(item, _) and
not e = item.getAttributeMacroExpansion() and
// Don't consider attributes themselves to be superseded. E.g., in `#[a] fn
// f() {}` the macro expansion supersedes `fn f() {}` but not `#[a]`.
not e instanceof Raw::Attr
or
exists(Raw::Element parent |
e = Raw::getImmediateChild(parent, _) and
supersededByAttributeMacroExpansionRaw(item, parent)
)
}

private predicate isMacroExpansion(AstNode macro, AstNode expansion) {
expansion = macro.(MacroCall).getMacroCallExpansion()
or
expansion = macro.(Adt).getDeriveMacroExpansion(_)
or
expansion = macro.(Item).getAttributeMacroExpansion()
}

/**
* Gets the immediately enclosing macro invocation for element `e`, if any.
*
* The result is either a `MacroCall`, an `Adt` with a derive macro expansion, or
* an `Item` with an attribute macro expansion.
*/
cached
AstNode getImmediatelyEnclosingMacroInvocation(Element e) {
isMacroExpansion(result, e)
or
exists(Element mid |
result = getImmediatelyEnclosingMacroInvocation(mid) and
mid = getImmediateParent(e) and
not isMacroExpansion(mid, e)
)
}

pragma[nomagic]
private predicate isAttributeMacroExpansionSourceLocation(Item i, Location l) {
exists(Raw::Locatable e, @location_default loc |
supersededByAttributeMacroExpansionRaw(Synth::convertElementToRaw(i), e) and
locatable_locations(e, loc) and
l = LocationImpl::TLocationDefault(loc)
)
}

/** Gets an AST node whose location is inside the token tree belonging to `mc`. */
pragma[nomagic]
private AstNode getATokenTreeNode(MacroCall mc) {
mc = getImmediatelyEnclosingMacroInvocation(result) and
mc.getTokenTree().getLocation().contains(result.getLocation())
}

/** Holds if `n` is inside a macro expansion. */
cached
predicate isInMacroExpansion(AstNode n) { exists(getImmediatelyEnclosingMacroInvocation(n)) }

/**
* Holds if `n` exists only as the result of a macro expansion.
*
* This is the same as `isInMacroExpansion(n)`, but excludes AST nodes corresponding
* to macro arguments, including attribute macro targets.
*
* Note: This predicate is a heuristic based on location information and may not be
* accurate in all cases.
*/
cached
predicate isFromMacroExpansion(AstNode n) {
exists(AstNode macro |
macro = getImmediatelyEnclosingMacroInvocation(n) and
not n = getATokenTreeNode(macro) and
not isAttributeMacroExpansionSourceLocation(macro, n.getLocation())
)
or
isFromMacroExpansion(getImmediatelyEnclosingMacroInvocation(n))
}

cached
predicate isRelevantElement(Generated::Element e) {
exists(Raw::Element raw |
raw = Synth::convertElementToRaw(e) and
not supersededByAttributeMacroExpansionRaw(_, raw)
)
or
// Synthetic elements are relevant when their parents are
Synth::convertFormatArgsExprToRaw(_) = Synth::getSynthParent(e)
}
}

class Element extends Generated::Element {
Element() { MacroExpansion::isRelevantElement(this) }

override string toStringImpl() { result = this.getAPrimaryQlClass() }

/**
Expand Down
8 changes: 7 additions & 1 deletion rust/ql/lib/codeql/rust/elements/internal/ItemImpl.qll
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// generated by codegen, remove this comment if you wish to edit this file
/**
* This module provides a hand-modifiable wrapper around the generated class `Item`.
*
Expand All @@ -12,6 +11,7 @@ private import codeql.rust.elements.internal.generated.Item
* be referenced directly.
*/
module Impl {
// the following QLdoc is generated: if you need to edit it, do it in the schema file
/**
* An item such as a function, struct, enum, etc.
*
Expand All @@ -23,4 +23,10 @@ module Impl {
* ```
*/
class Item extends Generated::Item { }

private class ItemWithAttributeMacroExpansion extends Item {
ItemWithAttributeMacroExpansion() { this.hasAttributeMacroExpansion() }

override string toStringImpl() { result = "(item with attribute macro expansion)" }
}
}
14 changes: 3 additions & 11 deletions rust/ql/lib/codeql/rust/elements/internal/LocatableImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import codeql.Locations
private import codeql.rust.elements.internal.ElementImpl::Impl as ElementImpl
private import codeql.rust.elements.internal.LocationImpl
private import codeql.rust.elements.internal.generated.Locatable
private import codeql.rust.elements.internal.generated.Synth
Expand Down Expand Up @@ -50,21 +51,12 @@ module Impl {
locatable_locations(Synth::convertLocatableToRaw(l), result)
}

private MacroCall getImmediatelyEnclosingMacroCall(AstNode n) {
result = n.getParentNode()
or
exists(AstNode mid |
result = getImmediatelyEnclosingMacroCall(mid) and
n.getParentNode() = mid and
not mid instanceof MacroCall
)
}

/** Gets the non-synthesized location of `l`, if any. */
LocationImpl::LocationDefault getLocationDefault(Locatable l) {
result = LocationImpl::TLocationDefault(getDbLocation(l))
or
not exists(getDbLocation(l)) and
result = getLocationDefault(getImmediatelyEnclosingMacroCall(l))
result =
getLocationDefault(ElementImpl::MacroExpansion::getImmediatelyEnclosingMacroInvocation(l))
}
}
22 changes: 1 addition & 21 deletions rust/ql/lib/codeql/rust/elements/internal/MacroCallImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@ module Impl {
private import rust
private import codeql.rust.internal.PathResolution

pragma[nomagic]
predicate isInMacroExpansion(AstNode root, AstNode n) {
n = root.(MacroCall).getMacroCallExpansion()
or
n = root.(Adt).getDeriveMacroExpansion(_)
or
n = root.(Item).getAttributeMacroExpansion()
or
isInMacroExpansion(root, n.getParentNode())
}

// the following QLdoc is generated: if you need to edit it, do it in the schema file
/**
* A macro invocation.
Expand All @@ -35,16 +24,7 @@ module Impl {
* ```
*/
class MacroCall extends Generated::MacroCall {
override string toStringImpl() {
if this.hasPath() then result = this.getPath().toAbbreviatedString() + "!..." else result = ""
}

/** Gets an AST node whose location is inside the token tree belonging to this macro call. */
pragma[nomagic]
AstNode getATokenTreeNode() {
isInMacroExpansion(this, result) and
this.getTokenTree().getLocation().contains(result.getLocation())
}
override string toStringImpl() { result = this.getPath().toAbbreviatedString() + "!..." }

/**
* Gets the macro definition that this macro call resolves to.
Expand Down
23 changes: 1 addition & 22 deletions rust/ql/lib/codeql/rust/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,6 @@ private module UseOption = Option<Use>;

private class UseOption = UseOption::Option;

/**
* Holds if `n` is superseded by an attribute macro expansion. That is, `n` is
* an item or a transitive child of an item with an attribute macro expansion.
*/
predicate supersededByAttributeMacroExpansion(AstNode n) {
n.(Item).hasAttributeMacroExpansion()
or
exists(AstNode parent |
n.getParentNode() = parent and
supersededByAttributeMacroExpansion(parent) and
// Don't exclude expansions themselves as they supercede other nodes.
not n = parent.(Item).getAttributeMacroExpansion() and
// Don't consider attributes themselves to be superseded. E.g., in `#[a] fn
// f() {}` the macro expansion supercedes `fn f() {}` but not `#[a]`.
not n instanceof Attr
)
}

/**
* An item that may be referred to by a path, and which is a node in
* the _item graph_.
Expand Down Expand Up @@ -186,10 +168,7 @@ predicate supersededByAttributeMacroExpansion(AstNode n) {
* - https://doc.rust-lang.org/reference/names/namespaces.html
*/
abstract class ItemNode extends Locatable {
ItemNode() {
// Exclude items that are superseded by the expansion of an attribute macro.
not supersededByAttributeMacroExpansion(this)
}
ItemNode() { not this.(Item).hasAttributeMacroExpansion() }

/** Gets the (original) name of this item. */
abstract string getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ private import utils.test.InlineExpectationsTest
private module ResolveTest implements TestSig {
string getARelevantTag() { result = "item" }

private predicate itemAt(ItemNode i, string filepath, int line, boolean inMacro) {
i.getLocation().hasLocationInfo(filepath, _, _, line, _) and
if i.(AstNode).isInMacroExpansion() then inMacro = true else inMacro = false
private predicate itemAt(ItemNode i, string filepath, int line) {
i.getLocation().hasLocationInfo(filepath, _, _, line, _)
}

private predicate commmentAt(string text, string filepath, int line) {
Expand All @@ -25,7 +24,7 @@ private module ResolveTest implements TestSig {
}

private predicate item(ItemNode i, string value) {
exists(string filepath, int line, boolean inMacro | itemAt(i, filepath, line, inMacro) |
exists(string filepath, int line | itemAt(i, filepath, line) |
if i instanceof SourceFile
then value = i.getFile().getBaseName()
else (
Expand Down
2 changes: 1 addition & 1 deletion rust/ql/src/queries/unusedentities/UnreachableCode.ql
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ predicate hiddenNode(AstNode n) {
n instanceof ControlFlowGraphImpl::PostOrderTree and // location is counter-intuitive
not n instanceof MacroExpr
or
n.isInMacroExpansion()
n.isFromMacroExpansion()
}

/**
Expand Down
2 changes: 1 addition & 1 deletion rust/ql/src/queries/unusedentities/UnusedValue.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ from AstNode write, Ssa::Variable v
where
variableWrite(_, write, v) and
not v instanceof DiscardVariable and
not write.isInMacroExpansion() and
not write.isFromMacroExpansion() and
not isAllowableUnused(v) and
// SSA definitions are only created for live writes
not write = any(Ssa::WriteDefinition def).getWriteAccess().getAstNode() and
Expand Down
2 changes: 1 addition & 1 deletion rust/ql/src/queries/unusedentities/UnusedVariable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class IncompleteCallable extends Callable {
*/
predicate isAllowableUnused(Variable v) {
// in a macro expansion
v.getPat().isInMacroExpansion()
v.getPat().isInMacroExpansion() // TODO: replace with `isFromMacroExpansion()` when false positives have been removed
or
// declared in an incomplete callable
v.getEnclosingCfgScope() instanceof IncompleteCallable
Expand Down
Loading