Skip to content

Commit 0ed6428

Browse files
authored
Merge pull request #20321 from paldepind/rust/trait-method-scope-2
Rust: Take trait visibility into account when resolving paths and methods
2 parents 1130595 + 322ef4d commit 0ed6428

File tree

11 files changed

+4650
-4410
lines changed

11 files changed

+4650
-4410
lines changed

rust/ql/lib/codeql/rust/internal/PathResolution.qll

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ abstract class ItemNode extends Locatable {
216216
// items made available through `use` are available to nodes that contain the `use`
217217
exists(UseItemNode use |
218218
use = this.getASuccessor(_, _) and
219-
result = use.(ItemNode).getASuccessor(name, kind)
219+
result = use.getASuccessor(name, kind)
220220
)
221221
or
222222
exists(ExternCrateItemNode ec | result = ec.(ItemNode).getASuccessor(name, kind) |
@@ -240,12 +240,7 @@ abstract class ItemNode extends Locatable {
240240
)
241241
or
242242
// items made available by an implementation where `this` is the implementing type
243-
exists(ItemNode node |
244-
this = node.(ImplItemNodeImpl).resolveSelfTyCand() and
245-
result = node.getASuccessor(name, kind) and
246-
kind.isExternalOrBoth() and
247-
result instanceof AssocItemNode
248-
)
243+
typeImplEdge(this, _, name, kind, result)
249244
or
250245
// trait items with default implementations made available in an implementation
251246
exists(ImplItemNodeImpl impl, ItemNode trait |
@@ -1311,6 +1306,7 @@ private predicate declares(ItemNode item, Namespace ns, string name) {
13111306
class RelevantPath extends Path {
13121307
RelevantPath() { not this = any(VariableAccess va).(PathExpr).getPath() }
13131308

1309+
/** Holds if this is an unqualified path with the textual value `name`. */
13141310
pragma[nomagic]
13151311
predicate isUnqualified(string name) {
13161312
not exists(this.getQualifier()) and
@@ -1421,6 +1417,35 @@ private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns, SuccessorKi
14211417
pragma[nomagic]
14221418
private predicate isUnqualifiedSelfPath(RelevantPath path) { path.isUnqualified("Self") }
14231419

1420+
/** Provides the input to `TraitIsVisible`. */
1421+
signature predicate relevantTraitVisibleSig(Element element, Trait trait);
1422+
1423+
/**
1424+
* Provides the `traitIsVisible` predicate for determining if a trait is visible
1425+
* at a given element.
1426+
*/
1427+
module TraitIsVisible<relevantTraitVisibleSig/2 relevantTraitVisible> {
1428+
/** Holds if the trait might be looked up in `encl`. */
1429+
private predicate traitLookup(ItemNode encl, Element element, Trait trait) {
1430+
// lookup in immediately enclosing item
1431+
relevantTraitVisible(element, trait) and
1432+
encl.getADescendant() = element
1433+
or
1434+
// lookup in an outer scope, but only if the trait is not declared in inner scope
1435+
exists(ItemNode mid |
1436+
traitLookup(mid, element, trait) and
1437+
not trait = mid.getASuccessor(_, _) and
1438+
encl = getOuterScope(mid)
1439+
)
1440+
}
1441+
1442+
/** Holds if the trait `trait` is visible at `element`. */
1443+
pragma[nomagic]
1444+
predicate traitIsVisible(Element element, Trait trait) {
1445+
exists(ItemNode encl | traitLookup(encl, element, trait) and trait = encl.getASuccessor(_, _))
1446+
}
1447+
}
1448+
14241449
pragma[nomagic]
14251450
private ItemNode resolvePathCand0(RelevantPath path, Namespace ns) {
14261451
exists(ItemNode res |
@@ -1446,6 +1471,10 @@ private ItemNode resolvePathCandQualifier(RelevantPath qualifier, RelevantPath p
14461471
name = path.getText()
14471472
}
14481473

1474+
/**
1475+
* Gets the item that `path` resolves to in `ns` when `qualifier` is the
1476+
* qualifier of `path` and `qualifier` resolves to `q`, if any.
1477+
*/
14491478
pragma[nomagic]
14501479
private ItemNode resolvePathCandQualified(
14511480
RelevantPath qualifier, ItemNode q, RelevantPath path, Namespace ns
@@ -1520,11 +1549,31 @@ private ItemNode resolvePathCand(RelevantPath path) {
15201549
)
15211550
}
15221551

1552+
/** Get a trait that should be visible when `path` resolves to `node`, if any. */
1553+
private Trait getResolvePathTraitUsed(RelevantPath path, AssocItemNode node) {
1554+
exists(TypeItemNode type, ImplItemNodeImpl impl |
1555+
node = resolvePathCandQualified(_, type, path, _) and
1556+
typeImplEdge(type, impl, _, _, node) and
1557+
result = impl.resolveTraitTyCand()
1558+
)
1559+
}
1560+
1561+
private predicate pathTraitUsed(Element path, Trait trait) {
1562+
trait = getResolvePathTraitUsed(path, _)
1563+
}
1564+
15231565
/** Gets the item that `path` resolves to, if any. */
15241566
cached
15251567
ItemNode resolvePath(RelevantPath path) {
15261568
result = resolvePathCand(path) and
1527-
not path = any(Path parent | exists(resolvePathCand(parent))).getQualifier()
1569+
not path = any(Path parent | exists(resolvePathCand(parent))).getQualifier() and
1570+
(
1571+
// When the result is an associated item of a trait implementation the
1572+
// implemented trait must be visible.
1573+
TraitIsVisible<pathTraitUsed/2>::traitIsVisible(path, getResolvePathTraitUsed(path, result))
1574+
or
1575+
not exists(getResolvePathTraitUsed(path, result))
1576+
)
15281577
or
15291578
// if `path` is the qualifier of a resolvable `parent`, then we should
15301579
// resolve `path` to something consistent with what `parent` resolves to
@@ -1606,8 +1655,16 @@ private predicate useImportEdge(Use use, string name, ItemNode item, SuccessorKi
16061655
not tree.hasRename() and
16071656
name = item.getName()
16081657
or
1609-
name = tree.getRename().getName().getText() and
1610-
name != "_"
1658+
exists(Rename rename | rename = tree.getRename() |
1659+
name = rename.getName().getText()
1660+
or
1661+
// When the rename doesn't have a name it's an underscore import. This
1662+
// makes the imported item visible but unnameable. We represent this
1663+
// by using the name `_` which can never occur in a path. See also:
1664+
// https://doc.rust-lang.org/reference/items/use-declarations.html#r-items.use.as-underscore
1665+
not rename.hasName() and
1666+
name = "_"
1667+
)
16111668
)
16121669
)
16131670
)
@@ -1629,6 +1686,18 @@ private predicate externCrateEdge(ExternCrateItemNode ec, string name, CrateItem
16291686
)
16301687
}
16311688

1689+
/**
1690+
* Holds if `typeItem` is the implementing type of `impl` and the implementation
1691+
* makes `assoc` available as `name` at `kind`.
1692+
*/
1693+
private predicate typeImplEdge(
1694+
TypeItemNode typeItem, ImplItemNodeImpl impl, string name, SuccessorKind kind, AssocItemNode assoc
1695+
) {
1696+
typeItem = impl.resolveSelfTyCand() and
1697+
assoc = impl.getASuccessor(name, kind) and
1698+
kind.isExternalOrBoth()
1699+
}
1700+
16321701
pragma[nomagic]
16331702
private predicate preludeItem(string name, ItemNode i) {
16341703
exists(Crate stdOrCore, ModuleLikeNode mod, ModuleItemNode prelude, ModuleItemNode rust |
@@ -1693,7 +1762,7 @@ private module Debug {
16931762
useImportEdge(use, name, item, kind)
16941763
}
16951764

1696-
ItemNode debuggetASuccessor(ItemNode i, string name, SuccessorKind kind) {
1765+
ItemNode debugGetASuccessor(ItemNode i, string name, SuccessorKind kind) {
16971766
i = getRelevantLocatable() and
16981767
result = i.getASuccessor(name, kind)
16991768
}

rust/ql/lib/codeql/rust/internal/TypeInference.qll

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,7 +1891,7 @@ private predicate methodCandidate(Type type, string name, int arity, Impl impl)
18911891
*/
18921892
pragma[nomagic]
18931893
private predicate methodCandidateTrait(Type type, Trait trait, string name, int arity, Impl impl) {
1894-
trait = resolvePath(impl.(ImplItemNode).getTraitPath()) and
1894+
trait = impl.(ImplItemNode).resolveTraitTy() and
18951895
methodCandidate(type, name, arity, impl)
18961896
}
18971897

@@ -1903,19 +1903,53 @@ private predicate isMethodCall(MethodCall mc, Type rootType, string name, int ar
19031903
}
19041904

19051905
private module IsInstantiationOfInput implements IsInstantiationOfInputSig<MethodCall> {
1906+
/** Holds if `mc` specifies a trait and might target a method in `impl`. */
19061907
pragma[nomagic]
1907-
predicate potentialInstantiationOf(MethodCall mc, TypeAbstraction impl, TypeMention constraint) {
1908+
private predicate methodCallTraitCandidate(MethodCall mc, Impl impl) {
19081909
exists(Type rootType, string name, int arity |
19091910
isMethodCall(mc, rootType, name, arity) and
1910-
constraint = impl.(ImplTypeAbstraction).getSelfTy()
1911-
|
19121911
methodCandidateTrait(rootType, mc.getTrait(), name, arity, impl)
1913-
or
1912+
)
1913+
}
1914+
1915+
/** Holds if `mc` does not specify a trait and might target a method in `impl`. */
1916+
pragma[nomagic]
1917+
private predicate methodCallCandidate(MethodCall mc, Impl impl) {
1918+
exists(Type rootType, string name, int arity |
19141919
not exists(mc.getTrait()) and
1920+
isMethodCall(mc, rootType, name, arity) and
19151921
methodCandidate(rootType, name, arity, impl)
19161922
)
19171923
}
19181924

1925+
private predicate relevantTraitVisible(Element mc, Trait trait) {
1926+
trait = any(ImplItemNode impl | methodCallCandidate(mc, impl)).resolveTraitTy()
1927+
}
1928+
1929+
bindingset[impl]
1930+
pragma[inline_late]
1931+
private TypeRepr getImplSelfTy(Impl impl) { result = impl.getSelfTy() }
1932+
1933+
pragma[nomagic]
1934+
predicate potentialInstantiationOf(MethodCall mc, TypeAbstraction impl, TypeMention constraint) {
1935+
constraint = getImplSelfTy(impl) and
1936+
(
1937+
methodCallTraitCandidate(mc, impl)
1938+
or
1939+
methodCallCandidate(mc, impl) and
1940+
(
1941+
not exists(impl.(ImplItemNode).resolveTraitTy())
1942+
or
1943+
// If the `impl` block implements a trait, that trait must be visible in
1944+
// order for the `impl` to be valid.
1945+
exists(Trait trait |
1946+
pragma[only_bind_into](trait) = impl.(ImplItemNode).resolveTraitTy() and
1947+
TraitIsVisible<relevantTraitVisible/2>::traitIsVisible(mc, pragma[only_bind_into](trait))
1948+
)
1949+
)
1950+
)
1951+
}
1952+
19191953
predicate relevantTypeMention(TypeMention constraint) {
19201954
exists(Impl impl | methodCandidate(_, _, _, impl) and constraint = impl.getSelfTy())
19211955
}

rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@ multipleCallTargets
1111
| test.rs:179:30:179:68 | ...::_print(...) |
1212
| test.rs:188:26:188:105 | ...::_print(...) |
1313
| test.rs:229:22:229:72 | ... .read_to_string(...) |
14-
| test.rs:513:22:513:50 | file.read_to_end(...) |
15-
| test.rs:519:22:519:53 | file.read_to_string(...) |
1614
| test.rs:697:18:697:38 | ...::_print(...) |
1715
| test.rs:702:18:702:45 | ...::_print(...) |
18-
| test.rs:706:25:706:49 | address.to_socket_addrs() |
1916
| test.rs:720:38:720:42 | ...::_print(...) |
2017
| test.rs:724:38:724:54 | ...::_print(...) |
2118
| test.rs:729:38:729:51 | ...::_print(...) |
@@ -76,12 +73,6 @@ multipleCallTargets
7673
| test.rs:977:14:977:29 | ...::_print(...) |
7774
| test.rs:979:27:979:36 | ...::_print(...) |
7875
| test.rs:980:28:980:41 | ...::_print(...) |
79-
| test_futures_io.rs:35:26:35:63 | pinned.poll_read(...) |
80-
| test_futures_io.rs:62:22:62:50 | pinned.poll_fill_buf(...) |
81-
| test_futures_io.rs:69:23:69:67 | ... .poll_fill_buf(...) |
82-
| test_futures_io.rs:93:26:93:63 | pinned.poll_read(...) |
83-
| test_futures_io.rs:116:22:116:50 | pinned.poll_fill_buf(...) |
84-
| test_futures_io.rs:145:26:145:49 | ...::with_capacity(...) |
8576
| web_frameworks.rs:13:14:13:22 | a.as_str() |
8677
| web_frameworks.rs:13:14:13:23 | a.as_str() |
8778
| web_frameworks.rs:14:14:14:24 | a.as_bytes() |

rust/ql/test/library-tests/path-resolution/main.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,58 @@ mod m16 {
460460
} // I83
461461
}
462462

463+
mod trait_visibility {
464+
mod m {
465+
pub trait Foo {
466+
fn a_method(&self); // Foo::a_method
467+
} // Foo
468+
469+
pub trait Bar {
470+
fn a_method(&self); // Bar::a_method
471+
} // Bar
472+
473+
pub struct X;
474+
#[rustfmt::skip]
475+
impl Foo for X { // $ item=Foo item=X
476+
fn a_method(&self) {
477+
println!("foo!");
478+
} // X_Foo::a_method
479+
}
480+
#[rustfmt::skip]
481+
impl Bar for X { // $ item=Bar item=X
482+
fn a_method(&self) {
483+
println!("bar!");
484+
} // X_Bar::a_method
485+
}
486+
}
487+
488+
use m::X; // $ item=X
489+
490+
pub fn f() {
491+
let x = X; // $ item=X
492+
{
493+
// Only the `Foo` trait is visible
494+
use m::Foo; // $ item=Foo
495+
X::a_method(&x); // $ item=X_Foo::a_method
496+
}
497+
{
498+
// Only the `Bar` trait is visible
499+
use m::Bar; // $ item=Bar
500+
X::a_method(&x); // $ item=X_Bar::a_method
501+
}
502+
{
503+
// Only the `Bar` trait is visible (but unnameable)
504+
use m::Bar as _; // $ item=Bar
505+
X::a_method(&x); // $ item=X_Bar::a_method
506+
}
507+
{
508+
// The `Bar` trait is not visible, but we can refer to its method
509+
// with a full path.
510+
m::Bar::a_method(&x); // $ item=Bar::a_method
511+
}
512+
} // trait_visibility::f
513+
}
514+
463515
mod m17 {
464516
trait MyTrait {
465517
fn f(&self); // I1
@@ -730,6 +782,7 @@ fn main() {
730782
m11::f(); // $ item=I63
731783
m15::f(); // $ item=I75
732784
m16::f(); // $ item=I83
785+
trait_visibility::f(); // $ item=trait_visibility::f
733786
m17::f(); // $ item=I99
734787
nested6::f(); // $ item=I116
735788
nested8::f(); // $ item=I119

0 commit comments

Comments
 (0)