Merge pull request #18609 from hvitved/rust/dataflow-path-resolution

Rust: Use `PathResolution` module in data flow
This commit is contained in:
Tom Hvitved
2025-02-04 10:11:54 +01:00
committed by GitHub
2 changed files with 211 additions and 173 deletions

View File

@@ -14,6 +14,7 @@ private import codeql.rust.controlflow.CfgNodes
private import codeql.rust.dataflow.Ssa
private import codeql.rust.dataflow.FlowSummary
private import FlowSummaryImpl as FlowSummaryImpl
private import codeql.rust.elements.internal.PathResolution as PathResolution
/**
* A return kind. A return kind describes how a value can be returned from a
@@ -603,46 +604,124 @@ module LocalFlow {
}
}
private import codeql.util.Option
/**
* Provides temporary modeling of built-in variants, for which no source code
* `Item`s are available.
*
* TODO: Remove once library code is extracted.
*/
private module VariantInLib {
private import codeql.util.Option
private class CrateOrigin extends string {
CrateOrigin() {
this = [any(Item i).getCrateOrigin(), any(Resolvable r).getResolvedCrateOrigin()]
private class CrateOrigin extends string {
CrateOrigin() { this = any(Resolvable r).getResolvedCrateOrigin() }
}
private class CrateOriginOption = Option<CrateOrigin>::Option;
private CrateOriginOption langCoreCrate() { result.asSome() = "lang:core" }
private newtype TVariantInLib =
MkVariantInLib(CrateOriginOption crate, string path, string name) {
crate = langCoreCrate() and
(
path = "crate::option::Option" and
name = "Some"
or
path = "crate::result::Result" and
name = ["Ok", "Err"]
)
}
/** An enum variant from library code, represented by the enum's canonical path and the variant's name. */
class VariantInLib extends MkVariantInLib {
CrateOriginOption crate;
string path;
string name;
VariantInLib() { this = MkVariantInLib(crate, path, name) }
int getAPosition() {
this = MkVariantInLib(langCoreCrate(), "crate::option::Option", "Some") and
result = 0
or
this = MkVariantInLib(langCoreCrate(), "crate::result::Result", ["Ok", "Err"]) and
result = 0
}
string getExtendedCanonicalPath() { result = path + "::" + name }
string toString() { result = name }
}
/** A tuple variant from library code. */
class VariantInLibTupleFieldContent extends VariantContent, TVariantInLibTupleFieldContent {
private VariantInLib::VariantInLib v;
private int pos_;
VariantInLibTupleFieldContent() { this = TVariantInLibTupleFieldContent(v, pos_) }
VariantInLib::VariantInLib getVariantInLib(int pos) { result = v and pos = pos_ }
string getExtendedCanonicalPath() { result = v.getExtendedCanonicalPath() }
int getPosition() { result = pos_ }
final override string toString() {
// only print indices when the arity is > 1
if exists(TVariantInLibTupleFieldContent(v, 1))
then result = v.toString() + "(" + pos_ + ")"
else result = v.toString()
}
final override Location getLocation() { result instanceof EmptyLocation }
}
pragma[nomagic]
private predicate resolveExtendedCanonicalPath(Resolvable r, CrateOriginOption crate, string path) {
path = r.getResolvedPath() and
(
crate.asSome() = r.getResolvedCrateOrigin()
or
crate.isNone() and
not r.hasResolvedCrateOrigin()
)
}
/** Holds if path `p` resolves to variant `v`. */
private predicate pathResolveToVariantInLib(PathAstNode p, VariantInLib v) {
exists(CrateOriginOption crate, string path, string name |
resolveExtendedCanonicalPath(p, pragma[only_bind_into](crate), path + "::" + name) and
v = MkVariantInLib(pragma[only_bind_into](crate), path, name)
)
}
/** Holds if `p` destructs an enum variant `v`. */
pragma[nomagic]
private predicate tupleVariantCanonicalDestruction(TupleStructPat p, VariantInLib v) {
pathResolveToVariantInLib(p, v)
}
bindingset[pos]
predicate tupleVariantCanonicalDestruction(
TupleStructPat pat, VariantInLibTupleFieldContent c, int pos
) {
tupleVariantCanonicalDestruction(pat, c.getVariantInLib(pos))
}
/** Holds if `ce` constructs an enum value of type `v`. */
pragma[nomagic]
private predicate tupleVariantCanonicalConstruction(CallExpr ce, VariantInLib v) {
pathResolveToVariantInLib(ce.getFunction().(PathExpr), v)
}
bindingset[pos]
predicate tupleVariantCanonicalConstruction(CallExpr ce, VariantInLibTupleFieldContent c, int pos) {
tupleVariantCanonicalConstruction(ce, c.getVariantInLib(pos))
}
}
private class CrateOriginOption = Option<CrateOrigin>::Option;
pragma[nomagic]
private predicate hasExtendedCanonicalPath(Item i, CrateOriginOption crate, string path) {
path = i.getExtendedCanonicalPath() and
(
crate.asSome() = i.getCrateOrigin()
or
crate.isNone() and
not i.hasCrateOrigin()
)
}
pragma[nomagic]
private predicate variantHasExtendedCanonicalPath(
Enum e, Variant v, CrateOriginOption crate, string path, string name
) {
hasExtendedCanonicalPath(e, crate, path) and
v = e.getVariantList().getAVariant() and
name = v.getName().getText()
}
pragma[nomagic]
private predicate resolveExtendedCanonicalPath(Resolvable r, CrateOriginOption crate, string path) {
path = r.getResolvedPath() and
(
crate.asSome() = r.getResolvedCrateOrigin()
or
crate.isNone() and
not r.hasResolvedCrateOrigin()
)
}
class VariantInLibTupleFieldContent = VariantInLib::VariantInLibTupleFieldContent;
/**
* A path to a value contained in an object. For example a field name of a struct.
@@ -650,24 +729,9 @@ private predicate resolveExtendedCanonicalPath(Resolvable r, CrateOriginOption c
abstract class Content extends TContent {
/** Gets a textual representation of this content. */
abstract string toString();
}
/** A canonical path pointing to an enum variant. */
class VariantCanonicalPath extends MkVariantCanonicalPath {
CrateOriginOption crate;
string path;
string name;
VariantCanonicalPath() { this = MkVariantCanonicalPath(crate, path, name) }
/** Gets the underlying variant. */
Variant getVariant() { variantHasExtendedCanonicalPath(_, result, crate, path, name) }
string getExtendedCanonicalPath() { result = path + "::" + name }
string toString() { result = name }
Location getLocation() { result = this.getVariant().getLocation() }
/** Gets the location of this content. */
abstract Location getLocation();
}
/**
@@ -676,67 +740,76 @@ class VariantCanonicalPath extends MkVariantCanonicalPath {
*/
abstract class VariantContent extends Content { }
private TupleField getVariantTupleField(Variant v, int i) {
result = v.getFieldList().(TupleFieldList).getField(i)
}
/** A tuple variant. */
private class VariantPositionContent extends VariantContent, TVariantPositionContent {
private VariantCanonicalPath v;
private class VariantTupleFieldContent extends VariantContent, TVariantTupleFieldContent {
private Variant v;
private int pos_;
VariantPositionContent() { this = TVariantPositionContent(v, pos_) }
VariantTupleFieldContent() { this = TVariantTupleFieldContent(v, pos_) }
VariantCanonicalPath getVariantCanonicalPath(int pos) { result = v and pos = pos_ }
Variant getVariant(int pos) { result = v and pos = pos_ }
final override string toString() {
// only print indices when the arity is > 1
if exists(TVariantPositionContent(v, 1))
then result = v.toString() + "(" + pos_ + ")"
else result = v.toString()
exists(string name |
name = v.getName().getText() and
// only print indices when the arity is > 1
if exists(getVariantTupleField(v, 1)) then result = name + "(" + pos_ + ")" else result = name
)
}
final override Location getLocation() { result = getVariantTupleField(v, pos_).getLocation() }
}
private RecordField getVariantRecordField(Variant v, string field) {
result = v.getFieldList().(RecordFieldList).getAField() and
field = result.getName().getText()
}
/** A record variant. */
private class VariantFieldContent extends VariantContent, TVariantFieldContent {
private VariantCanonicalPath v;
private class VariantRecordFieldContent extends VariantContent, TVariantRecordFieldContent {
private Variant v;
private string field_;
VariantFieldContent() { this = TVariantFieldContent(v, field_) }
VariantRecordFieldContent() { this = TVariantRecordFieldContent(v, field_) }
VariantCanonicalPath getVariantCanonicalPath(string field) { result = v and field = field_ }
Variant getVariant(string field) { result = v and field = field_ }
final override string toString() {
// only print field when the arity is > 1
if strictcount(string f | exists(TVariantFieldContent(v, f))) > 1
then result = v.toString() + "{" + field_ + "}"
else result = v.toString()
exists(string name |
name = v.getName().getText() and
// only print field when the arity is > 1
if strictcount(string f | exists(getVariantRecordField(v, f))) > 1
then result = name + "{" + field_ + "}"
else result = name
)
}
}
/** A canonical path pointing to a struct. */
class StructCanonicalPath extends MkStructCanonicalPath {
CrateOriginOption crate;
string path;
StructCanonicalPath() { this = MkStructCanonicalPath(crate, path) }
/** Gets the underlying struct. */
Struct getStruct() { hasExtendedCanonicalPath(result, crate, path) }
string getExtendedCanonicalPath() { result = path }
string toString() { result = this.getStruct().getName().getText() }
Location getLocation() { result = this.getStruct().getLocation() }
final override Location getLocation() {
result = getVariantRecordField(v, field_).getName().getLocation()
}
}
/** Content stored in a field on a struct. */
private class StructFieldContent extends Content, TStructFieldContent {
private StructCanonicalPath s;
private Struct s;
private string field_;
StructFieldContent() { this = TStructFieldContent(s, field_) }
StructCanonicalPath getStructCanonicalPath(string field) { result = s and field = field_ }
Struct getStruct(string field) { result = s and field = field_ }
override string toString() { result = s.toString() + "." + field_.toString() }
override string toString() { result = s.getName().getText() + "." + field_.toString() }
override Location getLocation() {
exists(Name f | f = s.getFieldList().(RecordFieldList).getAField().getName() |
f.getText() = field_ and
result = f.getLocation()
)
}
}
/** A captured variable. */
@@ -749,11 +822,15 @@ private class CapturedVariableContent extends Content, TCapturedVariableContent
Variable getVariable() { result = v }
override string toString() { result = "captured " + v }
override Location getLocation() { result = v.getLocation() }
}
/** A value referred to by a reference. */
final class ReferenceContent extends Content, TReferenceContent {
override string toString() { result = "&ref" }
override Location getLocation() { result instanceof EmptyLocation }
}
/**
@@ -766,6 +843,8 @@ final class ReferenceContent extends Content, TReferenceContent {
*/
final class ElementContent extends Content, TElementContent {
override string toString() { result = "element" }
override Location getLocation() { result instanceof EmptyLocation }
}
/**
@@ -782,6 +861,8 @@ final class TuplePositionContent extends Content, TTuplePositionContent {
int getPosition() { result = pos }
override string toString() { result = "tuple." + pos.toString() }
override Location getLocation() { result instanceof EmptyLocation }
}
/** Holds if `access` indexes a tuple at an index corresponding to `c`. */
@@ -1011,39 +1092,22 @@ module RustDataFlow implements InputSig<Location> {
node2.(Node::FlowSummaryNode).getSummaryNode())
}
/** Holds if path `p` resolves to struct `s`. */
private predicate pathResolveToStructCanonicalPath(PathAstNode p, StructCanonicalPath s) {
exists(CrateOriginOption crate, string path |
resolveExtendedCanonicalPath(p, crate, path) and
s = MkStructCanonicalPath(crate, path)
)
}
/** Holds if path `p` resolves to variant `v`. */
private predicate pathResolveToVariantCanonicalPath(PathAstNode p, VariantCanonicalPath v) {
exists(CrateOriginOption crate, string path, string name |
resolveExtendedCanonicalPath(p, pragma[only_bind_into](crate), path + "::" + name) and
v = MkVariantCanonicalPath(pragma[only_bind_into](crate), path, name)
)
/** Gets the item that `p` resolves to, if any. */
private PathResolution::ItemNode resolvePath(PathAstNode p) {
result = PathResolution::resolvePath(p.getPath())
}
/** Holds if `p` destructs an enum variant `v`. */
pragma[nomagic]
private predicate tupleVariantDestruction(TupleStructPat p, VariantCanonicalPath v) {
pathResolveToVariantCanonicalPath(p, v)
}
private predicate tupleVariantDestruction(TupleStructPat p, Variant v) { v = resolvePath(p) }
/** Holds if `p` destructs an enum variant `v`. */
pragma[nomagic]
private predicate recordVariantDestruction(RecordPat p, VariantCanonicalPath v) {
pathResolveToVariantCanonicalPath(p, v)
}
private predicate recordVariantDestruction(RecordPat p, Variant v) { v = resolvePath(p) }
/** Holds if `p` destructs a struct `s`. */
pragma[nomagic]
private predicate structDestruction(RecordPat p, StructCanonicalPath s) {
pathResolveToStructCanonicalPath(p, s)
}
private predicate structDestruction(RecordPat p, Struct s) { s = resolvePath(p) }
/**
* Holds if data can flow from `node1` to `node2` via a read of `c`. Thus,
@@ -1054,9 +1118,11 @@ module RustDataFlow implements InputSig<Location> {
exists(Content c | c = cs.(SingletonContentSet).getContent() |
exists(TupleStructPatCfgNode pat, int pos |
pat = node1.asPat() and
tupleVariantDestruction(pat.getPat(),
c.(VariantPositionContent).getVariantCanonicalPath(pos)) and
node2.asPat() = pat.getField(pos)
|
tupleVariantDestruction(pat.getPat(), c.(VariantTupleFieldContent).getVariant(pos))
or
VariantInLib::tupleVariantCanonicalDestruction(pat.getPat(), c, pos)
)
or
exists(TuplePatCfgNode pat, int pos |
@@ -1069,11 +1135,10 @@ module RustDataFlow implements InputSig<Location> {
pat = node1.asPat() and
(
// Pattern destructs a struct-like variant.
recordVariantDestruction(pat.getPat(),
c.(VariantFieldContent).getVariantCanonicalPath(field))
recordVariantDestruction(pat.getPat(), c.(VariantRecordFieldContent).getVariant(field))
or
// Pattern destructs a struct.
structDestruction(pat.getPat(), c.(StructFieldContent).getStructCanonicalPath(field))
structDestruction(pat.getPat(), c.(StructFieldContent).getStruct(field))
) and
node2.asPat() = pat.getFieldPat(field)
)
@@ -1110,7 +1175,7 @@ module RustDataFlow implements InputSig<Location> {
exists(TryExprCfgNode try |
node1.asExpr() = try.getExpr() and
node2.asExpr() = try and
c.(VariantPositionContent).getVariantCanonicalPath(0).getExtendedCanonicalPath() =
c.(VariantInLibTupleFieldContent).getVariantInLib(0).getExtendedCanonicalPath() =
["crate::option::Option::Some", "crate::result::Result::Ok"]
)
or
@@ -1130,21 +1195,17 @@ module RustDataFlow implements InputSig<Location> {
/** Holds if `ce` constructs an enum value of type `v`. */
pragma[nomagic]
private predicate tupleVariantConstruction(CallExpr ce, VariantCanonicalPath v) {
pathResolveToVariantCanonicalPath(ce.getFunction().(PathExpr), v)
private predicate tupleVariantConstruction(CallExpr ce, Variant v) {
v = resolvePath(ce.getFunction().(PathExpr))
}
/** Holds if `re` constructs an enum value of type `v`. */
pragma[nomagic]
private predicate recordVariantConstruction(RecordExpr re, VariantCanonicalPath v) {
pathResolveToVariantCanonicalPath(re, v)
}
private predicate recordVariantConstruction(RecordExpr re, Variant v) { v = resolvePath(re) }
/** Holds if `re` constructs a struct value of type `s`. */
pragma[nomagic]
private predicate structConstruction(RecordExpr re, StructCanonicalPath s) {
pathResolveToStructCanonicalPath(re, s)
}
private predicate structConstruction(RecordExpr re, Struct s) { s = resolvePath(re) }
private predicate tupleAssignment(Node node1, Node node2, TuplePositionContent c) {
exists(AssignmentExprCfgNode assignment, FieldExprCfgNode access |
@@ -1158,20 +1219,22 @@ module RustDataFlow implements InputSig<Location> {
pragma[nomagic]
private predicate storeContentStep(Node node1, Content c, Node node2) {
exists(CallExprCfgNode call, int pos |
tupleVariantConstruction(call.getCallExpr(),
c.(VariantPositionContent).getVariantCanonicalPath(pos)) and
node1.asExpr() = call.getArgument(pos) and
node2.asExpr() = call
|
tupleVariantConstruction(call.getCallExpr(), c.(VariantTupleFieldContent).getVariant(pos))
or
VariantInLib::tupleVariantCanonicalConstruction(call.getCallExpr(), c, pos)
)
or
exists(RecordExprCfgNode re, string field |
(
// Expression is for a struct-like enum variant.
recordVariantConstruction(re.getRecordExpr(),
c.(VariantFieldContent).getVariantCanonicalPath(field))
c.(VariantRecordFieldContent).getVariant(field))
or
// Expression is for a struct.
structConstruction(re.getRecordExpr(), c.(StructFieldContent).getStructCanonicalPath(field))
structConstruction(re.getRecordExpr(), c.(StructFieldContent).getStruct(field))
) and
node1.asExpr() = re.getFieldExpr(field) and
node2.asExpr() = re
@@ -1529,46 +1592,12 @@ private module Cached {
cached
newtype TReturnKind = TNormalReturnKind()
private CrateOriginOption langCoreCrate() { result.asSome() = "lang:core" }
cached
newtype TVariantCanonicalPath =
MkVariantCanonicalPath(CrateOriginOption crate, string path, string name) {
variantHasExtendedCanonicalPath(_, _, crate, path, name)
or
// TODO: Remove once library types are extracted
crate = langCoreCrate() and
(
path = "crate::option::Option" and
name = "Some"
or
path = "crate::result::Result" and
name = ["Ok", "Err"]
)
}
cached
newtype TStructCanonicalPath =
MkStructCanonicalPath(CrateOriginOption crate, string path) {
exists(Struct s | hasExtendedCanonicalPath(s, crate, path))
}
cached
newtype TContent =
TVariantPositionContent(VariantCanonicalPath v, int pos) {
pos in [0 .. v.getVariant().getFieldList().(TupleFieldList).getNumberOfFields() - 1]
or
// TODO: Remove once library types are extracted
v = MkVariantCanonicalPath(langCoreCrate(), "crate::option::Option", "Some") and
pos = 0
or
// TODO: Remove once library types are extracted
v = MkVariantCanonicalPath(langCoreCrate(), "crate::result::Result", ["Ok", "Err"]) and
pos = 0
} or
TVariantFieldContent(VariantCanonicalPath v, string field) {
field = v.getVariant().getFieldList().(RecordFieldList).getAField().getName().getText()
} or
TVariantTupleFieldContent(Variant v, int pos) { exists(getVariantTupleField(v, pos)) } or
// TODO: Remove once library types are extracted
TVariantInLibTupleFieldContent(VariantInLib::VariantInLib v, int pos) { pos = v.getAPosition() } or
TVariantRecordFieldContent(Variant v, string field) { exists(getVariantRecordField(v, field)) } or
TElementContent() or
TTuplePositionContent(int pos) {
pos in [0 .. max([
@@ -1577,8 +1606,8 @@ private module Cached {
]
)]
} or
TStructFieldContent(StructCanonicalPath s, string field) {
field = s.getStruct().getFieldList().(RecordFieldList).getAField().getName().getText()
TStructFieldContent(Struct s, string field) {
field = s.getFieldList().(RecordFieldList).getAField().getName().getText()
} or
TCapturedVariableContent(VariableCapture::CapturedVariable v) or
TReferenceContent()

View File

@@ -61,21 +61,30 @@ module Input implements InputSig<Location, RustDataFlow> {
string encodeContent(ContentSet cs, string arg) {
exists(Content c | cs = TSingletonContentSet(c) |
exists(VariantCanonicalPath v | result = "Variant" |
exists(Variant v | result = "Variant" |
exists(int pos |
c = TVariantPositionContent(v, pos) and
c = TVariantTupleFieldContent(v, pos) and
// TODO: calculate in QL
arg = v.getExtendedCanonicalPath() + "(" + pos + ")"
)
or
exists(string field |
c = TVariantFieldContent(v, field) and
// TODO: calculate in QL
c = TVariantRecordFieldContent(v, field) and
arg = v.getExtendedCanonicalPath() + "::" + field
)
)
or
exists(StructCanonicalPath s, string field |
result = "Variant" and
c =
any(VariantInLibTupleFieldContent v |
arg = v.getExtendedCanonicalPath() + "(" + v.getPosition() + ")"
)
or
exists(Struct s, string field |
result = "Struct" and
c = TStructFieldContent(s, field) and
// TODO: calculate in QL
arg = s.getExtendedCanonicalPath() + "::" + field
)
or