Skip to content

Commit 9a9e9ac

Browse files
authored
Merge pull request #21168 from hvitved/rust/type-inference-remove-blanket-constraint-restriction
Rust: Remove restriction that blanket(-like) impls must have a constraint
2 parents 5414bd2 + f76d85c commit 9a9e9ac

File tree

8 files changed

+229
-96
lines changed

8 files changed

+229
-96
lines changed

rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
private import rust
66
private import codeql.rust.Concepts
77
private import codeql.rust.dataflow.DataFlow
8+
private import codeql.rust.dataflow.FlowSummary
89
private import codeql.rust.internal.PathResolution
10+
private import codeql.rust.internal.typeinference.Type
11+
private import codeql.rust.internal.typeinference.TypeMention
912

1013
/**
1114
* A call to the `starts_with` method on a `Path`.
@@ -19,6 +22,34 @@ private class StartswithCall extends Path::SafeAccessCheck::Range, MethodCall {
1922
}
2023
}
2124

25+
/**
26+
* A flow summary for the [reflexive implementation of the `From` trait][1].
27+
*
28+
* Blanket implementations currently don't have a canonical path, so we cannot
29+
* use models-as-data for this model.
30+
*
31+
* [1]: https://doc.rust-lang.org/std/convert/trait.From.html#impl-From%3CT%3E-for-T
32+
*/
33+
private class ReflexiveFrom extends SummarizedCallable::Range {
34+
ReflexiveFrom() {
35+
exists(ImplItemNode impl |
36+
impl.resolveTraitTy().(Trait).getCanonicalPath() = "core::convert::From" and
37+
this = impl.getAssocItem("from") and
38+
resolvePath(this.getParam(0).getTypeRepr().(PathTypeRepr).getPath()) =
39+
impl.getBlanketImplementationTypeParam()
40+
)
41+
}
42+
43+
override predicate propagatesFlow(
44+
string input, string output, boolean preservesValue, string model
45+
) {
46+
input = "Argument[0]" and
47+
output = "ReturnValue" and
48+
preservesValue = true and
49+
model = "ReflexiveFrom"
50+
}
51+
}
52+
2253
/**
2354
* The [`Option` enum][1].
2455
*

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ module SatisfiesBlanketConstraint<
126126

127127
/**
128128
* Holds if the argument type `at` satisfies the first non-trivial blanket
129-
* constraint of `impl`.
129+
* constraint of `impl`, or if there are no non-trivial constraints of `impl`.
130130
*/
131131
pragma[nomagic]
132132
predicate satisfiesBlanketConstraint(ArgumentType at, ImplItemNode impl) {
@@ -135,6 +135,11 @@ module SatisfiesBlanketConstraint<
135135
SatisfiesBlanketConstraintInput::relevantConstraint(ato, impl, traitBound) and
136136
SatisfiesBlanketConstraint::satisfiesConstraintType(ato, TTrait(traitBound), _, _)
137137
)
138+
or
139+
exists(TypeParam blanketTypeParam |
140+
hasBlanketCandidate(at, impl, _, blanketTypeParam) and
141+
not hasFirstNonTrivialTraitBound(blanketTypeParam, _)
142+
)
138143
}
139144

140145
/**

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

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ private predicate implSiblingCandidate(
3535
rootType = selfTy.resolveType()
3636
}
3737

38+
pragma[nomagic]
39+
private predicate blanketImplSiblingCandidate(ImplItemNode impl, Trait trait) {
40+
impl.isBlanketImplementation() and
41+
trait = impl.resolveTraitTy()
42+
}
43+
3844
/**
3945
* Holds if `impl1` and `impl2` are a sibling implementations of `trait`. We
4046
* consider implementations to be siblings if they implement the same trait for
@@ -44,17 +50,22 @@ private predicate implSiblingCandidate(
4450
*/
4551
pragma[inline]
4652
private predicate implSiblings(TraitItemNode trait, Impl impl1, Impl impl2) {
47-
exists(Type rootType, TypeMention selfTy1, TypeMention selfTy2 |
48-
impl1 != impl2 and
49-
implSiblingCandidate(impl1, trait, rootType, selfTy1) and
50-
implSiblingCandidate(impl2, trait, rootType, selfTy2) and
51-
// In principle the second conjunct below should be superflous, but we still
52-
// have ill-formed type mentions for types that we don't understand. For
53-
// those checking both directions restricts further. Note also that we check
54-
// syntactic equality, whereas equality up to renaming would be more
55-
// correct.
56-
typeMentionEqual(selfTy1, selfTy2) and
57-
typeMentionEqual(selfTy2, selfTy1)
53+
impl1 != impl2 and
54+
(
55+
exists(Type rootType, TypeMention selfTy1, TypeMention selfTy2 |
56+
implSiblingCandidate(impl1, trait, rootType, selfTy1) and
57+
implSiblingCandidate(impl2, trait, rootType, selfTy2) and
58+
// In principle the second conjunct below should be superflous, but we still
59+
// have ill-formed type mentions for types that we don't understand. For
60+
// those checking both directions restricts further. Note also that we check
61+
// syntactic equality, whereas equality up to renaming would be more
62+
// correct.
63+
typeMentionEqual(selfTy1, selfTy2) and
64+
typeMentionEqual(selfTy2, selfTy1)
65+
)
66+
or
67+
blanketImplSiblingCandidate(impl1, trait) and
68+
blanketImplSiblingCandidate(impl2, trait)
5869
)
5970
}
6071

@@ -63,7 +74,7 @@ private predicate implSiblings(TraitItemNode trait, Impl impl1, Impl impl2) {
6374
* exists for the same type.
6475
*/
6576
pragma[nomagic]
66-
private predicate implHasSibling(Impl impl, Trait trait) { implSiblings(trait, impl, _) }
77+
private predicate implHasSibling(ImplItemNode impl, Trait trait) { implSiblings(trait, impl, _) }
6778

6879
/**
6980
* Holds if type parameter `tp` of `trait` occurs in the function `f` with the name

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ module ArgsAreInstantiationsOf<ArgsAreInstantiationsOfInputSig Input> {
356356
string toString() { result = call.toString() + " [arg " + pos + "]" }
357357
}
358358

359-
private module ArgIsInstantiationOfInput implements
359+
private module ArgIsInstantiationOfToIndexInput implements
360360
IsInstantiationOfInputSig<CallAndPos, AssocFunctionType>
361361
{
362362
pragma[nomagic]
@@ -389,7 +389,7 @@ module ArgsAreInstantiationsOf<ArgsAreInstantiationsOfInputSig Input> {
389389
}
390390

391391
private module ArgIsInstantiationOfToIndex =
392-
ArgIsInstantiationOf<CallAndPos, ArgIsInstantiationOfInput>;
392+
ArgIsInstantiationOf<CallAndPos, ArgIsInstantiationOfToIndexInput>;
393393

394394
pragma[nomagic]
395395
private predicate argsAreInstantiationsOfToIndex(
@@ -413,4 +413,24 @@ module ArgsAreInstantiationsOf<ArgsAreInstantiationsOfInputSig Input> {
413413
rnk = max(int r | toCheckRanked(i, f, _, r))
414414
)
415415
}
416+
417+
pragma[nomagic]
418+
private predicate argsAreNotInstantiationsOf0(
419+
Input::Call call, FunctionPosition pos, ImplOrTraitItemNode i
420+
) {
421+
ArgIsInstantiationOfToIndex::argIsNotInstantiationOf(MkCallAndPos(call, pos), i, _, _)
422+
}
423+
424+
/**
425+
* Holds if _some_ argument of `call` has a type that is not an instantiation of the
426+
* type of the corresponding parameter of `f` inside `i`.
427+
*/
428+
pragma[nomagic]
429+
predicate argsAreNotInstantiationsOf(Input::Call call, ImplOrTraitItemNode i, Function f) {
430+
exists(FunctionPosition pos |
431+
argsAreNotInstantiationsOf0(call, pos, i) and
432+
call.hasTargetCand(i, f) and
433+
Input::toCheck(i, f, pos, _)
434+
)
435+
}
416436
}

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

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,13 @@ private class BorrowKind extends TBorrowKind {
12891289
}
12901290
}
12911291

1292+
// for now, we do not handle ambiguous targets when one of the types is itself
1293+
// a constrained type parameter; we should be checking the constraints in this case
1294+
private predicate typeCanBeUsedForDisambiguation(Type t) {
1295+
not t instanceof TypeParameter or
1296+
t.(TypeParamTypeParameter).getTypeParam() = any(TypeParam tp | not tp.hasTypeBound())
1297+
}
1298+
12921299
/**
12931300
* Provides logic for resolving calls to methods.
12941301
*
@@ -2234,7 +2241,8 @@ private module MethodResolution {
22342241
methodCallBlanketLikeCandidate(mc, _, impl, _, blanketPath, blanketTypeParam) and
22352242
// Only apply blanket implementations when no other implementations are possible;
22362243
// this is to account for codebases that use the (unstable) specialization feature
2237-
// (https://rust-lang.github.io/rfcs/1210-impl-specialization.html)
2244+
// (https://rust-lang.github.io/rfcs/1210-impl-specialization.html), as well as
2245+
// cases where our blanket implementation filtering is not precise enough.
22382246
(mcc.hasNoCompatibleNonBlanketTarget() or not impl.isBlanketImplementation())
22392247
|
22402248
borrow.isNoBorrow()
@@ -2384,10 +2392,7 @@ private module MethodResolution {
23842392
exists(TypePath path, Type t0 |
23852393
FunctionOverloading::functionResolutionDependsOnArgument(i, f, pos, path, t0) and
23862394
t.appliesTo(f, i, pos) and
2387-
// for now, we do not handle ambiguous targets when one of the types it iself
2388-
// a type parameter; we should be checking the constraints on that type parameter
2389-
// in this case
2390-
not t0 instanceof TypeParameter
2395+
typeCanBeUsedForDisambiguation(t0)
23912396
)
23922397
}
23932398

@@ -2746,7 +2751,7 @@ private module NonMethodResolution {
27462751
* Gets the blanket function that this call may resolve to, if any.
27472752
*/
27482753
pragma[nomagic]
2749-
private NonMethodFunction resolveCallTargetBlanketCand(ImplItemNode impl) {
2754+
NonMethodFunction resolveCallTargetBlanketCand(ImplItemNode impl) {
27502755
exists(string name |
27512756
this.hasNameAndArity(pragma[only_bind_into](name), _) and
27522757
ArgIsInstantiationOfBlanketParam::argIsInstantiationOf(MkCallAndBlanketPos(this, _), impl, _) and
@@ -2761,12 +2766,11 @@ private module NonMethodResolution {
27612766
predicate hasTrait() { exists(this.getTrait()) }
27622767

27632768
pragma[nomagic]
2764-
NonMethodFunction resolveAssocCallTargetCand(ImplItemNode i) {
2769+
NonMethodFunction resolveCallTargetNonBlanketCand(ImplItemNode i) {
27652770
not this.hasTrait() and
27662771
result = this.getPathResolutionResolved() and
2767-
result = i.getASuccessor(_)
2768-
or
2769-
result = this.resolveCallTargetBlanketCand(i)
2772+
result = i.getASuccessor(_) and
2773+
FunctionOverloading::functionResolutionDependsOnArgument(_, result, _, _, _)
27702774
}
27712775

27722776
AstNode getNodeAt(FunctionPosition pos) {
@@ -2798,6 +2802,21 @@ private module NonMethodResolution {
27982802
trait = this.getTrait()
27992803
}
28002804

2805+
/**
2806+
* Holds if this call has no compatible non-blanket target, and it has some
2807+
* candidate blanket target.
2808+
*/
2809+
pragma[nomagic]
2810+
predicate hasNoCompatibleNonBlanketTarget() {
2811+
this.resolveCallTargetBlanketLikeCandidate(_, _, _, _) and
2812+
not exists(this.resolveCallTargetViaPathResolution()) and
2813+
forall(ImplOrTraitItemNode i, Function f |
2814+
this.(NonMethodArgsAreInstantiationsOfNonBlanketInput::Call).hasTargetCand(i, f)
2815+
|
2816+
NonMethodArgsAreInstantiationsOfNonBlanket::argsAreNotInstantiationsOf(this, i, f)
2817+
)
2818+
}
2819+
28012820
/**
28022821
* Gets the target of this call, which can be resolved using only path resolution.
28032822
*/
@@ -2816,7 +2835,9 @@ private module NonMethodResolution {
28162835
result = this.resolveCallTargetBlanketCand(i) and
28172836
not FunctionOverloading::functionResolutionDependsOnArgument(_, result, _, _, _)
28182837
or
2819-
NonMethodArgsAreInstantiationsOf::argsAreInstantiationsOf(this, i, result)
2838+
NonMethodArgsAreInstantiationsOfBlanket::argsAreInstantiationsOf(this, i, result)
2839+
or
2840+
NonMethodArgsAreInstantiationsOfNonBlanket::argsAreInstantiationsOf(this, i, result)
28202841
}
28212842

28222843
pragma[nomagic]
@@ -2855,7 +2876,12 @@ private module NonMethodResolution {
28552876
) {
28562877
exists(NonMethodCall fc, FunctionPosition pos |
28572878
fcp = MkCallAndBlanketPos(fc, pos) and
2858-
fc.resolveCallTargetBlanketLikeCandidate(impl, pos, blanketPath, blanketTypeParam)
2879+
fc.resolveCallTargetBlanketLikeCandidate(impl, pos, blanketPath, blanketTypeParam) and
2880+
// Only apply blanket implementations when no other implementations are possible;
2881+
// this is to account for codebases that use the (unstable) specialization feature
2882+
// (https://rust-lang.github.io/rfcs/1210-impl-specialization.html), as well as
2883+
// cases where our blanket implementation filtering is not precise enough.
2884+
(fc.hasNoCompatibleNonBlanketTarget() or not impl.isBlanketImplementation())
28592885
)
28602886
}
28612887
}
@@ -2890,37 +2916,24 @@ private module NonMethodResolution {
28902916
private module ArgIsInstantiationOfBlanketParam =
28912917
ArgIsInstantiationOf<CallAndBlanketPos, ArgIsInstantiationOfBlanketParamInput>;
28922918

2893-
private module NonMethodArgsAreInstantiationsOfInput implements ArgsAreInstantiationsOfInputSig {
2919+
private module NonMethodArgsAreInstantiationsOfBlanketInput implements
2920+
ArgsAreInstantiationsOfInputSig
2921+
{
28942922
predicate toCheck(ImplOrTraitItemNode i, Function f, FunctionPosition pos, AssocFunctionType t) {
28952923
t.appliesTo(f, i, pos) and
2896-
(
2897-
exists(Type t0 |
2898-
// for now, we do not handle ambiguous targets when one of the types it iself
2899-
// a type parameter; we should be checking the constraints on that type parameter
2900-
// in this case
2901-
not t0 instanceof TypeParameter
2902-
|
2903-
FunctionOverloading::functionResolutionDependsOnArgument(i, f, pos, _, t0)
2904-
or
2905-
traitFunctionDependsOnPos(_, _, pos, t0, i, f)
2906-
)
2924+
exists(Type t0 | typeCanBeUsedForDisambiguation(t0) |
2925+
FunctionOverloading::functionResolutionDependsOnArgument(i, f, pos, _, t0)
29072926
or
2908-
// match against the trait function itself
2909-
exists(Trait trait |
2910-
FunctionOverloading::traitTypeParameterOccurrence(trait, f, _, pos, _,
2911-
TSelfTypeParameter(trait))
2912-
)
2927+
traitFunctionDependsOnPos(_, _, pos, t0, i, f)
29132928
)
29142929
}
29152930

2916-
class Call extends NonMethodCall {
2931+
final class Call extends NonMethodCall {
29172932
Type getArgType(FunctionPosition pos, TypePath path) {
29182933
result = inferType(this.getNodeAt(pos), path)
29192934
}
29202935

2921-
predicate hasTargetCand(ImplOrTraitItemNode i, Function f) {
2922-
f = this.resolveAssocCallTargetCand(i)
2923-
or
2936+
predicate hasTraitResolvedCand(ImplOrTraitItemNode i, Function f) {
29242937
exists(TraitItemNode trait, NonMethodFunction resolved, ImplItemNode i1, Function f1 |
29252938
this.hasTraitResolved(trait, resolved) and
29262939
traitFunctionDependsOnPos(trait, resolved, _, _, i1, f1)
@@ -2932,11 +2945,45 @@ private module NonMethodResolution {
29322945
i = trait
29332946
)
29342947
}
2948+
2949+
predicate hasTargetCand(ImplOrTraitItemNode i, Function f) {
2950+
f = this.resolveCallTargetBlanketCand(i)
2951+
or
2952+
this.hasTraitResolvedCand(i, f) and
2953+
BlanketImplementation::isBlanketLike(i, _, _)
2954+
}
2955+
}
2956+
}
2957+
2958+
private module NonMethodArgsAreInstantiationsOfBlanket =
2959+
ArgsAreInstantiationsOf<NonMethodArgsAreInstantiationsOfBlanketInput>;
2960+
2961+
private module NonMethodArgsAreInstantiationsOfNonBlanketInput implements
2962+
ArgsAreInstantiationsOfInputSig
2963+
{
2964+
predicate toCheck(ImplOrTraitItemNode i, Function f, FunctionPosition pos, AssocFunctionType t) {
2965+
NonMethodArgsAreInstantiationsOfBlanketInput::toCheck(i, f, pos, t)
2966+
or
2967+
// match against the trait function itself
2968+
t.appliesTo(f, i, pos) and
2969+
exists(Trait trait |
2970+
FunctionOverloading::traitTypeParameterOccurrence(trait, f, _, pos, _,
2971+
TSelfTypeParameter(trait))
2972+
)
2973+
}
2974+
2975+
class Call extends NonMethodArgsAreInstantiationsOfBlanketInput::Call {
2976+
predicate hasTargetCand(ImplOrTraitItemNode i, Function f) {
2977+
f = this.resolveCallTargetNonBlanketCand(i)
2978+
or
2979+
this.hasTraitResolvedCand(i, f) and
2980+
not BlanketImplementation::isBlanketLike(i, _, _)
2981+
}
29352982
}
29362983
}
29372984

2938-
private module NonMethodArgsAreInstantiationsOf =
2939-
ArgsAreInstantiationsOf<NonMethodArgsAreInstantiationsOfInput>;
2985+
private module NonMethodArgsAreInstantiationsOfNonBlanket =
2986+
ArgsAreInstantiationsOf<NonMethodArgsAreInstantiationsOfNonBlanketInput>;
29402987
}
29412988

29422989
abstract private class TupleLikeConstructor extends Addressable {

0 commit comments

Comments
 (0)