-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Rust: Rework call disambiguation logic #21217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
38929dd to
50ce393
Compare
| hasTypeConstraint(tt, constraint) and | ||
| t = getTypeAt(tt, path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This conjunction has non-linear recursion, hence the magic'ed version of hasTypeConstraint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a type inference explosion issue in Rust queries by correcting two bugs in the type inference implementation:
- Path resolution bug: Changed
getASuccessor(_)togetAnAssocItem()to correctly resolve trait functions only from the trait itself, not from sub-traits - Context-based return type checking: Enhanced the logic to only use return type for disambiguation when all argument positions are trivially satisfied
Changes:
- Fixed trait function resolution to prevent matching sub-trait implementations
- Added logic to check return type matches calling context for polymorphic functions
- Refactored Input modules into a unified structure
- Added regression test for the
Default::default()andFrom::from()pattern
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| shared/typeinference/codeql/typeinference/internal/TypeInference.qll | Added helper predicate with pragma[nomagic] to optimize type constraint checking |
| rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll | Fixed getASuccessor → getAnAssocItem bug, merged Input modules, fixed parameter ordering in context typing |
| rust/ql/lib/codeql/rust/internal/typeinference/FunctionOverloading.qll | Split predicate and added return type disambiguation logic |
| rust/ql/test/library-tests/type-inference/main.rs | Added regression test for from_default module |
| rust/ql/test/library-tests/type-inference/type-inference.expected | Updated expected test output |
| rust/ql/test/query-tests/security/CWE-312/CONSISTENCY/PathResolutionConsistency.expected | Removed spurious multiple resolution error |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
geoffw0
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks helpful.
Is there a reason this is still draft?
I would like to rework this a bit more; while it does fix the example posted in the description, there are still cases resulting in timeouts that I would like to cover as well. |
50ce393 to
3244471
Compare
c1b3551 to
9079fde
Compare
e2ebf44 to
026ddfe
Compare
026ddfe to
ee7c24b
Compare
ee7c24b to
dde7c24
Compare
dde7c24 to
9aad861
Compare
c663247 to
b61ef72
Compare
b61ef72 to
7b9ae9f
Compare
2158fdb to
7289906
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. This is a complex CodeQL change that's difficult to review closely - if there are any particular areas you'd like to be scrutinised in more detail, please point to them. The excellent new tests, the existing tests, and the DCA run add a lot of confidence for me.
| * Holds if all arguments of `call` have types that are instantiations of the | ||
| * types of the corresponding parameters of `f` inside `i`. | ||
| * | ||
| * TODO: Check type parameter constraints as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the two TODO comments intended as follow-up work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
7289906 to
32aaac2
Compare
paldepind
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great improvements!
| #[derive(Default)] | ||
| struct S; | ||
| impl FirstTrait for S { | ||
| // S::method2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method and the other method2 below have the same comment, so we can't distinguish them in the expectations.
| // S::method2 | |
| // S_as_FirstTrait::method2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops.
| * Gets a prefix of this list, including the list itself. | ||
| */ | ||
| bindingset[this] | ||
| UnboundList getAPrefixOrSelf() { result = [this, this.getAPrefix()] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about this renaming:
getPrefix->getProperPrefixgetAPrefix->getAProperPrefixgetAPrefixOrSelf->getAPrefix
These names a more precise and the most used predicate, getAPrefixOrSelf, gets a shorter name.
| result = this.getDefaultPositionalTypeArgument(pragma[only_bind_into](i), path) and | ||
| tp = this.resolveRootType().getPositionalTypeParameter(pragma[only_bind_into](i)) | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a bit of duplication here and in getTypeForTypeParameterAt below.
Perhaps we could get rid of it with these predicates:
private Type getPositionalTypeArgument(int i, TypePath path, boolean isDefault) {
result = getPathTypeArgument(this, i).getTypeAt(path) and
isDefault = false
or
result = this.getDefaultPositionalTypeArgument(i, path) and
isDefault = true
}
pragma[nomagic]
Type getTypeArgument(TypeParameter tp, TypePath path, boolean isDefault) {
exists(int i |
result = this.getPositionalTypeArgument(pragma[only_bind_into](i), path, isDefault) and
tp = this.resolveRootType().getPositionalTypeParameter(pragma[only_bind_into](i))
)
}Where getTypeArgument can be used in getTypeForTypeParameterAt and in place of getDefaultTypeForTypeParameterInNonAnnotationAt as in this diff:
diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll
index 3e9c823c570..27052410294 100644
--- a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll
+++ b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll
@@ -1071,8 +1071,7 @@ private Type getCallExprTypeQualifier(CallExpr ce, TypePath path, boolean isDefa
isDefaultTypeArg = false
or
exists(TypeParameter tp, TypePath suffix |
- result =
- tm.(NonAliasPathTypeMention).getDefaultTypeForTypeParameterInNonAnnotationAt(tp, suffix) and
+ result = tm.(NonAliasPathTypeMention).getTypeArgument(tp, suffix, true) and
path = TypePath::cons(tp, suffix) and
isDefaultTypeArg = true
)
diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/TypeMention.qll b/rust/ql/lib/codeql/rust/internal/typeinference/TypeMention.qll
index a1177e55cc9..b41e86ed9a9 100644
--- a/rust/ql/lib/codeql/rust/internal/typeinference/TypeMention.qll
+++ b/rust/ql/lib/codeql/rust/internal/typeinference/TypeMention.qll
@@ -247,12 +247,20 @@ private module MkTypeMention<getAdditionalPathTypeAtSig/2 getAdditionalPathTypeA
)
}
- private Type getPositionalTypeArgument(int i, TypePath path) {
- result = getPathTypeArgument(this, i).getTypeAt(path)
+ private Type getPositionalTypeArgument(int i, TypePath path, boolean isDefault) {
+ result = getPathTypeArgument(this, i).getTypeAt(path) and
+ isDefault = false
or
- // Defaults only apply to type mentions in type annotations
- this.isInTypeAnnotation() and
- result = this.getDefaultPositionalTypeArgument(i, path)
+ result = this.getDefaultPositionalTypeArgument(i, path) and
+ isDefault = true
+ }
+
+ pragma[nomagic]
+ Type getTypeArgument(TypeParameter tp, TypePath path, boolean isDefault) {
+ exists(int i |
+ result = this.getPositionalTypeArgument(pragma[only_bind_into](i), path, isDefault) and
+ tp = this.resolveRootType().getPositionalTypeParameter(pragma[only_bind_into](i))
+ )
}
/**
@@ -260,9 +268,8 @@ private module MkTypeMention<getAdditionalPathTypeAtSig/2 getAdditionalPathTypeA
* type parameter does not correspond directly to a type mention.
*/
private Type getTypeForTypeParameterAt(TypeParameter tp, TypePath path) {
- exists(int i |
- result = this.getPositionalTypeArgument(pragma[only_bind_into](i), path) and
- tp = this.resolveRootType().getPositionalTypeParameter(pragma[only_bind_into](i))
+ exists(boolean isDefault | result = this.getTypeArgument(tp, path, isDefault) |
+ isDefault = false or this.isInTypeAnnotation()
)
or
// Handle the special syntactic sugar for function traits. The syntacticThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went for a slightly different refactor.
| result = this.getPathResolutionResolved() and | ||
| result = i.getASuccessor(_) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| result = this.getPathResolutionResolved() and | |
| result = i.getASuccessor(_) and | |
| result = this.getPathResolutionResolved(i) and |
This is equivalent to changing i.getASuccessor(_) to i.getAnAssocItem() which I think this is what we want.
On rust db this roughly halves the size of this predicate with no changes to inferType.
More generally, I rarely think that we actually want to use getASuccessor on ImplItem in type inference. With recent changes that I made to path resolution getASuccessor can give anything that is available on Self inside the impl block, which is not necessarily functions that are actually within the impl block (see relevantSelfFunctionName).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
| f = impl.getASuccessor(_) and | ||
| not impl.(Impl).hasTrait() and | ||
| tp = TTypeParamTypeParameter(impl.resolveSelfTy().getTypeParam(0)) and | ||
| not f.hasSelfParam() and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| not f.hasSelfParam() and |
f has type NonMethodFunction so this is superflous.
| // For inherent implementations of generic types, we also need to check the type being | ||
| // implemented. We arbitrarily choose the first type parameter of the type being implemented | ||
| // to represent this case. | ||
| f = impl.getASuccessor(_) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| f = impl.getASuccessor(_) and | |
| f = impl.getAnAssocItem() and |
Similar to this comment.
The following simple program would make our type inference implementation explode:
Firstly, because of a
getSuccessorvsgetAssocItembug,From::fromwould allow for any sub trait ofFrom, for example theInttrait, which meant thatxcould be inferred to have typebool. Secondly, since manyfromimplementations are polymorphic in their argument, we should really be checking that the return type matches the type of the calling context.This PR resolves the above, and other issues (see tests) by rewriting when and how we do type-based call disambiguation, aligning the cases for methods and non-methods. Key changes:
Selftype for calls to associated non-methodsf. The type at the call site may be obtained by looking at the qualifier, e.g.Vec::<i32>::new()(which also means we need extra care for default type arguments, such as the defaultGlobalforAinVec). Additionally, whenfis (an implementation of) a trait function (that is, not in an inherent implementation), we may additionally obtain the type at any of the positions at whichSelfis mentioned in the trait function, for example inDefault::default()the type may be obtained from the context of the call.Selftype is now always checked, both cases can now use thefunctionResolutionDependsOnArgumentpredicate fromOverloading.qll, because the sibling logic assumes we only want to disambiguate targets with the sameSelftype.ArgsAreInstantiationsOfhas been changed to do a ranked forall over type parameters instead of over type positions, because what we really want is to check that there is some position at which a given type parameter occurs, where the types match. This allows us to handle cases like this one.typeCanBeUsedForDisambiguationlogic has been removed, which gives us more call targets, but also some false targets like this one. I tried to take type parameter constraints into account as well, but that turned out quite difficult, since in cases likelet mut i = 0i64; let mut pin = Pin::new(&i);we need type information from both thePinqualifier and the&iargument to first match against theSelftype, and subsequently to validate thePtr : Derefconstraint of thenewfunction.DCADCA looks great; while we fix the performance issue, we increasePercentage of calls with call targetby1.671.73 % point. There is an increase inNodes With Type At Length Limit, in particular onradiance, which is a consequence of removingtypeCanBeUsedForDisambiguation. This PR also fixes 8 projects that currently timeout during analysis.