Skip to content

Commit ed08709

Browse files
committed
Rust: Disambiguate some method calls based on argument types
1 parent 7efad77 commit ed08709

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,11 +1206,126 @@ private Function getTypeParameterMethod(TypeParameter tp, string name) {
12061206
result = getMethodSuccessor(tp.(ImplTraitTypeTypeParameter).getImplTraitTypeRepr(), name)
12071207
}
12081208

1209+
bindingset[t1, t2]
1210+
private predicate typeMentionEqual(TypeMention t1, TypeMention t2) {
1211+
forex(TypePath path, Type type | t1.resolveTypeAt(path) = type | t2.resolveTypeAt(path) = type)
1212+
}
1213+
1214+
pragma[nomagic]
1215+
private predicate implSiblingCandidate(
1216+
Impl impl, TraitItemNode trait, Type rootType, TypeMention selfTy
1217+
) {
1218+
trait = impl.(ImplItemNode).resolveTraitTy() and
1219+
not exists(impl.getAttributeMacroExpansion()) and
1220+
// We use this for resolving methods, so exclude traits that do not have methods.
1221+
exists(Function f | f = trait.getASuccessor(_) and f.getParamList().hasSelfParam()) and
1222+
selfTy = impl.getSelfTy() and
1223+
rootType = selfTy.resolveType()
1224+
}
1225+
1226+
/**
1227+
* Holds if `impl1` and `impl2` are a sibling implementations of `trait`. We
1228+
* consider implementations to be siblings if they implement the same trait for
1229+
* the same type. In that case `Self` is the same type in both implementations,
1230+
* and method calls to the implementations cannot be resolved unambiguously
1231+
* based only on the receiver type.
1232+
*/
1233+
pragma[inline]
1234+
private predicate implSiblings(TraitItemNode trait, Impl impl1, Impl impl2) {
1235+
exists(Type rootType, TypeMention selfTy1, TypeMention selfTy2 |
1236+
impl1 != impl2 and
1237+
implSiblingCandidate(impl1, trait, rootType, selfTy1) and
1238+
implSiblingCandidate(impl2, trait, rootType, selfTy2) and
1239+
// In principle the second conjunct below should be superflous, but we still
1240+
// have ill-formed type mentions for types that we don't understand. For
1241+
// those checking both directions restricts further. Note also that we check
1242+
// syntactic equality, whereas equality up to renaming would be more
1243+
// correct.
1244+
typeMentionEqual(selfTy1, selfTy2) and
1245+
typeMentionEqual(selfTy2, selfTy1)
1246+
)
1247+
}
1248+
1249+
/**
1250+
* Holds if `impl` is an implementation of `trait` and if another implementation
1251+
* exists for the same type.
1252+
*/
1253+
pragma[nomagic]
1254+
private predicate implHasSibling(Impl impl, Trait trait) { implSiblings(trait, impl, _) }
1255+
1256+
/**
1257+
* Holds if a type parameter of `trait` occurs in the method with the name
1258+
* `methodName` at the `pos`th parameter at `path`.
1259+
*/
1260+
bindingset[trait]
1261+
pragma[inline_late]
1262+
private predicate traitTypeParameterOccurrence(
1263+
TraitItemNode trait, string methodName, int pos, TypePath path
1264+
) {
1265+
exists(Function f | f = trait.getASuccessor(methodName) |
1266+
f.getParam(pos).getTypeRepr().(TypeMention).resolveTypeAt(path) =
1267+
trait.(TraitTypeAbstraction).getATypeParameter()
1268+
)
1269+
}
1270+
1271+
bindingset[f, pos, path]
1272+
pragma[inline_late]
1273+
private predicate getMethodTypeAtPath(Function f, int pos, TypePath path, Type type) {
1274+
f.getParam(pos).getTypeRepr().(TypeMention).resolveTypeAt(path) = type
1275+
}
1276+
1277+
/**
1278+
* Holds if resolving the method in `impl` with the name `methodName` requires
1279+
* inspecting the types of applied _arguments_ in order to determine whether it
1280+
* is the correct resolution.
1281+
*/
1282+
private predicate methodResolutionDependsOnArgument(
1283+
Impl impl, string methodName, int pos, TypePath path, Type type
1284+
) {
1285+
/*
1286+
* As seen in the example below, when an implementation has a sibling for a
1287+
* trait we find occurrences of a type parameter of the trait in a method
1288+
* signature in the trait. We then find the type given in the implementation
1289+
* at the same position, which is a position that might disambiguate the
1290+
* method from its siblings.
1291+
*
1292+
* ```rust
1293+
* trait MyTrait<T> {
1294+
* fn method(&self, value: Foo<T>) -> Self;
1295+
* // ^^^^^^^^^^^^^ `pos` = 0
1296+
* // ^ `path` = "T"
1297+
* }
1298+
* impl MyAdd<i64> for i64 {
1299+
* fn method(&self, value: i64) -> Self { ... }
1300+
* // ^^^ `type` = 0
1301+
* }
1302+
* ```
1303+
*
1304+
* Note that we only check the root type symbol at the position. If the type
1305+
* at that position is a type constructor (for instance `Vec<..>`) then
1306+
* inspecting the entire type tree could be necessary to disambiguate the
1307+
* method. In that case we will still resolve several methods.
1308+
*/
1309+
1310+
exists(TraitItemNode trait |
1311+
implHasSibling(impl, trait) and
1312+
traitTypeParameterOccurrence(trait, methodName, pos, path) and
1313+
getMethodTypeAtPath(getMethodSuccessor(impl, methodName), pos, path, type)
1314+
)
1315+
}
1316+
12091317
/** Gets a method from an `impl` block that matches the method call `mc`. */
12101318
private Function getMethodFromImpl(MethodCall mc) {
12111319
exists(Impl impl |
12121320
IsInstantiationOf<MethodCall, IsInstantiationOfInput>::isInstantiationOf(mc, impl, _) and
12131321
result = getMethodSuccessor(impl, mc.getMethodName())
1322+
|
1323+
not methodResolutionDependsOnArgument(impl, _, _, _, _)
1324+
or
1325+
exists(int pos, TypePath path, Type type |
1326+
methodResolutionDependsOnArgument(impl, mc.getMethodName(), pos, path, type) and
1327+
inferType(mc.getArgument(pos), path) = type
1328+
)
12141329
)
12151330
}
12161331

0 commit comments

Comments
 (0)