From 301bd44a4caf52202b17647e7667f89a519d09f8 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 4 Jun 2025 10:05:56 +0200 Subject: [PATCH 1/2] Rust: Add type inference tests for index expressions --- .../test/library-tests/type-inference/main.rs | 53 +++++++++++++++ .../type-inference/type-inference.expected | 67 +++++++++++++++++-- .../type-inference/type-inference.ql | 5 +- 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 497bf331e511..1cec65224bd0 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -1753,6 +1753,58 @@ mod impl_trait { } } +mod indexers { + use std::ops::Index; + + #[derive(Debug)] + struct S; + + impl S { + fn foo(&self) -> Self { + S + } + } + + #[derive(Debug)] + struct MyVec { + data: Vec, + } + + impl MyVec { + fn new() -> Self { + MyVec { data: Vec::new() } + } + + fn push(&mut self, value: T) { + self.data.push(value); // $ fieldof=MyVec method=push + } + } + + impl Index for MyVec { + type Output = T; + + // MyVec::index + fn index(&self, index: usize) -> &Self::Output { + &self.data[index] // $ fieldof=MyVec + } + } + + fn analyze_slice(slice: &[S]) { + let x = slice[0].foo(); // $ MISSING: method=foo MISSING: type=x:S + } + + pub fn f() { + let mut vec = MyVec::new(); // $ type=vec:T.S + vec.push(S); // $ method=push + vec[0].foo(); // $ MISSING: method=foo + + let xs: [S; 1] = [S]; + let x = xs[0].foo(); // $ MISSING: method=foo MISSING: type=x:S + + analyze_slice(&xs); + } +} + fn main() { field_access::f(); method_impl::f(); @@ -1774,4 +1826,5 @@ fn main() { operators::f(); async_::f(); impl_trait::f(); + indexers::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 30672ca89e52..bac5761b6863 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -2525,9 +2525,66 @@ inferType | main.rs:1752:13:1752:13 | d | | main.rs:1700:5:1700:14 | S2 | | main.rs:1752:17:1752:34 | uses_my_trait2(...) | | main.rs:1700:5:1700:14 | S2 | | main.rs:1752:32:1752:33 | S1 | | main.rs:1699:5:1699:14 | S1 | -| main.rs:1758:5:1758:20 | ...::f(...) | | main.rs:67:5:67:21 | Foo | -| main.rs:1759:5:1759:60 | ...::g(...) | | main.rs:67:5:67:21 | Foo | -| main.rs:1759:20:1759:38 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | -| main.rs:1759:41:1759:59 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | -| main.rs:1775:5:1775:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | +| main.rs:1763:16:1763:20 | SelfParam | | file://:0:0:0:0 | & | +| main.rs:1763:16:1763:20 | SelfParam | &T | main.rs:1759:5:1760:13 | S | +| main.rs:1763:31:1765:9 | { ... } | | main.rs:1759:5:1760:13 | S | +| main.rs:1764:13:1764:13 | S | | main.rs:1759:5:1760:13 | S | +| main.rs:1774:26:1776:9 | { ... } | | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1774:26:1776:9 | { ... } | T | main.rs:1773:10:1773:10 | T | +| main.rs:1775:13:1775:38 | MyVec {...} | | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1775:13:1775:38 | MyVec {...} | T | main.rs:1773:10:1773:10 | T | +| main.rs:1775:27:1775:36 | ...::new(...) | | {EXTERNAL LOCATION} | Vec | +| main.rs:1775:27:1775:36 | ...::new(...) | T | main.rs:1773:10:1773:10 | T | +| main.rs:1778:17:1778:25 | SelfParam | | file://:0:0:0:0 | & | +| main.rs:1778:17:1778:25 | SelfParam | &T | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1778:17:1778:25 | SelfParam | &T.T | main.rs:1773:10:1773:10 | T | +| main.rs:1778:28:1778:32 | value | | main.rs:1773:10:1773:10 | T | +| main.rs:1779:13:1779:16 | self | | file://:0:0:0:0 | & | +| main.rs:1779:13:1779:16 | self | &T | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1779:13:1779:16 | self | &T.T | main.rs:1773:10:1773:10 | T | +| main.rs:1779:13:1779:21 | self.data | | {EXTERNAL LOCATION} | Vec | +| main.rs:1779:13:1779:21 | self.data | T | main.rs:1773:10:1773:10 | T | +| main.rs:1779:28:1779:32 | value | | main.rs:1773:10:1773:10 | T | +| main.rs:1787:18:1787:22 | SelfParam | | file://:0:0:0:0 | & | +| main.rs:1787:18:1787:22 | SelfParam | &T | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1787:18:1787:22 | SelfParam | &T.T | main.rs:1783:10:1783:10 | T | +| main.rs:1787:25:1787:29 | index | | {EXTERNAL LOCATION} | usize | +| main.rs:1787:56:1789:9 | { ... } | | file://:0:0:0:0 | & | +| main.rs:1787:56:1789:9 | { ... } | &T | main.rs:1783:10:1783:10 | T | +| main.rs:1788:13:1788:29 | &... | | file://:0:0:0:0 | & | +| main.rs:1788:13:1788:29 | &... | &T | main.rs:1783:10:1783:10 | T | +| main.rs:1788:14:1788:17 | self | | file://:0:0:0:0 | & | +| main.rs:1788:14:1788:17 | self | &T | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1788:14:1788:17 | self | &T.T | main.rs:1783:10:1783:10 | T | +| main.rs:1788:14:1788:22 | self.data | | {EXTERNAL LOCATION} | Vec | +| main.rs:1788:14:1788:22 | self.data | T | main.rs:1783:10:1783:10 | T | +| main.rs:1788:14:1788:29 | ...[index] | | main.rs:1783:10:1783:10 | T | +| main.rs:1788:24:1788:28 | index | | {EXTERNAL LOCATION} | usize | +| main.rs:1792:22:1792:26 | slice | | file://:0:0:0:0 | & | +| main.rs:1793:17:1793:21 | slice | | file://:0:0:0:0 | & | +| main.rs:1793:23:1793:23 | 0 | | {EXTERNAL LOCATION} | i32 | +| main.rs:1797:13:1797:19 | mut vec | | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1797:13:1797:19 | mut vec | T | main.rs:1759:5:1760:13 | S | +| main.rs:1797:23:1797:34 | ...::new(...) | | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1797:23:1797:34 | ...::new(...) | T | main.rs:1759:5:1760:13 | S | +| main.rs:1798:9:1798:11 | vec | | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1798:9:1798:11 | vec | T | main.rs:1759:5:1760:13 | S | +| main.rs:1798:18:1798:18 | S | | main.rs:1759:5:1760:13 | S | +| main.rs:1799:9:1799:11 | vec | | main.rs:1768:5:1771:5 | MyVec | +| main.rs:1799:9:1799:11 | vec | T | main.rs:1759:5:1760:13 | S | +| main.rs:1799:13:1799:13 | 0 | | {EXTERNAL LOCATION} | i32 | +| main.rs:1801:13:1801:14 | xs | | file://:0:0:0:0 | [] | +| main.rs:1801:21:1801:21 | 1 | | {EXTERNAL LOCATION} | i32 | +| main.rs:1801:26:1801:28 | [...] | | file://:0:0:0:0 | [] | +| main.rs:1801:27:1801:27 | S | | main.rs:1759:5:1760:13 | S | +| main.rs:1802:17:1802:18 | xs | | file://:0:0:0:0 | [] | +| main.rs:1802:20:1802:20 | 0 | | {EXTERNAL LOCATION} | i32 | +| main.rs:1804:23:1804:25 | &xs | | file://:0:0:0:0 | & | +| main.rs:1804:23:1804:25 | &xs | &T | file://:0:0:0:0 | [] | +| main.rs:1804:24:1804:25 | xs | | file://:0:0:0:0 | [] | +| main.rs:1810:5:1810:20 | ...::f(...) | | main.rs:67:5:67:21 | Foo | +| main.rs:1811:5:1811:60 | ...::g(...) | | main.rs:67:5:67:21 | Foo | +| main.rs:1811:20:1811:38 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | +| main.rs:1811:41:1811:59 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | +| main.rs:1827:5:1827:15 | ...::f(...) | | {EXTERNAL LOCATION} | trait Future | testFailures diff --git a/rust/ql/test/library-tests/type-inference/type-inference.ql b/rust/ql/test/library-tests/type-inference/type-inference.ql index e7c11bcaebf1..2278cde8a8fb 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.ql +++ b/rust/ql/test/library-tests/type-inference/type-inference.ql @@ -55,10 +55,13 @@ module TypeTest implements TestSig { exists(AstNode n, TypePath path, Type t | t = TypeInference::inferType(n, path) and location = n.getLocation() and - element = n.toString() and if path.isEmpty() then value = element + ":" + t else value = element + ":" + path.toString() + "." + t.toString() + | + element = n.toString() + or + element = n.(IdentPat).getName().getText() ) } } From 133aca07736f39fa7f61d3f7ddd94eae84df2436 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 3 Jun 2025 19:02:13 +0200 Subject: [PATCH 2/2] Rust: Simple type inference for index expressions --- rust/ql/lib/codeql/rust/internal/Type.qll | 45 ++++++++++++++- .../codeql/rust/internal/TypeInference.qll | 56 ++++++++++++++++++- .../lib/codeql/rust/internal/TypeMention.qll | 6 ++ .../test/library-tests/type-inference/main.rs | 6 +- .../type-inference/type-inference.expected | 25 +++++++++ 5 files changed, 132 insertions(+), 6 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/Type.qll b/rust/ql/lib/codeql/rust/internal/Type.qll index a88bc8e344f4..47ff0e2dd3f9 100644 --- a/rust/ql/lib/codeql/rust/internal/Type.qll +++ b/rust/ql/lib/codeql/rust/internal/Type.qll @@ -16,10 +16,13 @@ newtype TType = TArrayType() or // todo: add size? TRefType() or // todo: add mut? TImplTraitType(ImplTraitTypeRepr impl) or + TSliceType() or TTypeParamTypeParameter(TypeParam t) or TAssociatedTypeTypeParameter(TypeAlias t) { any(TraitItemNode trait).getAnAssocItem() = t } or + TArrayTypeParameter() or TRefTypeParameter() or - TSelfTypeParameter(Trait t) + TSelfTypeParameter(Trait t) or + TSliceTypeParameter() /** * A type without type arguments. @@ -149,7 +152,8 @@ class ArrayType extends Type, TArrayType { override TupleField getTupleField(int i) { none() } override TypeParameter getTypeParameter(int i) { - none() // todo + result = TArrayTypeParameter() and + i = 0 } override string toString() { result = "[]" } @@ -227,6 +231,29 @@ class ImplTraitReturnType extends ImplTraitType { override Function getFunction() { result = function } } +/** + * A slice type. + * + * Slice types like `[i64]` are modeled as normal generic types + * with a single type argument. + */ +class SliceType extends Type, TSliceType { + SliceType() { this = TSliceType() } + + override StructField getStructField(string name) { none() } + + override TupleField getTupleField(int i) { none() } + + override TypeParameter getTypeParameter(int i) { + result = TSliceTypeParameter() and + i = 0 + } + + override string toString() { result = "[]" } + + override Location getLocation() { result instanceof EmptyLocation } +} + /** A type parameter. */ abstract class TypeParameter extends Type { override StructField getStructField(string name) { none() } @@ -306,6 +333,13 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara override Location getLocation() { result = typeAlias.getLocation() } } +/** An implicit array type parameter. */ +class ArrayTypeParameter extends TypeParameter, TArrayTypeParameter { + override string toString() { result = "[T;...]" } + + override Location getLocation() { result instanceof EmptyLocation } +} + /** An implicit reference type parameter. */ class RefTypeParameter extends TypeParameter, TRefTypeParameter { override string toString() { result = "&T" } @@ -313,6 +347,13 @@ class RefTypeParameter extends TypeParameter, TRefTypeParameter { override Location getLocation() { result instanceof EmptyLocation } } +/** An implicit slice type parameter. */ +class SliceTypeParameter extends TypeParameter, TSliceTypeParameter { + override string toString() { result = "[T]" } + + override Location getLocation() { result instanceof EmptyLocation } +} + /** * The implicit `Self` type parameter of a trait, that refers to the * implementing type of the trait. diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index a6151eb62e79..86b8b7983ba1 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -80,10 +80,18 @@ private module Input1 implements InputSig1 { int getTypeParameterId(TypeParameter tp) { tp = rank[result](TypeParameter tp0, int kind, int id | - tp0 instanceof RefTypeParameter and + tp0 instanceof ArrayTypeParameter and kind = 0 and id = 0 or + tp0 instanceof RefTypeParameter and + kind = 0 and + id = 1 + or + tp0 instanceof SliceTypeParameter and + kind = 0 and + id = 2 + or kind = 1 and exists(AstNode node | id = idOfTypeParameterAstNode(node) | node = tp0.(TypeParamTypeParameter).getTypeParam() or @@ -1128,6 +1136,50 @@ private Type inferAwaitExprType(AstNode n, TypePath path) { ) } +private class Vec extends Struct { + Vec() { this.getCanonicalPath() = "alloc::vec::Vec" } + + TypeParamTypeParameter getElementTypeParameter() { + result.getTypeParam() = this.getGenericParamList().getTypeParam(0) + } +} + +/** + * According to [the Rust reference][1]: _"array and slice-typed expressions + * can be indexed with a `usize` index ... For other types an index expression + * `a[b]` is equivalent to *std::ops::Index::index(&a, b)"_. + * + * The logic below handles array and slice indexing, but for other types it is + * currently limited to `Vec`. + * + * [1]: https://doc.rust-lang.org/reference/expressions/array-expr.html#r-expr.array.index + */ +pragma[nomagic] +private Type inferIndexExprType(IndexExpr ie, TypePath path) { + // TODO: Should be implemented as method resolution, using the special + // `std::ops::Index` trait. + exists(TypePath exprPath, Builtins::BuiltinType t | + TStruct(t) = inferType(ie.getIndex()) and + ( + // also allow `i32`, since that is currently the type that we infer for + // integer literals like `0` + t instanceof Builtins::I32 + or + t instanceof Builtins::Usize + ) and + result = inferType(ie.getBase(), exprPath) + | + exprPath.isCons(any(Vec v).getElementTypeParameter(), path) + or + exprPath.isCons(any(ArrayTypeParameter tp), path) + or + exists(TypePath path0 | + exprPath.isCons(any(RefTypeParameter tp), path0) and + path0.isCons(any(SliceTypeParameter tp), path) + ) + ) +} + private module MethodCall { /** An expression that calls a method. */ abstract private class MethodCallImpl extends Expr { @@ -1487,6 +1539,8 @@ private module Cached { path.isEmpty() or result = inferAwaitExprType(n, path) + or + result = inferIndexExprType(n, path) } } diff --git a/rust/ql/lib/codeql/rust/internal/TypeMention.qll b/rust/ql/lib/codeql/rust/internal/TypeMention.qll index 32006041334a..f14291103c76 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeMention.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeMention.qll @@ -43,6 +43,12 @@ class RefTypeReprMention extends TypeMention instanceof RefTypeRepr { override Type resolveType() { result = TRefType() } } +class SliceTypeReprMention extends TypeMention instanceof SliceTypeRepr { + override TypeMention getTypeArgument(int i) { result = super.getTypeRepr() and i = 0 } + + override Type resolveType() { result = TSliceType() } +} + class PathTypeReprMention extends TypeMention instanceof PathTypeRepr { Path path; ItemNode resolved; diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 1cec65224bd0..b4c4ecdab2cd 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -1790,16 +1790,16 @@ mod indexers { } fn analyze_slice(slice: &[S]) { - let x = slice[0].foo(); // $ MISSING: method=foo MISSING: type=x:S + let x = slice[0].foo(); // $ method=foo type=x:S } pub fn f() { let mut vec = MyVec::new(); // $ type=vec:T.S vec.push(S); // $ method=push - vec[0].foo(); // $ MISSING: method=foo + vec[0].foo(); // $ MISSING: method=foo -- type inference does not support the `Index` trait yet let xs: [S; 1] = [S]; - let x = xs[0].foo(); // $ MISSING: method=foo MISSING: type=x:S + let x = xs[0].foo(); // $ method=foo type=x:S analyze_slice(&xs); } 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 bac5761b6863..4d4c3e92311c 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -2561,7 +2561,14 @@ inferType | main.rs:1788:14:1788:29 | ...[index] | | main.rs:1783:10:1783:10 | T | | main.rs:1788:24:1788:28 | index | | {EXTERNAL LOCATION} | usize | | main.rs:1792:22:1792:26 | slice | | file://:0:0:0:0 | & | +| main.rs:1792:22:1792:26 | slice | &T | file://:0:0:0:0 | [] | +| main.rs:1792:22:1792:26 | slice | &T.[T] | main.rs:1759:5:1760:13 | S | +| main.rs:1793:13:1793:13 | x | | main.rs:1759:5:1760:13 | S | | main.rs:1793:17:1793:21 | slice | | file://:0:0:0:0 | & | +| main.rs:1793:17:1793:21 | slice | &T | file://:0:0:0:0 | [] | +| main.rs:1793:17:1793:21 | slice | &T.[T] | main.rs:1759:5:1760:13 | S | +| main.rs:1793:17:1793:24 | slice[0] | | main.rs:1759:5:1760:13 | S | +| main.rs:1793:17:1793:30 | ... .foo() | | main.rs:1759:5:1760:13 | S | | main.rs:1793:23:1793:23 | 0 | | {EXTERNAL LOCATION} | i32 | | main.rs:1797:13:1797:19 | mut vec | | main.rs:1768:5:1771:5 | MyVec | | main.rs:1797:13:1797:19 | mut vec | T | main.rs:1759:5:1760:13 | S | @@ -2574,14 +2581,32 @@ inferType | main.rs:1799:9:1799:11 | vec | T | main.rs:1759:5:1760:13 | S | | main.rs:1799:13:1799:13 | 0 | | {EXTERNAL LOCATION} | i32 | | main.rs:1801:13:1801:14 | xs | | file://:0:0:0:0 | [] | +| main.rs:1801:13:1801:14 | xs | | file://:0:0:0:0 | [] | +| main.rs:1801:13:1801:14 | xs | [T;...] | main.rs:1759:5:1760:13 | S | +| main.rs:1801:13:1801:14 | xs | [T] | main.rs:1759:5:1760:13 | S | | main.rs:1801:21:1801:21 | 1 | | {EXTERNAL LOCATION} | i32 | | main.rs:1801:26:1801:28 | [...] | | file://:0:0:0:0 | [] | +| main.rs:1801:26:1801:28 | [...] | | file://:0:0:0:0 | [] | +| main.rs:1801:26:1801:28 | [...] | [T;...] | main.rs:1759:5:1760:13 | S | +| main.rs:1801:26:1801:28 | [...] | [T] | main.rs:1759:5:1760:13 | S | | main.rs:1801:27:1801:27 | S | | main.rs:1759:5:1760:13 | S | +| main.rs:1802:13:1802:13 | x | | main.rs:1759:5:1760:13 | S | +| main.rs:1802:17:1802:18 | xs | | file://:0:0:0:0 | [] | | main.rs:1802:17:1802:18 | xs | | file://:0:0:0:0 | [] | +| main.rs:1802:17:1802:18 | xs | [T;...] | main.rs:1759:5:1760:13 | S | +| main.rs:1802:17:1802:18 | xs | [T] | main.rs:1759:5:1760:13 | S | +| main.rs:1802:17:1802:21 | xs[0] | | main.rs:1759:5:1760:13 | S | +| main.rs:1802:17:1802:27 | ... .foo() | | main.rs:1759:5:1760:13 | S | | main.rs:1802:20:1802:20 | 0 | | {EXTERNAL LOCATION} | i32 | | main.rs:1804:23:1804:25 | &xs | | file://:0:0:0:0 | & | | main.rs:1804:23:1804:25 | &xs | &T | file://:0:0:0:0 | [] | +| main.rs:1804:23:1804:25 | &xs | &T | file://:0:0:0:0 | [] | +| main.rs:1804:23:1804:25 | &xs | &T.[T;...] | main.rs:1759:5:1760:13 | S | +| main.rs:1804:23:1804:25 | &xs | &T.[T] | main.rs:1759:5:1760:13 | S | +| main.rs:1804:24:1804:25 | xs | | file://:0:0:0:0 | [] | | main.rs:1804:24:1804:25 | xs | | file://:0:0:0:0 | [] | +| main.rs:1804:24:1804:25 | xs | [T;...] | main.rs:1759:5:1760:13 | S | +| main.rs:1804:24:1804:25 | xs | [T] | main.rs:1759:5:1760:13 | S | | main.rs:1810:5:1810:20 | ...::f(...) | | main.rs:67:5:67:21 | Foo | | main.rs:1811:5:1811:60 | ...::g(...) | | main.rs:67:5:67:21 | Foo | | main.rs:1811:20:1811:38 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo |