Skip to content

Commit 8c91ef0

Browse files
committed
Rust: Add support for resolving methods from blanket implementations
1 parent 14be2c6 commit 8c91ef0

File tree

7 files changed

+272
-9
lines changed

7 files changed

+272
-9
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,14 @@ final class TypeParamItemNode extends TypeItemNode instanceof TypeParam {
10061006
Path getABoundPath() { result = this.getTypeBoundAt(_, _).getTypeRepr().(PathTypeRepr).getPath() }
10071007

10081008
pragma[nomagic]
1009+
ItemNode resolveBound(int index) {
1010+
result =
1011+
rank[index + 1](int i, int j |
1012+
|
1013+
resolvePath(this.getTypeBoundAt(i, j).getTypeRepr().(PathTypeRepr).getPath()) order by i, j
1014+
)
1015+
}
1016+
10091017
ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) }
10101018

10111019
/**

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

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int
19031903
methodCandidate(type, name, arity, impl)
19041904
}
19051905

1906+
/**
1907+
* Holds if `mc` has `rootType` as the root type of the reciever and the target
1908+
* method is named `name` and has arity `arity`
1909+
*/
19061910
pragma[nomagic]
19071911
private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) {
19081912
rootType = mc.getTypeAt(TypePath::nil()) and
@@ -2141,6 +2145,155 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) {
21412145
else any()
21422146
}
21432147

2148+
private module BlanketImplementation {
2149+
/**
2150+
* Gets the type parameter for which `impl` is a blanket implementation, if
2151+
* any.
2152+
*/
2153+
private TypeParamItemNode getBlanketImplementationTypeParam(Impl impl) {
2154+
result = impl.(ImplItemNode).resolveSelfTy() and
2155+
result = impl.getGenericParamList().getAGenericParam() and
2156+
// This impl block is not superseded by the expansion of an attribute macro.
2157+
not exists(impl.getAttributeMacroExpansion())
2158+
}
2159+
2160+
predicate isBlanketImplementation(Impl impl) { exists(getBlanketImplementationTypeParam(impl)) }
2161+
2162+
private Impl getPotentialDuplicated(string fileName, string traitName, int arity, string tpName) {
2163+
tpName = getBlanketImplementationTypeParam(result).getName() and
2164+
fileName = result.getLocation().getFile().getBaseName() and
2165+
traitName = result.(ImplItemNode).resolveTraitTy().getName() and
2166+
arity = result.(ImplItemNode).resolveTraitTy().(Trait).getNumberOfGenericParams()
2167+
}
2168+
2169+
/**
2170+
* Holds if `impl1` and `impl2` are duplicates and `impl2` is strictly more
2171+
* "canonical" than `impl1`.
2172+
*
2173+
* Libraries can often occur several times in the database for different
2174+
* library versions. This causes the same blanket implementations to exist
2175+
* multiple times, and these add no useful information.
2176+
*
2177+
* We detect these duplicates based on some simple heuristics (same trait
2178+
* name, file name, etc.). For these duplicates we select the one with the
2179+
* greatest file name (which usually is also the one with the greatest library
2180+
* version in the path)
2181+
*/
2182+
predicate duplicatedImpl(Impl impl1, Impl impl2) {
2183+
exists(string fileName, string traitName, int arity, string tpName |
2184+
impl1 = getPotentialDuplicated(fileName, traitName, arity, tpName) and
2185+
impl2 = getPotentialDuplicated(fileName, traitName, arity, tpName) and
2186+
impl1.getLocation().getFile().getAbsolutePath() <
2187+
impl2.getLocation().getFile().getAbsolutePath()
2188+
)
2189+
}
2190+
2191+
predicate isCanonicalImpl(Impl impl) {
2192+
not duplicatedImpl(impl, _) and isBlanketImplementation(impl)
2193+
}
2194+
2195+
Impl getCanonicalImpl(Impl impl) {
2196+
result =
2197+
max(Impl impl0, Location l |
2198+
duplicatedImpl(impl, impl0) and l = impl0.getLocation()
2199+
|
2200+
impl0 order by l.getFile().getAbsolutePath(), l.getStartLine()
2201+
)
2202+
or
2203+
isCanonicalImpl(impl) and result = impl
2204+
}
2205+
2206+
predicate isCanonicalBlanketImplementation(Impl impl) { impl = getCanonicalImpl(impl) }
2207+
2208+
/**
2209+
* Holds if `impl` is a blanket implementation for a type parameter and the type
2210+
* parameter must implement `trait`.
2211+
*/
2212+
private predicate blanketImplementationTraitBound(Impl impl, Trait t) {
2213+
t =
2214+
min(Trait trait, int i |
2215+
trait = getBlanketImplementationTypeParam(impl).resolveBound(i) and
2216+
// Exclude traits that are known to not narrow things down very much.
2217+
not trait.getName().getText() =
2218+
[
2219+
"Sized", "Clone",
2220+
// The auto traits
2221+
"Send", "Sync", "Unpin", "UnwindSafe", "RefUnwindSafe"
2222+
]
2223+
|
2224+
trait order by i
2225+
)
2226+
}
2227+
2228+
/**
2229+
* Holds if `impl` is a relevant blanket implementation that requires the
2230+
* trait `trait` and provides `f`, a method with name `name` and arity
2231+
* `arity`.
2232+
*/
2233+
private predicate blanketImplementationMethod(
2234+
ImplItemNode impl, Trait trait, string name, int arity, Function f
2235+
) {
2236+
isCanonicalBlanketImplementation(impl) and
2237+
blanketImplementationTraitBound(impl, trait) and
2238+
f.getParamList().hasSelfParam() and
2239+
arity = f.getParamList().getNumberOfParams() and
2240+
(
2241+
f = impl.getAssocItem(name)
2242+
or
2243+
// If the trait has a method with a default implementation, then that
2244+
// target is interesting as well.
2245+
not exists(impl.getAssocItem(name)) and
2246+
f = impl.resolveTraitTy().getAssocItem(name)
2247+
) and
2248+
// If the method is already available through one of the trait bounds on the
2249+
// type parameter (because they share a common ancestor trait) then ignore
2250+
// it.
2251+
not getBlanketImplementationTypeParam(impl).resolveABound().(TraitItemNode).getASuccessor(name) =
2252+
f
2253+
}
2254+
2255+
predicate methodCallMatchesBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) {
2256+
// Only check method calls where we have ruled out inherent method targets.
2257+
// Ideally we would also check if non-blanket method targets have been ruled
2258+
// out.
2259+
methodCallHasNoInherentTarget(mc) and
2260+
exists(string name, int arity |
2261+
isMethodCall(mc, t, name, arity) and
2262+
blanketImplementationMethod(impl, trait, name, arity, f)
2263+
)
2264+
}
2265+
2266+
private predicate relevantTraitVisible(Element mc, Trait trait) {
2267+
exists(ImplItemNode impl |
2268+
methodCallMatchesBlanketImpl(mc, _, impl, _, _) and
2269+
trait = impl.resolveTraitTy()
2270+
)
2271+
}
2272+
2273+
module SatisfiesConstraintInput implements SatisfiesConstraintInputSig<MethodCall> {
2274+
pragma[nomagic]
2275+
predicate relevantConstraint(MethodCall mc, Type constraint) {
2276+
exists(Trait trait, Trait trait2, ImplItemNode impl |
2277+
methodCallMatchesBlanketImpl(mc, _, impl, trait, _) and
2278+
TraitIsVisible<relevantTraitVisible/2>::traitIsVisible(mc, pragma[only_bind_into](trait2)) and
2279+
trait2 = pragma[only_bind_into](impl.resolveTraitTy()) and
2280+
trait = constraint.(TraitType).getTrait()
2281+
)
2282+
}
2283+
2284+
predicate useUniversalConditions() { none() }
2285+
}
2286+
2287+
predicate hasBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) {
2288+
SatisfiesConstraint<MethodCall, SatisfiesConstraintInput>::satisfiesConstraintType(mc,
2289+
TTrait(trait), _, _) and
2290+
methodCallMatchesBlanketImpl(mc, t, impl, trait, f)
2291+
}
2292+
2293+
pragma[nomagic]
2294+
Function getMethodFromBlanketImpl(MethodCall mc) { hasBlanketImpl(mc, _, _, _, result) }
2295+
}
2296+
21442297
/** Gets a method from an `impl` block that matches the method call `mc`. */
21452298
pragma[nomagic]
21462299
private Function getMethodFromImpl(MethodCall mc) {
@@ -2176,6 +2329,8 @@ private Function resolveMethodCallTarget(MethodCall mc) {
21762329
// The method comes from an `impl` block targeting the type of the receiver.
21772330
result = getMethodFromImpl(mc)
21782331
or
2332+
result = BlanketImplementation::getMethodFromBlanketImpl(mc)
2333+
or
21792334
// The type of the receiver is a type parameter and the method comes from a
21802335
// trait bound on the type parameter.
21812336
result = getTypeParameterMethod(mc.getTypeAt(TypePath::nil()), mc.getMethodName())

rust/ql/test/library-tests/type-inference/blanket_impl.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ mod basic_blanket_impl {
3232
pub fn test_basic_blanket() {
3333
let x = S1.clone1(); // $ target=S1::clone1
3434
println!("{x:?}");
35-
let y = S1.duplicate(); // $ MISSING: target=Clone1duplicate
35+
let y = S1.duplicate(); // $ target=Clone1duplicate
3636
println!("{y:?}");
3737
}
3838
}
@@ -109,7 +109,7 @@ mod extension_trait_blanket_impl {
109109

110110
fn test() {
111111
let my_try_flag = MyTryFlag { flag: true };
112-
let result = my_try_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice
112+
let result = my_try_flag.try_read_flag_twice(); // $ target=TryFlagExt::try_read_flag_twice
113113

114114
let my_flag = MyFlag { flag: true };
115115
// Here `TryFlagExt::try_read_flag_twice` is since there is a blanket
@@ -150,11 +150,11 @@ pub mod sql_exec {
150150
pub fn f() {
151151
let c = MySqlConnection {}; // $ type=c:MySqlConnection
152152

153-
c.execute1(); // $ MISSING: target=execute1
153+
c.execute1(); // $ target=execute1
154154
MySqlConnection::execute1(&c); // $ MISSING: target=execute1
155155

156-
c.execute2("SELECT * FROM users"); // $ MISSING: target=execute2
157-
c.execute2::<&str>("SELECT * FROM users"); // $ MISSING: target=execute2
156+
c.execute2("SELECT * FROM users"); // $ target=execute2
157+
c.execute2::<&str>("SELECT * FROM users"); // $ target=execute2
158158
MySqlConnection::execute2(&c, "SELECT * FROM users"); // $ MISSING: target=execute2
159159
MySqlConnection::execute2::<&str>(&c, "SELECT * FROM users"); // $ MISSING: target=execute2
160160
}

rust/ql/test/library-tests/type-inference/dyn_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn test_assoc_type(obj: &dyn AssocTrait<i64, AP = bool>) {
101101
pub fn test() {
102102
test_basic_dyn_trait(&MyStruct { value: 42 }); // $ target=test_basic_dyn_trait
103103
test_generic_dyn_trait(&GenStruct {
104-
value: "".to_string(),
104+
value: "".to_string(), // $ target=to_string
105105
}); // $ target=test_generic_dyn_trait
106106
test_poly_dyn_trait(); // $ target=test_poly_dyn_trait
107107
test_assoc_type(&GenStruct { value: 100 }); // $ target=test_assoc_type

rust/ql/test/library-tests/type-inference/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ mod method_non_parametric_trait_impl {
365365

366366
fn type_bound_type_parameter_impl<TP: MyTrait<S1>>(thing: TP) -> S1 {
367367
// The trait bound on `TP` makes the implementation of `ConvertTo` valid
368-
thing.convert_to() // $ MISSING: target=T::convert_to
368+
thing.convert_to() // $ target=T::convert_to
369369
}
370370

371371
pub fn f() {
@@ -437,7 +437,7 @@ mod method_non_parametric_trait_impl {
437437
let x = get_snd_fst(c); // $ type=x:S1 target=get_snd_fst
438438

439439
let thing = MyThing { a: S1 };
440-
let i = thing.convert_to(); // $ MISSING: type=i:S1 target=T::convert_to
440+
let i = thing.convert_to(); // $ type=i:S1 target=T::convert_to
441441
let j = convert_to(thing); // $ type=j:S1 target=convert_to
442442
}
443443
}
@@ -1376,7 +1376,7 @@ mod method_call_type_conversion {
13761376
let t = x7.m1(); // $ target=m1 type=t:& type=t:&T.S2
13771377
println!("{:?}", x7);
13781378

1379-
let x9: String = "Hello".to_string(); // $ certainType=x9:String
1379+
let x9: String = "Hello".to_string(); // $ certainType=x9:String target=to_string
13801380

13811381
// Implicit `String` -> `str` conversion happens via the `Deref` trait:
13821382
// https://doc.rust-lang.org/std/string/struct.String.html#deref.

0 commit comments

Comments
 (0)