From dfb59d31a08c3dec2f135c8590dd297139f78ad0 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 30 Oct 2026 10:02:14 +0100 Subject: [PATCH 1/2] Type inference: Consistency check for escaping type parameters --- .../rust/internal/typeinference/Type.qll | 6 +++++ .../internal/typeinference/TypeInference.qll | 22 +++++++++++-------- .../typeinference/internal/TypeInference.qll | 16 ++++++++++++++ .../unified/internal/typeinference/Type.qll | 12 ++++++++++ .../internal/typeinference/TypeInference.qll | 2 ++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/Type.qll b/rust/ql/lib/codeql/rust/internal/typeinference/Type.qll index 66151047eca2..7521ff33929c 100644 --- a/rust/ql/lib/codeql/rust/internal/typeinference/Type.qll +++ b/rust/ql/lib/codeql/rust/internal/typeinference/Type.qll @@ -401,6 +401,8 @@ abstract class TypeParameter extends Type { override TypeParameter getPositionalTypeParameter(int i) { none() } abstract ItemNode getDeclaringItem(); + + AstNode getScope() { result = this.getDeclaringItem() } } private class RawTypeParameter = @type_param or @trait or @type_alias or @impl_trait_type_repr; @@ -583,6 +585,8 @@ class ImplTraitTypeParameter extends TypeParameter, TImplTraitTypeParameter { override ItemNode getDeclaringItem() { none() } + override AstNode getScope() { result = implTrait.getFunction() } + override string toString() { result = "impl(" + typeParam.toString() + ")" } override Location getLocation() { result = typeParam.getLocation() } @@ -633,6 +637,8 @@ class ImplTraitTypeTypeParameter extends ImplTraitType, TypeParameter { override ItemNode getDeclaringItem() { none() } + override AstNode getScope() { result = function } + override Function getFunction() { result = function } override TypeParameter getPositionalTypeParameter(int i) { none() } diff --git a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll index 38376c6e0b1d..c6daffd4007c 100644 --- a/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll @@ -2272,7 +2272,11 @@ private module Input3 implements InputSig3 { BoolType() { this.getTypeItem() instanceof Builtins::Bool } } - class AstNode = Rust::AstNode; + class AstNode extends Rust::AstNode { + AstNode getParent() { result = this.getParentNode() } + } + + AstNode getTypeParameterScope(TypeParameter tp) { result = tp.getScope() } final class Expr = ExprImpl; @@ -2286,13 +2290,13 @@ private module Input3 implements InputSig3 { TypeMention getType() { result = this.getTypeRepr() } } - class Switch extends Rust::MatchExpr { + class Switch extends Expr, Rust::MatchExpr { Expr getExpr() { result = this.getScrutinee() } Case getCase(int index) { result = this.getArm(index) } } - class Case extends Rust::MatchArm { + class Case extends AstNode, Rust::MatchArm { AstNode getAPattern() { result = this.getPat() } AstNode getBody() { result = this.getExpr() } @@ -2449,8 +2453,8 @@ private module Input3 implements InputSig3 { // mutual recursion; resolving fields requires resolving types and vice versa result = [ - resolveStructFieldExpr(this, _).(AstNode), - resolveTupleFieldExpr(this, _) + resolveStructFieldExpr(this, _).(Field), + resolveTupleFieldExpr(this, _).(Field) ] } } @@ -2480,7 +2484,7 @@ private module Input3 implements InputSig3 { ) } - class Return extends ReturnExpr { + class Return extends Expr, ReturnExpr { Expr getExpr() { result = super.getExpr() } } @@ -2583,7 +2587,7 @@ private module Input3 implements InputSig3 { override TypeItem getTypeItem() { result = this } override Parameter getParameter(int i) { - result = [super.getTupleField(i).(AstNode), super.getNthStructField(i)] + result = [super.getTupleField(i).(Parameter), super.getNthStructField(i).(Parameter)] } } @@ -2591,7 +2595,7 @@ private module Input3 implements InputSig3 { override TypeItem getTypeItem() { result = super.getEnum() } override Parameter getParameter(int i) { - result = [super.getTupleField(i).(AstNode), super.getNthStructField(i)] + result = [super.getTupleField(i).(Parameter), super.getNthStructField(i).(Parameter)] } } @@ -3155,7 +3159,7 @@ module Consistency = M3::Consistency; module CachedStage = M3::CachedStage; -private predicate typeTestAstNodeRepr(AstNode n, string repr) { +private predicate typeTestAstNodeRepr(Input3::AstNode n, string repr) { repr = [n.toString(), n.(IdentPat).getName().getText()] } diff --git a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll index 0e259e7b0e02..64cc4399ba61 100644 --- a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll +++ b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll @@ -2140,6 +2140,9 @@ module Make1 Input1> { /** An AST node. */ class AstNode { + /** Gets the parent of this AST node, if any. */ + AstNode getParent(); + /** Gets a textual representation of this AST node. */ string toString(); @@ -2147,6 +2150,14 @@ module Make1 Input1> { Location getLocation(); } + /** + * Gets the scope of type parameter `tp`. + * + * Only used in a consistency check to ensure that type parameters do not + * escape their scope. + */ + AstNode getTypeParameterScope(TypeParameter tp); + /** An expression. */ class Expr extends AstNode; @@ -3747,6 +3758,11 @@ module Make1 Input1> { Input3::inferTypeForDefaults(n, path) = t and not inferType(n, path) = t } + + query predicate typeParameterEscapesScope(AstNode n, TypePath path, TypeParameter tp) { + tp = inferType(n, path) and + not n.getParent*() = getTypeParameterScope(tp) + } } /** diff --git a/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll b/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll index 0ffe9fccdd7a..de2e026028c6 100644 --- a/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll +++ b/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll @@ -213,6 +213,8 @@ abstract class TypeParameter extends TypeImpl { override TypeParameter getPositionalTypeParameter(int i) { none() } abstract Member getDeclaringItem(); + + Member getScope() { result = this.getDeclaringItem() } } private class IdAstNode = @@ -241,6 +243,12 @@ class TypeParameterType extends TypeParameter, TTypeParameterType { typeParam = result.(Unified::FunctionDeclaration).getATypeParameter() } + override Member getScope() { + result = super.getScope() + or + result.(ClassLikeDeclaration).resolve().getEnclosingClassResolved*() = this.getDeclaringItem() + } + override Identifier getNameNode() { result = typeParam.getNameNode() } override string toString() { result = typeParam.getName() } @@ -262,6 +270,8 @@ class AssociatedTypeParameterType extends TypeParameter, TAssociatedTypeParamete override ClassLikeDeclaration getDeclaringItem() { result = c } + override ClassLikeDeclaration getScope() { result.resolve() = c } + override Identifier getNameNode() { result = assocTypeDecl.getNameNode() } override string toString() { @@ -307,6 +317,8 @@ class ParentTypeParameterType extends TypeParameter, TParentTypeParameterType { override Member getDeclaringItem() { result = m } + override Member getScope() { none() } + override Identifier getNameNode() { none() } override string toString() { result = "(parent:" + m.getNameOrFallback() + ")" } diff --git a/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll b/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll index f7a773c1dc6d..1b6b7ae96a9c 100644 --- a/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll +++ b/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll @@ -141,6 +141,8 @@ module Input3 implements InputSig3 { class AstNode = Unified::AstNode; + AstNode getTypeParameterScope(TypeParameter tp) { result = tp.getScope() } + class Expr extends Unified::Expr { Expr() { isInAssignmentContext(this, _) or From be318bb4b6091186f7c9ae2bc0fcec4a090bb358 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 2 Nov 2026 11:51:15 +0100 Subject: [PATCH 2/2] Rust: Accept inconsistencies --- .../TypeInferenceConsistency.qlref | 2 ++ .../TypeInferenceConsistency.expected | 35 +++++++++++++++++++ .../TypeInferenceConsistency.expected | 4 +++ .../TypeInferenceConsistency.expected | 6 ++++ .../TypeInferenceConsistency.expected | 9 +++++ .../TypeInferenceConsistency.expected | 23 ++++++++++++ 6 files changed, 79 insertions(+) create mode 100644 rust/ql/consistency-queries/TypeInferenceConsistency.qlref create mode 100644 rust/ql/test/library-tests/dataflow/sources/net/CONSISTENCY/TypeInferenceConsistency.expected create mode 100644 rust/ql/test/library-tests/path-resolution/CONSISTENCY/TypeInferenceConsistency.expected create mode 100644 rust/ql/test/library-tests/type-inference/CONSISTENCY/TypeInferenceConsistency.expected create mode 100644 rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/TypeInferenceConsistency.expected create mode 100644 rust/ql/test/query-tests/security/CWE-312/CONSISTENCY/TypeInferenceConsistency.expected diff --git a/rust/ql/consistency-queries/TypeInferenceConsistency.qlref b/rust/ql/consistency-queries/TypeInferenceConsistency.qlref new file mode 100644 index 000000000000..96023d41b87f --- /dev/null +++ b/rust/ql/consistency-queries/TypeInferenceConsistency.qlref @@ -0,0 +1,2 @@ +query: TypeInferenceConsistency.ql +postprocess: utils/test/ExternalLocationPostProcessing.ql \ No newline at end of file diff --git a/rust/ql/test/library-tests/dataflow/sources/net/CONSISTENCY/TypeInferenceConsistency.expected b/rust/ql/test/library-tests/dataflow/sources/net/CONSISTENCY/TypeInferenceConsistency.expected new file mode 100644 index 000000000000..054a4ed78371 --- /dev/null +++ b/rust/ql/test/library-tests/dataflow/sources/net/CONSISTENCY/TypeInferenceConsistency.expected @@ -0,0 +1,35 @@ +typeParameterEscapesScope +| test.rs:83:17:83:20 | data | B | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:83:17:83:20 | data | B | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:83:24:83:43 | await ... | E | {EXTERNAL LOCATION} | T::Error[Body] | +| test.rs:83:24:83:43 | await ... | T.B | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:83:24:83:44 | TryExpr | B | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:84:18:84:22 | &data | TRef.B | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:84:19:84:22 | data | B | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:86:25:86:28 | data | B | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:92:23:92:33 | Some(...) | T.E | {EXTERNAL LOCATION} | T::Error[Body] | +| test.rs:92:23:92:33 | Some(...) | T.T.T | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:92:28:92:32 | frame | E | {EXTERNAL LOCATION} | T::Error[Body] | +| test.rs:92:28:92:32 | frame | E | {EXTERNAL LOCATION} | T::Error[Body] | +| test.rs:92:28:92:32 | frame | T.T | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:92:28:92:32 | frame | T.T | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:92:37:92:58 | await ... | T.E | {EXTERNAL LOCATION} | T::Error[Body] | +| test.rs:92:37:92:58 | await ... | T.T.T | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:93:24:93:33 | Some(...) | T.TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:93:29:93:32 | data | TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:93:29:93:32 | data | TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:93:37:93:41 | frame | E | {EXTERNAL LOCATION} | T::Error[Body] | +| test.rs:93:37:93:41 | frame | T.T | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:93:37:93:42 | TryExpr | T | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:93:37:93:53 | ... .data_ref() | T.TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:94:49:94:52 | data | TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:95:26:95:29 | data | TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:96:26:96:29 | data | TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:97:33:97:36 | data | TRef | {EXTERNAL LOCATION} | T::Data[Body] | +| test.rs:557:18:557:42 | await ... | T | {EXTERNAL LOCATION} | St::Item[Stream] | +| test.rs:557:18:557:51 | ... .unwrap() | | {EXTERNAL LOCATION} | St::Item[Stream] | +| test.rs:558:23:558:32 | Some(...) | T | {EXTERNAL LOCATION} | St::Item[Stream] | +| test.rs:558:28:558:31 | line | | {EXTERNAL LOCATION} | St::Item[Stream] | +| test.rs:558:28:558:31 | line | | {EXTERNAL LOCATION} | St::Item[Stream] | +| test.rs:558:36:558:60 | await ... | T | {EXTERNAL LOCATION} | St::Item[Stream] | +| test.rs:559:22:559:25 | line | | {EXTERNAL LOCATION} | St::Item[Stream] | diff --git a/rust/ql/test/library-tests/path-resolution/CONSISTENCY/TypeInferenceConsistency.expected b/rust/ql/test/library-tests/path-resolution/CONSISTENCY/TypeInferenceConsistency.expected new file mode 100644 index 000000000000..d8109f725f3f --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/CONSISTENCY/TypeInferenceConsistency.expected @@ -0,0 +1,4 @@ +typeParameterEscapesScope +| main.rs:405:26:410:9 | { ... } | | main.rs:380:7:380:7 | T | +| main.rs:409:13:409:19 | ...::c | | main.rs:380:7:380:7 | T | +| main.rs:466:9:470:12 | ...::c | | main.rs:380:7:380:7 | T | diff --git a/rust/ql/test/library-tests/type-inference/CONSISTENCY/TypeInferenceConsistency.expected b/rust/ql/test/library-tests/type-inference/CONSISTENCY/TypeInferenceConsistency.expected new file mode 100644 index 000000000000..06af21e0ce59 --- /dev/null +++ b/rust/ql/test/library-tests/type-inference/CONSISTENCY/TypeInferenceConsistency.expected @@ -0,0 +1,6 @@ +typeParameterEscapesScope +| main.rs:2240:9:2240:31 | ... .my_add(...) | T.T | main.rs:2126:10:2126:17 | T::Output[MyAdd] | +| main.rs:2240:9:2240:31 | ... .my_add(...) | T.T | main.rs:2135:10:2135:17 | T::Output[MyAdd] | +| main.rs:2240:9:2240:31 | ... .my_add(...) | T.T | main.rs:2144:14:2144:14 | T::Output[MyAdd] | +| main.rs:2296:13:2296:13 | s | | {EXTERNAL LOCATION} | I::Item[Iterator] | +| main.rs:2296:13:2296:13 | s | | {EXTERNAL LOCATION} | I::Item[Iterator] | diff --git a/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/TypeInferenceConsistency.expected b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/TypeInferenceConsistency.expected new file mode 100644 index 000000000000..028b5fd2776d --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/TypeInferenceConsistency.expected @@ -0,0 +1,9 @@ +typeParameterEscapesScope +| sqlx.rs:43:61:43:80 | await ... | T.DB | {EXTERNAL LOCATION} | DB | +| sqlx.rs:43:61:43:81 | TryExpr | DB | {EXTERNAL LOCATION} | DB | +| sqlx.rs:169:9:169:16 | mut conn | DB | {EXTERNAL LOCATION} | DB | +| sqlx.rs:169:13:169:16 | conn | DB | {EXTERNAL LOCATION} | DB | +| sqlx.rs:169:20:169:39 | await ... | T.DB | {EXTERNAL LOCATION} | DB | +| sqlx.rs:169:20:169:40 | TryExpr | DB | {EXTERNAL LOCATION} | DB | +| sqlx.rs:179:13:179:16 | conn | DB | {EXTERNAL LOCATION} | DB | +| sqlx.rs:181:17:181:20 | conn | DB | {EXTERNAL LOCATION} | DB | diff --git a/rust/ql/test/query-tests/security/CWE-312/CONSISTENCY/TypeInferenceConsistency.expected b/rust/ql/test/query-tests/security/CWE-312/CONSISTENCY/TypeInferenceConsistency.expected new file mode 100644 index 000000000000..90663c2cb9ae --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-312/CONSISTENCY/TypeInferenceConsistency.expected @@ -0,0 +1,23 @@ +typeParameterEscapesScope +| test_storage.rs:64:9:64:17 | mut conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:64:13:64:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:64:21:64:41 | await ... | T.DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:64:21:64:42 | TryExpr | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:85:13:85:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:86:13:86:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:87:13:87:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:88:13:88:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:89:13:89:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:90:13:90:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:91:13:91:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:92:13:92:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:93:13:93:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:94:13:94:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:95:13:95:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:96:13:96:17 | conn1 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:131:9:131:17 | mut conn3 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:131:13:131:17 | conn3 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:131:21:131:41 | await ... | T.DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:131:21:131:42 | TryExpr | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:134:13:134:17 | conn3 | DB | {EXTERNAL LOCATION} | DB | +| test_storage.rs:135:13:135:17 | conn3 | DB | {EXTERNAL LOCATION} | DB |