Skip to content

Commit b69f419

Browse files
committed
Rust: Implement basic support for blanket implementations
1 parent db25d7a commit b69f419

File tree

7 files changed

+252
-6
lines changed

7 files changed

+252
-6
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: 142 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,142 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) {
21412145
else any()
21422146
}
21432147

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

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

Lines changed: 2 additions & 2 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
}
@@ -108,7 +108,7 @@ mod extension_trait_blanket_impl {
108108

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

113113
let my_flag = MyFlag { flag: true };
114114
// Here `TryFlagExt::try_read_flag_twice` is since there is a blanket

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)