[llvm-branch-commits] [clang] [SSAF][PointerFlow] Change unsafe-buffer reachability analysis back to simple graph search (PR #218209)

Ziqing Luo via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sat Aug 22 23:12:12 PDT 2026


https://github.com/ziqingluo-90 created https://github.com/llvm/llvm-project/pull/218209

Because of #218207, we no longer need unsafe-buffer reachability analysis to "uncompress" pointer flow graphs. It can go back to simple DFS. Since it deals with large data, simplicity is important.

In addition, unit tests for the "compressed" pointer flow graph DFS are moved to lit tests because they are no longer suitable as WPA unit tests. As lit tests, they are end-to-end tests where the extractor is involved and is responsible for generating "uncompressed" graphs.

Final step of
rdar://183529483

>From 5e2623c98229603cdf75d03219167c91c0d07a9e Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Fri, 21 Aug 2026 18:22:03 -0700
Subject: [PATCH] [SSAF][PointerFlow] Change unsafe-buffer reachability
 analysis back to simple graph search

Because of commit 30cd4297b, we no longer need unsafe-buffer
reachability analysis to "uncompress" pointer flow graphs. It can go
back to simple DFS. Since it deals with large data, simplicity is
important.

In addition, unit tests for the "compressed" pointer flow graphs are
moved to lit tests because they are no longer suitable as WPA unit
tests. As lit tests, they are end-to-end tests where the extractor is
involved and is responsible for generating "uncompressed" graphs.

Final step of
rdar://183529483
---
 .../UnsafeBufferUsageAnalysis.cpp             |  50 +--
 .../unsafe-buffer-reachable-cast-cycle.test   |  52 +++
 .../unsafe-buffer-reachable-topologies.test   | 367 ++++++++++++++++++
 .../UnsafeBufferReachableAnalysisTest.cpp     | 190 ---------
 4 files changed, 424 insertions(+), 235 deletions(-)
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-cast-cycle.test
 create mode 100644 clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-topologies.test

diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
index 4c92a37a078d1..90a0e3633151a 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -145,56 +145,16 @@ class UnsafeBufferReachableAnalysis
                              TypeConstrainedPointersAnalysisResult,
                              UnsafeBufferUsageAnalysisResult> {
 
-  /// BoundsPropagationGraph adds bounds propagation semantics to the
-  /// pointer-flow graph, which represents the set of static pointer assignment
-  /// sites collected from the source code. Consider the following example:
-  ///
-  /// void f(int ***p, int **q) {
-  ///   *p = q;
-  ///   (**p)[5] = 0;
-  /// }
-  ///
-  /// There is one static pointer assignment thus one pointer-flow edge: (p, 2)
-  /// -> (q, 1). In terms of bounds propagation, this assignment implies that if
-  /// 'p' at pointer level 2 requires bounds, 'q' at pointer level 1 must also
-  /// have them. Furthermore, this relationship propagates to deeper indirection
-  /// levels: if 'p' at level 3 requires bounds, so does 'q' at level 2.
-  ///
-  /// In the example above, `(**p)` requires bounds (due to the array index),
-  /// and therefore `*q` must require bounds as well.
-  ///
-  /// To generalize the idea, the BoundsPropagationGraph is defined as a super
-  /// graph of the input pointer-flow graph by:
-  ///
-  ///   For each edge (src, i) -> (dest, j) in the pointer-flow graph, the
-  ///   BoundsPropagationGraph has a finite set of edges
-  ///   {(src, i + d) -> (dest, j + d) | 0 <= d < UB}, where UB is an upper
-  ///   bound based on the maximum pointer level the pointer type can have.
   struct BoundsPropagationGraph {
-  private:
     EdgeSet PointerFlows;
 
-  public:
-    BoundsPropagationGraph(EdgeSet PointerFlows)
-        : PointerFlows(std::move(PointerFlows)) {}
-
     /// Returns the EntityPointerLevelSet that are reachable from \p Src by
     /// one edge in the BoundsPropagationGraph.
     EntityPointerLevelSet getDestNodes(const EntityPointerLevel &Src) const {
-      unsigned SrcPtrLv = Src.getPointerLevel();
-      EntityPointerLevelSet Result;
-
-      for (unsigned P = 1; P <= SrcPtrLv; ++P) {
-        auto I = PointerFlows.find(buildEntityPointerLevel(Src.getEntity(), P));
-
-        if (I != PointerFlows.end()) {
-          unsigned Delta = SrcPtrLv - P;
-          for (const auto &EPL : I->second)
-            Result.insert(buildEntityPointerLevel(
-                EPL.getEntity(), EPL.getPointerLevel() + Delta));
-        }
-      }
-      return Result;
+      auto I = PointerFlows.find(Src);
+      if (I == PointerFlows.end())
+        return {};
+      return I->second;
     }
   };
 
@@ -265,7 +225,7 @@ class UnsafeBufferReachableAnalysis
                                        FilteredDstRange.end());
       }
       if (!FilteredSubGraph.empty())
-        BPG.try_emplace(Id, std::move(FilteredSubGraph));
+        BPG.try_emplace(Id, BoundsPropagationGraph{std::move(FilteredSubGraph)});
     }
 
     // Filter out type-constrained pointers from `UnsafePtrs`:
diff --git a/clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-cast-cycle.test b/clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-cast-cycle.test
new file mode 100644
index 0000000000000..46a3092b801ea
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-cast-cycle.test
@@ -0,0 +1,52 @@
+// Regression test: UnsafeBufferReachableAnalysis used to hang on
+//
+//   void f(char **a, char ***b, int i, int j) {
+//     a[i][j] = 0;      // 'a' is unsafe at levels 1 and 2
+//     a = *b;           // (a, 1) -> (b, 2)
+//     b = (char ***)*a; // (b, 1) -> (a, 2)
+//   }
+//
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -fsyntax-only %t/src.cpp \
+// RUN:   --ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers \
+// RUN:   --ssaf-compilation-unit-id="tu-1" \
+// RUN:   --ssaf-tu-summary-file=%t/src.summary.json
+
+// RUN: clang-ssaf-linker %t/src.summary.json -o %t/lu.json
+
+// RUN: clang-ssaf-analyzer %t/lu.json -o %t/wpa.json \
+// RUN:   -a UnsafeBufferReachableAnalysisResult
+
+// The CHECK lines below use readable tokens instead of inline FileCheck regex.
+// Expand the tokens into regex, then run FileCheck on the expanded copy:
+//   $NS - skip the "namespace" array, up to its closing ']'.
+//   $WS - whitespace, possibly spanning newlines.
+//   $PTR_Ln - a reachable-set entry closing as "}, n ]", i.e. pointer level n.
+// RUN: sed -e 's|$NS|{{([^]]\|[[:space:]])+\\],}}|g' \
+// RUN:     -e 's|$WS|{{[[:space:]]+}}|g' \
+// RUN:     -e 's|$PTR_L1|{{[[:space:]]+\\},[[:space:]]+1[[:space:]]+\\]}}|g' \
+// RUN:     -e 's|$PTR_L2|{{[[:space:]]+\\},[[:space:]]+2[[:space:]]+\\]}}|g' \
+// RUN:     -e 's|$PTR_L3|{{[[:space:]]+\\},[[:space:]]+3[[:space:]]+\\]}}|g' \
+// RUN:     %s | FileCheck - --input-file=%t/wpa.json
+
+//--- src.cpp
+void f(char **a, char ***b, int i, int j) {
+  a[i][j] = 0;      // 'a' is unsafe at levels 1 and 2
+  a = *b;           // (a, 1) -> (b, 2)
+  b = (char ***)*a; // (b, 1) -> (a, 2)
+}
+
+// Capture the entity ids for parameters 'a' (suffix "1") and 'b' (suffix "2").
+// CHECK-DAG: "id": [[a_ID:[0-9]+]],$NS$WS"suffix": "1",$WS"usr": "c:@F at f#**C#*S0_#I#I#"
+// CHECK-DAG: "id": [[b_ID:[0-9]+]],$NS$WS"suffix": "2",$WS"usr": "c:@F at f#**C#*S0_#I#I#"
+
+// The reachable set is exactly {(a, 1), (a, 2), (b, 2), (b, 3)} 
+// CHECK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// CHECK: "@": [[a_ID]]$PTR_L1
+// CHECK: "@": [[a_ID]]$PTR_L2
+// CHECK: "@": [[b_ID]]$PTR_L2
+// CHECK: "@": [[b_ID]]$PTR_L3
+// CHECK-NOT: "@":
+// CHECK: "analysis_name":
diff --git a/clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-topologies.test b/clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-topologies.test
new file mode 100644
index 0000000000000..397b7df48b51e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/unsafe-buffer-reachable-topologies.test
@@ -0,0 +1,367 @@
+// End-to-end tests of UnsafeBufferReachableAnalysis. Testing against
+// pointer flow graphs of different shapes.
+//
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: split-file %s %t
+
+// The extract -> link -> analyze -> check pipeline is defined once and reused;
+// each topology only redefines its source name and FileCheck prefix.
+//
+// The CHECK lines use readable tokens instead of inline FileCheck regex; the
+// sed below expands them into regex on the fly and pipes the result straight
+// into FileCheck (no intermediate file):
+//   $NS - skip the "namespace" array, up to its closing ']'.
+//   $WS - whitespace, possibly spanning newlines.
+//   $PTR_Ln - a reachable-set entry closing as "}, n ]", i.e. pointer level n.
+//
+// DEFINE: %{name} =
+// DEFINE: %{prefix} =
+// DEFINE: %{check} = \
+// DEFINE:   %clang_cc1 -fsyntax-only -Wno-self-assign %t/%{name}.cpp \
+// DEFINE:     --ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers \
+// DEFINE:     --ssaf-compilation-unit-id=%{name} \
+// DEFINE:     --ssaf-tu-summary-file=%t/%{name}.summary.json && \
+// DEFINE:   clang-ssaf-linker %t/%{name}.summary.json -o %t/%{name}.lu.json && \
+// DEFINE:   clang-ssaf-analyzer %t/%{name}.lu.json -o %t/%{name}.wpa.json \
+// DEFINE:     -a UnsafeBufferReachableAnalysisResult && \
+// DEFINE:   sed -e 's|$NS|{{([^]]\|[[:space:]])+\],}}|g' \
+// DEFINE:       -e 's|$WS|{{[[:space:]]+}}|g' \
+// DEFINE:       -e 's|$PTR_L1|{{[[:space:]]+\\},[[:space:]]+1[[:space:]]+\\]}}|g' \
+// DEFINE:       -e 's|$PTR_L2|{{[[:space:]]+\\},[[:space:]]+2[[:space:]]+\\]}}|g' \
+// DEFINE:       -e 's|$PTR_L3|{{[[:space:]]+\\},[[:space:]]+3[[:space:]]+\\]}}|g' \
+// DEFINE:       -e 's|$PTR_L4|{{[[:space:]]+\\},[[:space:]]+4[[:space:]]+\\]}}|g' \
+// DEFINE:       -e 's|$PTR_L5|{{[[:space:]]+\\},[[:space:]]+5[[:space:]]+\\]}}|g' \
+// DEFINE:       %s \
+// DEFINE:   | FileCheck - --check-prefix=%{prefix} --input-file=%t/%{name}.wpa.json
+
+// REDEFINE: %{name} = linear_chain
+// REDEFINE: %{prefix} = CHAIN
+// RUN: %{check}
+//--- linear_chain.cpp
+// (a,1)->(b,2), (b,1)->(c,2), (c,1)->(d,2); start (a,2).
+// Result: {(a,2),(b,3),(c,4),(d,5)}.
+void f(char **a, char ***b, char ****c, char *****d, int i) {
+  a = *b;
+  b = *c;
+  c = *d;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// CHAIN-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// CHAIN-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// CHAIN-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// CHAIN-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// CHAIN: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// CHAIN: "@": [[A]]$PTR_L2
+// CHAIN: "@": [[B]]$PTR_L3
+// CHAIN: "@": [[C]]$PTR_L4
+// CHAIN: "@": [[D]]$PTR_L5
+// CHAIN-NOT: "@":
+// CHAIN: "analysis_name":
+
+// REDEFINE: %{name} = linear_chain_disconnected
+// REDEFINE: %{prefix} = CHAINDISC
+// RUN: %{check}
+//--- linear_chain_disconnected.cpp
+// (a,1)->(b,2), (b,4)->(c,1), (c,1)->(d,1); start (a,2).
+// Result: {(a,2),(b,3)}.
+void f(char ***a, char ****b, char *c, char *d, int i) {
+  a = *b;
+  ***b = c;
+  c = d;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// CHAINDISC-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// CHAINDISC-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// CHAINDISC: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// CHAINDISC: "@": [[A]]$PTR_L2
+// CHAINDISC: "@": [[B]]$PTR_L3
+// CHAINDISC-NOT: "@":
+// CHAINDISC: "analysis_name":
+
+// REDEFINE: %{name} = diamond
+// REDEFINE: %{prefix} = DIAMOND
+// RUN: %{check}
+//--- diamond.cpp
+// (a,1)->(b,2), (a,1)->(c,2), (b,1)->(d,2), (c,1)->(d,2); start (a,2).
+// Result: {(a,2),(b,3),(c,3),(d,4)}.
+void f(char **a, char ***b, char ***c, char ****d, int i) {
+  a = *b;
+  a = *c;
+  b = *d;
+  c = *d;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// DIAMOND-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// DIAMOND-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// DIAMOND-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// DIAMOND-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// DIAMOND: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// DIAMOND: "@": [[A]]$PTR_L2
+// DIAMOND: "@": [[B]]$PTR_L3
+// DIAMOND: "@": [[C]]$PTR_L3
+// DIAMOND: "@": [[D]]$PTR_L4
+// DIAMOND-NOT: "@":
+// DIAMOND: "analysis_name":
+
+// REDEFINE: %{name} = disconnected_diamond
+// REDEFINE: %{prefix} = DISCONN
+// RUN: %{check}
+//--- disconnected_diamond.cpp
+// (a,1)->(b,2), (a,1)->(c,2), (b,5)->(d,1), (c,5)->(d,1); start (a,2).
+// Result: {(a,2),(b,3),(c,3)}.
+void f(char ****a, char *****b, char *****c, char *d, int i) {
+  a = *b;
+  a = *c;
+  ****b = d;
+  ****c = d;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// DISCONN-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// DISCONN-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// DISCONN-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// DISCONN: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// DISCONN: "@": [[A]]$PTR_L2
+// DISCONN: "@": [[B]]$PTR_L3
+// DISCONN: "@": [[C]]$PTR_L3
+// DISCONN-NOT: "@":
+// DISCONN: "analysis_name":
+
+// REDEFINE: %{name} = disconnected_subgraphs
+// REDEFINE: %{prefix} = DISCONNSUB
+// RUN: %{check}
+//--- disconnected_subgraphs.cpp
+// (a,1)->(b,1), (c,1)->(d,1); start from tail (b,1).
+// Result: {(b,1)}.
+void f(char *a, char *b, char *c, char *d, int i) {
+  a = b;
+  c = d;
+  b[i] = 0; // starter: (b,1)
+}
+// DISCONNSUB-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// DISCONNSUB: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// DISCONNSUB: "@": [[B]]$PTR_L1
+// DISCONNSUB-NOT: "@":
+// DISCONNSUB: "analysis_name":
+
+// REDEFINE: %{name} = cycle
+// REDEFINE: %{prefix} = CYCLE
+// RUN: %{check}
+//--- cycle.cpp
+// (a,1)->(b,1)->(c,1)->(d,1)->(a,1); start (c,2).
+// Result: {(a,2),(b,2),(c,2),(d,2)}.
+void f(char **a, char **b, char **c, char **d, int i) {
+  a = b;
+  b = c;
+  c = d;
+  d = a;
+  (*c)[i] = 0; // starter: (c,2)
+}
+// CYCLE-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// CYCLE-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// CYCLE-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// CYCLE-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// CYCLE: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// CYCLE: "@": [[A]]$PTR_L2
+// CYCLE: "@": [[B]]$PTR_L2
+// CYCLE: "@": [[C]]$PTR_L2
+// CYCLE: "@": [[D]]$PTR_L2
+// CYCLE-NOT: "@":
+// CYCLE: "analysis_name":
+
+// REDEFINE: %{name} = cycle_increasing
+// REDEFINE: %{prefix} = CYCLEINC
+// RUN: %{check}
+//--- cycle_increasing.cpp
+// (a,1)->(b,2)->(c,3)->(d,4)->(a,1); start (a,2).
+// Result: {(a,2),(b,3),(c,4),(d,5)}.
+void f(char **a, char ***b, char ****c, char *****d, int i) {
+  a = *b;
+  *b = **c;
+  **c = ***d;
+  ***d = a;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// CYCLEINC-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// CYCLEINC-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// CYCLEINC-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// CYCLEINC-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// CYCLEINC: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// CYCLEINC: "@": [[A]]$PTR_L2
+// CYCLEINC: "@": [[B]]$PTR_L3
+// CYCLEINC: "@": [[C]]$PTR_L4
+// CYCLEINC: "@": [[D]]$PTR_L5
+// CYCLEINC-NOT: "@":
+// CYCLEINC: "analysis_name":
+
+// REDEFINE: %{name} = star_from_hub
+// REDEFINE: %{prefix} = HUB
+// RUN: %{check}
+//--- star_from_hub.cpp
+// (a,1)->(b,2), (a,1)->(c,2), (a,1)->(d,2); start (a,2).
+// Result: {(a,2),(b,3),(c,3),(d,3)}.
+void f(char **a, char ***b, char ***c, char ***d, int i) {
+  a = *b;
+  a = *c;
+  a = *d;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// HUB-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// HUB-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// HUB-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// HUB-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// HUB: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// HUB: "@": [[A]]$PTR_L2
+// HUB: "@": [[B]]$PTR_L3
+// HUB: "@": [[C]]$PTR_L3
+// HUB: "@": [[D]]$PTR_L3
+// HUB-NOT: "@":
+// HUB: "analysis_name":
+
+// REDEFINE: %{name} = star_from_hub_below_edge
+// REDEFINE: %{prefix} = HUBBELOW
+// RUN: %{check}
+//--- star_from_hub_below_edge.cpp
+// (a,2)->(b,1), (a,2)->(c,1), (a,2)->(d,1); start (a,1).
+// Result: {(a,1)}.
+void f(char **a, char *b, char *c, char *d, int i) {
+  *a = b;
+  *a = c;
+  *a = d;
+  a[i] = 0; // starter: (a,1)
+}
+// HUBBELOW-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// HUBBELOW: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// HUBBELOW: "@": [[A]]$PTR_L1
+// HUBBELOW-NOT: "@":
+// HUBBELOW: "analysis_name":
+
+// REDEFINE: %{name} = reverse_star_from_source
+// REDEFINE: %{prefix} = REVSRC
+// RUN: %{check}
+//--- reverse_star_from_source.cpp
+// (a,1)->(d,2), (b,1)->(d,2), (c,1)->(d,2); start (a,2).
+// Result: {(a,2),(d,3)}.
+void f(char **a, char **b, char **c, char ***d, int i) {
+  a = *d;
+  b = *d;
+  c = *d;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// REVSRC-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// REVSRC-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// REVSRC: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// REVSRC: "@": [[A]]$PTR_L2
+// REVSRC: "@": [[D]]$PTR_L3
+// REVSRC-NOT: "@":
+// REVSRC: "analysis_name":
+
+// REDEFINE: %{name} = reverse_star_from_sink
+// REDEFINE: %{prefix} = REVSINK
+// RUN: %{check}
+//--- reverse_star_from_sink.cpp
+// (a,1)->(d,1), (b,1)->(d,1), (c,1)->(d,1); start sink (d,2).
+// Result: {(d,2)}.
+void f(char **a, char **b, char **c, char **d, int i) {
+  a = d;
+  b = d;
+  c = d;
+  (*d)[i] = 0; // starter: (d,2)
+}
+// REVSINK-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// REVSINK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// REVSINK: "@": [[D]]$PTR_L2
+// REVSINK-NOT: "@":
+// REVSINK: "analysis_name":
+
+// REDEFINE: %{name} = self_loop_from_root
+// REDEFINE: %{prefix} = LOOPROOT
+// RUN: %{check}
+//--- self_loop_from_root.cpp
+// (a,1)->(b,1), (b,1)->(b,1), (b,1)->(c,2), (c,1)->(d,2); start (a,2).
+// Result: {(a,2),(b,2),(c,3),(d,4)}.
+void f(char **a, char **b, char ***c, char ****d, int i) {
+  a = b;
+  b = b;
+  b = *c;
+  c = *d;
+  (*a)[i] = 0; // starter: (a,2)
+}
+// LOOPROOT-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// LOOPROOT-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// LOOPROOT-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// LOOPROOT-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// LOOPROOT: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// LOOPROOT: "@": [[A]]$PTR_L2
+// LOOPROOT: "@": [[B]]$PTR_L2
+// LOOPROOT: "@": [[C]]$PTR_L3
+// LOOPROOT: "@": [[D]]$PTR_L4
+// LOOPROOT-NOT: "@":
+// LOOPROOT: "analysis_name":
+
+// REDEFINE: %{name} = self_loop_from_loop_node
+// REDEFINE: %{prefix} = LOOPNODE
+// RUN: %{check}
+//--- self_loop_from_loop_node.cpp
+// Same graph as self_loop_from_root; start on the loop node (b,2).
+// Result: {(b,2),(c,3),(d,4)}.
+void f(char **a, char **b, char ***c, char ****d, int i) {
+  a = b;
+  b = b;
+  b = *c;
+  c = *d;
+  (*b)[i] = 0; // starter: (b,2)
+}
+// LOOPNODE-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// LOOPNODE-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// LOOPNODE-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// LOOPNODE: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// LOOPNODE: "@": [[B]]$PTR_L2
+// LOOPNODE: "@": [[C]]$PTR_L3
+// LOOPNODE: "@": [[D]]$PTR_L4
+// LOOPNODE-NOT: "@":
+// LOOPNODE: "analysis_name":
+
+// REDEFINE: %{name} = multiple_starters
+// REDEFINE: %{prefix} = MULTISTART
+// RUN: %{check}
+//--- multiple_starters.cpp
+// (a,1)->(b,2), (c,1)->(d,2); start {(a,2),(c,2)}.
+// Result: {(a,2),(b,3),(c,2),(d,3)}.
+void f(char **a, char ***b, char **c, char ***d, int i) {
+  a = *b;
+  c = *d;
+  (*a)[i] = 0; // starter: (a,2)
+  (*c)[i] = 0; // starter: (c,2)
+}
+// MULTISTART-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// MULTISTART-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// MULTISTART-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// MULTISTART-DAG: "id": [[D:[0-9]+]],$NS$WS"suffix": "4"
+// MULTISTART: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// MULTISTART: "@": [[A]]$PTR_L2
+// MULTISTART: "@": [[B]]$PTR_L3
+// MULTISTART: "@": [[C]]$PTR_L2
+// MULTISTART: "@": [[D]]$PTR_L3
+// MULTISTART-NOT: "@":
+// MULTISTART: "analysis_name":
+
+// REDEFINE: %{name} = multiple_keys_same_entity
+// REDEFINE: %{prefix} = MULTIKEY
+// RUN: %{check}
+//--- multiple_keys_same_entity.cpp
+// (a,1)->(b,1), (a,2)->(c,1); start (a,3).
+// Result: {(a,3),(b,3),(c,2)}.
+void f(char ***a, char ***b, char **c, int i) {
+  a = b;
+  *a = c;
+  (**a)[i] = 0; // starter: (a,3)
+}
+// MULTIKEY-DAG: "id": [[A:[0-9]+]],$NS$WS"suffix": "1"
+// MULTIKEY-DAG: "id": [[B:[0-9]+]],$NS$WS"suffix": "2"
+// MULTIKEY-DAG: "id": [[C:[0-9]+]],$NS$WS"suffix": "3"
+// MULTIKEY: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// MULTIKEY: "@": [[A]]$PTR_L3
+// MULTIKEY: "@": [[B]]$PTR_L3
+// MULTIKEY: "@": [[C]]$PTR_L2
+// MULTIKEY-NOT: "@":
+// MULTIKEY: "analysis_name":
diff --git a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp
index cf5420b35e7dc..64486d1a22226 100644
--- a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/UnsafeBufferReachableAnalysisTest.cpp
@@ -205,31 +205,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, LinearChain) {
   EXPECT_EQ(Reachables.size(), 4u);
 }
 
-// Linear chain: (a,1) -> (b,2), (b,1) -> (c,2), (c,1) -> (d,2).
-// Start from {(a,2)} => {(a,2), (b,3), (c,4), (d,5)}
-TEST_F(UnsafeBufferReachableAnalysisTest, LinearChain2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 2}}, {{'b', 1}, {'c', 2}}, {{'c', 1}, {'d', 2}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables.size(), 4u);
-  EXPECT_EQ(Reachables,
-            (std::set<Node>{{'a', 2}, {'b', 3}, {'c', 4}, {'d', 5}}));
-}
-
-// Linear chain: (a,1) -> (b,2), (b,4) -> (c,1) -> (d,1).
-// Start from {(a,2)} => {(a,2), (b,3)} (halted at (b,3) — no key (b,j<=3))
-TEST_F(UnsafeBufferReachableAnalysisTest, LinearChain3) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 2}}, {{'b', 4}, {'c', 1}}, {{'c', 1}, {'d', 1}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables.size(), 2u);
-  EXPECT_EQ(Reachables, (std::set<Node>{{'a', 2}, {'b', 3}}));
-}
-
 // Linear chain: (a,1) -> (b,1) -> (c,1) -> (d,1).
 // Start from mid-chain {(c,1)} => {(c,1), (d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, LinearChainFromMiddle) {
@@ -257,35 +232,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, Diamond) {
   EXPECT_EQ(Reachables.size(), 4u);
 }
 
-// Diamond: (a,1) -> (b,2), (a,1) -> (c,2), (b,1) -> (d,2), (c,1) -> (d,2).
-// Start from {(a,2)} => {(a,2), (b,3), (c,3), (d,4)}
-TEST_F(UnsafeBufferReachableAnalysisTest, Diamond2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 2}},
-       {{'a', 1}, {'c', 2}},
-       {{'b', 1}, {'d', 2}},
-       {{'c', 1}, {'d', 2}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables,
-            (std::set<Node>{{'a', 2}, {'b', 3}, {'c', 3}, {'d', 4}}));
-}
-
-// DisconnectedDiamond: (a,1) -> (b,2), (a,1) -> (c,2), (b,5) -> (d,1), (c,5) ->
-// (d,1). Start from {(a,2)} => {(a,2), (b,3), (c,3)}
-TEST_F(UnsafeBufferReachableAnalysisTest, DisconnectedDiamond) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 2}},
-       {{'a', 1}, {'c', 2}},
-       {{'b', 5}, {'d', 1}},
-       {{'c', 5}, {'d', 1}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables, (std::set<Node>{{'a', 2}, {'b', 3}, {'c', 3}}));
-}
-
 // Diamond: (a,1) -> (b,1), (a,1) -> (c,1), (b,1) -> (d,1), (c,1) -> (d,1).
 // Start from one branch {(b,1)} => {(b,1), (d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, DiamondFromBranch) {
@@ -314,17 +260,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, DisconnectedSubgraphs) {
   EXPECT_TRUE(Reachables.count({'b', 1}));
 }
 
-// Disconnected subgraphs: (a,1) -> (b,1), (c,1) -> (d,1).
-// Start from tail {(b,1)} => {(b,1)}
-TEST_F(UnsafeBufferReachableAnalysisTest, DisconnectedSubgraphs2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */ {{{'a', 1}, {'b', 1}}, {{'c', 1}, {'d', 1}}},
-      /* StarterLayout */ {{'b', 1}}, __LINE__);
-  EXPECT_EQ(Reachables.size(), 1u);
-  EXPECT_TRUE(Reachables.count({'b', 1}));
-}
-
 // Cycle: (a,1) -> (b,1) -> (c,1) -> (d,1) -> (a,1).
 // Start from {(c,1)} => {(a,1), (b,1), (c,1), (d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, Cycle) {
@@ -343,36 +278,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, Cycle) {
   EXPECT_TRUE(Reachables.count({'d', 1}));
 }
 
-// Cycle: (a,1) -> (b,1) -> (c,1) -> (d,1) -> (a,1).
-// Start from {(c,2)} => {(a,2), (b,2), (c,2), (d,2)}
-TEST_F(UnsafeBufferReachableAnalysisTest, Cycle2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 1}},
-       {{'b', 1}, {'c', 1}},
-       {{'c', 1}, {'d', 1}},
-       {{'d', 1}, {'a', 1}}},
-      /* StarterLayout */ {{'c', 2}}, __LINE__);
-  EXPECT_EQ(Reachables,
-            (std::set<Node>{{'a', 2}, {'b', 2}, {'c', 2}, {'d', 2}}));
-}
-
-// Cycle: (a,1) -> (b,2) -> (c,3) -> (d,4) -> (a,1).
-// Start from {(a,2)} => {(a,2), (b,3), (c,4), (d,5)}
-TEST_F(UnsafeBufferReachableAnalysisTest, Cycle3) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 2}},
-       {{'b', 2}, {'c', 3}},
-       {{'c', 3}, {'d', 4}},
-       {{'d', 4}, {'a', 1}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables,
-            (std::set<Node>{{'a', 2}, {'b', 3}, {'c', 4}, {'d', 5}}));
-}
-
 // Empty graph: no edges, start from {(a,1)} => {(a,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, EmptyGraph) {
   auto Reachables = singlePartition(
@@ -394,29 +299,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, StarFromHub) {
   EXPECT_EQ(Reachables.size(), 4u);
 }
 
-// Star: (a,1) -> (b,2), (a,1) -> (c,2), (a,1) -> (d,2).
-// Start from {(a,2)} => {(a,2), (b,3), (c,3), (d,3)}
-TEST_F(UnsafeBufferReachableAnalysisTest, StarFromHub2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 2}}, {{'a', 1}, {'c', 2}}, {{'a', 1}, {'d', 2}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables,
-            (std::set<Node>{{'a', 2}, {'b', 3}, {'c', 3}, {'d', 3}}));
-}
-
-// Star: (a,2) -> (b,1), (a,2) -> (c,1), (a,2) -> (d,1).
-// Start from {(a,1)} => {(a,1)}
-TEST_F(UnsafeBufferReachableAnalysisTest, StarFromHub3) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 2}, {'b', 1}}, {{'a', 2}, {'c', 1}}, {{'a', 2}, {'d', 1}}},
-      /* StarterLayout */ {{'a', 1}}, __LINE__);
-  EXPECT_EQ(Reachables, (std::set<Node>{{'a', 1}}));
-}
-
 // Star: (a,1) -> (b,1), (a,1) -> (c,1), (a,1) -> (d,1).
 // Start from leaf {(c,1)} => {(c,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, StarFromLeaf) {
@@ -442,17 +324,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, ReverseStarFromSource) {
   EXPECT_TRUE(Reachables.count({'d', 1}));
 }
 
-// Reverse star: (a,1) -> (d,2), (b,1) -> (d,2), (c,1) -> (d,2).
-// Start from {(a,2)} => {(a,2), (d,3)}
-TEST_F(UnsafeBufferReachableAnalysisTest, ReverseStarFromSource2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'d', 2}}, {{'b', 1}, {'d', 2}}, {{'c', 1}, {'d', 2}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables, (std::set<Node>{{'a', 2}, {'d', 3}}));
-}
-
 // Reverse star: (a,1) -> (d,1), (b,1) -> (d,1), (c,1) -> (d,1).
 // Start from sink {(d,1)} => {(d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, ReverseStarFromSink) {
@@ -465,17 +336,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, ReverseStarFromSink) {
   EXPECT_TRUE(Reachables.count({'d', 1}));
 }
 
-// Reverse star: (a,1) -> (d,1), (b,1) -> (d,1), (c,1) -> (d,1).
-// Start from sink {(d,2)} => {(d,2)}
-TEST_F(UnsafeBufferReachableAnalysisTest, ReverseStarFromSink2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'d', 1}}, {{'b', 1}, {'d', 1}}, {{'c', 1}, {'d', 1}}},
-      /* StarterLayout */ {{'d', 2}}, __LINE__);
-  EXPECT_EQ(Reachables, (std::set<Node>{{'d', 2}}));
-}
-
 // Self-loop: (a,1) -> (b,1) -> (b,1) -> (c,1) -> (d,1).
 // Start from {(a,1)} => {(a,1), (b,1), (c,1), (d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, SelfLoopFromRoot) {
@@ -490,21 +350,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, SelfLoopFromRoot) {
   EXPECT_EQ(Reachables.size(), 4u);
 }
 
-// Self-loop: (a,1) -> (b,1) -> (b,1) -> (c,2) -> (d,2).
-// Start from {(a,2)} => {(a,2), (b,2), (c,3), (d,4)}
-TEST_F(UnsafeBufferReachableAnalysisTest, SelfLoopFromRoot2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 1}},
-       {{'b', 1}, {'b', 1}},
-       {{'b', 1}, {'c', 2}},
-       {{'c', 1}, {'d', 2}}},
-      /* StarterLayout */ {{'a', 2}}, __LINE__);
-  EXPECT_EQ(Reachables,
-            (std::set<Node>{{'a', 2}, {'b', 2}, {'c', 3}, {'d', 4}}));
-}
-
 // Self-loop: (a,1) -> (b,1) -> (b,1) -> (c,1) -> (d,1).
 // Start from {(b,1)} => {(b,1), (c,1), (d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, SelfLoopFromLoopNode) {
@@ -522,20 +367,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, SelfLoopFromLoopNode) {
   EXPECT_TRUE(Reachables.count({'d', 1}));
 }
 
-// Self-loop: (a,1) -> (b,1) -> (b,1) -> (c,2) -> (d,2).
-// Start from {(b,2)} => {(b,2), (c,3), (d,4)}
-TEST_F(UnsafeBufferReachableAnalysisTest, SelfLoopFromLoopNode2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */
-      {{{'a', 1}, {'b', 1}},
-       {{'b', 1}, {'b', 1}},
-       {{'b', 1}, {'c', 2}},
-       {{'c', 1}, {'d', 2}}},
-      /* StarterLayout */ {{'b', 2}}, __LINE__);
-  EXPECT_EQ(Reachables, (std::set<Node>{{'b', 2}, {'c', 3}, {'d', 4}}));
-}
-
 // Multiple starters: (a,1) -> (b,1), (c,1) -> (d,1) (disconnected).
 // Start from {(a,1), (c,1)} => {(a,1), (b,1), (c,1), (d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, MultipleStartersBothChains) {
@@ -546,17 +377,6 @@ TEST_F(UnsafeBufferReachableAnalysisTest, MultipleStartersBothChains) {
   EXPECT_EQ(Reachables.size(), 4u);
 }
 
-// Multiple starters: (a,1) -> (b,2), (c,1) -> (d,2).
-// Start from {(a,2), (c,2)} => {(a,2), (b,3), (c,2), (d,3)}
-TEST_F(UnsafeBufferReachableAnalysisTest, MultipleStartersBothChains2) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c', 'd'},
-      /* EdgeLayout */ {{{'a', 1}, {'b', 2}}, {{'c', 1}, {'d', 2}}},
-      /* StarterLayout */ {{'a', 2}, {'c', 2}}, __LINE__);
-  EXPECT_EQ(Reachables,
-            (std::set<Node>{{'a', 2}, {'b', 3}, {'c', 2}, {'d', 3}}));
-}
-
 // Multiple starters: (a,1) -> (b,1), (c,1) -> (d,1) (disconnected).
 // Start from leaves {(b,1), (d,1)} => {(b,1), (d,1)}
 TEST_F(UnsafeBufferReachableAnalysisTest, MultipleStartersLeaves) {
@@ -569,14 +389,4 @@ TEST_F(UnsafeBufferReachableAnalysisTest, MultipleStartersLeaves) {
   EXPECT_TRUE(Reachables.count({'d', 1}));
 }
 
-// Multi-key, same source entity: (a,1) -> (b,1), (a,2) -> (c,1).
-// Start from {(a,3)} => {(a,3), (b,3), (c,2)}
-TEST_F(UnsafeBufferReachableAnalysisTest, MultipleKeysSameEntity) {
-  auto Reachables = singlePartition(
-      /* EntityDomain */ {'a', 'b', 'c'},
-      /* EdgeLayout */ {{{'a', 1}, {'b', 1}}, {{'a', 2}, {'c', 1}}},
-      /* StarterLayout */ {{'a', 3}}, __LINE__);
-  EXPECT_EQ(Reachables, (std::set<Node>{{'a', 3}, {'b', 3}, {'c', 2}}));
-}
-
 } // namespace



More information about the llvm-branch-commits mailing list