From 854639d90eaf681eb26f62eafaa0a2d8ac218ea6 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Sat, 2 Aug 2025 14:51:59 +0200 Subject: [PATCH 1/2] Rust: Add tests with blanket implementation --- .../type-inference/blanket_impl.rs | 123 ++++++++++++++++++ .../test/library-tests/type-inference/main.rs | 1 + .../type-inference/type-inference.expected | 102 ++++++++++++++- 3 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 rust/ql/test/library-tests/type-inference/blanket_impl.rs diff --git a/rust/ql/test/library-tests/type-inference/blanket_impl.rs b/rust/ql/test/library-tests/type-inference/blanket_impl.rs new file mode 100644 index 000000000000..18fdf92e6329 --- /dev/null +++ b/rust/ql/test/library-tests/type-inference/blanket_impl.rs @@ -0,0 +1,123 @@ +// Tests for method resolution targeting blanket trait implementations + +mod basic_blanket_impl { + #[derive(Debug, Copy, Clone)] + struct S1; + + trait Clone1 { + fn clone1(&self) -> Self; + } + + trait Duplicatable { + fn duplicate(&self) -> Self + where + Self: Sized; + } + + impl Clone1 for S1 { + // S1::clone1 + fn clone1(&self) -> Self { + *self // $ target=deref + } + } + + // Blanket implementation for all types that implement Display and Clone + impl Duplicatable for T { + // Clone1duplicate + fn duplicate(&self) -> Self { + self.clone1() // $ target=clone1 + } + } + + pub fn test_basic_blanket() { + let x = S1.clone1(); // $ target=S1::clone1 + println!("{x:?}"); + let y = S1.duplicate(); // $ MISSING: target=Clone1duplicate + println!("{y:?}"); + } +} + +mod extension_trait_blanket_impl { + // 1. Elements a trait that is implemented for a type parameter + // 2. An extension trait + // 3. A blanket implementation of the extension trait for a type parameter + + trait Flag { + fn read_flag(&self) -> bool; + } + + trait TryFlag { + fn try_read_flag(&self) -> Option; + } + + impl TryFlag for Fl + where + Fl: Flag, + { + fn try_read_flag(&self) -> Option { + Some(self.read_flag()) // $ target=read_flag + } + } + + trait TryFlagExt: TryFlag { + // TryFlagExt::try_read_flag_twice + fn try_read_flag_twice(&self) -> Option { + self.try_read_flag() // $ target=try_read_flag + } + } + + impl TryFlagExt for T {} + + trait AnotherTryFlag { + // AnotherTryFlag::try_read_flag_twice + fn try_read_flag_twice(&self) -> Option; + } + + struct MyTryFlag { + flag: bool, + } + + impl TryFlag for MyTryFlag { + // MyTryFlag::try_read_flag + fn try_read_flag(&self) -> Option { + Some(self.flag) // $ fieldof=MyTryFlag + } + } + + struct MyFlag { + flag: bool, + } + + impl Flag for MyFlag { + // MyFlag::read_flag + fn read_flag(&self) -> bool { + self.flag // $ fieldof=MyFlag + } + } + + struct MyOtherFlag { + flag: bool, + } + + impl AnotherTryFlag for MyOtherFlag { + // MyOtherFlag::try_read_flag_twice + fn try_read_flag_twice(&self) -> Option { + Some(self.flag) // $ fieldof=MyOtherFlag + } + } + + fn test() { + let my_try_flag = MyTryFlag { flag: true }; + let result = my_try_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice + + let my_flag = MyFlag { flag: true }; + // Here `TryFlagExt::try_read_flag_twice` is since there is a blanket + // implementaton of `TryFlag` for `Flag`. + let result = my_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice + + let my_other_flag = MyOtherFlag { flag: true }; + // Here `TryFlagExt::try_read_flag_twice` is _not_ a target since + // `MyOtherFlag` does not implement `TryFlag`. + let result = my_other_flag.try_read_flag_twice(); // $ target=MyOtherFlag::try_read_flag_twice + } +} diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 145e6c3cabd4..0dc33fc90cba 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -2487,6 +2487,7 @@ pub mod pattern_matching_experimental { mod closure; mod dereference; mod dyn_type; +mod blanket_impl; fn main() { field_access::f(); // $ target=f diff --git a/rust/ql/test/library-tests/type-inference/type-inference.expected b/rust/ql/test/library-tests/type-inference/type-inference.expected index bedc10ba0e35..5572b4f6361c 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -1,4 +1,96 @@ inferType +| blanket_impl.rs:8:19:8:23 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:8:19:8:23 | SelfParam | &T | blanket_impl.rs:7:5:9:5 | Self [trait Clone1] | +| blanket_impl.rs:12:22:12:26 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:12:22:12:26 | SelfParam | &T | blanket_impl.rs:11:5:15:5 | Self [trait Duplicatable] | +| blanket_impl.rs:19:19:19:23 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:19:19:19:23 | SelfParam | &T | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:19:34:21:9 | { ... } | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:20:13:20:17 | * ... | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:20:14:20:17 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:20:14:20:17 | self | &T | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:27:22:27:26 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:27:22:27:26 | SelfParam | &T | blanket_impl.rs:25:10:25:18 | T | +| blanket_impl.rs:27:37:29:9 | { ... } | | blanket_impl.rs:25:10:25:18 | T | +| blanket_impl.rs:28:13:28:16 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:28:13:28:16 | self | &T | blanket_impl.rs:25:10:25:18 | T | +| blanket_impl.rs:28:13:28:25 | self.clone1() | | blanket_impl.rs:25:10:25:18 | T | +| blanket_impl.rs:33:13:33:13 | x | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:33:17:33:18 | S1 | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:33:17:33:27 | S1.clone1() | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:34:18:34:24 | "{x:?}\\n" | | file://:0:0:0:0 | & | +| blanket_impl.rs:34:18:34:24 | "{x:?}\\n" | &T | {EXTERNAL LOCATION} | str | +| blanket_impl.rs:34:18:34:24 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:34:18:34:24 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:34:20:34:20 | x | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:35:17:35:18 | S1 | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:36:18:36:24 | "{y:?}\\n" | | file://:0:0:0:0 | & | +| blanket_impl.rs:36:18:36:24 | "{y:?}\\n" | &T | {EXTERNAL LOCATION} | str | +| blanket_impl.rs:36:18:36:24 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:36:18:36:24 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:46:22:46:26 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:46:22:46:26 | SelfParam | &T | blanket_impl.rs:45:5:47:5 | Self [trait Flag] | +| blanket_impl.rs:50:26:50:30 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:50:26:50:30 | SelfParam | &T | blanket_impl.rs:49:5:51:5 | Self [trait TryFlag] | +| blanket_impl.rs:57:26:57:30 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:57:26:57:30 | SelfParam | &T | blanket_impl.rs:53:10:53:11 | Fl | +| blanket_impl.rs:57:49:59:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:57:49:59:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:58:13:58:34 | Some(...) | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:58:13:58:34 | Some(...) | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:58:18:58:21 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:58:18:58:21 | self | &T | blanket_impl.rs:53:10:53:11 | Fl | +| blanket_impl.rs:58:18:58:33 | self.read_flag() | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:64:32:64:36 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:64:32:64:36 | SelfParam | &T | blanket_impl.rs:62:5:67:5 | Self [trait TryFlagExt] | +| blanket_impl.rs:64:55:66:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:64:55:66:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:65:13:65:16 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:65:13:65:16 | self | &T | blanket_impl.rs:62:5:67:5 | Self [trait TryFlagExt] | +| blanket_impl.rs:65:13:65:32 | self.try_read_flag() | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:65:13:65:32 | self.try_read_flag() | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:73:32:73:36 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:73:32:73:36 | SelfParam | &T | blanket_impl.rs:71:5:74:5 | Self [trait AnotherTryFlag] | +| blanket_impl.rs:82:26:82:30 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:82:26:82:30 | SelfParam | &T | blanket_impl.rs:76:5:78:5 | MyTryFlag | +| blanket_impl.rs:82:49:84:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:82:49:84:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:83:13:83:27 | Some(...) | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:83:13:83:27 | Some(...) | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:83:18:83:21 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:83:18:83:21 | self | &T | blanket_impl.rs:76:5:78:5 | MyTryFlag | +| blanket_impl.rs:83:18:83:26 | self.flag | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:93:22:93:26 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:93:22:93:26 | SelfParam | &T | blanket_impl.rs:87:5:89:5 | MyFlag | +| blanket_impl.rs:93:37:95:9 | { ... } | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:94:13:94:16 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:94:13:94:16 | self | &T | blanket_impl.rs:87:5:89:5 | MyFlag | +| blanket_impl.rs:94:13:94:21 | self.flag | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:104:32:104:36 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:104:32:104:36 | SelfParam | &T | blanket_impl.rs:98:5:100:5 | MyOtherFlag | +| blanket_impl.rs:104:55:106:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:104:55:106:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:105:13:105:27 | Some(...) | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:105:13:105:27 | Some(...) | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:105:18:105:21 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:105:18:105:21 | self | &T | blanket_impl.rs:98:5:100:5 | MyOtherFlag | +| blanket_impl.rs:105:18:105:26 | self.flag | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:110:13:110:23 | my_try_flag | | blanket_impl.rs:76:5:78:5 | MyTryFlag | +| blanket_impl.rs:110:27:110:50 | MyTryFlag {...} | | blanket_impl.rs:76:5:78:5 | MyTryFlag | +| blanket_impl.rs:110:45:110:48 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:111:22:111:32 | my_try_flag | | blanket_impl.rs:76:5:78:5 | MyTryFlag | +| blanket_impl.rs:113:13:113:19 | my_flag | | blanket_impl.rs:87:5:89:5 | MyFlag | +| blanket_impl.rs:113:23:113:43 | MyFlag {...} | | blanket_impl.rs:87:5:89:5 | MyFlag | +| blanket_impl.rs:113:38:113:41 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:116:22:116:28 | my_flag | | blanket_impl.rs:87:5:89:5 | MyFlag | +| blanket_impl.rs:118:13:118:25 | my_other_flag | | blanket_impl.rs:98:5:100:5 | MyOtherFlag | +| blanket_impl.rs:118:29:118:54 | MyOtherFlag {...} | | blanket_impl.rs:98:5:100:5 | MyOtherFlag | +| blanket_impl.rs:118:49:118:52 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:121:13:121:18 | result | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:121:13:121:18 | result | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:121:22:121:34 | my_other_flag | | blanket_impl.rs:98:5:100:5 | MyOtherFlag | +| blanket_impl.rs:121:22:121:56 | my_other_flag.try_read_flag_twice() | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:121:22:121:56 | my_other_flag.try_read_flag_twice() | T | {EXTERNAL LOCATION} | bool | | closure.rs:6:13:6:22 | my_closure | | {EXTERNAL LOCATION} | dyn FnOnce | | closure.rs:6:13:6:22 | my_closure | dyn(Args) | file://:0:0:0:0 | (T_2) | | closure.rs:6:13:6:22 | my_closure | dyn(Args).0(2) | {EXTERNAL LOCATION} | bool | @@ -4840,11 +4932,11 @@ inferType | main.rs:2481:26:2481:43 | "Nested boxed: {}\\n" | &T | {EXTERNAL LOCATION} | str | | main.rs:2481:26:2481:59 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | | main.rs:2481:26:2481:59 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2493:5:2493:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2494:5:2494:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2494:20:2494:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2494:41:2494:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2510:5:2510:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | +| main.rs:2494:5:2494:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2495:5:2495:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2495:20:2495:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2495:41:2495:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2511:5:2511:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | | pattern_matching.rs:13:26:133:1 | { ... } | | {EXTERNAL LOCATION} | Option | | pattern_matching.rs:13:26:133:1 | { ... } | T | file://:0:0:0:0 | () | | pattern_matching.rs:14:9:14:13 | value | | {EXTERNAL LOCATION} | Option | From f130198b9e1f3ac10a68ae9dac43dc83e84c6413 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Sat, 2 Aug 2025 15:10:23 +0200 Subject: [PATCH 2/2] Rust: Implement basic support for blanket implementations --- .../codeql/rust/internal/PathResolution.qll | 29 +++- .../codeql/rust/internal/TypeInference.qll | 142 ++++++++++++++++++ .../type-inference/blanket_impl.rs | 4 +- .../library-tests/type-inference/dyn_type.rs | 2 +- .../test/library-tests/type-inference/main.rs | 6 +- .../type-inference/type-inference.expected | 26 ++++ .../typeinference/internal/TypeInference.qll | 3 + 7 files changed, 202 insertions(+), 10 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index f9097ee39657..8abee1d98bbe 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -933,15 +933,36 @@ class TypeParamItemNode extends TypeItemNode instanceof TypeParam { } pragma[nomagic] - Path getABoundPath() { - exists(TypeBoundList tbl | result = tbl.getABound().getTypeRepr().(PathTypeRepr).getPath() | - tbl = super.getTypeBoundList() + TypeBound getBound(int index) { + result = super.getTypeBoundList().getBound(index) + or + exists(int offset | + offset = super.getTypeBoundList().getNumberOfBounds() or - tbl = this.getAWherePred().getTypeBoundList() + not super.hasTypeBoundList() and + offset = 0 + | + result = this.getAWherePred().getTypeBoundList().getBound(index - offset) ) } pragma[nomagic] + Path getBoundPath(int index) { + result = this.getBound(index).getTypeRepr().(PathTypeRepr).getPath() + } + + Path getABoundPath() { result = this.getBoundPath(_) } + + pragma[nomagic] + ItemNode resolveBound(int index) { + result = + rank[index + 1](int i, ItemNode item | + item = resolvePath(this.getBoundPath(i)) + | + item order by i + ) + } + ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) } /** diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index c571a2610435..57f670924cb3 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -1643,6 +1643,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int methodCandidate(type, name, arity, impl) } +/** + * Holds if `mc` has `rootType` as the root type of the reciever and the target + * method is named `name` and has arity `arity` + */ pragma[nomagic] private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) { rootType = mc.getTypeAt(TypePath::nil()) and @@ -1841,6 +1845,142 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) { else any() } +private module BlanketImplementation { + /** + * Holds if `impl` is a blanket implementation, that is, an implementation of a + * trait for a type parameter. + */ + private TypeParamItemNode getBlanketImplementationTypeParam(Impl impl) { + result = impl.(ImplItemNode).resolveSelfTy() and + result = impl.getGenericParamList().getAGenericParam() and + not exists(impl.getAttributeMacroExpansion()) + } + + predicate isBlanketImplementation(Impl impl) { exists(getBlanketImplementationTypeParam(impl)) } + + private Impl getPotentialDuplicated(string fileName, string traitName, int arity, string tpName) { + tpName = getBlanketImplementationTypeParam(result).getName() and + fileName = result.getLocation().getFile().getBaseName() and + traitName = result.(ImplItemNode).resolveTraitTy().getName() and + arity = result.(ImplItemNode).resolveTraitTy().(Trait).getNumberOfGenericParams() + } + + /** + * Holds if `impl1` and `impl2` are duplicates and `impl2` is more "canonical" + * than `impl1`. + */ + predicate duplicatedImpl(Impl impl1, Impl impl2) { + exists(string fileName, string traitName, int arity, string tpName | + impl1 = getPotentialDuplicated(fileName, traitName, arity, tpName) and + impl2 = getPotentialDuplicated(fileName, traitName, arity, tpName) and + impl1.getLocation().getFile().getAbsolutePath() < + impl2.getLocation().getFile().getAbsolutePath() + ) + } + + predicate hasNoDuplicates(Impl impl) { + not duplicatedImpl(impl, _) and isBlanketImplementation(impl) + } + + /** + * We currently consider blanket implementations to be in scope "globally", + * even though they actually need to be imported to be used. One downside of + * this is that the libraries included in the database can often occur several + * times for different library versions. This causes the same blanket + * implementations to exist multiple times, and these add no useful + * information. + * + * We detect these duplicates based on some files heuristic (same trait name, + * file name, etc.). For these duplicates we select the one with the greatest + * file name (which usually is also the one with the greatest library version + * in the path) + */ + Impl getCanonicalImpl(Impl impl) { + result = + max(Impl impl0, Location l | + duplicatedImpl(impl, impl0) and l = impl0.getLocation() + | + impl0 order by l.getFile().getAbsolutePath(), l.getStartLine() + ) + or + hasNoDuplicates(impl) and result = impl + } + + predicate isCanonicalBlanketImplementation(Impl impl) { impl = getCanonicalImpl(impl) } + + /** + * Holds if `impl` is a blanket implementation for a type parameter and the type + * parameter must implement `trait`. + */ + private predicate blanketImplementationTraitBound(Impl impl, Trait t) { + t = + min(Trait trait, int i | + trait = getBlanketImplementationTypeParam(impl).resolveBound(i) and + // Exclude traits that are "trivial" in the sense that they are known to + // not narrow things down very much. + not trait.getName().getText() = + [ + "Sized", "Clone", "Fn", "FnOnce", "FnMut", + // The auto traits + "Send", "Sync", "Unpin", "UnwindSafe", "RefUnwindSafe" + ] + | + trait order by i + ) + } + + private predicate blanketImplementationMethod( + Impl impl, Trait trait, string name, int arity, Function f + ) { + isCanonicalBlanketImplementation(impl) and + blanketImplementationTraitBound(impl, trait) and + f.getParamList().hasSelfParam() and + arity = f.getParamList().getNumberOfParams() and + ( + f = impl.(ImplItemNode).getAssocItem(name) + or + // If the the trait has a method with a default implementation, then that + // target is interesting as well. + not exists(impl.(ImplItemNode).getAssocItem(name)) and + f = impl.(ImplItemNode).resolveTraitTy().getAssocItem(name) + ) and + // If the method is already available through one of the trait bounds on the + // type parameter (because they share a common trait ancestor) then ignore + // it. + not getBlanketImplementationTypeParam(impl).resolveABound().(TraitItemNode).getASuccessor(name) = + f + } + + predicate methodCallMatchesBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) { + // Only check method calls where we have ruled out inherent method targets. + // Ideally we would also check if non-blanket method targets have been ruled + // out. + methodCallHasNoInherentTarget(mc) and + exists(string name, int arity | + isMethodCall(mc, t, name, arity) and + blanketImplementationMethod(impl, trait, name, arity, f) + ) + } + + module SatisfiesConstraintInput implements SatisfiesConstraintInputSig { + pragma[nomagic] + predicate relevantConstraint(MethodCall mc, Type constraint) { + methodCallMatchesBlanketImpl(mc, _, _, constraint.(TraitType).getTrait(), _) + } + + predicate useUniversalConditions() { none() } + } + + predicate hasBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) { + SatisfiesConstraint::satisfiesConstraintType(mc, + TTrait(trait), _, _) and + methodCallMatchesBlanketImpl(mc, t, impl, trait, f) + } + + pragma[nomagic] + Function getMethodFromBlanketImpl(MethodCall mc) { hasBlanketImpl(mc, _, _, _, result) } +} + /** Gets a method from an `impl` block that matches the method call `mc`. */ pragma[nomagic] private Function getMethodFromImpl(MethodCall mc) { @@ -1876,6 +2016,8 @@ private Function resolveMethodCallTarget(MethodCall mc) { // The method comes from an `impl` block targeting the type of the receiver. result = getMethodFromImpl(mc) or + result = BlanketImplementation::getMethodFromBlanketImpl(mc) + or // The type of the receiver is a type parameter and the method comes from a // trait bound on the type parameter. result = getTypeParameterMethod(mc.getTypeAt(TypePath::nil()), mc.getMethodName()) diff --git a/rust/ql/test/library-tests/type-inference/blanket_impl.rs b/rust/ql/test/library-tests/type-inference/blanket_impl.rs index 18fdf92e6329..25e1fb975a65 100644 --- a/rust/ql/test/library-tests/type-inference/blanket_impl.rs +++ b/rust/ql/test/library-tests/type-inference/blanket_impl.rs @@ -32,7 +32,7 @@ mod basic_blanket_impl { pub fn test_basic_blanket() { let x = S1.clone1(); // $ target=S1::clone1 println!("{x:?}"); - let y = S1.duplicate(); // $ MISSING: target=Clone1duplicate + let y = S1.duplicate(); // $ target=Clone1duplicate println!("{y:?}"); } } @@ -108,7 +108,7 @@ mod extension_trait_blanket_impl { fn test() { let my_try_flag = MyTryFlag { flag: true }; - let result = my_try_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice + let result = my_try_flag.try_read_flag_twice(); // $ target=TryFlagExt::try_read_flag_twice let my_flag = MyFlag { flag: true }; // Here `TryFlagExt::try_read_flag_twice` is since there is a blanket diff --git a/rust/ql/test/library-tests/type-inference/dyn_type.rs b/rust/ql/test/library-tests/type-inference/dyn_type.rs index 24f320ec3f40..c4514a2872a2 100644 --- a/rust/ql/test/library-tests/type-inference/dyn_type.rs +++ b/rust/ql/test/library-tests/type-inference/dyn_type.rs @@ -101,7 +101,7 @@ fn test_assoc_type(obj: &dyn AssocTrait) { pub fn test() { test_basic_dyn_trait(&MyStruct { value: 42 }); // $ target=test_basic_dyn_trait test_generic_dyn_trait(&GenStruct { - value: "".to_string(), + value: "".to_string(), // $ target=to_string }); // $ target=test_generic_dyn_trait test_poly_dyn_trait(); // $ target=test_poly_dyn_trait test_assoc_type(&GenStruct { value: 100 }); // $ target=test_assoc_type diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 0dc33fc90cba..a4b75e090bc7 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -316,7 +316,7 @@ mod method_non_parametric_trait_impl { fn type_bound_type_parameter_impl>(thing: TP) -> S1 { // The trait bound on `TP` makes the implementation of `ConvertTo` valid - thing.convert_to() // $ MISSING: target=T::convert_to + thing.convert_to() // $ target=T::convert_to } pub fn f() { @@ -388,7 +388,7 @@ mod method_non_parametric_trait_impl { let x = get_snd_fst(c); // $ type=x:S1 target=get_snd_fst let thing = MyThing { a: S1 }; - let i = thing.convert_to(); // $ MISSING: type=i:S1 target=T::convert_to + let i = thing.convert_to(); // $ type=i:S1 target=T::convert_to let j = convert_to(thing); // $ type=j:S1 target=convert_to } } @@ -1292,7 +1292,7 @@ mod method_call_type_conversion { let t = x7.m1(); // $ target=m1 type=t:& type=t:&T.S2 println!("{:?}", x7); - let x9: String = "Hello".to_string(); // $ type=x9:String + let x9: String = "Hello".to_string(); // $ type=x9:String target=to_string // Implicit `String` -> `str` conversion happens via the `Deref` trait: // https://doc.rust-lang.org/std/string/struct.String.html#deref. diff --git a/rust/ql/test/library-tests/type-inference/type-inference.expected b/rust/ql/test/library-tests/type-inference/type-inference.expected index 5572b4f6361c..764a12781ff6 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -23,11 +23,14 @@ inferType | blanket_impl.rs:34:18:34:24 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | | blanket_impl.rs:34:18:34:24 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | | blanket_impl.rs:34:20:34:20 | x | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:35:13:35:13 | y | | blanket_impl.rs:4:5:5:14 | S1 | | blanket_impl.rs:35:17:35:18 | S1 | | blanket_impl.rs:4:5:5:14 | S1 | +| blanket_impl.rs:35:17:35:30 | S1.duplicate() | | blanket_impl.rs:4:5:5:14 | S1 | | blanket_impl.rs:36:18:36:24 | "{y:?}\\n" | | file://:0:0:0:0 | & | | blanket_impl.rs:36:18:36:24 | "{y:?}\\n" | &T | {EXTERNAL LOCATION} | str | | blanket_impl.rs:36:18:36:24 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | | blanket_impl.rs:36:18:36:24 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:36:20:36:20 | y | | blanket_impl.rs:4:5:5:14 | S1 | | blanket_impl.rs:46:22:46:26 | SelfParam | | file://:0:0:0:0 | & | | blanket_impl.rs:46:22:46:26 | SelfParam | &T | blanket_impl.rs:45:5:47:5 | Self [trait Flag] | | blanket_impl.rs:50:26:50:30 | SelfParam | | file://:0:0:0:0 | & | @@ -78,7 +81,11 @@ inferType | blanket_impl.rs:110:13:110:23 | my_try_flag | | blanket_impl.rs:76:5:78:5 | MyTryFlag | | blanket_impl.rs:110:27:110:50 | MyTryFlag {...} | | blanket_impl.rs:76:5:78:5 | MyTryFlag | | blanket_impl.rs:110:45:110:48 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:111:13:111:18 | result | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:111:13:111:18 | result | T | {EXTERNAL LOCATION} | bool | | blanket_impl.rs:111:22:111:32 | my_try_flag | | blanket_impl.rs:76:5:78:5 | MyTryFlag | +| blanket_impl.rs:111:22:111:54 | my_try_flag.try_read_flag_twice() | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:111:22:111:54 | my_try_flag.try_read_flag_twice() | T | {EXTERNAL LOCATION} | bool | | blanket_impl.rs:113:13:113:19 | my_flag | | blanket_impl.rs:87:5:89:5 | MyFlag | | blanket_impl.rs:113:23:113:43 | MyFlag {...} | | blanket_impl.rs:87:5:89:5 | MyFlag | | blanket_impl.rs:113:38:113:41 | true | | {EXTERNAL LOCATION} | bool | @@ -649,12 +656,15 @@ inferType | dyn_type.rs:103:28:105:5 | &... | | file://:0:0:0:0 | & | | dyn_type.rs:103:28:105:5 | &... | &T | dyn_type.rs:10:1:13:1 | dyn GenericGet | | dyn_type.rs:103:28:105:5 | &... | &T | dyn_type.rs:33:1:36:1 | GenStruct | +| dyn_type.rs:103:28:105:5 | &... | &T.A | {EXTERNAL LOCATION} | String | | dyn_type.rs:103:28:105:5 | &... | &T.dyn(A) | {EXTERNAL LOCATION} | String | | dyn_type.rs:103:29:105:5 | GenStruct {...} | | dyn_type.rs:10:1:13:1 | dyn GenericGet | | dyn_type.rs:103:29:105:5 | GenStruct {...} | | dyn_type.rs:33:1:36:1 | GenStruct | +| dyn_type.rs:103:29:105:5 | GenStruct {...} | A | {EXTERNAL LOCATION} | String | | dyn_type.rs:103:29:105:5 | GenStruct {...} | dyn(A) | {EXTERNAL LOCATION} | String | | dyn_type.rs:104:16:104:17 | "" | | file://:0:0:0:0 | & | | dyn_type.rs:104:16:104:17 | "" | &T | {EXTERNAL LOCATION} | str | +| dyn_type.rs:104:16:104:29 | "".to_string() | | {EXTERNAL LOCATION} | String | | dyn_type.rs:107:21:107:45 | &... | | file://:0:0:0:0 | & | | dyn_type.rs:107:21:107:45 | &... | &T | dyn_type.rs:15:1:19:1 | dyn AssocTrait | | dyn_type.rs:107:21:107:45 | &... | &T | dyn_type.rs:33:1:36:1 | GenStruct | @@ -1267,8 +1277,10 @@ inferType | main.rs:390:21:390:37 | MyThing {...} | | main.rs:175:5:178:5 | MyThing | | main.rs:390:21:390:37 | MyThing {...} | A | main.rs:186:5:187:14 | S1 | | main.rs:390:34:390:35 | S1 | | main.rs:186:5:187:14 | S1 | +| main.rs:391:13:391:13 | i | | main.rs:186:5:187:14 | S1 | | main.rs:391:17:391:21 | thing | | main.rs:175:5:178:5 | MyThing | | main.rs:391:17:391:21 | thing | A | main.rs:186:5:187:14 | S1 | +| main.rs:391:17:391:34 | thing.convert_to() | | main.rs:186:5:187:14 | S1 | | main.rs:392:13:392:13 | j | | main.rs:186:5:187:14 | S1 | | main.rs:392:17:392:33 | convert_to(...) | | main.rs:186:5:187:14 | S1 | | main.rs:392:28:392:32 | thing | | main.rs:175:5:178:5 | MyThing | @@ -3168,14 +3180,22 @@ inferType | main.rs:1622:13:1625:13 | Vec2 {...} | | main.rs:1502:5:1507:5 | Vec2 | | main.rs:1623:20:1623:23 | self | | main.rs:1502:5:1507:5 | Vec2 | | main.rs:1623:20:1623:25 | self.x | | {EXTERNAL LOCATION} | i64 | +| main.rs:1623:20:1623:33 | ... \| ... | | {EXTERNAL LOCATION} | NonZero | | main.rs:1623:20:1623:33 | ... \| ... | | {EXTERNAL LOCATION} | i64 | +| main.rs:1623:20:1623:33 | ... \| ... | T | {EXTERNAL LOCATION} | i64 | | main.rs:1623:29:1623:31 | rhs | | main.rs:1502:5:1507:5 | Vec2 | +| main.rs:1623:29:1623:33 | rhs.x | | {EXTERNAL LOCATION} | NonZero | | main.rs:1623:29:1623:33 | rhs.x | | {EXTERNAL LOCATION} | i64 | +| main.rs:1623:29:1623:33 | rhs.x | T | {EXTERNAL LOCATION} | i64 | | main.rs:1624:20:1624:23 | self | | main.rs:1502:5:1507:5 | Vec2 | | main.rs:1624:20:1624:25 | self.y | | {EXTERNAL LOCATION} | i64 | +| main.rs:1624:20:1624:33 | ... \| ... | | {EXTERNAL LOCATION} | NonZero | | main.rs:1624:20:1624:33 | ... \| ... | | {EXTERNAL LOCATION} | i64 | +| main.rs:1624:20:1624:33 | ... \| ... | T | {EXTERNAL LOCATION} | i64 | | main.rs:1624:29:1624:31 | rhs | | main.rs:1502:5:1507:5 | Vec2 | +| main.rs:1624:29:1624:33 | rhs.y | | {EXTERNAL LOCATION} | NonZero | | main.rs:1624:29:1624:33 | rhs.y | | {EXTERNAL LOCATION} | i64 | +| main.rs:1624:29:1624:33 | rhs.y | T | {EXTERNAL LOCATION} | i64 | | main.rs:1630:25:1630:33 | SelfParam | | file://:0:0:0:0 | & | | main.rs:1630:25:1630:33 | SelfParam | &T | main.rs:1502:5:1507:5 | Vec2 | | main.rs:1630:36:1630:38 | rhs | | main.rs:1502:5:1507:5 | Vec2 | @@ -3513,10 +3533,16 @@ inferType | main.rs:1773:26:1773:30 | 33i64 | | {EXTERNAL LOCATION} | i64 | | main.rs:1773:26:1773:38 | ... & ... | | {EXTERNAL LOCATION} | i64 | | main.rs:1773:34:1773:38 | 34i64 | | {EXTERNAL LOCATION} | i64 | +| main.rs:1774:13:1774:21 | i64_bitor | | {EXTERNAL LOCATION} | NonZero | | main.rs:1774:13:1774:21 | i64_bitor | | {EXTERNAL LOCATION} | i64 | +| main.rs:1774:13:1774:21 | i64_bitor | T | {EXTERNAL LOCATION} | i64 | | main.rs:1774:25:1774:29 | 35i64 | | {EXTERNAL LOCATION} | i64 | +| main.rs:1774:25:1774:37 | ... \| ... | | {EXTERNAL LOCATION} | NonZero | | main.rs:1774:25:1774:37 | ... \| ... | | {EXTERNAL LOCATION} | i64 | +| main.rs:1774:25:1774:37 | ... \| ... | T | {EXTERNAL LOCATION} | i64 | +| main.rs:1774:33:1774:37 | 36i64 | | {EXTERNAL LOCATION} | NonZero | | main.rs:1774:33:1774:37 | 36i64 | | {EXTERNAL LOCATION} | i64 | +| main.rs:1774:33:1774:37 | 36i64 | T | {EXTERNAL LOCATION} | i64 | | main.rs:1775:13:1775:22 | i64_bitxor | | {EXTERNAL LOCATION} | i64 | | main.rs:1775:26:1775:30 | 37i64 | | {EXTERNAL LOCATION} | i64 | | main.rs:1775:26:1775:38 | ... ^ ... | | {EXTERNAL LOCATION} | i64 | diff --git a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll index 1eaf6ef8e840..9fbf33029a1e 100644 --- a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll +++ b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll @@ -903,6 +903,8 @@ module Make1 Input1> { signature module SatisfiesConstraintInputSig { /** Holds if it is relevant to know if `term` satisfies `constraint`. */ predicate relevantConstraint(HasTypeTree term, Type constraint); + + default predicate useUniversalConditions() { any() } } module SatisfiesConstraint< @@ -944,6 +946,7 @@ module Make1 Input1> { not exists(countConstraintImplementations(type, constraint)) and conditionSatisfiesConstraintTypeAt(abs, sub, constraintMention, _, _) and resolveTypeMentionRoot(sub) = abs.getATypeParameter() and + useUniversalConditions() and constraint = resolveTypeMentionRoot(constraintMention) or countConstraintImplementations(type, constraint) > 0 and