Skip to content

Commit 78389c8

Browse files
authored
Merge pull request #20133 from paldepind/rust/type-inference-blanket-impl
Rust: Support blanket implementations
2 parents af49301 + 3543829 commit 78389c8

File tree

15 files changed

+611
-165
lines changed

15 files changed

+611
-165
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,18 @@ final class ImplItemNode extends ImplOrTraitItemNode instanceof Impl {
648648

649649
override Visibility getVisibility() { result = Impl.super.getVisibility() }
650650

651+
TypeParamItemNode getBlanketImplementationTypeParam() {
652+
result = this.resolveSelfTy() and
653+
// This impl block is not superseded by the expansion of an attribute macro.
654+
not exists(super.getAttributeMacroExpansion())
655+
}
656+
657+
/**
658+
* Holds if this impl block is a blanket implementation. That is, the
659+
* implementation targets a generic parameter of the impl block.
660+
*/
661+
predicate isBlanketImplementation() { exists(this.getBlanketImplementationTypeParam()) }
662+
651663
override predicate hasCanonicalPath(Crate c) { this.resolveSelfTy().hasCanonicalPathPrefix(c) }
652664

653665
/**
@@ -1006,6 +1018,14 @@ final class TypeParamItemNode extends TypeItemNode instanceof TypeParam {
10061018
Path getABoundPath() { result = this.getTypeBoundAt(_, _).getTypeRepr().(PathTypeRepr).getPath() }
10071019

10081020
pragma[nomagic]
1021+
ItemNode resolveBound(int index) {
1022+
result =
1023+
rank[index + 1](int i, int j |
1024+
|
1025+
resolvePath(this.getTypeBoundAt(i, j).getTypeRepr().(PathTypeRepr).getPath()) order by i, j
1026+
)
1027+
}
1028+
10091029
ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) }
10101030

10111031
/**

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

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,6 +1915,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int
19151915
methodCandidate(type, name, arity, impl)
19161916
}
19171917

1918+
/**
1919+
* Holds if `mc` has `rootType` as the root type of the receiver and the target
1920+
* method is named `name` and has arity `arity`
1921+
*/
19181922
pragma[nomagic]
19191923
private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) {
19201924
rootType = mc.getTypeAt(TypePath::nil()) and
@@ -2153,6 +2157,130 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) {
21532157
else any()
21542158
}
21552159

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

rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,12 +1005,15 @@ readStep
10051005
| main.rs:458:5:458:11 | mut_arr | file://:0:0:0:0 | element | main.rs:458:5:458:14 | mut_arr[1] |
10061006
| main.rs:459:13:459:19 | mut_arr | file://:0:0:0:0 | element | main.rs:459:13:459:22 | mut_arr[1] |
10071007
| main.rs:461:10:461:16 | mut_arr | file://:0:0:0:0 | element | main.rs:461:10:461:19 | mut_arr[0] |
1008+
| main.rs:467:24:467:33 | [post] receiver for source(...) | file://:0:0:0:0 | &ref | main.rs:467:24:467:33 | [post] source(...) |
10081009
| main.rs:468:9:468:20 | TuplePat | file://:0:0:0:0 | tuple.0 | main.rs:468:10:468:13 | cond |
10091010
| main.rs:468:9:468:20 | TuplePat | file://:0:0:0:0 | tuple.1 | main.rs:468:16:468:19 | name |
10101011
| main.rs:468:25:468:29 | names | file://:0:0:0:0 | element | main.rs:468:9:468:20 | TuplePat |
10111012
| main.rs:470:41:470:67 | [post] \|...\| ... | main.rs:467:9:467:20 | captured default_name | main.rs:470:41:470:67 | [post] default_name |
1013+
| main.rs:470:44:470:55 | [post] receiver for default_name | file://:0:0:0:0 | &ref | main.rs:470:44:470:55 | [post] default_name |
10121014
| main.rs:470:44:470:55 | this | main.rs:467:9:467:20 | captured default_name | main.rs:470:44:470:55 | default_name |
10131015
| main.rs:471:18:471:18 | [post] receiver for n | file://:0:0:0:0 | &ref | main.rs:471:18:471:18 | [post] n |
1016+
| main.rs:494:13:494:13 | [post] receiver for a | file://:0:0:0:0 | &ref | main.rs:494:13:494:13 | [post] a |
10141017
| main.rs:495:13:495:13 | [post] receiver for b | file://:0:0:0:0 | &ref | main.rs:495:13:495:13 | [post] b |
10151018
| main.rs:496:18:496:18 | [post] receiver for b | file://:0:0:0:0 | &ref | main.rs:496:18:496:18 | [post] b |
10161019
| main.rs:507:10:507:11 | vs | file://:0:0:0:0 | element | main.rs:507:10:507:14 | vs[0] |
@@ -1110,8 +1113,11 @@ storeStep
11101113
| main.rs:455:27:455:27 | 2 | file://:0:0:0:0 | element | main.rs:455:23:455:31 | [...] |
11111114
| main.rs:455:30:455:30 | 3 | file://:0:0:0:0 | element | main.rs:455:23:455:31 | [...] |
11121115
| main.rs:458:18:458:27 | source(...) | file://:0:0:0:0 | element | main.rs:458:5:458:11 | [post] mut_arr |
1116+
| main.rs:467:24:467:33 | source(...) | file://:0:0:0:0 | &ref | main.rs:467:24:467:33 | receiver for source(...) |
11131117
| main.rs:470:41:470:67 | default_name | main.rs:467:9:467:20 | captured default_name | main.rs:470:41:470:67 | \|...\| ... |
1118+
| main.rs:470:44:470:55 | default_name | file://:0:0:0:0 | &ref | main.rs:470:44:470:55 | receiver for default_name |
11141119
| main.rs:471:18:471:18 | n | file://:0:0:0:0 | &ref | main.rs:471:18:471:18 | receiver for n |
1120+
| main.rs:494:13:494:13 | a | file://:0:0:0:0 | &ref | main.rs:494:13:494:13 | receiver for a |
11151121
| main.rs:495:13:495:13 | b | file://:0:0:0:0 | &ref | main.rs:495:13:495:13 | receiver for b |
11161122
| main.rs:496:18:496:18 | b | file://:0:0:0:0 | &ref | main.rs:496:18:496:18 | receiver for b |
11171123
| main.rs:505:15:505:24 | source(...) | file://:0:0:0:0 | element | main.rs:505:14:505:34 | [...] |

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ multipleCallTargets
7373
| test.rs:977:14:977:29 | ...::_print(...) |
7474
| test.rs:979:27:979:36 | ...::_print(...) |
7575
| test.rs:980:28:980:41 | ...::_print(...) |
76+
| test_futures_io.rs:45:27:45:84 | ...::read(...) |
77+
| test_futures_io.rs:49:27:49:51 | reader.read(...) |
78+
| test_futures_io.rs:83:22:83:39 | reader2.fill_buf() |
79+
| test_futures_io.rs:103:27:103:85 | ...::read(...) |
80+
| test_futures_io.rs:107:27:107:52 | reader2.read(...) |
81+
| test_futures_io.rs:125:22:125:39 | reader2.fill_buf() |
82+
| test_futures_io.rs:132:27:132:62 | reader2.read_until(...) |
83+
| test_futures_io.rs:139:27:139:54 | reader2.read_line(...) |
84+
| test_futures_io.rs:146:27:146:58 | reader2.read_to_end(...) |
85+
| test_futures_io.rs:152:32:152:46 | reader2.lines() |
86+
| test_futures_io.rs:153:14:153:32 | lines_stream.next() |
87+
| test_futures_io.rs:154:32:154:50 | lines_stream.next() |
7688
| web_frameworks.rs:13:14:13:22 | a.as_str() |
7789
| web_frameworks.rs:13:14:13:23 | a.as_str() |
7890
| web_frameworks.rs:14:14:14:24 | a.as_bytes() |

0 commit comments

Comments
 (0)