Skip to content

Commit 237d004

Browse files
committed
Rust: Rework call disambiguation logic
1 parent 67ef176 commit 237d004

File tree

11 files changed

+347
-184
lines changed

11 files changed

+347
-184
lines changed

rust/ql/lib/codeql/rust/internal/typeinference/FunctionOverloading.qll

Lines changed: 115 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,32 +77,23 @@ pragma[nomagic]
7777
private predicate implHasSibling(ImplItemNode impl, Trait trait) { implSiblings(trait, impl, _) }
7878

7979
/**
80-
* Holds if type parameter `tp` of `trait` occurs in the function `f` with the name
81-
* `functionName` at position `pos` and path `path`.
82-
*
83-
* Note that `pos` can also be the special `return` position, which is sometimes
84-
* needed to disambiguate associated function calls like `Default::default()`
85-
* (in this case, `tp` is the special `Self` type parameter).
80+
* Holds if `f` is a function declared inside `trait`, and the type of `f` at
81+
* `pos` and `path` is `traitTp`, which is a type parameter of `trait`.
8682
*/
87-
bindingset[trait]
88-
pragma[inline_late]
83+
pragma[nomagic]
8984
predicate traitTypeParameterOccurrence(
9085
TraitItemNode trait, Function f, string functionName, FunctionPosition pos, TypePath path,
91-
TypeParameter tp
86+
TypeParameter traitTp
9287
) {
93-
f = trait.getASuccessor(functionName) and
94-
tp = getAssocFunctionTypeAt(f, trait, pos, path) and
95-
tp = trait.(TraitTypeAbstraction).getATypeParameter()
88+
f = trait.getAssocItem(functionName) and
89+
traitTp = getAssocFunctionTypeAt(f, trait, pos, path) and
90+
traitTp = trait.(TraitTypeAbstraction).getATypeParameter()
9691
}
9792

98-
/**
99-
* Holds if resolving the function `f` in `impl` with the name `functionName`
100-
* requires inspecting the type of applied _arguments_ at position `pos` in
101-
* order to determine whether it is the correct resolution.
102-
*/
10393
pragma[nomagic]
104-
predicate functionResolutionDependsOnArgument(
105-
ImplItemNode impl, Function f, FunctionPosition pos, TypePath path, Type type
94+
private predicate functionResolutionDependsOnArgumentCand(
95+
ImplItemNode impl, Function f, string functionName, TypeParameter traitTp, FunctionPosition pos,
96+
TypePath path
10697
) {
10798
/*
10899
* As seen in the example below, when an implementation has a sibling for a
@@ -129,11 +120,113 @@ predicate functionResolutionDependsOnArgument(
129120
* method. In that case we will still resolve several methods.
130121
*/
131122

132-
exists(TraitItemNode trait, string functionName |
123+
exists(TraitItemNode trait |
133124
implHasSibling(impl, trait) and
134-
traitTypeParameterOccurrence(trait, _, functionName, pos, path, _) and
135-
type = getAssocFunctionTypeAt(f, impl, pos, path) and
125+
traitTypeParameterOccurrence(trait, _, functionName, pos, path, traitTp) and
136126
f = impl.getASuccessor(functionName) and
127+
not pos.isSelf()
128+
)
129+
}
130+
131+
private predicate functionResolutionDependsOnPositionalArgumentCand(
132+
ImplItemNode impl, Function f, string functionName, TypeParameter traitTp
133+
) {
134+
exists(FunctionPosition pos |
135+
functionResolutionDependsOnArgumentCand(impl, f, functionName, traitTp, pos, _) and
136+
pos.isPosition()
137+
)
138+
}
139+
140+
pragma[nomagic]
141+
private Type getAssocFunctionNonTypeParameterTypeAt(
142+
ImplItemNode impl, Function f, FunctionPosition pos, TypePath path
143+
) {
144+
result = getAssocFunctionTypeAt(f, impl, pos, path) and
145+
not result instanceof TypeParameter
146+
}
147+
148+
/**
149+
* Holds if `f` inside `impl` has a sibling implementation inside `sibling`, where
150+
* those two implementations agree on the instantiation of `traitTp`, which occurs
151+
* in a positional position inside `f`.
152+
*/
153+
pragma[nomagic]
154+
private predicate hasEquivalentPositionalSibling(
155+
ImplItemNode impl, ImplItemNode sibling, Function f, TypeParameter traitTp
156+
) {
157+
exists(string functionName, FunctionPosition pos, TypePath path |
158+
functionResolutionDependsOnArgumentCand(impl, f, functionName, traitTp, pos, path) and
137159
pos.isPosition()
160+
|
161+
exists(Function f1 |
162+
implSiblings(_, impl, sibling) and
163+
f1 = sibling.getASuccessor(functionName)
164+
|
165+
forall(TypePath path0, Type t |
166+
t = getAssocFunctionNonTypeParameterTypeAt(impl, f, pos, path0) and
167+
path = path0.getAPrefixOrSelf()
168+
|
169+
t = getAssocFunctionNonTypeParameterTypeAt(sibling, f1, pos, path0)
170+
) and
171+
forall(TypePath path0, Type t |
172+
t = getAssocFunctionNonTypeParameterTypeAt(sibling, f1, pos, path0) and
173+
path = path0.getAPrefixOrSelf()
174+
|
175+
t = getAssocFunctionNonTypeParameterTypeAt(impl, f, pos, path0)
176+
)
177+
)
178+
)
179+
}
180+
181+
/**
182+
* Holds if resolving the function `f` in `impl` requires inspecting the type
183+
* of applied _arguments_ or possibly knowing the return type.
184+
*
185+
* `traitTp` is a type parameter of the trait being implemented by `impl`, and
186+
* we need to check that the type of `f` corresponding to `traitTp` is satisfied
187+
* at any one of the positions `pos` in which that type occurs in `f`.
188+
*
189+
* Type parameters that only occur in return positions are only included when
190+
* all other type parameters that occur in a positional position are insufficient
191+
* to disambiguate.
192+
*
193+
* Example:
194+
*
195+
* ```rust
196+
* trait Trait1<T1> {
197+
* fn f(self, x: T1) -> T1;
198+
* }
199+
*
200+
* impl Trait1<i32> for i32 {
201+
* fn f(self, x: i32) -> i32 { 0 } // f1
202+
* }
203+
*
204+
* impl Trait1<i64> for i32 {
205+
* fn f(self, x: i64) -> i64 { 0 } // f2
206+
* }
207+
* ```
208+
*
209+
* The type for `T1` above occurs in both a positional position and a return position
210+
* in `f`, so both may be used to disambiguate between `f1` and `f2`. That is, `f(0i32)`
211+
* is sufficient to resolve to `f1`, and so is `let y: i64 = f(Default::default())`.
212+
*/
213+
pragma[nomagic]
214+
predicate functionResolutionDependsOnArgument(
215+
ImplItemNode impl, Function f, TypeParameter traitTp, FunctionPosition pos
216+
) {
217+
exists(string functionName |
218+
functionResolutionDependsOnArgumentCand(impl, f, functionName, traitTp, pos, _)
219+
|
220+
if functionResolutionDependsOnPositionalArgumentCand(impl, f, functionName, traitTp)
221+
then any()
222+
else
223+
exists(ImplItemNode sibling |
224+
implSiblings(_, impl, sibling) and
225+
forall(TypeParameter otherTraitTp |
226+
functionResolutionDependsOnPositionalArgumentCand(impl, f, functionName, otherTraitTp)
227+
|
228+
hasEquivalentPositionalSibling(impl, sibling, f, otherTraitTp)
229+
)
230+
)
138231
)
139232
}

rust/ql/lib/codeql/rust/internal/typeinference/FunctionType.qll

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,12 @@ module ArgIsInstantiationOf<
294294
*/
295295
signature module ArgsAreInstantiationsOfInputSig {
296296
/**
297-
* Holds if types need to be matched against the type `t` at position `pos` of
298-
* `f` inside `i`.
297+
* Holds if `f` implements a trait function with type parameter `traitTp`, where
298+
* we need to check that the type of `f` for `traitTp` is satisfied.
299+
*
300+
* `pos` is one of the positions in `f` in which the relevant type occours.
299301
*/
300-
predicate toCheck(ImplOrTraitItemNode i, Function f, FunctionPosition pos, AssocFunctionType t);
302+
predicate toCheck(ImplOrTraitItemNode i, Function f, TypeParameter traitTp, FunctionPosition pos);
301303

302304
/** A call whose argument types are to be checked. */
303305
class Call {
@@ -318,23 +320,28 @@ signature module ArgsAreInstantiationsOfInputSig {
318320
*/
319321
module ArgsAreInstantiationsOf<ArgsAreInstantiationsOfInputSig Input> {
320322
pragma[nomagic]
321-
private predicate toCheckRanked(ImplOrTraitItemNode i, Function f, FunctionPosition pos, int rnk) {
322-
Input::toCheck(i, f, pos, _) and
323-
pos =
324-
rank[rnk + 1](FunctionPosition pos0, int j |
325-
Input::toCheck(i, f, pos0, _) and
326-
(
327-
j = pos0.asPosition()
328-
or
329-
pos0.isSelf() and j = -1
330-
or
331-
pos0.isReturn() and j = -2
332-
)
323+
private predicate toCheckRanked(
324+
ImplOrTraitItemNode i, Function f, TypeParameter traitTp, FunctionPosition pos, int rnk
325+
) {
326+
Input::toCheck(i, f, traitTp, pos) and
327+
traitTp =
328+
rank[rnk + 1](TypeParameter traitTp0, int j |
329+
Input::toCheck(i, f, traitTp0, _) and
330+
j = getTypeParameterId(traitTp0)
333331
|
334-
pos0 order by j
332+
traitTp0 order by j
335333
)
336334
}
337335

336+
pragma[nomagic]
337+
private predicate toCheck(
338+
ImplOrTraitItemNode i, Function f, TypeParameter traitTp, FunctionPosition pos,
339+
AssocFunctionType t
340+
) {
341+
Input::toCheck(i, f, traitTp, pos) and
342+
t.appliesTo(f, i, pos)
343+
}
344+
338345
private newtype TCallAndPos =
339346
MkCallAndPos(Input::Call call, FunctionPosition pos) { exists(call.getArgType(pos, _)) }
340347

@@ -356,36 +363,34 @@ module ArgsAreInstantiationsOf<ArgsAreInstantiationsOfInputSig Input> {
356363
string toString() { result = call.toString() + " [arg " + pos + "]" }
357364
}
358365

366+
pragma[nomagic]
367+
private predicate potentialInstantiationOf0(
368+
CallAndPos cp, Input::Call call, TypeParameter traitTp, FunctionPosition pos, Function f,
369+
TypeAbstraction abs, AssocFunctionType constraint
370+
) {
371+
cp = MkCallAndPos(call, pragma[only_bind_into](pos)) and
372+
call.hasTargetCand(abs, f) and
373+
toCheck(abs, f, traitTp, pragma[only_bind_into](pos), constraint)
374+
}
375+
359376
private module ArgIsInstantiationOfToIndexInput implements
360377
IsInstantiationOfInputSig<CallAndPos, AssocFunctionType>
361378
{
362-
pragma[nomagic]
363-
private predicate potentialInstantiationOf0(
364-
CallAndPos cp, Input::Call call, FunctionPosition pos, int rnk, Function f,
365-
TypeAbstraction abs, AssocFunctionType constraint
366-
) {
367-
cp = MkCallAndPos(call, pragma[only_bind_into](pos)) and
368-
call.hasTargetCand(abs, f) and
369-
toCheckRanked(abs, f, pragma[only_bind_into](pos), rnk) and
370-
Input::toCheck(abs, f, pragma[only_bind_into](pos), constraint)
371-
}
372-
373379
pragma[nomagic]
374380
predicate potentialInstantiationOf(
375381
CallAndPos cp, TypeAbstraction abs, AssocFunctionType constraint
376382
) {
377-
exists(Input::Call call, int rnk, Function f |
378-
potentialInstantiationOf0(cp, call, _, rnk, f, abs, constraint)
383+
exists(Input::Call call, TypeParameter traitTp, FunctionPosition pos, int rnk, Function f |
384+
potentialInstantiationOf0(cp, call, traitTp, pos, f, abs, constraint) and
385+
toCheckRanked(abs, f, traitTp, pos, rnk)
379386
|
380387
rnk = 0
381388
or
382389
argsAreInstantiationsOfToIndex(call, abs, f, rnk - 1)
383390
)
384391
}
385392

386-
predicate relevantConstraint(AssocFunctionType constraint) {
387-
Input::toCheck(_, _, _, constraint)
388-
}
393+
predicate relevantConstraint(AssocFunctionType constraint) { toCheck(_, _, _, _, constraint) }
389394
}
390395

391396
private module ArgIsInstantiationOfToIndex =
@@ -398,39 +403,63 @@ module ArgsAreInstantiationsOf<ArgsAreInstantiationsOfInputSig Input> {
398403
exists(FunctionPosition pos |
399404
ArgIsInstantiationOfToIndex::argIsInstantiationOf(MkCallAndPos(call, pos), i, _) and
400405
call.hasTargetCand(i, f) and
401-
toCheckRanked(i, f, pos, rnk)
406+
toCheckRanked(i, f, _, pos, rnk)
407+
|
408+
rnk = 0
409+
or
410+
argsAreInstantiationsOfToIndex(call, i, f, rnk - 1)
402411
)
403412
}
404413

405414
/**
406415
* Holds if all arguments of `call` have types that are instantiations of the
407416
* types of the corresponding parameters of `f` inside `i`.
417+
*
418+
* TODO: Check type parameter constraints as well.
408419
*/
409420
pragma[nomagic]
410421
predicate argsAreInstantiationsOf(Input::Call call, ImplOrTraitItemNode i, Function f) {
411422
exists(int rnk |
412423
argsAreInstantiationsOfToIndex(call, i, f, rnk) and
413-
rnk = max(int r | toCheckRanked(i, f, _, r))
424+
rnk = max(int r | toCheckRanked(i, f, _, _, r))
414425
)
415426
}
416427

428+
private module ArgsAreNotInstantiationOfInput implements
429+
IsInstantiationOfInputSig<CallAndPos, AssocFunctionType>
430+
{
431+
pragma[nomagic]
432+
predicate potentialInstantiationOf(
433+
CallAndPos cp, TypeAbstraction abs, AssocFunctionType constraint
434+
) {
435+
potentialInstantiationOf0(cp, _, _, _, _, abs, constraint)
436+
}
437+
438+
predicate relevantConstraint(AssocFunctionType constraint) { toCheck(_, _, _, _, constraint) }
439+
}
440+
441+
private module ArgsAreNotInstantiationOf =
442+
ArgIsInstantiationOf<CallAndPos, ArgsAreNotInstantiationOfInput>;
443+
417444
pragma[nomagic]
418445
private predicate argsAreNotInstantiationsOf0(
419446
Input::Call call, FunctionPosition pos, ImplOrTraitItemNode i
420447
) {
421-
ArgIsInstantiationOfToIndex::argIsNotInstantiationOf(MkCallAndPos(call, pos), i, _, _)
448+
ArgsAreNotInstantiationOf::argIsNotInstantiationOf(MkCallAndPos(call, pos), i, _, _)
422449
}
423450

424451
/**
425452
* Holds if _some_ argument of `call` has a type that is not an instantiation of the
426453
* type of the corresponding parameter of `f` inside `i`.
454+
*
455+
* TODO: Check type parameter constraints as well.
427456
*/
428457
pragma[nomagic]
429458
predicate argsAreNotInstantiationsOf(Input::Call call, ImplOrTraitItemNode i, Function f) {
430459
exists(FunctionPosition pos |
431460
argsAreNotInstantiationsOf0(call, pos, i) and
432461
call.hasTargetCand(i, f) and
433-
Input::toCheck(i, f, pos, _)
462+
Input::toCheck(i, f, _, pos)
434463
)
435464
}
436465
}

0 commit comments

Comments
 (0)