From c77c3ec2f875660a4e52b851dd7fa05cdf5c70b7 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 15 Jan 2026 10:04:03 +0100 Subject: [PATCH 1/2] Rust: Remove restriction that blanket(-like) impls must have a constraint --- .../typeinference/BlanketImplementation.qll | 7 +- .../typeinference/FunctionOverloading.qll | 16 ++- .../internal/typeinference/FunctionType.qll | 24 +++- .../internal/typeinference/TypeInference.qll | 101 ++++++++++++----- .../dataflow/local/inline-flow.expected | 103 +++++++++++------- .../test/library-tests/dataflow/local/main.rs | 6 +- .../test/library-tests/type-inference/main.rs | 2 +- 7 files changed, 179 insertions(+), 80 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/BlanketImplementation.qll b/rust/ql/lib/codeql/rust/internal/typeinference/BlanketImplementation.qll index 1d2eda61ddb4..63a21f2867b8 100644 --- a/rust/ql/lib/codeql/rust/internal/typeinference/BlanketImplementation.qll +++ b/rust/ql/lib/codeql/rust/internal/typeinference/BlanketImplementation.qll @@ -126,7 +126,7 @@ module SatisfiesBlanketConstraint< /** * Holds if the argument type `at` satisfies the first non-trivial blanket - * constraint of `impl`. + * constraint of `impl`, or if there are no non-trivial constraints of `impl`. */ pragma[nomagic] predicate satisfiesBlanketConstraint(ArgumentType at, ImplItemNode impl) { @@ -135,6 +135,11 @@ module SatisfiesBlanketConstraint< SatisfiesBlanketConstraintInput::relevantConstraint(ato, impl, traitBound) and SatisfiesBlanketConstraint::satisfiesConstraintType(ato, TTrait(traitBound), _, _) ) + or + exists(TypeParam blanketTypeParam | + hasBlanketCandidate(at, impl, _, blanketTypeParam) and + not hasFirstNonTrivialTraitBound(blanketTypeParam, _) + ) } /** diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/FunctionOverloading.qll b/rust/ql/lib/codeql/rust/internal/typeinference/FunctionOverloading.qll index e67fb05485f5..aaa6c5212950 100644 --- a/rust/ql/lib/codeql/rust/internal/typeinference/FunctionOverloading.qll +++ b/rust/ql/lib/codeql/rust/internal/typeinference/FunctionOverloading.qll @@ -57,12 +57,26 @@ private predicate implSiblings(TraitItemNode trait, Impl impl1, Impl impl2) { ) } +pragma[nomagic] +private predicate isBlanketImpl(ImplItemNode impl, Trait trait) { + impl.isBlanketImplementation() and + trait = impl.resolveTraitTy() +} + /** * Holds if `impl` is an implementation of `trait` and if another implementation * exists for the same type. */ pragma[nomagic] -private predicate implHasSibling(Impl impl, Trait trait) { implSiblings(trait, impl, _) } +private predicate implHasSibling(ImplItemNode impl, Trait trait) { + implSiblings(trait, impl, _) + or + exists(ImplItemNode other | + isBlanketImpl(impl, trait) and + isBlanketImpl(other, trait) and + impl != other + ) +} /** * Holds if type parameter `tp` of `trait` occurs in the function `f` with the name diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/FunctionType.qll b/rust/ql/lib/codeql/rust/internal/typeinference/FunctionType.qll index 8a72c967867d..28ac68628dcb 100644 --- a/rust/ql/lib/codeql/rust/internal/typeinference/FunctionType.qll +++ b/rust/ql/lib/codeql/rust/internal/typeinference/FunctionType.qll @@ -355,7 +355,7 @@ module ArgsAreInstantiationsOf { string toString() { result = call.toString() + " [arg " + pos + "]" } } - private module ArgIsInstantiationOfInput implements + private module ArgIsInstantiationOfToIndexInput implements IsInstantiationOfInputSig { pragma[nomagic] @@ -388,7 +388,7 @@ module ArgsAreInstantiationsOf { } private module ArgIsInstantiationOfToIndex = - ArgIsInstantiationOf; + ArgIsInstantiationOf; pragma[nomagic] private predicate argsAreInstantiationsOfToIndex( @@ -412,4 +412,24 @@ module ArgsAreInstantiationsOf { rnk = max(int r | toCheckRanked(i, f, _, r)) ) } + + pragma[nomagic] + private predicate argsAreNotInstantiationsOf0( + Input::Call call, FunctionPosition pos, ImplOrTraitItemNode i + ) { + ArgIsInstantiationOfToIndex::argIsNotInstantiationOf(MkCallAndPos(call, pos), i, _, _) + } + + /** + * Holds if _some_ argument of `call` has a type that is not an instantiation of the + * type of the corresponding parameter of `f` inside `i`. + */ + pragma[nomagic] + predicate argsAreNotInstantiationsOf(Input::Call call, ImplOrTraitItemNode i, Function f) { + exists(FunctionPosition pos | + argsAreNotInstantiationsOf0(call, pos, i) and + call.hasTargetCand(i, f) and + Input::toCheck(i, f, pos, _) + ) + } } diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll index 3be7565ebaa1..04256d8c0d58 100644 --- a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll @@ -2744,7 +2744,7 @@ private module NonMethodResolution { * Gets the blanket function that this call may resolve to, if any. */ pragma[nomagic] - private NonMethodFunction resolveCallTargetBlanketCand(ImplItemNode impl) { + NonMethodFunction resolveCallTargetBlanketCand(ImplItemNode impl) { exists(string name | this.hasNameAndArity(pragma[only_bind_into](name), _) and ArgIsInstantiationOfBlanketParam::argIsInstantiationOf(MkCallAndBlanketPos(this, _), impl, _) and @@ -2759,12 +2759,11 @@ private module NonMethodResolution { predicate hasTrait() { exists(this.getTrait()) } pragma[nomagic] - NonMethodFunction resolveAssocCallTargetCand(ImplItemNode i) { + NonMethodFunction resolveCallTargetNonBlanketCand(ImplItemNode i) { not this.hasTrait() and result = this.getPathResolutionResolved() and - result = i.getASuccessor(_) - or - result = this.resolveCallTargetBlanketCand(i) + result = i.getASuccessor(_) and + FunctionOverloading::functionResolutionDependsOnArgument(_, result, _, _, _) } AstNode getNodeAt(FunctionPosition pos) { @@ -2796,6 +2795,16 @@ private module NonMethodResolution { trait = this.getTrait() } + pragma[nomagic] + predicate hasNoCompatibleNonBlanketTarget() { + not exists(this.resolveCallTargetViaPathResolution()) and + forall(ImplOrTraitItemNode i, Function f | + this.(NonMethodArgsAreInstantiationsOfNonBlanketInput::Call).hasTargetCand(i, f) + | + NonMethodArgsAreInstantiationsOfNonBlanket::argsAreNotInstantiationsOf(this, i, f) + ) + } + /** * Gets the target of this call, which can be resolved using only path resolution. */ @@ -2814,7 +2823,9 @@ private module NonMethodResolution { result = this.resolveCallTargetBlanketCand(i) and not FunctionOverloading::functionResolutionDependsOnArgument(_, result, _, _, _) or - NonMethodArgsAreInstantiationsOf::argsAreInstantiationsOf(this, i, result) + NonMethodArgsAreInstantiationsOfBlanket::argsAreInstantiationsOf(this, i, result) + or + NonMethodArgsAreInstantiationsOfNonBlanket::argsAreInstantiationsOf(this, i, result) } pragma[nomagic] @@ -2853,7 +2864,11 @@ private module NonMethodResolution { ) { exists(NonMethodCall fc, FunctionPosition pos | fcp = MkCallAndBlanketPos(fc, pos) and - fc.resolveCallTargetBlanketLikeCandidate(impl, pos, blanketPath, blanketTypeParam) + fc.resolveCallTargetBlanketLikeCandidate(impl, pos, blanketPath, blanketTypeParam) and + // Only apply blanket implementations when no other implementations are possible; + // this is to account for codebases that use the (unstable) specialization feature + // (https://rust-lang.github.io/rfcs/1210-impl-specialization.html) + (fc.hasNoCompatibleNonBlanketTarget() or not impl.isBlanketImplementation()) ) } } @@ -2888,37 +2903,29 @@ private module NonMethodResolution { private module ArgIsInstantiationOfBlanketParam = ArgIsInstantiationOf; - private module NonMethodArgsAreInstantiationsOfInput implements ArgsAreInstantiationsOfInputSig { + private module NonMethodArgsAreInstantiationsOfBlanketInput implements + ArgsAreInstantiationsOfInputSig + { predicate toCheck(ImplOrTraitItemNode i, Function f, FunctionPosition pos, AssocFunctionType t) { t.appliesTo(f, i, pos) and - ( - exists(Type t0 | - // for now, we do not handle ambiguous targets when one of the types it iself - // a type parameter; we should be checking the constraints on that type parameter - // in this case - not t0 instanceof TypeParameter - | - FunctionOverloading::functionResolutionDependsOnArgument(i, f, pos, _, t0) - or - traitFunctionDependsOnPos(_, _, pos, t0, i, f) - ) + exists(Type t0 | + // for now, we do not handle ambiguous targets when one of the types it iself + // a type parameter; we should be checking the constraints on that type parameter + // in this case + not t0 instanceof TypeParameter + | + FunctionOverloading::functionResolutionDependsOnArgument(i, f, pos, _, t0) or - // match against the trait function itself - exists(Trait trait | - FunctionOverloading::traitTypeParameterOccurrence(trait, f, _, pos, _, - TSelfTypeParameter(trait)) - ) + traitFunctionDependsOnPos(_, _, pos, t0, i, f) ) } - class Call extends NonMethodCall { + final class Call extends NonMethodCall { Type getArgType(FunctionPosition pos, TypePath path) { result = inferType(this.getNodeAt(pos), path) } - predicate hasTargetCand(ImplOrTraitItemNode i, Function f) { - f = this.resolveAssocCallTargetCand(i) - or + predicate hasTraitResolvedCand(ImplOrTraitItemNode i, Function f) { exists(TraitItemNode trait, NonMethodFunction resolved, ImplItemNode i1, Function f1 | this.hasTraitResolved(trait, resolved) and traitFunctionDependsOnPos(trait, resolved, _, _, i1, f1) @@ -2930,11 +2937,45 @@ private module NonMethodResolution { i = trait ) } + + predicate hasTargetCand(ImplOrTraitItemNode i, Function f) { + f = this.resolveCallTargetBlanketCand(i) + or + this.hasTraitResolvedCand(i, f) and + BlanketImplementation::isBlanketLike(i, _, _) + } + } + } + + private module NonMethodArgsAreInstantiationsOfBlanket = + ArgsAreInstantiationsOf; + + private module NonMethodArgsAreInstantiationsOfNonBlanketInput implements + ArgsAreInstantiationsOfInputSig + { + predicate toCheck(ImplOrTraitItemNode i, Function f, FunctionPosition pos, AssocFunctionType t) { + NonMethodArgsAreInstantiationsOfBlanketInput::toCheck(i, f, pos, t) + or + // match against the trait function itself + t.appliesTo(f, i, pos) and + exists(Trait trait | + FunctionOverloading::traitTypeParameterOccurrence(trait, f, _, pos, _, + TSelfTypeParameter(trait)) + ) + } + + class Call extends NonMethodArgsAreInstantiationsOfBlanketInput::Call { + predicate hasTargetCand(ImplOrTraitItemNode i, Function f) { + f = this.resolveCallTargetNonBlanketCand(i) + or + this.hasTraitResolvedCand(i, f) and + not BlanketImplementation::isBlanketLike(i, _, _) + } } } - private module NonMethodArgsAreInstantiationsOf = - ArgsAreInstantiationsOf; + private module NonMethodArgsAreInstantiationsOfNonBlanket = + ArgsAreInstantiationsOf; } abstract private class TupleLikeConstructor extends Addressable { diff --git a/rust/ql/test/library-tests/dataflow/local/inline-flow.expected b/rust/ql/test/library-tests/dataflow/local/inline-flow.expected index 27a4192867fe..1943efe41435 100644 --- a/rust/ql/test/library-tests/dataflow/local/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/local/inline-flow.expected @@ -2,22 +2,24 @@ models | 1 | Summary: <& as core::ops::deref::Deref>::deref; Argument[self].Reference; ReturnValue; value | | 2 | Summary: <_ as alloc::string::ToString>::to_string; Argument[self].Reference; ReturnValue; taint | | 3 | Summary: <_ as core::convert::From>::from; Argument[0]; ReturnValue; taint | -| 4 | Summary: <_ as core::ops::deref::Deref>::deref; Argument[self].Reference; ReturnValue.Reference; taint | -| 5 | Summary: <_ as core::ops::index::Index>::index; Argument[self].Reference.Element; ReturnValue.Reference; value | -| 6 | Summary: ::deref; Argument[self].Reference.Field[alloc::boxed::Box(0)]; ReturnValue.Reference; value | -| 7 | Summary: ::new; Argument[0]; ReturnValue.Field[alloc::boxed::Box(0)]; value | -| 8 | Summary: ::deref; Argument[self]; ReturnValue; value | -| 9 | Summary: ::from; Argument[0]; ReturnValue; taint | -| 10 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 11 | Summary: ::unwrap_or; Argument[0]; ReturnValue; value | -| 12 | Summary: ::unwrap_or; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 13 | Summary: ::unwrap_or_else; Argument[0].ReturnValue; ReturnValue; value | -| 14 | Summary: ::unwrap_or_else; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | -| 15 | Summary: ::err; Argument[self].Field[core::result::Result::Err(0)]; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 16 | Summary: ::expect; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | -| 17 | Summary: ::expect_err; Argument[self].Field[core::result::Result::Err(0)]; ReturnValue; value | -| 18 | Summary: ::ok; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue.Field[core::option::Option::Some(0)]; value | -| 19 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 4 | Summary: <_ as core::convert::Into>::into; Argument[self].Element; ReturnValue.Element; taint | +| 5 | Summary: <_ as core::convert::Into>::into; Argument[self].Reference.Element; ReturnValue.Element; taint | +| 6 | Summary: <_ as core::ops::deref::Deref>::deref; Argument[self].Reference; ReturnValue.Reference; taint | +| 7 | Summary: <_ as core::ops::index::Index>::index; Argument[self].Reference.Element; ReturnValue.Reference; value | +| 8 | Summary: ::deref; Argument[self].Reference.Field[alloc::boxed::Box(0)]; ReturnValue.Reference; value | +| 9 | Summary: ::new; Argument[0]; ReturnValue.Field[alloc::boxed::Box(0)]; value | +| 10 | Summary: ::deref; Argument[self]; ReturnValue; value | +| 11 | Summary: ::from; Argument[0]; ReturnValue; taint | +| 12 | Summary: ::unwrap; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 13 | Summary: ::unwrap_or; Argument[0]; ReturnValue; value | +| 14 | Summary: ::unwrap_or; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 15 | Summary: ::unwrap_or_else; Argument[0].ReturnValue; ReturnValue; value | +| 16 | Summary: ::unwrap_or_else; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value | +| 17 | Summary: ::err; Argument[self].Field[core::result::Result::Err(0)]; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 18 | Summary: ::expect; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | +| 19 | Summary: ::expect_err; Argument[self].Field[core::result::Result::Err(0)]; ReturnValue; value | +| 20 | Summary: ::ok; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue.Field[core::option::Option::Some(0)]; value | +| 21 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value | edges | main.rs:23:9:23:9 | s | main.rs:24:10:24:10 | s | provenance | | | main.rs:23:9:23:9 | s | main.rs:26:12:26:12 | x | provenance | | @@ -45,8 +47,8 @@ edges | main.rs:82:5:82:5 | l | main.rs:83:10:83:10 | l | provenance | | | main.rs:115:9:115:9 | i [Box(0)] | main.rs:116:11:116:11 | i [Box(0)] | provenance | | | main.rs:115:13:115:31 | ...::new(...) [Box(0)] | main.rs:115:9:115:9 | i [Box(0)] | provenance | | -| main.rs:115:22:115:30 | source(...) | main.rs:115:13:115:31 | ...::new(...) [Box(0)] | provenance | MaD:7 | -| main.rs:116:11:116:11 | i [Box(0)] | main.rs:116:10:116:11 | * ... | provenance | MaD:6 | +| main.rs:115:22:115:30 | source(...) | main.rs:115:13:115:31 | ...::new(...) [Box(0)] | provenance | MaD:9 | +| main.rs:116:11:116:11 | i [Box(0)] | main.rs:116:10:116:11 | * ... | provenance | MaD:8 | | main.rs:123:9:123:9 | a [tuple.0] | main.rs:124:10:124:10 | a [tuple.0] | provenance | | | main.rs:123:13:123:26 | TupleExpr [tuple.0] | main.rs:123:9:123:9 | a [tuple.0] | provenance | | | main.rs:123:14:123:22 | source(...) | main.rs:123:13:123:26 | TupleExpr [tuple.0] | provenance | | @@ -129,17 +131,17 @@ edges | main.rs:278:9:278:10 | s1 [Some] | main.rs:279:10:279:11 | s1 [Some] | provenance | | | main.rs:278:14:278:29 | Some(...) [Some] | main.rs:278:9:278:10 | s1 [Some] | provenance | | | main.rs:278:19:278:28 | source(...) | main.rs:278:14:278:29 | Some(...) [Some] | provenance | | -| main.rs:279:10:279:11 | s1 [Some] | main.rs:279:10:279:20 | s1.unwrap() | provenance | MaD:10 | +| main.rs:279:10:279:11 | s1 [Some] | main.rs:279:10:279:20 | s1.unwrap() | provenance | MaD:12 | | main.rs:283:9:283:10 | s1 [Some] | main.rs:284:10:284:11 | s1 [Some] | provenance | | | main.rs:283:14:283:29 | Some(...) [Some] | main.rs:283:9:283:10 | s1 [Some] | provenance | | | main.rs:283:19:283:28 | source(...) | main.rs:283:14:283:29 | Some(...) [Some] | provenance | | -| main.rs:284:10:284:11 | s1 [Some] | main.rs:284:10:284:24 | s1.unwrap_or(...) | provenance | MaD:12 | -| main.rs:287:23:287:32 | source(...) | main.rs:287:10:287:33 | s2.unwrap_or(...) | provenance | MaD:11 | +| main.rs:284:10:284:11 | s1 [Some] | main.rs:284:10:284:24 | s1.unwrap_or(...) | provenance | MaD:14 | +| main.rs:287:23:287:32 | source(...) | main.rs:287:10:287:33 | s2.unwrap_or(...) | provenance | MaD:13 | | main.rs:291:9:291:10 | s1 [Some] | main.rs:292:10:292:11 | s1 [Some] | provenance | | | main.rs:291:14:291:29 | Some(...) [Some] | main.rs:291:9:291:10 | s1 [Some] | provenance | | | main.rs:291:19:291:28 | source(...) | main.rs:291:14:291:29 | Some(...) [Some] | provenance | | -| main.rs:292:10:292:11 | s1 [Some] | main.rs:292:10:292:32 | s1.unwrap_or_else(...) | provenance | MaD:14 | -| main.rs:295:31:295:40 | source(...) | main.rs:295:10:295:41 | s2.unwrap_or_else(...) | provenance | MaD:13 | +| main.rs:292:10:292:11 | s1 [Some] | main.rs:292:10:292:32 | s1.unwrap_or_else(...) | provenance | MaD:16 | +| main.rs:295:31:295:40 | source(...) | main.rs:295:10:295:41 | s2.unwrap_or_else(...) | provenance | MaD:15 | | main.rs:299:9:299:10 | s1 [Some] | main.rs:301:14:301:15 | s1 [Some] | provenance | | | main.rs:299:14:299:29 | Some(...) [Some] | main.rs:299:9:299:10 | s1 [Some] | provenance | | | main.rs:299:19:299:28 | source(...) | main.rs:299:14:299:29 | Some(...) [Some] | provenance | | @@ -150,16 +152,16 @@ edges | main.rs:308:32:308:45 | Ok(...) [Ok] | main.rs:308:9:308:10 | r1 [Ok] | provenance | | | main.rs:308:35:308:44 | source(...) | main.rs:308:32:308:45 | Ok(...) [Ok] | provenance | | | main.rs:309:9:309:11 | o1a [Some] | main.rs:311:10:311:12 | o1a [Some] | provenance | | -| main.rs:309:28:309:29 | r1 [Ok] | main.rs:309:28:309:34 | r1.ok() [Some] | provenance | MaD:18 | +| main.rs:309:28:309:29 | r1 [Ok] | main.rs:309:28:309:34 | r1.ok() [Some] | provenance | MaD:20 | | main.rs:309:28:309:34 | r1.ok() [Some] | main.rs:309:9:309:11 | o1a [Some] | provenance | | -| main.rs:311:10:311:12 | o1a [Some] | main.rs:311:10:311:21 | o1a.unwrap() | provenance | MaD:10 | +| main.rs:311:10:311:12 | o1a [Some] | main.rs:311:10:311:21 | o1a.unwrap() | provenance | MaD:12 | | main.rs:314:9:314:10 | r2 [Err] | main.rs:316:28:316:29 | r2 [Err] | provenance | | | main.rs:314:32:314:46 | Err(...) [Err] | main.rs:314:9:314:10 | r2 [Err] | provenance | | | main.rs:314:36:314:45 | source(...) | main.rs:314:32:314:46 | Err(...) [Err] | provenance | | | main.rs:316:9:316:11 | o2b [Some] | main.rs:318:10:318:12 | o2b [Some] | provenance | | -| main.rs:316:28:316:29 | r2 [Err] | main.rs:316:28:316:35 | r2.err() [Some] | provenance | MaD:15 | +| main.rs:316:28:316:29 | r2 [Err] | main.rs:316:28:316:35 | r2.err() [Some] | provenance | MaD:17 | | main.rs:316:28:316:35 | r2.err() [Some] | main.rs:316:9:316:11 | o2b [Some] | provenance | | -| main.rs:318:10:318:12 | o2b [Some] | main.rs:318:10:318:21 | o2b.unwrap() | provenance | MaD:10 | +| main.rs:318:10:318:12 | o2b [Some] | main.rs:318:10:318:21 | o2b.unwrap() | provenance | MaD:12 | | main.rs:322:9:322:10 | s1 [Ok] | main.rs:325:14:325:15 | s1 [Ok] | provenance | | | main.rs:322:32:322:45 | Ok(...) [Ok] | main.rs:322:9:322:10 | s1 [Ok] | provenance | | | main.rs:322:35:322:44 | source(...) | main.rs:322:32:322:45 | Ok(...) [Ok] | provenance | | @@ -169,11 +171,11 @@ edges | main.rs:335:9:335:10 | s1 [Ok] | main.rs:336:10:336:11 | s1 [Ok] | provenance | | | main.rs:335:32:335:45 | Ok(...) [Ok] | main.rs:335:9:335:10 | s1 [Ok] | provenance | | | main.rs:335:35:335:44 | source(...) | main.rs:335:32:335:45 | Ok(...) [Ok] | provenance | | -| main.rs:336:10:336:11 | s1 [Ok] | main.rs:336:10:336:22 | s1.expect(...) | provenance | MaD:16 | +| main.rs:336:10:336:11 | s1 [Ok] | main.rs:336:10:336:22 | s1.expect(...) | provenance | MaD:18 | | main.rs:339:9:339:10 | s2 [Err] | main.rs:341:10:341:11 | s2 [Err] | provenance | | | main.rs:339:32:339:46 | Err(...) [Err] | main.rs:339:9:339:10 | s2 [Err] | provenance | | | main.rs:339:36:339:45 | source(...) | main.rs:339:32:339:46 | Err(...) [Err] | provenance | | -| main.rs:341:10:341:11 | s2 [Err] | main.rs:341:10:341:26 | s2.expect_err(...) | provenance | MaD:17 | +| main.rs:341:10:341:11 | s2 [Err] | main.rs:341:10:341:26 | s2.expect_err(...) | provenance | MaD:19 | | main.rs:350:9:350:10 | s1 [A] | main.rs:352:11:352:12 | s1 [A] | provenance | | | main.rs:350:14:350:39 | ...::A(...) [A] | main.rs:350:9:350:10 | s1 [A] | provenance | | | main.rs:350:29:350:38 | source(...) | main.rs:350:14:350:39 | ...::A(...) [A] | provenance | | @@ -222,13 +224,13 @@ edges | main.rs:430:16:430:33 | [...] [element] | main.rs:430:9:430:12 | arr1 [element] | provenance | | | main.rs:430:23:430:32 | source(...) | main.rs:430:16:430:33 | [...] [element] | provenance | | | main.rs:431:9:431:10 | n1 | main.rs:432:10:432:11 | n1 | provenance | | -| main.rs:431:14:431:17 | arr1 [element] | main.rs:431:14:431:20 | arr1[2] | provenance | MaD:5 | +| main.rs:431:14:431:17 | arr1 [element] | main.rs:431:14:431:20 | arr1[2] | provenance | MaD:7 | | main.rs:431:14:431:20 | arr1[2] | main.rs:431:9:431:10 | n1 | provenance | | | main.rs:434:9:434:12 | arr2 [element] | main.rs:435:14:435:17 | arr2 [element] | provenance | | | main.rs:434:16:434:31 | [...; 10] [element] | main.rs:434:9:434:12 | arr2 [element] | provenance | | | main.rs:434:17:434:26 | source(...) | main.rs:434:16:434:31 | [...; 10] [element] | provenance | | | main.rs:435:9:435:10 | n2 | main.rs:436:10:436:11 | n2 | provenance | | -| main.rs:435:14:435:17 | arr2 [element] | main.rs:435:14:435:20 | arr2[4] | provenance | MaD:5 | +| main.rs:435:14:435:17 | arr2 [element] | main.rs:435:14:435:20 | arr2[4] | provenance | MaD:7 | | main.rs:435:14:435:20 | arr2[4] | main.rs:435:9:435:10 | n2 | provenance | | | main.rs:444:9:444:12 | arr1 [element] | main.rs:445:15:445:18 | arr1 [element] | provenance | | | main.rs:444:16:444:33 | [...] [element] | main.rs:444:9:444:12 | arr1 [element] | provenance | | @@ -249,9 +251,9 @@ edges | main.rs:470:5:470:11 | [post] mut_arr [element] | main.rs:473:10:473:16 | mut_arr [element] | provenance | | | main.rs:470:18:470:27 | source(...) | main.rs:470:5:470:11 | [post] mut_arr [element] | provenance | | | main.rs:471:9:471:9 | d | main.rs:472:10:472:10 | d | provenance | | -| main.rs:471:13:471:19 | mut_arr [element] | main.rs:471:13:471:22 | mut_arr[1] | provenance | MaD:5 | +| main.rs:471:13:471:19 | mut_arr [element] | main.rs:471:13:471:22 | mut_arr[1] | provenance | MaD:7 | | main.rs:471:13:471:22 | mut_arr[1] | main.rs:471:9:471:9 | d | provenance | | -| main.rs:473:10:473:16 | mut_arr [element] | main.rs:473:10:473:19 | mut_arr[0] | provenance | MaD:5 | +| main.rs:473:10:473:16 | mut_arr [element] | main.rs:473:10:473:19 | mut_arr[0] | provenance | MaD:7 | | main.rs:496:9:496:9 | s | main.rs:497:10:497:10 | s | provenance | | | main.rs:496:25:496:26 | source(...) | main.rs:496:9:496:9 | s | provenance | | | main.rs:505:9:505:9 | a | main.rs:506:13:506:13 | a | provenance | | @@ -263,26 +265,26 @@ edges | main.rs:506:13:506:13 | a | main.rs:506:13:506:25 | a.to_string() | provenance | MaD:2 | | main.rs:506:13:506:25 | a.to_string() | main.rs:506:9:506:9 | b | provenance | | | main.rs:507:9:507:9 | c | main.rs:512:10:512:10 | c | provenance | | -| main.rs:507:13:507:13 | b | main.rs:507:13:507:28 | b.parse() [Ok] | provenance | MaD:4 | -| main.rs:507:13:507:13 | b | main.rs:507:13:507:28 | b.parse() [Ok] | provenance | MaD:8 | -| main.rs:507:13:507:28 | b.parse() [Ok] | main.rs:507:13:507:37 | ... .unwrap() | provenance | MaD:19 | +| main.rs:507:13:507:13 | b | main.rs:507:13:507:28 | b.parse() [Ok] | provenance | MaD:6 | +| main.rs:507:13:507:13 | b | main.rs:507:13:507:28 | b.parse() [Ok] | provenance | MaD:10 | +| main.rs:507:13:507:28 | b.parse() [Ok] | main.rs:507:13:507:37 | ... .unwrap() | provenance | MaD:21 | | main.rs:507:13:507:37 | ... .unwrap() | main.rs:507:9:507:9 | c | provenance | | | main.rs:508:9:508:9 | d | main.rs:513:10:513:10 | d | provenance | | -| main.rs:508:18:508:18 | b | main.rs:508:18:508:26 | b.parse() [Ok] | provenance | MaD:4 | -| main.rs:508:18:508:18 | b | main.rs:508:18:508:26 | b.parse() [Ok] | provenance | MaD:8 | -| main.rs:508:18:508:26 | b.parse() [Ok] | main.rs:508:18:508:35 | ... .unwrap() | provenance | MaD:19 | +| main.rs:508:18:508:18 | b | main.rs:508:18:508:26 | b.parse() [Ok] | provenance | MaD:6 | +| main.rs:508:18:508:18 | b | main.rs:508:18:508:26 | b.parse() [Ok] | provenance | MaD:10 | +| main.rs:508:18:508:26 | b.parse() [Ok] | main.rs:508:18:508:35 | ... .unwrap() | provenance | MaD:21 | | main.rs:508:18:508:35 | ... .unwrap() | main.rs:508:9:508:9 | d | provenance | | | main.rs:517:9:517:10 | vs [element] | main.rs:519:10:519:11 | vs [element] | provenance | | | main.rs:517:9:517:10 | vs [element] | main.rs:523:14:523:15 | vs [element] | provenance | | | main.rs:517:14:517:34 | [...] [element] | main.rs:517:9:517:10 | vs [element] | provenance | | | main.rs:517:15:517:24 | source(...) | main.rs:517:14:517:34 | [...] [element] | provenance | | -| main.rs:519:10:519:11 | vs [element] | main.rs:519:10:519:14 | vs[0] | provenance | MaD:5 | +| main.rs:519:10:519:11 | vs [element] | main.rs:519:10:519:14 | vs[0] | provenance | MaD:7 | | main.rs:523:9:523:9 | v | main.rs:524:14:524:14 | v | provenance | | | main.rs:523:14:523:15 | vs [element] | main.rs:523:9:523:9 | v | provenance | | | main.rs:542:9:542:18 | mut vs_mut [element] | main.rs:544:10:544:15 | vs_mut [element] | provenance | | | main.rs:542:22:542:42 | [...] [element] | main.rs:542:9:542:18 | mut vs_mut [element] | provenance | | | main.rs:542:23:542:32 | source(...) | main.rs:542:22:542:42 | [...] [element] | provenance | | -| main.rs:544:10:544:15 | vs_mut [element] | main.rs:544:10:544:18 | vs_mut[0] | provenance | MaD:5 | +| main.rs:544:10:544:15 | vs_mut [element] | main.rs:544:10:544:18 | vs_mut[0] | provenance | MaD:7 | | main.rs:554:9:554:9 | a | main.rs:559:10:559:10 | a | provenance | | | main.rs:554:13:554:22 | source(...) | main.rs:554:9:554:9 | a | provenance | | | main.rs:555:9:555:9 | b | main.rs:560:15:560:15 | b | provenance | | @@ -296,12 +298,20 @@ edges | main.rs:560:15:560:15 | b | main.rs:560:14:560:15 | &b | provenance | | | main.rs:562:11:562:15 | c_ref [&ref] | main.rs:562:10:562:15 | * ... | provenance | MaD:1 | | main.rs:566:9:566:9 | a | main.rs:568:10:568:17 | a as i64 | provenance | | +| main.rs:566:9:566:9 | a | main.rs:569:10:569:10 | a | provenance | | +| main.rs:566:9:566:9 | a | main.rs:570:20:570:20 | a | provenance | | | main.rs:566:18:566:27 | source(...) | main.rs:566:9:566:9 | a | provenance | | +| main.rs:569:10:569:10 | a | main.rs:569:10:569:17 | a.into() | provenance | MaD:4 | +| main.rs:569:10:569:10 | a | main.rs:569:10:569:17 | a.into() | provenance | MaD:5 | +| main.rs:570:20:570:20 | a | main.rs:570:10:570:21 | ...::from(...) | provenance | MaD:3 | | main.rs:572:9:572:9 | b | main.rs:574:10:574:17 | b as i64 | provenance | | +| main.rs:572:9:572:9 | b | main.rs:575:10:575:10 | b | provenance | | | main.rs:572:9:572:9 | b | main.rs:576:20:576:20 | b | provenance | | | main.rs:572:18:572:27 | source(...) | main.rs:572:9:572:9 | b | provenance | | +| main.rs:575:10:575:10 | b | main.rs:575:10:575:17 | b.into() | provenance | MaD:4 | +| main.rs:575:10:575:10 | b | main.rs:575:10:575:17 | b.into() | provenance | MaD:5 | | main.rs:576:20:576:20 | b | main.rs:576:10:576:21 | ...::from(...) | provenance | MaD:3 | -| main.rs:576:20:576:20 | b | main.rs:576:10:576:21 | ...::from(...) | provenance | MaD:9 | +| main.rs:576:20:576:20 | b | main.rs:576:10:576:21 | ...::from(...) | provenance | MaD:11 | nodes | main.rs:19:10:19:18 | source(...) | semmle.label | source(...) | | main.rs:23:9:23:9 | s | semmle.label | s | @@ -630,9 +640,15 @@ nodes | main.rs:566:9:566:9 | a | semmle.label | a | | main.rs:566:18:566:27 | source(...) | semmle.label | source(...) | | main.rs:568:10:568:17 | a as i64 | semmle.label | a as i64 | +| main.rs:569:10:569:10 | a | semmle.label | a | +| main.rs:569:10:569:17 | a.into() | semmle.label | a.into() | +| main.rs:570:10:570:21 | ...::from(...) | semmle.label | ...::from(...) | +| main.rs:570:20:570:20 | a | semmle.label | a | | main.rs:572:9:572:9 | b | semmle.label | b | | main.rs:572:18:572:27 | source(...) | semmle.label | source(...) | | main.rs:574:10:574:17 | b as i64 | semmle.label | b as i64 | +| main.rs:575:10:575:10 | b | semmle.label | b | +| main.rs:575:10:575:17 | b.into() | semmle.label | b.into() | | main.rs:576:10:576:21 | ...::from(...) | semmle.label | ...::from(...) | | main.rs:576:20:576:20 | b | semmle.label | b | subpaths @@ -707,5 +723,8 @@ testFailures | main.rs:561:14:561:18 | c_ref | main.rs:556:13:556:22 | source(...) | main.rs:561:14:561:18 | c_ref | $@ | main.rs:556:13:556:22 | source(...) | source(...) | | main.rs:562:10:562:15 | * ... | main.rs:556:13:556:22 | source(...) | main.rs:562:10:562:15 | * ... | $@ | main.rs:556:13:556:22 | source(...) | source(...) | | main.rs:568:10:568:17 | a as i64 | main.rs:566:18:566:27 | source(...) | main.rs:568:10:568:17 | a as i64 | $@ | main.rs:566:18:566:27 | source(...) | source(...) | +| main.rs:569:10:569:17 | a.into() | main.rs:566:18:566:27 | source(...) | main.rs:569:10:569:17 | a.into() | $@ | main.rs:566:18:566:27 | source(...) | source(...) | +| main.rs:570:10:570:21 | ...::from(...) | main.rs:566:18:566:27 | source(...) | main.rs:570:10:570:21 | ...::from(...) | $@ | main.rs:566:18:566:27 | source(...) | source(...) | | main.rs:574:10:574:17 | b as i64 | main.rs:572:18:572:27 | source(...) | main.rs:574:10:574:17 | b as i64 | $@ | main.rs:572:18:572:27 | source(...) | source(...) | +| main.rs:575:10:575:17 | b.into() | main.rs:572:18:572:27 | source(...) | main.rs:575:10:575:17 | b.into() | $@ | main.rs:572:18:572:27 | source(...) | source(...) | | main.rs:576:10:576:21 | ...::from(...) | main.rs:572:18:572:27 | source(...) | main.rs:576:10:576:21 | ...::from(...) | $@ | main.rs:572:18:572:27 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/local/main.rs b/rust/ql/test/library-tests/dataflow/local/main.rs index b3c40bd45130..88c1b8142972 100644 --- a/rust/ql/test/library-tests/dataflow/local/main.rs +++ b/rust/ql/test/library-tests/dataflow/local/main.rs @@ -566,13 +566,13 @@ fn conversions() { let a: i64 = source(50); sink(a as i64); // $ hasTaintFlow=50 - sink(a.into()); // $ MISSING: hasValueFlow=50 - sink(i64::from(a)); // $ MISSING: hasTaintFlow=50 -- we cannot resolve the `impl From for T` implementation + sink(a.into()); // $ SPURIOUS: hasTaintFlow=50 $ MISSING: hasValueFlow=50 -- it is not possible to define a value-preserving summary for `into` since it depends on which `from` function is called + sink(i64::from(a)); // $ hasTaintFlow=50 let b: i32 = source(51) as i32; sink(b as i64); // $ hasTaintFlow=51 - sink(b.into()); // $ MISSING: hasTaintFlow=51 + sink(b.into()); // $ hasTaintFlow=51 sink(i64::from(b)); // $ hasTaintFlow=51 } diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index a7efa447647a..451f76dfe92f 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -2595,7 +2595,7 @@ mod tuples { let i: i64 = pair.0; // $ fieldof=Tuple2 let j: bool = pair.1; // $ fieldof=Tuple2 - let pair = [1, 1].into(); // $ type=pair:(T_2) type=pair:T0.i32 type=pair:T1.i32 MISSING: target=into + let pair = [1, 1].into(); // $ type=pair:(T_2) type=pair:T0.i32 type=pair:T1.i32 target=into match pair { (0, 0) => print!("unexpected"), _ => print!("expected"), From 4bae300828ff2cb91e6d75dcfd184f3f00902dce Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 15 Jan 2026 20:53:59 +0100 Subject: [PATCH 2/2] wip --- .../codeql/rust/frameworks/stdlib/Stdlib.qll | 28 +++++++++++++++++++ .../internal/typeinference/TypeInference.qll | 23 +++++++++------ .../dataflow/local/inline-flow.expected | 2 +- .../test/library-tests/dataflow/local/main.rs | 2 +- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll b/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll index 114c283bbdb1..55e4f32dbc15 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll @@ -5,7 +5,10 @@ private import rust private import codeql.rust.Concepts private import codeql.rust.dataflow.DataFlow +private import codeql.rust.dataflow.FlowSummary private import codeql.rust.internal.PathResolution +private import codeql.rust.internal.typeinference.Type +private import codeql.rust.internal.typeinference.TypeMention /** * A call to the `starts_with` method on a `Path`. @@ -297,3 +300,28 @@ class Vec extends Struct { /** Gets the type parameter representing the element type. */ TypeParam getElementTypeParam() { result = this.getGenericParamList().getTypeParam(0) } } + +private class ReflexiveFrom extends SummarizedCallable::Range { + ReflexiveFrom() { + exists(ImplItemNode impl | + impl.resolveTraitTy().(Trait).getCanonicalPath() = "core::convert::From" and + this = impl.getAnAssocItem() and + impl.isBlanketImplementation() and + this.getParam(0) + .getTypeRepr() + .(TypeMention) + .resolveType() + .(TypeParamTypeParameter) + .getTypeParam() = impl.getTypeParam(0) + ) + } + + override predicate propagatesFlow( + string input, string output, boolean preservesValue, string model + ) { + input = "Argument[0]" and + output = "ReturnValue" and + preservesValue = true and + model = "ReflexiveFrom" + } +} diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll index 04256d8c0d58..c74b7056c534 100644 --- a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll @@ -1287,6 +1287,11 @@ private class BorrowKind extends TBorrowKind { } } +private predicate isNotConstrainedTypeParameter(Type t) { + not t instanceof TypeParameter or + t.(TypeParamTypeParameter).getTypeParam() = any(TypeParam tp | not exists(tp.getATypeBound())) +} + /** * Provides logic for resolving calls to methods. * @@ -2383,9 +2388,8 @@ private module MethodResolution { FunctionOverloading::functionResolutionDependsOnArgument(i, f, pos, path, t0) and t.appliesTo(f, i, pos) and // for now, we do not handle ambiguous targets when one of the types it iself - // a type parameter; we should be checking the constraints on that type parameter - // in this case - not t0 instanceof TypeParameter + // a constrained type parameter; we should be checking the constraints in this case + isNotConstrainedTypeParameter(t0) ) } @@ -2796,8 +2800,7 @@ private module NonMethodResolution { } pragma[nomagic] - predicate hasNoCompatibleNonBlanketTarget() { - not exists(this.resolveCallTargetViaPathResolution()) and + predicate hasNoCompatibleNonBlanketTargetViaTypeInference() { forall(ImplOrTraitItemNode i, Function f | this.(NonMethodArgsAreInstantiationsOfNonBlanketInput::Call).hasTargetCand(i, f) | @@ -2805,6 +2808,11 @@ private module NonMethodResolution { ) } + predicate hasNoCompatibleNonBlanketTarget() { + not exists(this.resolveCallTargetViaPathResolution()) and + this.hasNoCompatibleNonBlanketTargetViaTypeInference() + } + /** * Gets the target of this call, which can be resolved using only path resolution. */ @@ -2910,9 +2918,8 @@ private module NonMethodResolution { t.appliesTo(f, i, pos) and exists(Type t0 | // for now, we do not handle ambiguous targets when one of the types it iself - // a type parameter; we should be checking the constraints on that type parameter - // in this case - not t0 instanceof TypeParameter + // a constrained type parameter; we should be checking the constraints in this case + isNotConstrainedTypeParameter(t0) | FunctionOverloading::functionResolutionDependsOnArgument(i, f, pos, _, t0) or diff --git a/rust/ql/test/library-tests/dataflow/local/inline-flow.expected b/rust/ql/test/library-tests/dataflow/local/inline-flow.expected index 1943efe41435..0a130b8a2888 100644 --- a/rust/ql/test/library-tests/dataflow/local/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/local/inline-flow.expected @@ -303,7 +303,7 @@ edges | main.rs:566:18:566:27 | source(...) | main.rs:566:9:566:9 | a | provenance | | | main.rs:569:10:569:10 | a | main.rs:569:10:569:17 | a.into() | provenance | MaD:4 | | main.rs:569:10:569:10 | a | main.rs:569:10:569:17 | a.into() | provenance | MaD:5 | -| main.rs:570:20:570:20 | a | main.rs:570:10:570:21 | ...::from(...) | provenance | MaD:3 | +| main.rs:570:20:570:20 | a | main.rs:570:10:570:21 | ...::from(...) | provenance | ReflexiveFrom | | main.rs:572:9:572:9 | b | main.rs:574:10:574:17 | b as i64 | provenance | | | main.rs:572:9:572:9 | b | main.rs:575:10:575:10 | b | provenance | | | main.rs:572:9:572:9 | b | main.rs:576:20:576:20 | b | provenance | | diff --git a/rust/ql/test/library-tests/dataflow/local/main.rs b/rust/ql/test/library-tests/dataflow/local/main.rs index 88c1b8142972..41f34da6122d 100644 --- a/rust/ql/test/library-tests/dataflow/local/main.rs +++ b/rust/ql/test/library-tests/dataflow/local/main.rs @@ -567,7 +567,7 @@ fn conversions() { sink(a as i64); // $ hasTaintFlow=50 sink(a.into()); // $ SPURIOUS: hasTaintFlow=50 $ MISSING: hasValueFlow=50 -- it is not possible to define a value-preserving summary for `into` since it depends on which `from` function is called - sink(i64::from(a)); // $ hasTaintFlow=50 + sink(i64::from(a)); // $ hasValueFlow=50 let b: i32 = source(51) as i32;