From 69a1c7e1e875a68b0eb5ade2c89465f342cda091 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Sat, 2 Aug 2025 14:51:59 +0200 Subject: [PATCH 1/7] Rust: Add tests with blanket implementation --- .../type-inference/blanket_impl.rs | 124 ++++++++++++++++++ .../test/library-tests/type-inference/main.rs | 1 + .../type-inference/type-inference.expected | 102 +++++++++++++- 3 files changed, 222 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..701dae4fd834 --- /dev/null +++ b/rust/ql/test/library-tests/type-inference/blanket_impl.rs @@ -0,0 +1,124 @@ +// 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 { + // This tests: + // 1. 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 21fabe3716ad..fca3f27f7b74 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -2655,6 +2655,7 @@ pub mod path_buf { 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 4de63e1ff452..ab16c104e4bf 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:47:22:47:26 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:47:22:47:26 | SelfParam | &T | blanket_impl.rs:46:5:48:5 | Self [trait Flag] | +| blanket_impl.rs:51:26:51:30 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:51:26:51:30 | SelfParam | &T | blanket_impl.rs:50:5:52:5 | Self [trait TryFlag] | +| blanket_impl.rs:58:26:58:30 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:58:26:58:30 | SelfParam | &T | blanket_impl.rs:54:10:54:11 | Fl | +| blanket_impl.rs:58:49:60:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:58:49:60:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:59:13:59:34 | Some(...) | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:59:13:59:34 | Some(...) | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:59:18:59:21 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:59:18:59:21 | self | &T | blanket_impl.rs:54:10:54:11 | Fl | +| blanket_impl.rs:59:18:59:33 | self.read_flag() | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:65:32:65:36 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:65:32:65:36 | SelfParam | &T | blanket_impl.rs:63:5:68:5 | Self [trait TryFlagExt] | +| blanket_impl.rs:65:55:67:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:65:55:67:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:66:13:66:16 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:66:13:66:16 | self | &T | blanket_impl.rs:63:5:68:5 | Self [trait TryFlagExt] | +| blanket_impl.rs:66:13:66:32 | self.try_read_flag() | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:66:13:66:32 | self.try_read_flag() | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:74:32:74:36 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:74:32:74:36 | SelfParam | &T | blanket_impl.rs:72:5:75:5 | Self [trait AnotherTryFlag] | +| blanket_impl.rs:83:26:83:30 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:83:26:83:30 | SelfParam | &T | blanket_impl.rs:77:5:79:5 | MyTryFlag | +| blanket_impl.rs:83:49:85:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:83:49:85:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:84:13:84:27 | Some(...) | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:84:13:84:27 | Some(...) | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:84:18:84:21 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:84:18:84:21 | self | &T | blanket_impl.rs:77:5:79:5 | MyTryFlag | +| blanket_impl.rs:84:18:84:26 | self.flag | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:94:22:94:26 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:94:22:94:26 | SelfParam | &T | blanket_impl.rs:88:5:90:5 | MyFlag | +| blanket_impl.rs:94:37:96:9 | { ... } | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:95:13:95:16 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:95:13:95:16 | self | &T | blanket_impl.rs:88:5:90:5 | MyFlag | +| blanket_impl.rs:95:13:95:21 | self.flag | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:105:32:105:36 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:105:32:105:36 | SelfParam | &T | blanket_impl.rs:99:5:101:5 | MyOtherFlag | +| blanket_impl.rs:105:55:107:9 | { ... } | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:105:55:107:9 | { ... } | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:106:13:106:27 | Some(...) | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:106:13:106:27 | Some(...) | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:106:18:106:21 | self | | file://:0:0:0:0 | & | +| blanket_impl.rs:106:18:106:21 | self | &T | blanket_impl.rs:99:5:101:5 | MyOtherFlag | +| blanket_impl.rs:106:18:106:26 | self.flag | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:111:13:111:23 | my_try_flag | | blanket_impl.rs:77:5:79:5 | MyTryFlag | +| blanket_impl.rs:111:27:111:50 | MyTryFlag {...} | | blanket_impl.rs:77:5:79:5 | MyTryFlag | +| blanket_impl.rs:111:45:111:48 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:112:22:112:32 | my_try_flag | | blanket_impl.rs:77:5:79:5 | MyTryFlag | +| blanket_impl.rs:114:13:114:19 | my_flag | | blanket_impl.rs:88:5:90:5 | MyFlag | +| blanket_impl.rs:114:23:114:43 | MyFlag {...} | | blanket_impl.rs:88:5:90:5 | MyFlag | +| blanket_impl.rs:114:38:114:41 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:117:22:117:28 | my_flag | | blanket_impl.rs:88:5:90:5 | MyFlag | +| blanket_impl.rs:119:13:119:25 | my_other_flag | | blanket_impl.rs:99:5:101:5 | MyOtherFlag | +| blanket_impl.rs:119:29:119:54 | MyOtherFlag {...} | | blanket_impl.rs:99:5:101:5 | MyOtherFlag | +| blanket_impl.rs:119:49:119:52 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:122:13:122:18 | result | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:122:13:122:18 | result | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:122:22:122:34 | my_other_flag | | blanket_impl.rs:99:5:101:5 | MyOtherFlag | +| blanket_impl.rs:122:22:122:56 | my_other_flag.try_read_flag_twice() | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:122:22:122: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 | @@ -5000,11 +5092,11 @@ inferType | main.rs:2649:13:2649:20 | pathbuf1 | | main.rs:2624:5:2624:25 | PathBuf | | main.rs:2649:24:2649:37 | ...::new(...) | | main.rs:2624:5:2624:25 | PathBuf | | main.rs:2650:24:2650:31 | pathbuf1 | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2661:5:2661:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2662:5:2662:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2662:20:2662:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2662:41:2662:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2678:5:2678:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | +| main.rs:2662:5:2662:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2663:5:2663:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2663:20:2663:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2663:41:2663:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2679:5:2679: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 d10cdfb7f1bb2a6bcc7353a410436a7a619cace8 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Wed, 10 Sep 2025 08:54:45 +0200 Subject: [PATCH 2/7] Rust: Move existing blanket implementation test --- .../type-inference/blanket_impl.rs | 37 ++++ .../test/library-tests/type-inference/main.rs | 38 ---- .../type-inference/type-inference.expected | 166 +++++++++--------- 3 files changed, 120 insertions(+), 121 deletions(-) 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 701dae4fd834..8083de26f962 100644 --- a/rust/ql/test/library-tests/type-inference/blanket_impl.rs +++ b/rust/ql/test/library-tests/type-inference/blanket_impl.rs @@ -122,3 +122,40 @@ mod extension_trait_blanket_impl { let result = my_other_flag.try_read_flag_twice(); // $ target=MyOtherFlag::try_read_flag_twice } } + +pub mod sql_exec { + // a highly simplified model of `MySqlConnection.execute` in SQLx + + trait Connection {} + + trait Executor { + fn execute1(&self); + fn execute2(&self, query: E); + } + + impl Executor for T { + fn execute1(&self) { + println!("Executor::execute1"); + } + + fn execute2(&self, _query: E) { + println!("Executor::execute2"); + } + } + + struct MySqlConnection {} + + impl Connection for MySqlConnection {} + + pub fn f() { + let c = MySqlConnection {}; // $ certainType=c:MySqlConnection + + c.execute1(); // $ MISSING: target=execute1 + MySqlConnection::execute1(&c); // $ MISSING: target=execute1 + + c.execute2("SELECT * FROM users"); // $ MISSING: target=execute2 + c.execute2::<&str>("SELECT * FROM users"); // $ MISSING: target=execute2 + MySqlConnection::execute2(&c, "SELECT * FROM users"); // $ MISSING: target=execute2 + MySqlConnection::execute2::<&str>(&c, "SELECT * FROM users"); // $ MISSING: target=execute2 + } +} diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index fca3f27f7b74..fc0a817bbb1c 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -2569,43 +2569,6 @@ pub mod pattern_matching_experimental { } } -pub mod exec { - // a highly simplified model of `MySqlConnection.execute` in SQLx - - trait Connection {} - - trait Executor { - fn execute1(&self); - fn execute2(&self, query: E); - } - - impl Executor for T { - fn execute1(&self) { - println!("Executor::execute1"); - } - - fn execute2(&self, _query: E) { - println!("Executor::execute2"); - } - } - - struct MySqlConnection {} - - impl Connection for MySqlConnection {} - - pub fn f() { - let c = MySqlConnection {}; // $ certainType=c:MySqlConnection - - c.execute1(); // $ MISSING: target=execute1 - MySqlConnection::execute1(&c); // $ MISSING: target=execute1 - - c.execute2("SELECT * FROM users"); // $ MISSING: target=execute2 - c.execute2::<&str>("SELECT * FROM users"); // $ MISSING: target=execute2 - MySqlConnection::execute2(&c, "SELECT * FROM users"); // $ MISSING: target=execute2 - MySqlConnection::execute2::<&str>(&c, "SELECT * FROM users"); // $ MISSING: target=execute2 - } -} - pub mod path_buf { // a highly simplified model of `PathBuf::canonicalize` @@ -2684,7 +2647,6 @@ fn main() { macros::f(); // $ target=f method_determined_by_argument_type::f(); // $ target=f tuples::f(); // $ target=f - exec::f(); // $ target=f path_buf::f(); // $ target=f dereference::test(); // $ target=test pattern_matching::test_all_patterns(); // $ target=test_all_patterns 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 ab16c104e4bf..bc77d762ea2d 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -91,6 +91,46 @@ inferType | blanket_impl.rs:122:22:122:34 | my_other_flag | | blanket_impl.rs:99:5:101:5 | MyOtherFlag | | blanket_impl.rs:122:22:122:56 | my_other_flag.try_read_flag_twice() | | {EXTERNAL LOCATION} | Option | | blanket_impl.rs:122:22:122:56 | my_other_flag.try_read_flag_twice() | T | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:132:21:132:25 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:132:21:132:25 | SelfParam | &T | blanket_impl.rs:131:5:134:5 | Self [trait Executor] | +| blanket_impl.rs:133:24:133:28 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:133:24:133:28 | SelfParam | &T | blanket_impl.rs:131:5:134:5 | Self [trait Executor] | +| blanket_impl.rs:133:31:133:35 | query | | blanket_impl.rs:133:21:133:21 | E | +| blanket_impl.rs:137:21:137:25 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:137:21:137:25 | SelfParam | &T | blanket_impl.rs:136:10:136:22 | T | +| blanket_impl.rs:138:22:138:41 | "Executor::execute1\\n" | | file://:0:0:0:0 | & | +| blanket_impl.rs:138:22:138:41 | "Executor::execute1\\n" | &T | {EXTERNAL LOCATION} | str | +| blanket_impl.rs:138:22:138:41 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:138:22:138:41 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:141:24:141:28 | SelfParam | | file://:0:0:0:0 | & | +| blanket_impl.rs:141:24:141:28 | SelfParam | &T | blanket_impl.rs:136:10:136:22 | T | +| blanket_impl.rs:141:31:141:36 | _query | | blanket_impl.rs:141:21:141:21 | E | +| blanket_impl.rs:142:22:142:41 | "Executor::execute2\\n" | | file://:0:0:0:0 | & | +| blanket_impl.rs:142:22:142:41 | "Executor::execute2\\n" | &T | {EXTERNAL LOCATION} | str | +| blanket_impl.rs:142:22:142:41 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:142:22:142:41 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | +| blanket_impl.rs:151:13:151:13 | c | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:151:17:151:34 | MySqlConnection {...} | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:153:9:153:9 | c | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:154:35:154:36 | &c | | file://:0:0:0:0 | & | +| blanket_impl.rs:154:35:154:36 | &c | &T | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:154:36:154:36 | c | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:156:9:156:9 | c | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:156:20:156:40 | "SELECT * FROM users" | | file://:0:0:0:0 | & | +| blanket_impl.rs:156:20:156:40 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | +| blanket_impl.rs:157:9:157:9 | c | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:157:28:157:48 | "SELECT * FROM users" | | file://:0:0:0:0 | & | +| blanket_impl.rs:157:28:157:48 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | +| blanket_impl.rs:158:35:158:36 | &c | | file://:0:0:0:0 | & | +| blanket_impl.rs:158:35:158:36 | &c | &T | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:158:36:158:36 | c | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:158:39:158:59 | "SELECT * FROM users" | | file://:0:0:0:0 | & | +| blanket_impl.rs:158:39:158:59 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | +| blanket_impl.rs:159:43:159:44 | &c | | file://:0:0:0:0 | & | +| blanket_impl.rs:159:43:159:44 | &c | &T | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:159:44:159:44 | c | | blanket_impl.rs:146:5:146:29 | MySqlConnection | +| blanket_impl.rs:159:47:159:67 | "SELECT * FROM users" | | file://:0:0:0:0 | & | +| blanket_impl.rs:159:47:159:67 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | | 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 | @@ -5014,89 +5054,49 @@ inferType | main.rs:2566:26:2566:43 | "Nested boxed: {}\\n" | &T | {EXTERNAL LOCATION} | str | | main.rs:2566:26:2566:59 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | | main.rs:2566:26:2566:59 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2578:21:2578:25 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2578:21:2578:25 | SelfParam | &T | main.rs:2577:5:2580:5 | Self [trait Executor] | -| main.rs:2579:24:2579:28 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2579:24:2579:28 | SelfParam | &T | main.rs:2577:5:2580:5 | Self [trait Executor] | -| main.rs:2579:31:2579:35 | query | | main.rs:2579:21:2579:21 | E | -| main.rs:2583:21:2583:25 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2583:21:2583:25 | SelfParam | &T | main.rs:2582:10:2582:22 | T | -| main.rs:2584:22:2584:41 | "Executor::execute1\\n" | | file://:0:0:0:0 | & | -| main.rs:2584:22:2584:41 | "Executor::execute1\\n" | &T | {EXTERNAL LOCATION} | str | -| main.rs:2584:22:2584:41 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2584:22:2584:41 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2587:24:2587:28 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2587:24:2587:28 | SelfParam | &T | main.rs:2582:10:2582:22 | T | -| main.rs:2587:31:2587:36 | _query | | main.rs:2587:21:2587:21 | E | -| main.rs:2588:22:2588:41 | "Executor::execute2\\n" | | file://:0:0:0:0 | & | -| main.rs:2588:22:2588:41 | "Executor::execute2\\n" | &T | {EXTERNAL LOCATION} | str | -| main.rs:2588:22:2588:41 | FormatArgsExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2588:22:2588:41 | MacroExpr | | {EXTERNAL LOCATION} | Arguments | -| main.rs:2597:13:2597:13 | c | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2597:17:2597:34 | MySqlConnection {...} | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2599:9:2599:9 | c | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2600:35:2600:36 | &c | | file://:0:0:0:0 | & | -| main.rs:2600:35:2600:36 | &c | &T | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2600:36:2600:36 | c | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2602:9:2602:9 | c | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2602:20:2602:40 | "SELECT * FROM users" | | file://:0:0:0:0 | & | -| main.rs:2602:20:2602:40 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | -| main.rs:2603:9:2603:9 | c | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2603:28:2603:48 | "SELECT * FROM users" | | file://:0:0:0:0 | & | -| main.rs:2603:28:2603:48 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | -| main.rs:2604:35:2604:36 | &c | | file://:0:0:0:0 | & | -| main.rs:2604:35:2604:36 | &c | &T | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2604:36:2604:36 | c | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2604:39:2604:59 | "SELECT * FROM users" | | file://:0:0:0:0 | & | -| main.rs:2604:39:2604:59 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | -| main.rs:2605:43:2605:44 | &c | | file://:0:0:0:0 | & | -| main.rs:2605:43:2605:44 | &c | &T | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2605:44:2605:44 | c | | main.rs:2592:5:2592:29 | MySqlConnection | -| main.rs:2605:47:2605:67 | "SELECT * FROM users" | | file://:0:0:0:0 | & | -| main.rs:2605:47:2605:67 | "SELECT * FROM users" | &T | {EXTERNAL LOCATION} | str | -| main.rs:2615:36:2617:9 | { ... } | | main.rs:2612:5:2612:22 | Path | -| main.rs:2616:13:2616:19 | Path {...} | | main.rs:2612:5:2612:22 | Path | -| main.rs:2619:29:2619:33 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2619:29:2619:33 | SelfParam | &T | main.rs:2612:5:2612:22 | Path | -| main.rs:2619:59:2621:9 | { ... } | | {EXTERNAL LOCATION} | Result | -| main.rs:2619:59:2621:9 | { ... } | E | file://:0:0:0:0 | () | -| main.rs:2619:59:2621:9 | { ... } | T | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2620:13:2620:30 | Ok(...) | | {EXTERNAL LOCATION} | Result | -| main.rs:2620:13:2620:30 | Ok(...) | E | file://:0:0:0:0 | () | -| main.rs:2620:13:2620:30 | Ok(...) | T | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2620:16:2620:29 | ...::new(...) | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2627:39:2629:9 | { ... } | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2628:13:2628:22 | PathBuf {...} | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2637:18:2637:22 | SelfParam | | file://:0:0:0:0 | & | -| main.rs:2637:18:2637:22 | SelfParam | &T | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2637:34:2641:9 | { ... } | | file://:0:0:0:0 | & | -| main.rs:2637:34:2641:9 | { ... } | &T | main.rs:2612:5:2612:22 | Path | -| main.rs:2639:33:2639:43 | ...::new(...) | | main.rs:2612:5:2612:22 | Path | -| main.rs:2640:13:2640:17 | &path | | file://:0:0:0:0 | & | -| main.rs:2640:13:2640:17 | &path | &T | main.rs:2612:5:2612:22 | Path | -| main.rs:2640:14:2640:17 | path | | main.rs:2612:5:2612:22 | Path | -| main.rs:2645:13:2645:17 | path1 | | main.rs:2612:5:2612:22 | Path | -| main.rs:2645:21:2645:31 | ...::new(...) | | main.rs:2612:5:2612:22 | Path | -| main.rs:2646:13:2646:17 | path2 | | {EXTERNAL LOCATION} | Result | -| main.rs:2646:13:2646:17 | path2 | E | file://:0:0:0:0 | () | -| main.rs:2646:13:2646:17 | path2 | T | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2646:21:2646:25 | path1 | | main.rs:2612:5:2612:22 | Path | -| main.rs:2646:21:2646:40 | path1.canonicalize() | | {EXTERNAL LOCATION} | Result | -| main.rs:2646:21:2646:40 | path1.canonicalize() | E | file://:0:0:0:0 | () | -| main.rs:2646:21:2646:40 | path1.canonicalize() | T | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2647:13:2647:17 | path3 | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2647:21:2647:25 | path2 | | {EXTERNAL LOCATION} | Result | -| main.rs:2647:21:2647:25 | path2 | E | file://:0:0:0:0 | () | -| main.rs:2647:21:2647:25 | path2 | T | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2647:21:2647:34 | path2.unwrap() | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2649:13:2649:20 | pathbuf1 | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2649:24:2649:37 | ...::new(...) | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2650:24:2650:31 | pathbuf1 | | main.rs:2624:5:2624:25 | PathBuf | -| main.rs:2662:5:2662:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2663:5:2663:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | -| main.rs:2663:20:2663:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2663:41:2663:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | -| main.rs:2679:5:2679:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | +| main.rs:2578:36:2580:9 | { ... } | | main.rs:2575:5:2575:22 | Path | +| main.rs:2579:13:2579:19 | Path {...} | | main.rs:2575:5:2575:22 | Path | +| main.rs:2582:29:2582:33 | SelfParam | | file://:0:0:0:0 | & | +| main.rs:2582:29:2582:33 | SelfParam | &T | main.rs:2575:5:2575:22 | Path | +| main.rs:2582:59:2584:9 | { ... } | | {EXTERNAL LOCATION} | Result | +| main.rs:2582:59:2584:9 | { ... } | E | file://:0:0:0:0 | () | +| main.rs:2582:59:2584:9 | { ... } | T | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2583:13:2583:30 | Ok(...) | | {EXTERNAL LOCATION} | Result | +| main.rs:2583:13:2583:30 | Ok(...) | E | file://:0:0:0:0 | () | +| main.rs:2583:13:2583:30 | Ok(...) | T | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2583:16:2583:29 | ...::new(...) | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2590:39:2592:9 | { ... } | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2591:13:2591:22 | PathBuf {...} | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2600:18:2600:22 | SelfParam | | file://:0:0:0:0 | & | +| main.rs:2600:18:2600:22 | SelfParam | &T | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2600:34:2604:9 | { ... } | | file://:0:0:0:0 | & | +| main.rs:2600:34:2604:9 | { ... } | &T | main.rs:2575:5:2575:22 | Path | +| main.rs:2602:33:2602:43 | ...::new(...) | | main.rs:2575:5:2575:22 | Path | +| main.rs:2603:13:2603:17 | &path | | file://:0:0:0:0 | & | +| main.rs:2603:13:2603:17 | &path | &T | main.rs:2575:5:2575:22 | Path | +| main.rs:2603:14:2603:17 | path | | main.rs:2575:5:2575:22 | Path | +| main.rs:2608:13:2608:17 | path1 | | main.rs:2575:5:2575:22 | Path | +| main.rs:2608:21:2608:31 | ...::new(...) | | main.rs:2575:5:2575:22 | Path | +| main.rs:2609:13:2609:17 | path2 | | {EXTERNAL LOCATION} | Result | +| main.rs:2609:13:2609:17 | path2 | E | file://:0:0:0:0 | () | +| main.rs:2609:13:2609:17 | path2 | T | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2609:21:2609:25 | path1 | | main.rs:2575:5:2575:22 | Path | +| main.rs:2609:21:2609:40 | path1.canonicalize() | | {EXTERNAL LOCATION} | Result | +| main.rs:2609:21:2609:40 | path1.canonicalize() | E | file://:0:0:0:0 | () | +| main.rs:2609:21:2609:40 | path1.canonicalize() | T | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2610:13:2610:17 | path3 | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2610:21:2610:25 | path2 | | {EXTERNAL LOCATION} | Result | +| main.rs:2610:21:2610:25 | path2 | E | file://:0:0:0:0 | () | +| main.rs:2610:21:2610:25 | path2 | T | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2610:21:2610:34 | path2.unwrap() | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2612:13:2612:20 | pathbuf1 | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2612:24:2612:37 | ...::new(...) | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2613:24:2613:31 | pathbuf1 | | main.rs:2587:5:2587:25 | PathBuf | +| main.rs:2625:5:2625:20 | ...::f(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2626:5:2626:60 | ...::g(...) | | main.rs:72:5:72:21 | Foo | +| main.rs:2626:20:2626:38 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2626:41:2626:59 | ...::Foo {...} | | main.rs:72:5:72:21 | Foo | +| main.rs:2642:5:2642: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 29ba0135803b37f108c360710f9bac3995802ed2 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Wed, 10 Sep 2025 10:22:48 +0200 Subject: [PATCH 3/7] Rust: Add support for resolving methods from blanket implementations --- .../codeql/rust/internal/PathResolution.qll | 8 + .../codeql/rust/internal/TypeInference.qll | 155 ++++++++++++++++++ .../type-inference/blanket_impl.rs | 10 +- .../library-tests/type-inference/dyn_type.rs | 2 +- .../test/library-tests/type-inference/main.rs | 6 +- .../type-inference/type-inference.expected | 24 +++ .../typeinference/internal/TypeInference.qll | 10 ++ 7 files changed, 206 insertions(+), 9 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 77faaa747b96..3ec7de5fad67 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -1006,6 +1006,14 @@ final class TypeParamItemNode extends TypeItemNode instanceof TypeParam { Path getABoundPath() { result = this.getTypeBoundAt(_, _).getTypeRepr().(PathTypeRepr).getPath() } pragma[nomagic] + ItemNode resolveBound(int index) { + result = + rank[index + 1](int i, int j | + | + resolvePath(this.getTypeBoundAt(i, j).getTypeRepr().(PathTypeRepr).getPath()) order by i, j + ) + } + 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 070a7d4c6cc2..a0bbdeb759db 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -1915,6 +1915,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 @@ -2153,6 +2157,155 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) { else any() } +private module BlanketImplementation { + /** + * Gets the type parameter for which `impl` is a blanket implementation, if + * any. + */ + private TypeParamItemNode getBlanketImplementationTypeParam(Impl impl) { + result = impl.(ImplItemNode).resolveSelfTy() and + result = impl.getGenericParamList().getAGenericParam() and + // This impl block is not superseded by the expansion of an attribute macro. + 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 strictly more + * "canonical" than `impl1`. + * + * Libraries can often occur several times in the database 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 simple heuristics (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) + */ + 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 isCanonicalImpl(Impl impl) { + not duplicatedImpl(impl, _) and isBlanketImplementation(impl) + } + + 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 + isCanonicalImpl(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 known to not narrow things down very much. + not trait.getName().getText() = + [ + "Sized", "Clone", + // The auto traits + "Send", "Sync", "Unpin", "UnwindSafe", "RefUnwindSafe" + ] + | + trait order by i + ) + } + + /** + * Holds if `impl` is a relevant blanket implementation that requires the + * trait `trait` and provides `f`, a method with name `name` and arity + * `arity`. + */ + private predicate blanketImplementationMethod( + ImplItemNode 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.getAssocItem(name) + or + // If the trait has a method with a default implementation, then that + // target is interesting as well. + not exists(impl.getAssocItem(name)) and + f = impl.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 ancestor trait) 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) + ) + } + + private predicate relevantTraitVisible(Element mc, Trait trait) { + exists(ImplItemNode impl | + methodCallMatchesBlanketImpl(mc, _, impl, _, _) and + trait = impl.resolveTraitTy() + ) + } + + module SatisfiesConstraintInput implements SatisfiesConstraintInputSig { + pragma[nomagic] + predicate relevantConstraint(MethodCall mc, Type constraint) { + exists(Trait trait, Trait trait2, ImplItemNode impl | + methodCallMatchesBlanketImpl(mc, _, impl, trait, _) and + TraitIsVisible::traitIsVisible(mc, pragma[only_bind_into](trait2)) and + trait2 = pragma[only_bind_into](impl.resolveTraitTy()) and + trait = 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) { @@ -2188,6 +2341,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 8083de26f962..a4e14eda0c02 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:?}"); } } @@ -109,7 +109,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 @@ -150,11 +150,11 @@ pub mod sql_exec { pub fn f() { let c = MySqlConnection {}; // $ certainType=c:MySqlConnection - c.execute1(); // $ MISSING: target=execute1 + c.execute1(); // $ target=execute1 MySqlConnection::execute1(&c); // $ MISSING: target=execute1 - c.execute2("SELECT * FROM users"); // $ MISSING: target=execute2 - c.execute2::<&str>("SELECT * FROM users"); // $ MISSING: target=execute2 + c.execute2("SELECT * FROM users"); // $ target=execute2 + c.execute2::<&str>("SELECT * FROM users"); // $ target=execute2 MySqlConnection::execute2(&c, "SELECT * FROM users"); // $ MISSING: target=execute2 MySqlConnection::execute2::<&str>(&c, "SELECT * FROM users"); // $ MISSING: target=execute2 } 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 fc0a817bbb1c..107a133fa222 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -365,7 +365,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() { @@ -437,7 +437,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 } } @@ -1376,7 +1376,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(); // $ certainType=x9:String + let x9: String = "Hello".to_string(); // $ certainType=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 bc77d762ea2d..1a9a6456ce8d 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:47:22:47:26 | SelfParam | | file://:0:0:0:0 | & | | blanket_impl.rs:47:22:47:26 | SelfParam | &T | blanket_impl.rs:46:5:48:5 | Self [trait Flag] | | blanket_impl.rs:51:26:51:30 | SelfParam | | file://:0:0:0:0 | & | @@ -78,7 +81,11 @@ inferType | blanket_impl.rs:111:13:111:23 | my_try_flag | | blanket_impl.rs:77:5:79:5 | MyTryFlag | | blanket_impl.rs:111:27:111:50 | MyTryFlag {...} | | blanket_impl.rs:77:5:79:5 | MyTryFlag | | blanket_impl.rs:111:45:111:48 | true | | {EXTERNAL LOCATION} | bool | +| blanket_impl.rs:112:13:112:18 | result | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:112:13:112:18 | result | T | {EXTERNAL LOCATION} | bool | | blanket_impl.rs:112:22:112:32 | my_try_flag | | blanket_impl.rs:77:5:79:5 | MyTryFlag | +| blanket_impl.rs:112:22:112:54 | my_try_flag.try_read_flag_twice() | | {EXTERNAL LOCATION} | Option | +| blanket_impl.rs:112:22:112:54 | my_try_flag.try_read_flag_twice() | T | {EXTERNAL LOCATION} | bool | | blanket_impl.rs:114:13:114:19 | my_flag | | blanket_impl.rs:88:5:90:5 | MyFlag | | blanket_impl.rs:114:23:114:43 | MyFlag {...} | | blanket_impl.rs:88:5:90:5 | MyFlag | | blanket_impl.rs:114:38:114:41 | true | | {EXTERNAL LOCATION} | bool | @@ -756,10 +763,13 @@ 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:33:1:36:1 | GenStruct | +| dyn_type.rs:103:29:105:5 | GenStruct {...} | 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 | @@ -1392,8 +1402,10 @@ inferType | main.rs:439:21:439:37 | MyThing {...} | | main.rs:224:5:227:5 | MyThing | | main.rs:439:21:439:37 | MyThing {...} | A | main.rs:235:5:236:14 | S1 | | main.rs:439:34:439:35 | S1 | | main.rs:235:5:236:14 | S1 | +| main.rs:440:13:440:13 | i | | main.rs:235:5:236:14 | S1 | | main.rs:440:17:440:21 | thing | | main.rs:224:5:227:5 | MyThing | | main.rs:440:17:440:21 | thing | A | main.rs:235:5:236:14 | S1 | +| main.rs:440:17:440:34 | thing.convert_to() | | main.rs:235:5:236:14 | S1 | | main.rs:441:13:441:13 | j | | main.rs:235:5:236:14 | S1 | | main.rs:441:17:441:33 | convert_to(...) | | main.rs:235:5:236:14 | S1 | | main.rs:441:28:441:32 | thing | | main.rs:224:5:227:5 | MyThing | @@ -3319,14 +3331,22 @@ inferType | main.rs:1706:13:1709:13 | Vec2 {...} | | main.rs:1586:5:1591:5 | Vec2 | | main.rs:1707:20:1707:23 | self | | main.rs:1586:5:1591:5 | Vec2 | | main.rs:1707:20:1707:25 | self.x | | {EXTERNAL LOCATION} | i64 | +| main.rs:1707:20:1707:33 | ... \| ... | | {EXTERNAL LOCATION} | NonZero | | main.rs:1707:20:1707:33 | ... \| ... | | {EXTERNAL LOCATION} | i64 | +| main.rs:1707:20:1707:33 | ... \| ... | T | {EXTERNAL LOCATION} | i64 | | main.rs:1707:29:1707:31 | rhs | | main.rs:1586:5:1591:5 | Vec2 | +| main.rs:1707:29:1707:33 | rhs.x | | {EXTERNAL LOCATION} | NonZero | | main.rs:1707:29:1707:33 | rhs.x | | {EXTERNAL LOCATION} | i64 | +| main.rs:1707:29:1707:33 | rhs.x | T | {EXTERNAL LOCATION} | i64 | | main.rs:1708:20:1708:23 | self | | main.rs:1586:5:1591:5 | Vec2 | | main.rs:1708:20:1708:25 | self.y | | {EXTERNAL LOCATION} | i64 | +| main.rs:1708:20:1708:33 | ... \| ... | | {EXTERNAL LOCATION} | NonZero | | main.rs:1708:20:1708:33 | ... \| ... | | {EXTERNAL LOCATION} | i64 | +| main.rs:1708:20:1708:33 | ... \| ... | T | {EXTERNAL LOCATION} | i64 | | main.rs:1708:29:1708:31 | rhs | | main.rs:1586:5:1591:5 | Vec2 | +| main.rs:1708:29:1708:33 | rhs.y | | {EXTERNAL LOCATION} | NonZero | | main.rs:1708:29:1708:33 | rhs.y | | {EXTERNAL LOCATION} | i64 | +| main.rs:1708:29:1708:33 | rhs.y | T | {EXTERNAL LOCATION} | i64 | | main.rs:1714:25:1714:33 | SelfParam | | file://:0:0:0:0 | & | | main.rs:1714:25:1714:33 | SelfParam | &T | main.rs:1586:5:1591:5 | Vec2 | | main.rs:1714:36:1714:38 | rhs | | main.rs:1586:5:1591:5 | Vec2 | @@ -3664,9 +3684,13 @@ inferType | main.rs:1857:26:1857:30 | 33i64 | | {EXTERNAL LOCATION} | i64 | | main.rs:1857:26:1857:38 | ... & ... | | {EXTERNAL LOCATION} | i64 | | main.rs:1857:34:1857:38 | 34i64 | | {EXTERNAL LOCATION} | i64 | +| main.rs:1858:13:1858:21 | i64_bitor | | {EXTERNAL LOCATION} | NonZero | | main.rs:1858:13:1858:21 | i64_bitor | | {EXTERNAL LOCATION} | i64 | +| main.rs:1858:13:1858:21 | i64_bitor | T | {EXTERNAL LOCATION} | i64 | | main.rs:1858:25:1858:29 | 35i64 | | {EXTERNAL LOCATION} | i64 | +| main.rs:1858:25:1858:37 | ... \| ... | | {EXTERNAL LOCATION} | NonZero | | main.rs:1858:25:1858:37 | ... \| ... | | {EXTERNAL LOCATION} | i64 | +| main.rs:1858:25:1858:37 | ... \| ... | T | {EXTERNAL LOCATION} | i64 | | main.rs:1858:33:1858:37 | 36i64 | | {EXTERNAL LOCATION} | i64 | | main.rs:1859:13:1859:22 | i64_bitxor | | {EXTERNAL LOCATION} | i64 | | main.rs:1859:26:1859:30 | 37i64 | | {EXTERNAL LOCATION} | i64 | diff --git a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll index 5bf4032a3b81..2cc557ff3071 100644 --- a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll +++ b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll @@ -919,6 +919,15 @@ module Make1 Input1> { signature module SatisfiesConstraintInputSig { /** Holds if it is relevant to know if `term` satisfies `constraint`. */ predicate relevantConstraint(HasTypeTree term, Type constraint); + + /** + * Holds if constraints that are satisfied through conditions that are + * universally quantified type parameters should be used. Such type + * parameters might have type parameter constraints, and these are _not_ + * checked. Hence using these represent a trade-off between too many + * constraints and too few constraints being satisfied. + */ + default predicate useUniversalConditions() { any() } } module SatisfiesConstraint< @@ -961,6 +970,7 @@ module Make1 Input1> { TypeMention constraintMention ) { exists(Type type | hasTypeConstraint(tt, type, constraint) | + useUniversalConditions() and not exists(countConstraintImplementations(type, constraint)) and conditionSatisfiesConstraintTypeAt(abs, condition, constraintMention, _, _) and resolveTypeMentionRoot(condition) = abs.getATypeParameter() and From 12dcd751d39ccabc176295d4ed0f298daa18a069 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Wed, 10 Sep 2025 11:15:16 +0200 Subject: [PATCH 4/7] Rust: Accept test changes --- .../dataflow/local/DataFlowStep.expected | 6 ++ .../PathResolutionConsistency.expected | 12 ++++ .../library-tests/dataflow/sources/test.rs | 58 +++++++++---------- .../dataflow/sources/test_futures_io.rs | 18 +++--- .../test/library-tests/path-resolution/my.rs | 2 +- .../PathResolutionConsistency.expected | 2 + .../security/CWE-089/SqlInjection.expected | 26 +++++++++ .../test/query-tests/security/CWE-089/sqlx.rs | 2 +- 8 files changed, 86 insertions(+), 40 deletions(-) diff --git a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected index 86bd270ba935..f4fb726ad37c 100644 --- a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected +++ b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected @@ -1005,12 +1005,15 @@ readStep | main.rs:458:5:458:11 | mut_arr | file://:0:0:0:0 | element | main.rs:458:5:458:14 | mut_arr[1] | | main.rs:459:13:459:19 | mut_arr | file://:0:0:0:0 | element | main.rs:459:13:459:22 | mut_arr[1] | | main.rs:461:10:461:16 | mut_arr | file://:0:0:0:0 | element | main.rs:461:10:461:19 | mut_arr[0] | +| main.rs:467:24:467:33 | [post] receiver for source(...) | file://:0:0:0:0 | &ref | main.rs:467:24:467:33 | [post] source(...) | | main.rs:468:9:468:20 | TuplePat | file://:0:0:0:0 | tuple.0 | main.rs:468:10:468:13 | cond | | main.rs:468:9:468:20 | TuplePat | file://:0:0:0:0 | tuple.1 | main.rs:468:16:468:19 | name | | main.rs:468:25:468:29 | names | file://:0:0:0:0 | element | main.rs:468:9:468:20 | TuplePat | | main.rs:470:41:470:67 | [post] \|...\| ... | main.rs:467:9:467:20 | captured default_name | main.rs:470:41:470:67 | [post] default_name | +| main.rs:470:44:470:55 | [post] receiver for default_name | file://:0:0:0:0 | &ref | main.rs:470:44:470:55 | [post] default_name | | main.rs:470:44:470:55 | this | main.rs:467:9:467:20 | captured default_name | main.rs:470:44:470:55 | default_name | | main.rs:471:18:471:18 | [post] receiver for n | file://:0:0:0:0 | &ref | main.rs:471:18:471:18 | [post] n | +| main.rs:494:13:494:13 | [post] receiver for a | file://:0:0:0:0 | &ref | main.rs:494:13:494:13 | [post] a | | main.rs:495:13:495:13 | [post] receiver for b | file://:0:0:0:0 | &ref | main.rs:495:13:495:13 | [post] b | | main.rs:496:18:496:18 | [post] receiver for b | file://:0:0:0:0 | &ref | main.rs:496:18:496:18 | [post] b | | main.rs:507:10:507:11 | vs | file://:0:0:0:0 | element | main.rs:507:10:507:14 | vs[0] | @@ -1110,8 +1113,11 @@ storeStep | main.rs:455:27:455:27 | 2 | file://:0:0:0:0 | element | main.rs:455:23:455:31 | [...] | | main.rs:455:30:455:30 | 3 | file://:0:0:0:0 | element | main.rs:455:23:455:31 | [...] | | main.rs:458:18:458:27 | source(...) | file://:0:0:0:0 | element | main.rs:458:5:458:11 | [post] mut_arr | +| main.rs:467:24:467:33 | source(...) | file://:0:0:0:0 | &ref | main.rs:467:24:467:33 | receiver for source(...) | | main.rs:470:41:470:67 | default_name | main.rs:467:9:467:20 | captured default_name | main.rs:470:41:470:67 | \|...\| ... | +| main.rs:470:44:470:55 | default_name | file://:0:0:0:0 | &ref | main.rs:470:44:470:55 | receiver for default_name | | main.rs:471:18:471:18 | n | file://:0:0:0:0 | &ref | main.rs:471:18:471:18 | receiver for n | +| main.rs:494:13:494:13 | a | file://:0:0:0:0 | &ref | main.rs:494:13:494:13 | receiver for a | | main.rs:495:13:495:13 | b | file://:0:0:0:0 | &ref | main.rs:495:13:495:13 | receiver for b | | main.rs:496:18:496:18 | b | file://:0:0:0:0 | &ref | main.rs:496:18:496:18 | receiver for b | | main.rs:505:15:505:24 | source(...) | file://:0:0:0:0 | element | main.rs:505:14:505:34 | [...] | diff --git a/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected index 8448e7cd99a5..5ba71c14933d 100644 --- a/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected +++ b/rust/ql/test/library-tests/dataflow/sources/CONSISTENCY/PathResolutionConsistency.expected @@ -73,6 +73,18 @@ multipleCallTargets | test.rs:977:14:977:29 | ...::_print(...) | | test.rs:979:27:979:36 | ...::_print(...) | | test.rs:980:28:980:41 | ...::_print(...) | +| test_futures_io.rs:45:27:45:84 | ...::read(...) | +| test_futures_io.rs:49:27:49:51 | reader.read(...) | +| test_futures_io.rs:83:22:83:39 | reader2.fill_buf() | +| test_futures_io.rs:103:27:103:85 | ...::read(...) | +| test_futures_io.rs:107:27:107:52 | reader2.read(...) | +| test_futures_io.rs:125:22:125:39 | reader2.fill_buf() | +| test_futures_io.rs:132:27:132:62 | reader2.read_until(...) | +| test_futures_io.rs:139:27:139:54 | reader2.read_line(...) | +| test_futures_io.rs:146:27:146:58 | reader2.read_to_end(...) | +| test_futures_io.rs:152:32:152:46 | reader2.lines() | +| test_futures_io.rs:153:14:153:32 | lines_stream.next() | +| test_futures_io.rs:154:32:154:50 | lines_stream.next() | | web_frameworks.rs:13:14:13:22 | a.as_str() | | web_frameworks.rs:13:14:13:23 | a.as_str() | | web_frameworks.rs:14:14:14:24 | a.as_bytes() | diff --git a/rust/ql/test/library-tests/dataflow/sources/test.rs b/rust/ql/test/library-tests/dataflow/sources/test.rs index 64d74d9527d4..6e30159ea1a3 100644 --- a/rust/ql/test/library-tests/dataflow/sources/test.rs +++ b/rust/ql/test/library-tests/dataflow/sources/test.rs @@ -309,28 +309,28 @@ async fn test_tokio_stdin() -> Result<(), Box> { let mut stdin = tokio::io::stdin(); // $ Alert[rust/summary/taint-sources] let mut buffer = [0u8; 100]; let _bytes = stdin.read(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow -- we cannot resolve the `read` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow } { let mut stdin = tokio::io::stdin(); // $ Alert[rust/summary/taint-sources] let mut buffer = Vec::::new(); let _bytes = stdin.read_to_end(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_to_end` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow } { let mut stdin = tokio::io::stdin(); // $ Alert[rust/summary/taint-sources] let mut buffer = String::new(); let _bytes = stdin.read_to_string(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_to_string` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow } { let mut stdin = tokio::io::stdin(); // $ Alert[rust/summary/taint-sources] let mut buffer = [0; 100]; stdin.read_exact(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_exact` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow } { @@ -339,17 +339,17 @@ async fn test_tokio_stdin() -> Result<(), Box> { let v2 = stdin.read_i16().await?; let v3 = stdin.read_f32().await?; let v4 = stdin.read_i64_le().await?; - sink(v1); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_u8` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(v2); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_i16` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(v3); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_f32` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(v4); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_i64_le` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(v1); // $ hasTaintFlow + sink(v2); // $ hasTaintFlow + sink(v3); // $ hasTaintFlow + sink(v4); // $ hasTaintFlow } { let mut stdin = tokio::io::stdin(); // $ Alert[rust/summary/taint-sources] let mut buffer = bytes::BytesMut::new(); stdin.read_buf(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_buf` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow } // --- async reading from stdin (BufReader) --- @@ -357,7 +357,7 @@ async fn test_tokio_stdin() -> Result<(), Box> { { let mut reader = tokio::io::BufReader::new(tokio::io::stdin()); // $ Alert[rust/summary/taint-sources] let data = reader.fill_buf().await?; - sink(&data); // $ MISSING: hasTaintFlow -- we cannot resolve the `fill_buf` call above, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` + sink(&data); // $ hasTaintFlow } { @@ -370,31 +370,31 @@ async fn test_tokio_stdin() -> Result<(), Box> { let mut buffer = String::new(); let mut reader = tokio::io::BufReader::new(tokio::io::stdin()); // $ Alert[rust/summary/taint-sources] reader.read_line(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_line` call above, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` + sink(&buffer); // $ hasTaintFlow } { let mut buffer = Vec::::new(); let mut reader = tokio::io::BufReader::new(tokio::io::stdin()); // $ Alert[rust/summary/taint-sources] reader.read_until(b',', &mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_until` call above, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` - sink(buffer[0]); // $ MISSING: hasTaintFlow -- we cannot resolve the `read_until` call above, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` + sink(&buffer); // $ hasTaintFlow + sink(buffer[0]); // $ hasTaintFlow } { let mut reader_split = tokio::io::BufReader::new(tokio::io::stdin()).split(b','); // $ Alert[rust/summary/taint-sources] - sink(reader_split.next_segment().await?.unwrap()); // $ MISSING: hasTaintFlow -- we cannot resolve the `split` call above, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` + sink(reader_split.next_segment().await?.unwrap()); // $ hasTaintFlow while let Some(chunk) = reader_split.next_segment().await? { - sink(chunk); // $ MISSING: hasTaintFlow + sink(chunk); // $ hasTaintFlow } } { let reader = tokio::io::BufReader::new(tokio::io::stdin()); // $ Alert[rust/summary/taint-sources] let mut lines = reader.lines(); - sink(lines.next_line().await?.unwrap()); // $ MISSING: hasTaintFlow -- we cannot resolve the `lines` call above, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` + sink(lines.next_line().await?.unwrap()); // $ hasTaintFlow while let Some(line) = lines.next_line().await? { - sink(line); // $ MISSING: hasTaintFlow + sink(line); // $ hasTaintFlow } } @@ -583,25 +583,25 @@ async fn test_tokio_file() -> std::io::Result<()> { { let mut buffer = [0u8; 100]; let _bytes = file.read(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow="file.txt" } { let mut buffer = Vec::::new(); let _bytes = file.read_to_end(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_to_end` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow="file.txt" } { let mut buffer = String::new(); let _bytes = file.read_to_string(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_to_string` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow="file.txt" } { let mut buffer = [0; 100]; file.read_exact(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_exact` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow="file.txt" } { @@ -609,16 +609,16 @@ async fn test_tokio_file() -> std::io::Result<()> { let v2 = file.read_i16().await?; let v3 = file.read_f32().await?; let v4 = file.read_i64_le().await?; - sink(v1); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_u8` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(v2); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_i16` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(v3); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_f32` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(v4); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_i64_le` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(v1); // $ hasTaintFlow="file.txt" + sink(v2); // $ hasTaintFlow="file.txt" + sink(v3); // $ hasTaintFlow="file.txt" + sink(v4); // $ hasTaintFlow="file.txt" } { let mut buffer = bytes::BytesMut::new(); file.read_buf(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow="file.txt" -- we cannot resolve the `read_buf` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer); // $ hasTaintFlow="file.txt" } // --- OpenOptions --- @@ -627,7 +627,7 @@ async fn test_tokio_file() -> std::io::Result<()> { let mut f1 = tokio::fs::OpenOptions::new().open("f1.txt").await?; // $ Alert[rust/summary/taint-sources] let mut buffer = [0u8; 1024]; let _bytes = f1.read(&mut buffer).await?; - sink(&buffer); // $ MISSING: hasTaintFlow="f1.txt" + sink(&buffer); // $ hasTaintFlow="f1.txt" } // --- misc operations --- @@ -775,8 +775,8 @@ async fn test_tokio_tcpstream(case: i64) -> std::io::Result<()> { sink(buffer1[0]); // $ hasTaintFlow=address println!("buffer2 = {:?}", buffer2); - sink(&buffer2); // $ MISSING: hasTaintFlow=address -- we cannot resolve the `read` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(buffer2[0]); // $ MISSING: hasTaintFlow=address -- we cannot resolve the `read` call above, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` + sink(&buffer2); // $ hasTaintFlow=address + sink(buffer2[0]); // $ hasTaintFlow=address let buffer_string = String::from_utf8_lossy(&buffer2[..n2]); println!("string = {}", buffer_string); diff --git a/rust/ql/test/library-tests/dataflow/sources/test_futures_io.rs b/rust/ql/test/library-tests/dataflow/sources/test_futures_io.rs index 1acc5ce21b0f..b93f03535258 100644 --- a/rust/ql/test/library-tests/dataflow/sources/test_futures_io.rs +++ b/rust/ql/test/library-tests/dataflow/sources/test_futures_io.rs @@ -43,12 +43,12 @@ async fn test_futures_rustls_futures_io() -> io::Result<()> { // using the `AsyncReadExt::read` extension method (higher-level) let mut buffer1 = [0u8; 64]; let bytes_read1 = futures::io::AsyncReadExt::read(&mut reader, &mut buffer1).await?; // we cannot resolve the `read` call, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(&buffer1[..bytes_read1]); // $ MISSING: hasTaintFlow=url + sink(&buffer1[..bytes_read1]); // $ hasTaintFlow=url let mut buffer2 = [0u8; 64]; let bytes_read2 = reader.read(&mut buffer2).await?; // we cannot resolve the `read` call, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(&buffer2[..bytes_read2]); // $ MISSING: hasTaintFlow=url + sink(&buffer2[..bytes_read2]); // $ hasTaintFlow=url } let mut reader2 = futures::io::BufReader::new(reader); @@ -81,7 +81,7 @@ async fn test_futures_rustls_futures_io() -> io::Result<()> { { // using the `AsyncBufReadExt::fill_buf` extension method (higher-level) let buffer = reader2.fill_buf().await?; // we cannot resolve the `fill_buf` call, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` - sink(buffer); // $ MISSING: hasTaintFlow=url + sink(buffer); // $ hasTaintFlow=url } { @@ -101,11 +101,11 @@ async fn test_futures_rustls_futures_io() -> io::Result<()> { // using the `AsyncReadExt::read` extension method (higher-level) let mut buffer1 = [0u8; 64]; let bytes_read1 = futures::io::AsyncReadExt::read(&mut reader2, &mut buffer1).await?; // we cannot resolve the `read` call, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(&buffer1[..bytes_read1]); // $ MISSING: hasTaintFlow=url + sink(&buffer1[..bytes_read1]); // $ hasTaintFlow=url let mut buffer2 = [0u8; 64]; let bytes_read2 = reader2.read(&mut buffer2).await?; // we cannot resolve the `read` call, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(&buffer2[..bytes_read2]); // $ MISSING: hasTaintFlow=url + sink(&buffer2[..bytes_read2]); // $ hasTaintFlow=url } { @@ -123,28 +123,28 @@ async fn test_futures_rustls_futures_io() -> io::Result<()> { { // using the `AsyncBufReadExt::fill_buf` extension method (higher-level) let buffer = reader2.fill_buf().await?; // we cannot resolve the `fill_buf` call, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` - sink(buffer); // $ MISSING: hasTaintFlow=url + sink(buffer); // $ hasTaintFlow=url } { // using the `AsyncBufReadExt::read_until` extension method let mut line = Vec::new(); let _bytes_read = reader2.read_until(b'\n', &mut line).await?; // we cannot resolve the `read_until` call, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` - sink(&line); // $ MISSING: hasTaintFlow=url + sink(&line); // $ hasTaintFlow=url } { // using the `AsyncBufReadExt::read_line` extension method let mut line = String::new(); let _bytes_read = reader2.read_line(&mut line).await?; // we cannot resolve the `read_line` call, which comes from `impl AsyncBufReadExt for R {}` in `async_buf_read_ext.rs` - sink(&line); // $ MISSING: hasTaintFlow=url + sink(&line); // $ hasTaintFlow=url } { // using the `AsyncBufReadExt::read_to_end` extension method let mut buffer = Vec::with_capacity(1024); let _bytes_read = reader2.read_to_end(&mut buffer).await?; // we cannot resolve the `read` call, which comes from `impl AsyncReadExt for R {}` in `async_read_ext.rs` - sink(&buffer); // $ MISSING: hasTaintFlow=url + sink(&buffer); // $ hasTaintFlow=url } { diff --git a/rust/ql/test/library-tests/path-resolution/my.rs b/rust/ql/test/library-tests/path-resolution/my.rs index f2488df4959c..af2d35ed2753 100644 --- a/rust/ql/test/library-tests/path-resolution/my.rs +++ b/rust/ql/test/library-tests/path-resolution/my.rs @@ -30,7 +30,7 @@ fn int_div( ) -> Result // $ item=my::Result $ item=i32 { if y == 0 { - return Err("Div by zero".to_string()); // $ item=Err + return Err("Div by zero".to_string()); // $ item=Err item=to_string } Ok(x / y) // $ item=Ok } diff --git a/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected index b5d4fea346d2..d195a145aafc 100644 --- a/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected +++ b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected @@ -12,12 +12,14 @@ multipleCallTargets | sqlx.rs:67:26:67:48 | unsafe_query_1.as_str() | | sqlx.rs:69:30:69:52 | unsafe_query_2.as_str() | | sqlx.rs:70:30:70:52 | unsafe_query_3.as_str() | +| sqlx.rs:71:30:71:52 | unsafe_query_4.as_str() | | sqlx.rs:75:25:75:45 | safe_query_1.as_str() | | sqlx.rs:76:25:76:45 | safe_query_2.as_str() | | sqlx.rs:77:25:77:45 | safe_query_3.as_str() | | sqlx.rs:78:25:78:47 | unsafe_query_1.as_str() | | sqlx.rs:80:29:80:51 | unsafe_query_2.as_str() | | sqlx.rs:81:29:81:51 | unsafe_query_3.as_str() | +| sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() | | sqlx.rs:84:25:84:49 | prepared_query_1.as_str() | | sqlx.rs:85:25:85:49 | prepared_query_1.as_str() | | sqlx.rs:87:29:87:53 | prepared_query_1.as_str() | diff --git a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected index 2168ca56a444..329138efa245 100644 --- a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected +++ b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected @@ -2,6 +2,7 @@ | sqlx.rs:77:13:77:23 | ...::query | sqlx.rs:48:25:48:46 | ...::get | sqlx.rs:77:13:77:23 | ...::query | This query depends on a $@. | sqlx.rs:48:25:48:46 | ...::get | user-provided value | | sqlx.rs:78:13:78:23 | ...::query | sqlx.rs:47:22:47:35 | ...::args | sqlx.rs:78:13:78:23 | ...::query | This query depends on a $@. | sqlx.rs:47:22:47:35 | ...::args | user-provided value | | sqlx.rs:80:17:80:27 | ...::query | sqlx.rs:48:25:48:46 | ...::get | sqlx.rs:80:17:80:27 | ...::query | This query depends on a $@. | sqlx.rs:48:25:48:46 | ...::get | user-provided value | +| sqlx.rs:82:17:82:27 | ...::query | sqlx.rs:48:25:48:46 | ...::get | sqlx.rs:82:17:82:27 | ...::query | This query depends on a $@. | sqlx.rs:48:25:48:46 | ...::get | user-provided value | edges | sqlx.rs:47:9:47:18 | arg_string | sqlx.rs:53:27:53:36 | arg_string | provenance | | | sqlx.rs:47:22:47:35 | ...::args | sqlx.rs:47:22:47:37 | ...::args(...) [element] | provenance | Src:MaD:3 | @@ -11,6 +12,7 @@ edges | sqlx.rs:48:9:48:21 | remote_string | sqlx.rs:49:25:49:52 | remote_string.parse() [Ok] | provenance | MaD:10 | | sqlx.rs:48:9:48:21 | remote_string | sqlx.rs:49:25:49:52 | remote_string.parse() [Ok] | provenance | MaD:10 | | sqlx.rs:48:9:48:21 | remote_string | sqlx.rs:54:27:54:39 | remote_string | provenance | | +| sqlx.rs:48:9:48:21 | remote_string | sqlx.rs:59:17:59:72 | MacroExpr | provenance | | | sqlx.rs:48:25:48:46 | ...::get | sqlx.rs:48:25:48:69 | ...::get(...) [Ok] | provenance | Src:MaD:2 | | sqlx.rs:48:25:48:69 | ...::get(...) [Ok] | sqlx.rs:48:25:48:78 | ... .unwrap() | provenance | MaD:7 | | sqlx.rs:48:25:48:78 | ... .unwrap() | sqlx.rs:48:25:48:85 | ... .text() [Ok] | provenance | MaD:11 | @@ -38,6 +40,15 @@ edges | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:80:29:80:51 | unsafe_query_2.as_str() [&ref] | provenance | MaD:9 | | sqlx.rs:54:26:54:39 | &remote_string [&ref] | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | provenance | | | sqlx.rs:54:27:54:39 | remote_string | sqlx.rs:54:26:54:39 | &remote_string [&ref] | provenance | | +| sqlx.rs:56:9:56:22 | unsafe_query_4 | sqlx.rs:82:29:82:42 | unsafe_query_4 | provenance | | +| sqlx.rs:56:9:56:22 | unsafe_query_4 | sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() | provenance | MaD:9 | +| sqlx.rs:56:9:56:22 | unsafe_query_4 | sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() | provenance | MaD:5 | +| sqlx.rs:56:9:56:22 | unsafe_query_4 | sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() | provenance | MaD:9 | +| sqlx.rs:59:9:59:15 | res | sqlx.rs:59:17:59:72 | { ... } | provenance | | +| sqlx.rs:59:17:59:72 | ...::format(...) | sqlx.rs:59:9:59:15 | res | provenance | | +| sqlx.rs:59:17:59:72 | ...::must_use(...) | sqlx.rs:56:9:56:22 | unsafe_query_4 | provenance | | +| sqlx.rs:59:17:59:72 | MacroExpr | sqlx.rs:59:17:59:72 | ...::format(...) | provenance | MaD:12 | +| sqlx.rs:59:17:59:72 | { ... } | sqlx.rs:59:17:59:72 | ...::must_use(...) | provenance | MaD:13 | | sqlx.rs:77:25:77:36 | safe_query_3 | sqlx.rs:77:25:77:45 | safe_query_3.as_str() [&ref] | provenance | MaD:9 | | sqlx.rs:77:25:77:36 | safe_query_3 | sqlx.rs:77:25:77:45 | safe_query_3.as_str() [&ref] | provenance | MaD:5 | | sqlx.rs:77:25:77:36 | safe_query_3 | sqlx.rs:77:25:77:45 | safe_query_3.as_str() [&ref] | provenance | MaD:9 | @@ -45,6 +56,11 @@ edges | sqlx.rs:77:25:77:45 | safe_query_3.as_str() [&ref] | sqlx.rs:77:13:77:23 | ...::query | provenance | MaD:1 Sink:MaD:1 | | sqlx.rs:78:25:78:47 | unsafe_query_1.as_str() [&ref] | sqlx.rs:78:13:78:23 | ...::query | provenance | MaD:1 Sink:MaD:1 | | sqlx.rs:80:29:80:51 | unsafe_query_2.as_str() [&ref] | sqlx.rs:80:17:80:27 | ...::query | provenance | MaD:1 Sink:MaD:1 | +| sqlx.rs:82:29:82:42 | unsafe_query_4 | sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() [&ref] | provenance | MaD:9 | +| sqlx.rs:82:29:82:42 | unsafe_query_4 | sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() [&ref] | provenance | MaD:5 | +| sqlx.rs:82:29:82:42 | unsafe_query_4 | sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() [&ref] | provenance | MaD:9 | +| sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() | sqlx.rs:82:17:82:27 | ...::query | provenance | MaD:1 Sink:MaD:1 | +| sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() [&ref] | sqlx.rs:82:17:82:27 | ...::query | provenance | MaD:1 Sink:MaD:1 | models | 1 | Sink: sqlx_core::query::query; Argument[0]; sql-injection | | 2 | Source: reqwest::blocking::get; ReturnValue.Field[core::result::Result::Ok(0)]; remote | @@ -86,6 +102,12 @@ nodes | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | semmle.label | unsafe_query_2 [&ref] | | sqlx.rs:54:26:54:39 | &remote_string [&ref] | semmle.label | &remote_string [&ref] | | sqlx.rs:54:27:54:39 | remote_string | semmle.label | remote_string | +| sqlx.rs:56:9:56:22 | unsafe_query_4 | semmle.label | unsafe_query_4 | +| sqlx.rs:59:9:59:15 | res | semmle.label | res | +| sqlx.rs:59:17:59:72 | ...::format(...) | semmle.label | ...::format(...) | +| sqlx.rs:59:17:59:72 | ...::must_use(...) | semmle.label | ...::must_use(...) | +| sqlx.rs:59:17:59:72 | MacroExpr | semmle.label | MacroExpr | +| sqlx.rs:59:17:59:72 | { ... } | semmle.label | { ... } | | sqlx.rs:77:13:77:23 | ...::query | semmle.label | ...::query | | sqlx.rs:77:25:77:36 | safe_query_3 | semmle.label | safe_query_3 | | sqlx.rs:77:25:77:45 | safe_query_3.as_str() | semmle.label | safe_query_3.as_str() | @@ -94,4 +116,8 @@ nodes | sqlx.rs:78:25:78:47 | unsafe_query_1.as_str() [&ref] | semmle.label | unsafe_query_1.as_str() [&ref] | | sqlx.rs:80:17:80:27 | ...::query | semmle.label | ...::query | | sqlx.rs:80:29:80:51 | unsafe_query_2.as_str() [&ref] | semmle.label | unsafe_query_2.as_str() [&ref] | +| sqlx.rs:82:17:82:27 | ...::query | semmle.label | ...::query | +| sqlx.rs:82:29:82:42 | unsafe_query_4 | semmle.label | unsafe_query_4 | +| sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() | semmle.label | unsafe_query_4.as_str() | +| sqlx.rs:82:29:82:51 | unsafe_query_4.as_str() [&ref] | semmle.label | unsafe_query_4.as_str() [&ref] | subpaths diff --git a/rust/ql/test/query-tests/security/CWE-089/sqlx.rs b/rust/ql/test/query-tests/security/CWE-089/sqlx.rs index 8e1afa9f0e8f..7f244c4b4cbb 100644 --- a/rust/ql/test/query-tests/security/CWE-089/sqlx.rs +++ b/rust/ql/test/query-tests/security/CWE-089/sqlx.rs @@ -79,7 +79,7 @@ async fn test_sqlx_mysql(url: &str, enable_remote: bool) -> Result<(), sqlx::Err if enable_remote { let _ = sqlx::query(unsafe_query_2.as_str()).execute(&pool).await?; // $ sql-sink Alert[rust/sql-injection]=remote1 let _ = sqlx::query(unsafe_query_3.as_str()).execute(&pool).await?; // $ sql-sink MISSING: Alert[rust/sql-injection]=remote1 - let _ = sqlx::query(unsafe_query_4.as_str()).execute(&pool).await?; // $ sql-sink MISSING: Alert[rust/sql-injection]=remote1 + let _ = sqlx::query(unsafe_query_4.as_str()).execute(&pool).await?; // $ sql-sink Alert[rust/sql-injection]=remote1 } let _ = sqlx::query(prepared_query_1.as_str()).bind(const_string).execute(&pool).await?; // $ sql-sink let _ = sqlx::query(prepared_query_1.as_str()).bind(arg_string).execute(&pool).await?; // $ sql-sink From e2e6fd068332bea6b125aef0bf4e9d2852849fe7 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Fri, 12 Sep 2025 15:18:44 +0200 Subject: [PATCH 5/7] Rust: Address feedback from PR review --- .../codeql/rust/internal/PathResolution.qll | 13 ++ .../codeql/rust/internal/TypeInference.qll | 111 +++++++----------- .../type-inference/blanket_impl.rs | 4 +- 3 files changed, 58 insertions(+), 70 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 3ec7de5fad67..ce1232ed1cc0 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -648,6 +648,19 @@ final class ImplItemNode extends ImplOrTraitItemNode instanceof Impl { override Visibility getVisibility() { result = Impl.super.getVisibility() } + TypeParamItemNode getBlanketImplementationTypeParam() { + result = this.resolveSelfTy() and + result = super.getGenericParamList().getAGenericParam() and + // This impl block is not superseded by the expansion of an attribute macro. + not exists(super.getAttributeMacroExpansion()) + } + + /** + * Holds if this impl block is a blanket implementation. That is, the + * implementation targets a generic parameter of the impl block. + */ + predicate isBlanketImplementation() { exists(this.getBlanketImplementationTypeParam()) } + override predicate hasCanonicalPath(Crate c) { this.resolveSelfTy().hasCanonicalPathPrefix(c) } /** diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index a0bbdeb759db..85d3937459e4 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -2158,29 +2158,26 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) { } private module BlanketImplementation { - /** - * Gets the type parameter for which `impl` is a blanket implementation, if - * any. - */ - private TypeParamItemNode getBlanketImplementationTypeParam(Impl impl) { - result = impl.(ImplItemNode).resolveSelfTy() and - result = impl.getGenericParamList().getAGenericParam() and - // This impl block is not superseded by the expansion of an attribute macro. - not exists(impl.getAttributeMacroExpansion()) + private ImplItemNode getPotentialDuplicated( + string fileName, string traitName, int arity, string tpName + ) { + tpName = result.getBlanketImplementationTypeParam().getName() and + fileName = result.getLocation().getFile().getBaseName() and + traitName = result.resolveTraitTy().getName() and + arity = result.resolveTraitTy().(Trait).getNumberOfGenericParams() } - 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() + private 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() + ) } /** - * Holds if `impl1` and `impl2` are duplicates and `impl2` is strictly more - * "canonical" than `impl1`. + * Holds if `impl` is a canonical blanket implementation. * * Libraries can often occur several times in the database for different * library versions. This causes the same blanket implementations to exist @@ -2189,42 +2186,20 @@ private module BlanketImplementation { * We detect these duplicates based on some simple heuristics (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) + * version in the path) as the "canonical" implementation. */ - 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 isCanonicalImpl(Impl impl) { - not duplicatedImpl(impl, _) and isBlanketImplementation(impl) - } - - 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 - isCanonicalImpl(impl) and result = impl + private predicate isCanonicalImpl(Impl impl) { + not duplicatedImpl(impl, _) and impl.(ImplItemNode).isBlanketImplementation() } - 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`. + * Holds if `impl` is a blanket implementation for a type parameter with trait + * bound `traitBound`. */ - private predicate blanketImplementationTraitBound(Impl impl, Trait t) { - t = + private predicate blanketImplementationTraitBound(ImplItemNode impl, Trait traitBound) { + traitBound = min(Trait trait, int i | - trait = getBlanketImplementationTypeParam(impl).resolveBound(i) and + trait = impl.getBlanketImplementationTypeParam().resolveBound(i) and // Exclude traits that are known to not narrow things down very much. not trait.getName().getText() = [ @@ -2243,10 +2218,10 @@ private module BlanketImplementation { * `arity`. */ private predicate blanketImplementationMethod( - ImplItemNode impl, Trait trait, string name, int arity, Function f + ImplItemNode impl, Trait traitBound, string name, int arity, Function f ) { - isCanonicalBlanketImplementation(impl) and - blanketImplementationTraitBound(impl, trait) and + isCanonicalImpl(impl) and + blanketImplementationTraitBound(impl, traitBound) and f.getParamList().hasSelfParam() and arity = f.getParamList().getNumberOfParams() and ( @@ -2258,48 +2233,48 @@ private module BlanketImplementation { f = impl.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 ancestor trait) then ignore + // type parameter (because they share a common super trait) then ignore // it. - not getBlanketImplementationTypeParam(impl).resolveABound().(TraitItemNode).getASuccessor(name) = + not impl.getBlanketImplementationTypeParam().resolveABound().(TraitItemNode).getASuccessor(name) = f } - predicate methodCallMatchesBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) { + pragma[nomagic] + predicate methodCallMatchesBlanketImpl( + MethodCall mc, Type t, ImplItemNode impl, Trait traitBound, Trait traitImpl, 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) - ) + blanketImplementationMethod(impl, traitBound, name, arity, f) + ) and + traitImpl = impl.resolveTraitTy() } private predicate relevantTraitVisible(Element mc, Trait trait) { - exists(ImplItemNode impl | - methodCallMatchesBlanketImpl(mc, _, impl, _, _) and - trait = impl.resolveTraitTy() - ) + methodCallMatchesBlanketImpl(mc, _, _, _, trait, _) } module SatisfiesConstraintInput implements SatisfiesConstraintInputSig { pragma[nomagic] predicate relevantConstraint(MethodCall mc, Type constraint) { - exists(Trait trait, Trait trait2, ImplItemNode impl | - methodCallMatchesBlanketImpl(mc, _, impl, trait, _) and - TraitIsVisible::traitIsVisible(mc, pragma[only_bind_into](trait2)) and - trait2 = pragma[only_bind_into](impl.resolveTraitTy()) and - trait = constraint.(TraitType).getTrait() + exists(Trait traitBound, Trait traitImpl | + methodCallMatchesBlanketImpl(mc, _, _, traitBound, traitImpl, _) and + TraitIsVisible::traitIsVisible(mc, traitImpl) and + traitBound = constraint.(TraitType).getTrait() ) } predicate useUniversalConditions() { none() } } - predicate hasBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) { + predicate hasBlanketImpl(MethodCall mc, Type t, Impl impl, Trait traitBound, Function f) { SatisfiesConstraint::satisfiesConstraintType(mc, - TTrait(trait), _, _) and - methodCallMatchesBlanketImpl(mc, t, impl, trait, f) + TTrait(traitBound), _, _) and + methodCallMatchesBlanketImpl(mc, t, impl, traitBound, _, f) } pragma[nomagic] 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 a4e14eda0c02..aa642854268c 100644 --- a/rust/ql/test/library-tests/type-inference/blanket_impl.rs +++ b/rust/ql/test/library-tests/type-inference/blanket_impl.rs @@ -112,8 +112,8 @@ mod extension_trait_blanket_impl { 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 - // implementaton of `TryFlag` for `Flag`. + // Here `TryFlagExt::try_read_flag_twice` is a target 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 }; From 875c7da87c4a3d27e581154cfa3197ec815bf43d Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Mon, 15 Sep 2025 10:37:38 +0200 Subject: [PATCH 6/7] Rust: Improve comments in type inference --- rust/ql/lib/codeql/rust/internal/TypeInference.qll | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index 85d3937459e4..16c0b30332ef 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -1916,7 +1916,7 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int } /** - * Holds if `mc` has `rootType` as the root type of the reciever and the target + * Holds if `mc` has `rootType` as the root type of the receiver and the target * method is named `name` and has arity `arity` */ pragma[nomagic] @@ -2193,8 +2193,8 @@ private module BlanketImplementation { } /** - * Holds if `impl` is a blanket implementation for a type parameter with trait - * bound `traitBound`. + * Holds if `impl` is a blanket implementation for a type parameter and + * `traitBound` is the first non-trivial trait bound of that type parameter. */ private predicate blanketImplementationTraitBound(ImplItemNode impl, Trait traitBound) { traitBound = @@ -2214,7 +2214,7 @@ private module BlanketImplementation { /** * Holds if `impl` is a relevant blanket implementation that requires the - * trait `trait` and provides `f`, a method with name `name` and arity + * trait `traitBound` and provides `f`, a method with name `name` and arity * `arity`. */ private predicate blanketImplementationMethod( @@ -2233,8 +2233,8 @@ private module BlanketImplementation { f = impl.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 super trait) then ignore - // it. + // type parameter (because they implement the trait targeted by the impl + // block) then ignore it. not impl.getBlanketImplementationTypeParam().resolveABound().(TraitItemNode).getASuccessor(name) = f } From 35438294d10d9c9e8af6444d8d33dde5879e8647 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Mon, 15 Sep 2025 10:58:27 +0200 Subject: [PATCH 7/7] Rust: Remove condition that always holds --- rust/ql/lib/codeql/rust/internal/PathResolution.qll | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index ce1232ed1cc0..4b718fc43998 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -650,7 +650,6 @@ final class ImplItemNode extends ImplOrTraitItemNode instanceof Impl { TypeParamItemNode getBlanketImplementationTypeParam() { result = this.resolveSelfTy() and - result = super.getGenericParamList().getAGenericParam() and // This impl block is not superseded by the expansion of an attribute macro. not exists(super.getAttributeMacroExpansion()) }