Skip to content

Commit c220f3d

Browse files
committed
Rust: Implement basic support for blanket implementations
1 parent 2efccaa commit c220f3d

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
@@ -1895,6 +1895,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int
18951895
methodCandidate(type, name, arity, impl)
18961896
}
18971897

1898+
/**
1899+
* Holds if `mc` has `rootType` as the root type of the reciever and the target
1900+
* method is named `name` and has arity `arity`
1901+
*/
18981902
pragma[nomagic]
18991903
private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) {
19001904
rootType = mc.getTypeAt(TypePath::nil()) and
@@ -2127,6 +2131,142 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) {
21272131
else any()
21282132
}
21292133

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