Skip to content

Commit 3b9f683

Browse files
committed
Rust: Implement basic support for blanket implementations
1 parent 7f46b97 commit 3b9f683

File tree

7 files changed

+253
-6
lines changed

7 files changed

+253
-6
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,10 +967,19 @@ class TypeParamItemNode extends TypeItemNode instanceof TypeParam {
967967
)
968968
}
969969

970+
pragma[nomagic]
971+
Path getBoundPath(int index) {
972+
result = super.getTypeBound(index).getTypeRepr().(PathTypeRepr).getPath()
973+
}
974+
970975
pragma[nomagic]
971976
Path getABoundPath() { result = super.getATypeBound().getTypeRepr().(PathTypeRepr).getPath() }
972977

973978
pragma[nomagic]
979+
ItemNode resolveBound(int index) {
980+
result = rank[index + 1](int i | | resolvePath(this.getBoundPath(i)) order by i)
981+
}
982+
974983
ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) }
975984

976985
/**

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

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int
18631863
methodCandidate(type, name, arity, impl)
18641864
}
18651865

1866+
/**
1867+
* Holds if `mc` has `rootType` as the root type of the reciever and the target
1868+
* method is named `name` and has arity `arity`
1869+
*/
18661870
pragma[nomagic]
18671871
private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) {
18681872
rootType = mc.getTypeAt(TypePath::nil()) and
@@ -2061,6 +2065,142 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) {
20612065
else any()
20622066
}
20632067

2068+
private module BlanketImplementation {
2069+
/**
2070+
* Holds if `impl` is a blanket implementation, that is, an implementation of a
2071+
* trait for a type parameter.
2072+
*/
2073+
private TypeParamItemNode getBlanketImplementationTypeParam(Impl impl) {
2074+
result = impl.(ImplItemNode).resolveSelfTy() and
2075+
result = impl.getGenericParamList().getAGenericParam() and
2076+
not exists(impl.getAttributeMacroExpansion())
2077+
}
2078+
2079+
predicate isBlanketImplementation(Impl impl) { exists(getBlanketImplementationTypeParam(impl)) }
2080+
2081+
private Impl getPotentialDuplicated(string fileName, string traitName, int arity, string tpName) {
2082+
tpName = getBlanketImplementationTypeParam(result).getName() and
2083+
fileName = result.getLocation().getFile().getBaseName() and
2084+
traitName = result.(ImplItemNode).resolveTraitTy().getName() and
2085+
arity = result.(ImplItemNode).resolveTraitTy().(Trait).getNumberOfGenericParams()
2086+
}
2087+
2088+
/**
2089+
* Holds if `impl1` and `impl2` are duplicates and `impl2` is more "canonical"
2090+
* than `impl1`.
2091+
*/
2092+
predicate duplicatedImpl(Impl impl1, Impl impl2) {
2093+
exists(string fileName, string traitName, int arity, string tpName |
2094+
impl1 = getPotentialDuplicated(fileName, traitName, arity, tpName) and
2095+
impl2 = getPotentialDuplicated(fileName, traitName, arity, tpName) and
2096+
impl1.getLocation().getFile().getAbsolutePath() <
2097+
impl2.getLocation().getFile().getAbsolutePath()
2098+
)
2099+
}
2100+
2101+
predicate hasNoDuplicates(Impl impl) {
2102+
not duplicatedImpl(impl, _) and isBlanketImplementation(impl)
2103+
}
2104+
2105+
/**
2106+
* We currently consider blanket implementations to be in scope "globally",
2107+
* even though they actually need to be imported to be used. One downside of
2108+
* this is that the libraries included in the database can often occur several
2109+
* times for different library versions. This causes the same blanket
2110+
* implementations to exist multiple times, and these add no useful
2111+
* information.
2112+
*
2113+
* We detect these duplicates based on some files heuristic (same trait name,
2114+
* file name, etc.). For these duplicates we select the one with the greatest
2115+
* file name (which usually is also the one with the greatest library version
2116+
* in the path)
2117+
*/
2118+
Impl getCanonicalImpl(Impl impl) {
2119+
result =
2120+
max(Impl impl0, Location l |
2121+
duplicatedImpl(impl, impl0) and l = impl0.getLocation()
2122+
|
2123+
impl0 order by l.getFile().getAbsolutePath(), l.getStartLine()
2124+
)
2125+
or
2126+
hasNoDuplicates(impl) and result = impl
2127+
}
2128+
2129+
predicate isCanonicalBlanketImplementation(Impl impl) { impl = getCanonicalImpl(impl) }
2130+
2131+
/**
2132+
* Holds if `impl` is a blanket implementation for a type parameter and the type
2133+
* parameter must implement `trait`.
2134+
*/
2135+
private predicate blanketImplementationTraitBound(Impl impl, Trait t) {
2136+
t =
2137+
min(Trait trait, int i |
2138+
trait = getBlanketImplementationTypeParam(impl).resolveBound(i) and
2139+
// Exclude traits that are "trivial" in the sense that they are known to
2140+
// not narrow things down very much.
2141+
not trait.getName().getText() =
2142+
[
2143+
"Sized", "Clone", "Fn", "FnOnce", "FnMut",
2144+
// The auto traits
2145+
"Send", "Sync", "Unpin", "UnwindSafe", "RefUnwindSafe"
2146+
]
2147+
|
2148+
trait order by i
2149+
)
2150+
}
2151+
2152+
private predicate blanketImplementationMethod(
2153+
Impl impl, Trait trait, string name, int arity, Function f
2154+
) {
2155+
isCanonicalBlanketImplementation(impl) and
2156+
blanketImplementationTraitBound(impl, trait) and
2157+
f.getParamList().hasSelfParam() and
2158+
arity = f.getParamList().getNumberOfParams() and
2159+
(
2160+
f = impl.(ImplItemNode).getAssocItem(name)
2161+
or
2162+
// If the the trait has a method with a default implementation, then that
2163+
// target is interesting as well.
2164+
not exists(impl.(ImplItemNode).getAssocItem(name)) and
2165+
f = impl.(ImplItemNode).resolveTraitTy().getAssocItem(name)
2166+
) and
2167+
// If the method is already available through one of the trait bounds on the
2168+
// type parameter (because they share a common trait ancestor) then ignore
2169+
// it.
2170+
not getBlanketImplementationTypeParam(impl).resolveABound().(TraitItemNode).getASuccessor(name) =
2171+
f
2172+
}
2173+
2174+
predicate methodCallMatchesBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) {
2175+
// Only check method calls where we have ruled out inherent method targets.
2176+
// Ideally we would also check if non-blanket method targets have been ruled
2177+
// out.
2178+
methodCallHasNoInherentTarget(mc) and
2179+
exists(string name, int arity |
2180+
isMethodCall(mc, t, name, arity) and
2181+
blanketImplementationMethod(impl, trait, name, arity, f)
2182+
)
2183+
}
2184+
2185+
module SatisfiesConstraintInput implements SatisfiesConstraintInputSig<MethodCall> {
2186+
pragma[nomagic]
2187+
predicate relevantConstraint(MethodCall mc, Type constraint) {
2188+
methodCallMatchesBlanketImpl(mc, _, _, constraint.(TraitType).getTrait(), _)
2189+
}
2190+
2191+
predicate useUniversalConditions() { none() }
2192+
}
2193+
2194+
predicate hasBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) {
2195+
SatisfiesConstraint<MethodCall, SatisfiesConstraintInput>::satisfiesConstraintType(mc,
2196+
TTrait(trait), _, _) and
2197+
methodCallMatchesBlanketImpl(mc, t, impl, trait, f)
2198+
}
2199+
2200+
pragma[nomagic]
2201+
Function getMethodFromBlanketImpl(MethodCall mc) { hasBlanketImpl(mc, _, _, _, result) }
2202+
}
2203+
20642204
/** Gets a method from an `impl` block that matches the method call `mc`. */
20652205
pragma[nomagic]
20662206
private Function getMethodFromImpl(MethodCall mc) {
@@ -2096,6 +2236,8 @@ private Function resolveMethodCallTarget(MethodCall mc) {
20962236
// The method comes from an `impl` block targeting the type of the receiver.
20972237
result = getMethodFromImpl(mc)
20982238
or
2239+
result = BlanketImplementation::getMethodFromBlanketImpl(mc)
2240+
or
20992241
// The type of the receiver is a type parameter and the method comes from a
21002242
// trait bound on the type parameter.
21012243
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
@@ -316,7 +316,7 @@ mod method_non_parametric_trait_impl {
316316

317317
fn type_bound_type_parameter_impl<TP: MyTrait<S1>>(thing: TP) -> S1 {
318318
// The trait bound on `TP` makes the implementation of `ConvertTo` valid
319-
thing.convert_to() // $ MISSING: target=T::convert_to
319+
thing.convert_to() // $ target=T::convert_to
320320
}
321321

322322
pub fn f() {
@@ -388,7 +388,7 @@ mod method_non_parametric_trait_impl {
388388
let x = get_snd_fst(c); // $ type=x:S1 target=get_snd_fst
389389

390390
let thing = MyThing { a: S1 };
391-
let i = thing.convert_to(); // $ MISSING: type=i:S1 target=T::convert_to
391+
let i = thing.convert_to(); // $ type=i:S1 target=T::convert_to
392392
let j = convert_to(thing); // $ type=j:S1 target=convert_to
393393
}
394394
}
@@ -1327,7 +1327,7 @@ mod method_call_type_conversion {
13271327
let t = x7.m1(); // $ target=m1 type=t:& type=t:&T.S2
13281328
println!("{:?}", x7);
13291329

1330-
let x9: String = "Hello".to_string(); // $ type=x9:String
1330+
let x9: String = "Hello".to_string(); // $ type=x9:String target=to_string
13311331

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

0 commit comments

Comments
 (0)