From dfc39272b4bc1e7222c42cf17b78d8a00fdc68e1 Mon Sep 17 00:00:00 2001
From: Tom Hvitved <hvitved@github.com>
Date: Sun, 16 Mar 2025 20:35:16 +0100
Subject: [PATCH] Rust: Limit `TypePath`s to at most length 10

---
 .../ExtractionConsistency.expected            |  2 +
 .../library-tests/type-inference/loop/main.rs | 14 +++++++
 .../type-inference/loop/options.yml           |  1 +
 .../type-inference/type-inference.expected    | 39 +++++++++++++++++++
 .../typeinference/internal/TypeInference.qll  | 23 ++++++++++-
 5 files changed, 78 insertions(+), 1 deletion(-)
 create mode 100644 rust/ql/test/library-tests/type-inference/CONSISTENCY/ExtractionConsistency.expected
 create mode 100644 rust/ql/test/library-tests/type-inference/loop/main.rs
 create mode 100644 rust/ql/test/library-tests/type-inference/loop/options.yml

diff --git a/rust/ql/test/library-tests/type-inference/CONSISTENCY/ExtractionConsistency.expected b/rust/ql/test/library-tests/type-inference/CONSISTENCY/ExtractionConsistency.expected
new file mode 100644
index 000000000000..0b4e0c12d0c8
--- /dev/null
+++ b/rust/ql/test/library-tests/type-inference/CONSISTENCY/ExtractionConsistency.expected
@@ -0,0 +1,2 @@
+extractionWarning
+| loop/main.rs:1:1:1:1 | semantic analyzer unavailable (not included as a module) |
diff --git a/rust/ql/test/library-tests/type-inference/loop/main.rs b/rust/ql/test/library-tests/type-inference/loop/main.rs
new file mode 100644
index 000000000000..da1d19e3f62d
--- /dev/null
+++ b/rust/ql/test/library-tests/type-inference/loop/main.rs
@@ -0,0 +1,14 @@
+// The code in this file is not valid Rust code, but it is used to test that
+// our type inference implementation does not run into an infinite loop.
+
+struct S<T>(T);
+
+trait T1<T>: T2<S<T>> {
+    fn foo(self) {}
+}
+
+trait T2<T>: T1<S<T>> {
+    fn bar(self) {
+        self.foo()
+    }
+}
diff --git a/rust/ql/test/library-tests/type-inference/loop/options.yml b/rust/ql/test/library-tests/type-inference/loop/options.yml
new file mode 100644
index 000000000000..cf148dd35f86
--- /dev/null
+++ b/rust/ql/test/library-tests/type-inference/loop/options.yml
@@ -0,0 +1 @@
+qltest_cargo_check: false
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 6f63ae4382e3..5974daaa4142 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,42 @@
 inferType
+| loop/main.rs:7:12:7:15 | SelfParam |  | loop/main.rs:6:1:8:1 | trait T1 |
+| loop/main.rs:7:12:7:15 | SelfParam | T | loop/main.rs:6:10:6:10 | T |
+| loop/main.rs:11:12:11:15 | SelfParam |  | loop/main.rs:6:1:8:1 | trait T1 |
+| loop/main.rs:11:12:11:15 | SelfParam |  | loop/main.rs:10:1:14:1 | trait T2 |
+| loop/main.rs:11:12:11:15 | SelfParam | T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T.T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:11:12:11:15 | SelfParam | T.T.T.T.T.T.T.T.T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:12:9:12:12 | self |  | loop/main.rs:6:1:8:1 | trait T1 |
+| loop/main.rs:12:9:12:12 | self |  | loop/main.rs:10:1:14:1 | trait T2 |
+| loop/main.rs:12:9:12:12 | self | T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:12:9:12:12 | self | T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:12:9:12:12 | self | T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T.T.T | loop/main.rs:10:10:10:10 | T |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T.T.T.T.T | loop/main.rs:4:1:4:15 | struct S |
+| loop/main.rs:12:9:12:12 | self | T.T.T.T.T.T.T.T.T.T | loop/main.rs:10:10:10:10 | T |
 | main.rs:5:19:5:22 | SelfParam |  | main.rs:2:5:2:21 | struct Foo |
 | main.rs:5:33:7:9 | { ... } |  | main.rs:2:5:2:21 | struct Foo |
 | main.rs:6:13:6:16 | self |  | main.rs:2:5:2:21 | struct Foo |
@@ -783,6 +821,7 @@ inferType
 | main.rs:582:11:582:20 | ...::Foo {...} |  | main.rs:2:5:2:21 | struct Foo |
 | main.rs:582:23:582:32 | ...::Foo {...} |  | main.rs:2:5:2:21 | struct Foo |
 resolveMethodCallExpr
+| loop/main.rs:12:9:12:18 | self.foo(...) | loop/main.rs:7:5:7:19 | fn foo |
 | main.rs:23:9:23:14 | x.m1(...) | main.rs:5:9:7:9 | fn m1 |
 | main.rs:24:9:24:14 | y.m2(...) | main.rs:9:9:11:9 | fn m2 |
 | main.rs:67:26:67:31 | x.m2(...) | main.rs:52:9:54:9 | fn m2 |
diff --git a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll
index 02c12a1a7817..6b2733085141 100644
--- a/shared/typeinference/codeql/typeinference/internal/TypeInference.qll
+++ b/shared/typeinference/codeql/typeinference/internal/TypeInference.qll
@@ -68,6 +68,15 @@ signature module InputSig1<LocationSig Location> {
   predicate typeArgumentParameterPositionMatch(
     TypeArgumentPosition tapos, TypeParameterPosition tppos
   );
+
+  /**
+   * Gets the limit on the length of type paths. Set to `none()` if there should
+   * be no limit.
+   *
+   * Having a limit can be useful to avoid inifinite recursion on malformed
+   * programs.
+   */
+  default int getTypePathLimit() { result = 10 }
 }
 
 module Make1<LocationSig Location, InputSig1<Location> Input1> {
@@ -143,6 +152,15 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
     /** Holds if this type path is empty. */
     predicate isEmpty() { this = "" }
 
+    /** Gets the length of this path. */
+    bindingset[this]
+    pragma[inline_late]
+    int length() {
+      this.isEmpty() and result = 0
+      or
+      result = strictcount(this.indexOf(".")) + 1
+    }
+
     /** Gets the path obtained by appending `suffix` onto this path. */
     bindingset[suffix, result]
     bindingset[this, result]
@@ -153,7 +171,10 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
       else
         if suffix.isEmpty()
         then result = this
-        else result = this + "." + suffix
+        else (
+          result = this + "." + suffix and
+          not result.length() > getTypePathLimit()
+        )
     }
 
     /** Holds if this path starts with `tp`, followed by `suffix`. */