https://github.com/ziqingluo-90 updated 
https://github.com/llvm/llvm-project/pull/209354

>From b218156690f92530f54a938fe015583bd6fc6ed8 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <[email protected]>
Date: Mon, 13 Jul 2026 18:44:23 -0700
Subject: [PATCH 1/6] [SSAF] Filter out type-constrained pointers from all
 reachable unsafe pointers

Integrate the TypeConstrainedPointers analysis results into
UnsafeBufferReachableAnalysis.  The final result is filtered with
type-constrainted pointers.

The pointer flow graph is untouched. Removing type-constrained
pointers from the graph will introduce unsoundness.

Final step for rdar://179151541&179151882
---
 .../UnsafeBufferUsageAnalysis.cpp             | 74 +++++++++++++++----
 .../WholeProgramAnalysis/AnalysisDriver.cpp   | 18 ++---
 .../external-inline-function-in-multi-tu.test |  2 +-
 .../PointerFlow/lref-to-rref-cast.test        |  2 +-
 .../PointerFlow/multi-decl-contributor.cpp    |  2 +-
 .../multi-dim-pointer-flow-constraint.test    |  2 +-
 ...achable-excludes-type-constrained-main.cpp | 40 ++++++++++
 ...e-excludes-type-constrained-new-delete.cpp | 74 +++++++++++++++++++
 .../ssaf-analyzer/Outputs/empty-pairs.json    | 26 +++++++
 .../Scalable/ssaf-analyzer/analyzer.test      | 10 +--
 10 files changed, 215 insertions(+), 35 deletions(-)
 create mode 100644 
clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
 create mode 100644 
clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
 create mode 100644 
clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/empty-pairs.json

diff --git 
a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
 
b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
index e404eb294ee4b..2d0a58f81a0f0 100644
--- 
a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
+++ 
b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -16,7 +16,9 @@
 #include 
"clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowAnalysis.h"
+#include 
"clang/ScalableStaticAnalysis/Analyses/TypeConstrainedPointers/TypeConstrainedPointers.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
 #include "clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h"
 #include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisRegistry.h"
 #include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/SummaryAnalysis.h"
@@ -124,12 +126,21 @@ 
JSONFormat::AnalysisResultRegistry::Add<UnsafeBufferReachableAnalysisResult>
         serializeUnsafeBufferReachableAnalysisResult,
         deserializeUnsafeBufferReachableAnalysisResult);
 
-/// Computes all the reachable "nodes" (pointers) in a pointer flow graph from 
a
-/// provided starter node set.  Specifically, the starter set is the unsafe
-/// pointers found by `UnsafeBufferUsageAnalysis`.
+/// \brief Computes pointers (EPLs) that satisfy a specific set of constraints.
+///
+/// The pointers must satisfy all of the following constraints:
+///
+/// 1. **C1 (Unsafe):** Any pointer in `UnsafeBufferUsageAnalysisResult`
+///    is considered unsafe.
+/// 2. **C2 (Reachable):** If a pointer is reachable from an unsafe pointer in
+///    the pointer flow graph (provided by `PointerFlowAnalysisResult`), it is
+///    also unsafe.
+/// 3. **C3 (Transformable):** Any pointer associated with type-constrained
+///    entities is NOT transformable.
 class UnsafeBufferReachableAnalysis
     : public DerivedAnalysis<UnsafeBufferReachableAnalysisResult,
                              PointerFlowAnalysisResult,
+                             TypeConstrainedPointersAnalysisResult,
                              UnsafeBufferUsageAnalysisResult> {
 
   /// BoundsPropagationGraph adds bounds propagation semantics to the
@@ -186,6 +197,7 @@ class UnsafeBufferReachableAnalysis
   };
 
   std::map<EntityId, BoundsPropagationGraph> BPG;
+  const std::set<EntityId> *TypeConstrainedEntities = nullptr;
 
   // Use pointers for efficiency. EPLs are in tree-based containers that only
   // grow. So pointers to them are stable.
@@ -207,18 +219,9 @@ class UnsafeBufferReachableAnalysis
     }
   }
 
-public:
-  llvm::Error
-  initialize(const PointerFlowAnalysisResult &PtrFlowGraph,
-             const UnsafeBufferUsageAnalysisResult &Starter) override {
-    for (auto &[Id, SubGraph] : PtrFlowGraph.Edges)
-      BPG.try_emplace(Id, BoundsPropagationGraph(SubGraph));
-    assert(getResult().Reachables.empty());
-    getResult().Reachables.insert(Starter.begin(), Starter.end());
-    return llvm::Error::success();
-  }
-
-  llvm::Expected<bool> step() override {
+  // Expand the initial set of C1 pointers in `getResult().Reachables` by
+  // computing and appending all reachable pointers, satisfying both C1 and C2.
+  void computeReachableUnsafePointers() {
     auto &Reachables = getResult().Reachables;
     // Simple DFS:
     std::vector<EPLPtr> Worklist;
@@ -233,6 +236,47 @@ class UnsafeBufferReachableAnalysis
 
       updateReachablesWithOutgoings(Node, Worklist);
     }
+  }
+
+  // Filter out non-transformable pointers from `getResult().Result`, leaving
+  // only those that satisfy C3.
+  void filterTransformablePointers() {
+    assert(TypeConstrainedEntities &&
+           "The initialize(...) method should initialize "
+           "TypeConstrainedEntities to non-null");
+    auto &Result = getResult().Reachables;
+
+    for (auto &[Key, EPLs] : Result) {
+      for (auto ConstrainedEntityId : *TypeConstrainedEntities) {
+        // FIXME: optimization chance here.  Since both sets are sorted, the
+        // next 'equal_range' search can ignore everything before
+        // `NonTransEPLs.second`.
+        auto NonTransEPLs = EPLs.equal_range(ConstrainedEntityId);
+
+        EPLs.erase(NonTransEPLs.first, NonTransEPLs.second);
+      }
+    }
+  }
+
+public:
+  llvm::Error
+  initialize(const PointerFlowAnalysisResult &PtrFlowGraph,
+             const TypeConstrainedPointersAnalysisResult &TypeConstraints,
+             const UnsafeBufferUsageAnalysisResult &UnsafePtrs) override {
+    for (auto &[Id, SubGraph] : PtrFlowGraph.Edges)
+      BPG.try_emplace(Id, BoundsPropagationGraph(SubGraph));
+    TypeConstrainedEntities = &TypeConstraints.Entities;
+    assert(getResult().Reachables.empty());
+    // C1: all pointers in UnsafeBufferUsageAnalysisResult are unsafe
+    getResult().Reachables.insert(UnsafePtrs.begin(), UnsafePtrs.end());
+    return llvm::Error::success();
+  }
+
+  llvm::Expected<bool> step() override {
+    // result meets C1 & C2:
+    computeReachableUnsafePointers();
+    // result meets C1 & C2 & C3:
+    filterTransformablePointers();
     // This is not an iterative algorithm so stop iteration by retruning false:
     return false;
   }
diff --git 
a/clang/lib/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisDriver.cpp 
b/clang/lib/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisDriver.cpp
index f60c916e10b67..5f7940bbb8bb5 100644
--- 
a/clang/lib/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisDriver.cpp
+++ 
b/clang/lib/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisDriver.cpp
@@ -103,23 +103,19 @@ AnalysisDriver::toposort(llvm::ArrayRef<AnalysisName> 
Roots) {
 llvm::Error AnalysisDriver::executeSummaryAnalysis(SummaryAnalysisBase 
&Summary,
                                                    WPASuite &Suite) const {
   SummaryName SN = Summary.getSummaryName();
-  auto DataIt = LU->Data.find(SN);
-  if (DataIt == LU->Data.end()) {
-    return ErrorBuilder::create(std::errc::invalid_argument,
-                                "no data for analysis '{0}' in LUSummary",
-                                Summary.getAnalysisName())
-        .build();
-  }
 
   if (auto Err = Summary.initialize()) {
     return Err;
   }
 
-  for (auto &[Id, EntitySummary] : DataIt->second) {
-    if (auto Err = Summary.add(Id, *EntitySummary)) {
-      return Err;
+  auto DataIt = LU->Data.find(SN);
+
+  if (DataIt != LU->Data.end())
+    for (auto &[Id, EntitySummary] : DataIt->second) {
+      if (auto Err = Summary.add(Id, *EntitySummary)) {
+        return Err;
+      }
     }
-  }
 
   if (auto Err = Summary.finalize()) {
     return Err;
diff --git 
a/clang/test/Analysis/Scalable/PointerFlow/external-inline-function-in-multi-tu.test
 
b/clang/test/Analysis/Scalable/PointerFlow/external-inline-function-in-multi-tu.test
index b0ffc1b3cf947..f0e98f23273d9 100644
--- 
a/clang/test/Analysis/Scalable/PointerFlow/external-inline-function-in-multi-tu.test
+++ 
b/clang/test/Analysis/Scalable/PointerFlow/external-inline-function-in-multi-tu.test
@@ -4,7 +4,7 @@
 // during bounds propagation.
 
 
-// DEFINE: %{extract} = %clang_cc1 -fsyntax-only -I %t 
--ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage
+// DEFINE: %{extract} = %clang_cc1 -fsyntax-only -I %t 
--ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers
 
 // RUN: rm -rf %t
 // RUN: mkdir -p %t
diff --git a/clang/test/Analysis/Scalable/PointerFlow/lref-to-rref-cast.test 
b/clang/test/Analysis/Scalable/PointerFlow/lref-to-rref-cast.test
index ca5df041240aa..fdb57741864e3 100644
--- a/clang/test/Analysis/Scalable/PointerFlow/lref-to-rref-cast.test
+++ b/clang/test/Analysis/Scalable/PointerFlow/lref-to-rref-cast.test
@@ -6,7 +6,7 @@
 
 // Extract per-TU PointerFlow + UnsafeBufferUsage summaries.
 // RUN: %clang_cc1 -fsyntax-only %t/tu.cpp \
-// RUN:   --ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage \
+// RUN:   
--ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers \
 // RUN:   --ssaf-tu-summary-file=%t/tu.summary.json \
 // RUN:   --ssaf-compilation-unit-id="tu-1"
 
diff --git 
a/clang/test/Analysis/Scalable/PointerFlow/multi-decl-contributor.cpp 
b/clang/test/Analysis/Scalable/PointerFlow/multi-decl-contributor.cpp
index 717a2875636b2..2d27026708eca 100644
--- a/clang/test/Analysis/Scalable/PointerFlow/multi-decl-contributor.cpp
+++ b/clang/test/Analysis/Scalable/PointerFlow/multi-decl-contributor.cpp
@@ -2,7 +2,7 @@
 
 
 // RUN: %clang_cc1 -fsyntax-only %s \
-// RUN:   --ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage \
+// RUN:   
--ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers \
 // RUN:   --ssaf-tu-summary-file=%t/tu.summary.json \
 // RUN:   --ssaf-compilation-unit-id="tu-1"
 
diff --git 
a/clang/test/Analysis/Scalable/PointerFlow/multi-dim-pointer-flow-constraint.test
 
b/clang/test/Analysis/Scalable/PointerFlow/multi-dim-pointer-flow-constraint.test
index 511b375c05f6c..d057f3e4c2ef9 100644
--- 
a/clang/test/Analysis/Scalable/PointerFlow/multi-dim-pointer-flow-constraint.test
+++ 
b/clang/test/Analysis/Scalable/PointerFlow/multi-dim-pointer-flow-constraint.test
@@ -6,7 +6,7 @@
 
 
 // RUN: %clang_cc1 -fsyntax-only %t/src.cpp \
-// RUN:   --ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage \
+// RUN:   
--ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers \
 // RUN:   --ssaf-compilation-unit-id="tu-1" \
 // RUN:   --ssaf-tu-summary-file=%t/src.summary.json
 
diff --git 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
new file mode 100644
index 0000000000000..f7ab07f517c03
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
@@ -0,0 +1,40 @@
+// Test that UnsafeBufferReachableAnalysis excludes type-constrained pointers
+
+// RUN: rm -rf %t && mkdir -p %t
+
+// RUN: %clang_cc1 -fsyntax-only %s \
+// RUN:   
--ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers \
+// RUN:   --ssaf-tu-summary-file=%t/tu.summary.json \
+// RUN:   --ssaf-compilation-unit-id="tu-1"
+
+// RUN: clang-ssaf-linker %t/tu.summary.json -o %t/lu.json
+
+// RUN: clang-ssaf-analyzer %t/lu.json -o %t/wpa.json \
+// RUN:   -a UnsafeBufferReachableAnalysisResult
+
+// RUN: FileCheck %s --input-file=%t/wpa.json
+
+
+int main(int argc, char **argv) {
+  argv[5] = 0; // unsafe use of a type-constrained pointer
+  return 0;
+}
+
+// 'q' is an ordinary unsafe pointer parameter and must remain in the result.
+void foo(int *q) {
+  q[5] = 0;
+}
+
+// CHECK-DAG: "id": 
[[Q_ID:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"1",[[:space:]]+"usr": }}"c:@F@foo#*I#"
+// CHECK-DAG: "id": 
[[ARGV_ID:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"2",[[:space:]]+"usr": }}"c:@F@main{{.*}}"
+
+// 'argv' is reported as type-constrained.
+// CHECK: "analysis_name": "TypeConstrainedPointersAnalysisResult"
+// CHECK: "@": [[ARGV_ID]]
+
+// In the reachable result 'q' is present but 'argv' is not.
+// CHECK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[Q_ID]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
+// CHECK-NOT: "@": [[ARGV_ID]]
+
+// CHECK: "analysis_name"
diff --git 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
new file mode 100644
index 0000000000000..c5a7a17bbf381
--- /dev/null
+++ 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
@@ -0,0 +1,74 @@
+// Test that UnsafeBufferReachableAnalysis excludes the
+// type-constrained pointers of 'operator new' / 'operator delete'
+// overloads.
+//
+// RUN: rm -rf %t && mkdir -p %t
+
+// RUN: %clang_cc1 -fsyntax-only %s \
+// RUN:   
--ssaf-extract-summaries=PointerFlow,UnsafeBufferUsage,TypeConstrainedPointers \
+// RUN:   --ssaf-tu-summary-file=%t/tu.summary.json \
+// RUN:   --ssaf-compilation-unit-id="tu-1"
+
+// RUN: clang-ssaf-linker %t/tu.summary.json -o %t/lu.json
+
+// RUN: clang-ssaf-analyzer %t/lu.json -o %t/wpa.json \
+// RUN:   -a UnsafeBufferReachableAnalysisResult
+
+// RUN: FileCheck %s --input-file=%t/wpa.json
+
+typedef __SIZE_TYPE__ size_t;
+
+// Return value and the 2nd parameter are type-constrained:
+void *operator new(size_t size, void *place) noexcept {
+  int *new_local = (int *)place;
+
+  return new_local;
+}
+
+// The parameter is type-constrained:
+void operator delete(void *ptr) noexcept {
+  int *delete_local = (int *)ptr;
+
+  delete_local[5] = 0;
+}
+
+void foo(int *p) {  
+  void *r = ::operator new(10, p);
+  int *q = (int *)r;
+
+  // 'q' is unsafe, it propagates along the path
+  // 'q -> r -> return_new -> new_local -> place'  
+  // All pointers along the path are rechable but return_new and place
+  // are type-constrained.
+  q[5] = 0; 
+  ::operator delete(q);
+}
+
+
+// CHECK: "id_table"
+// CHECK-DAG: "id": 
[[NEW_RET:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"0",[[:space:]]+"usr": }}"c:@F@operator new#l#*v#"
+// CHECK-DAG: "id": 
[[NEW_PLACE:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"2",[[:space:]]+"usr": }}"c:@F@operator new#l#*v#"
+// CHECK-DAG: "id": 
[[DEL_PTR:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"1",[[:space:]]+"usr": }}"c:@F@operator delete#*v#"
+
+// CHECK-DAG: "id": 
[[FOO_Q:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}foo#*I#@q"
+// CHECK-DAG: "id": 
[[FOO_R:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}foo#*I#@r"
+// CHECK-DAG: "id": 
[[NEW_LOCAL:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}operator new#l#*v#@new_local"
+// CHECK-DAG: "id": 
[[DELETE_LOCAL:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}operator delete#*v#@delete_local"
+
+// The return and every new/delete parameter are reported as type-constrained.
+// CHECK: "analysis_name": "TypeConstrainedPointersAnalysisResult"
+// CHECK-DAG: "@": [[NEW_RET]]
+// CHECK-DAG: "@": [[NEW_PLACE]]
+// CHECK-DAG: "@": [[DEL_PTR]]
+
+// CHECK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
+// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[FOO_Q]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
+// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[FOO_R]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
+// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[NEW_LOCAL]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
+// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[DELETE_LOCAL]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
+
+// CHECK-NOT: {{"@": }}[[NEW_RET]]{{[[:space:]]}}
+// CHECK-NOT: {{"@": }}[[NEW_PLACE]]{{[[:space:]]}}
+// CHECK-NOT: {{"@": }}[[DEL_PTR]]{{[[:space:]]}}
+
+// CHECK: "analysis_name"
diff --git 
a/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/empty-pairs.json 
b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/empty-pairs.json
new file mode 100644
index 0000000000000..2e59f60b3a3a6
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/empty-pairs.json
@@ -0,0 +1,26 @@
+{
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "LinkUnit",
+            "name": "test.exe"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F@foo#"
+      }
+    }
+  ],
+  "results": [
+    {
+      "analysis_name": "PairsAnalysisResult",
+      "result": {
+        "pair_counts": []
+      }
+    }
+  ],
+  "type": "WPASuite"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test 
b/clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test
index 0abdcef15a449..f76b2b78b52f4 100644
--- a/clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test
+++ b/clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test
@@ -15,13 +15,13 @@
 // UNKNOWN: no analysis registered for 'AnalysisName(NoSuchAnalysis)'
 
 // ============================================================================
-// Error: valid analysis name but LUSummary lacks entity data for it
+// Success: valid analysis name but LUSummary lacks entity data for it yields 
an
+// empty result (missing data is not an error)
 // ============================================================================
 
-// RUN: not %clang-ssaf-analyzer-with-plugin %S/Inputs/lu-tags-only.json \
-// RUN:   -o %t/missing-data.json -a PairsAnalysisResult 2>&1 \
-// RUN:   | FileCheck %s --check-prefix=MISSING-DATA
-// MISSING-DATA: no data for analysis 'AnalysisName(PairsAnalysisResult)' in 
LUSummary
+// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu-tags-only.json \
+// RUN:   -o %t/empty-pairs.json -a PairsAnalysisResult
+// RUN: diff %S/Outputs/empty-pairs.json %t/empty-pairs.json
 
 // ============================================================================
 // Success: run TagsAnalysisResult only (single analysis)

>From 486a7ad427b2bbcb6a48948287b78abd4fbfc694 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <[email protected]>
Date: Tue, 14 Jul 2026 13:25:02 -0700
Subject: [PATCH 2/6] fix unit tests that asserted empty analysis results are
 errors

---
 .../AnalysisDriverTest.cpp                    | 34 ++++++++++++-------
 1 file changed, 22 insertions(+), 12 deletions(-)

diff --git 
a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp
 
b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp
index 95077675fb2ec..9e518f08cc7de 100644
--- 
a/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp
+++ 
b/clang/unittests/ScalableStaticAnalysis/WholeProgramAnalysis/AnalysisDriverTest.cpp
@@ -468,15 +468,20 @@ TEST_F(AnalysisDriverTest, RunByName) {
           "no result for 'AnalysisName(Analysis2)' in WPASuite"));
 }
 
-// run(names) — error when a requested name has no data in LUSummary.
-TEST_F(AnalysisDriverTest, RunByNameErrorMissingData) {
+// run(names) — a requested name with no data in the LUSummary yields an empty
+// (but initialized and finalized) result rather than an error.
+TEST_F(AnalysisDriverTest, RunByNameEmptyWhenMissingData) {
   auto LU = makeLUSummary();
   AnalysisDriver Driver(std::move(LU));
 
-  EXPECT_THAT_EXPECTED(
-      Driver.run({AnalysisName("Analysis1")}),
-      llvm::FailedWithMessage(
-          "no data for analysis 'AnalysisName(Analysis1)' in LUSummary"));
+  auto WPAOrErr = Driver.run({AnalysisName("Analysis1")});
+  ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
+
+  auto R1OrErr = WPAOrErr->get<Analysis1Result>();
+  ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded());
+  EXPECT_TRUE(R1OrErr->Entries.empty());
+  EXPECT_TRUE(R1OrErr->WasInitialized);
+  EXPECT_TRUE(R1OrErr->WasFinalized);
 }
 
 // run(names) — error when a requested name has no registered analysis.
@@ -522,15 +527,20 @@ TEST_F(AnalysisDriverTest, RunByType) {
           "no result for 'AnalysisName(Analysis2)' in WPASuite"));
 }
 
-// run<ResultTs...>() — error when a requested type has no data in LUSummary.
-TEST_F(AnalysisDriverTest, RunByTypeErrorMissingData) {
+// run<ResultTs...>() — a requested type with no data in the LUSummary yields 
an
+// empty (but initialized and finalized) result rather than an error.
+TEST_F(AnalysisDriverTest, RunByTypeEmptyWhenMissingData) {
   auto LU = makeLUSummary();
   AnalysisDriver Driver(std::move(LU));
 
-  EXPECT_THAT_EXPECTED(
-      Driver.run<Analysis1Result>(),
-      llvm::FailedWithMessage(
-          "no data for analysis 'AnalysisName(Analysis1)' in LUSummary"));
+  auto WPAOrErr = Driver.run<Analysis1Result>();
+  ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
+
+  auto R1OrErr = WPAOrErr->get<Analysis1Result>();
+  ASSERT_THAT_EXPECTED(R1OrErr, llvm::Succeeded());
+  EXPECT_TRUE(R1OrErr->Entries.empty());
+  EXPECT_TRUE(R1OrErr->WasInitialized);
+  EXPECT_TRUE(R1OrErr->WasFinalized);
 }
 
 // contains() — present entries return true; absent entries return false.

>From 97c6a8880a7344dbfd7305d28fc0aeb66c716ac6 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <[email protected]>
Date: Tue, 14 Jul 2026 16:52:59 -0700
Subject: [PATCH 3/6] USR is not platform-independent. CheckFile matches should
 be flexible on types

---
 ...uffer-reachable-excludes-type-constrained-new-delete.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
index c5a7a17bbf381..6b3d037952f74 100644
--- 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
+++ 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
@@ -46,13 +46,13 @@ void foo(int *p) {
 
 
 // CHECK: "id_table"
-// CHECK-DAG: "id": 
[[NEW_RET:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"0",[[:space:]]+"usr": }}"c:@F@operator new#l#*v#"
-// CHECK-DAG: "id": 
[[NEW_PLACE:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"2",[[:space:]]+"usr": }}"c:@F@operator new#l#*v#"
+// CHECK-DAG: "id": 
[[NEW_RET:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"0",[[:space:]]+"usr": }}"c:@F@operator new#{{[^#]+}}#*v#"
+// CHECK-DAG: "id": 
[[NEW_PLACE:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"2",[[:space:]]+"usr": }}"c:@F@operator new#{{[^#]+}}#*v#"
 // CHECK-DAG: "id": 
[[DEL_PTR:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"1",[[:space:]]+"usr": }}"c:@F@operator delete#*v#"
 
 // CHECK-DAG: "id": 
[[FOO_Q:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}foo#*I#@q"
 // CHECK-DAG: "id": 
[[FOO_R:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}foo#*I#@r"
-// CHECK-DAG: "id": 
[[NEW_LOCAL:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}operator new#l#*v#@new_local"
+// CHECK-DAG: "id": 
[[NEW_LOCAL:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}operator new#{{[^#]+}}#*v#@new_local"
 // CHECK-DAG: "id": 
[[DELETE_LOCAL:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}operator delete#*v#@delete_local"
 
 // The return and every new/delete parameter are reported as type-constrained.

>From c216b849a3c044cb543b588678092daa7ff7b47d Mon Sep 17 00:00:00 2001
From: Ziqing Luo <[email protected]>
Date: Sat, 18 Jul 2026 22:40:42 -0700
Subject: [PATCH 4/6] address comments

---
 .../UnsafeBufferUsageAnalysis.cpp             | 81 ++++++++++++-------
 ...achable-excludes-type-constrained-main.cpp | 17 +++-
 ...e-excludes-type-constrained-new-delete.cpp | 66 ++++++++++-----
 3 files changed, 108 insertions(+), 56 deletions(-)

diff --git 
a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
 
b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
index 2d0a58f81a0f0..11a5d61645e36 100644
--- 
a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
+++ 
b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -15,6 +15,7 @@
 #include "SSAFAnalysesCommon.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlow.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowAnalysis.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/TypeConstrainedPointers/TypeConstrainedPointers.h"
 #include 
"clang/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h"
@@ -22,6 +23,8 @@
 #include "clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h"
 #include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisRegistry.h"
 #include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/SummaryAnalysis.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/iterator_range.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/JSON.h"
 #include <memory>
@@ -135,8 +138,7 @@ 
JSONFormat::AnalysisResultRegistry::Add<UnsafeBufferReachableAnalysisResult>
 /// 2. **C2 (Reachable):** If a pointer is reachable from an unsafe pointer in
 ///    the pointer flow graph (provided by `PointerFlowAnalysisResult`), it is
 ///    also unsafe.
-/// 3. **C3 (Transformable):** Any pointer associated with type-constrained
-///    entities is NOT transformable.
+/// 3. **C3 (Constrained):** Type-constrained entities are NOT unsafe.
 class UnsafeBufferReachableAnalysis
     : public DerivedAnalysis<UnsafeBufferReachableAnalysisResult,
                              PointerFlowAnalysisResult,
@@ -170,11 +172,11 @@ class UnsafeBufferReachableAnalysis
   ///   bound based on the maximum pointer level the pointer type can have.
   struct BoundsPropagationGraph {
   private:
-    const std::map<EntityPointerLevel, EntityPointerLevelSet> &PointerFlows;
+    EdgeSet PointerFlows;
 
   public:
-    BoundsPropagationGraph(const EdgeSet &PointerFlows)
-        : PointerFlows(PointerFlows) {}
+    BoundsPropagationGraph(EdgeSet PointerFlows)
+        : PointerFlows(std::move(PointerFlows)) {}
 
     /// Returns the EntityPointerLevelSet that are reachable from \p Src by
     /// one edge in the BoundsPropagationGraph.
@@ -197,7 +199,6 @@ class UnsafeBufferReachableAnalysis
   };
 
   std::map<EntityId, BoundsPropagationGraph> BPG;
-  const std::set<EntityId> *TypeConstrainedEntities = nullptr;
 
   // Use pointers for efficiency. EPLs are in tree-based containers that only
   // grow. So pointers to them are stable.
@@ -238,24 +239,37 @@ class UnsafeBufferReachableAnalysis
     }
   }
 
-  // Filter out non-transformable pointers from `getResult().Result`, leaving
-  // only those that satisfy C3.
-  void filterTransformablePointers() {
-    assert(TypeConstrainedEntities &&
-           "The initialize(...) method should initialize "
-           "TypeConstrainedEntities to non-null");
-    auto &Result = getResult().Reachables;
-
-    for (auto &[Key, EPLs] : Result) {
-      for (auto ConstrainedEntityId : *TypeConstrainedEntities) {
-        // FIXME: optimization chance here.  Since both sets are sorted, the
-        // next 'equal_range' search can ignore everything before
-        // `NonTransEPLs.second`.
-        auto NonTransEPLs = EPLs.equal_range(ConstrainedEntityId);
-
-        EPLs.erase(NonTransEPLs.first, NonTransEPLs.second);
-      }
+  /// \return a lazy range over the pointers in \p UnsafePtrs whose entity is
+  /// NOT type-constrained (per \p TypeConstraints).
+  auto filterNonConstrainedPointers(
+      const EntityPointerLevelSet &UnsafePtrs,
+      const TypeConstrainedPointersAnalysisResult &TypeConstraints) {
+    return llvm::make_filter_range(
+        UnsafePtrs, [&TypeConstraints](const EntityPointerLevel &EPL) {
+          return !TypeConstraints.contains(EPL.getEntity());
+        });
+  }
+
+  /// \return a copy of \p Edges but with no edge involving a type-constrained
+  /// entity (per \p TypeConstraints).
+  EdgeSet filterNonConstrainedEdges(
+      const EdgeSet &Edges,
+      const TypeConstrainedPointersAnalysisResult &TypeConstraints) {
+    EdgeSet Result;
+
+    for (const auto &[Src, Dsts] : Edges) {
+      if (TypeConstraints.contains(Src.getEntity()))
+        continue;
+
+      EntityPointerLevelSet FilteredDsts;
+
+      for (const EntityPointerLevel &Dst : Dsts)
+        if (!TypeConstraints.contains(Dst.getEntity()))
+          FilteredDsts.insert(Dst);
+      if (!FilteredDsts.empty())
+        Result.emplace(Src, std::move(FilteredDsts));
     }
+    return Result;
   }
 
 public:
@@ -263,20 +277,25 @@ class UnsafeBufferReachableAnalysis
   initialize(const PointerFlowAnalysisResult &PtrFlowGraph,
              const TypeConstrainedPointersAnalysisResult &TypeConstraints,
              const UnsafeBufferUsageAnalysisResult &UnsafePtrs) override {
+    // Remove type-constrained pointers from `PtrFlowGraph` and `UnsafePtrs` so
+    // that the final result satisfies C3.
     for (auto &[Id, SubGraph] : PtrFlowGraph.Edges)
-      BPG.try_emplace(Id, BoundsPropagationGraph(SubGraph));
-    TypeConstrainedEntities = &TypeConstraints.Entities;
-    assert(getResult().Reachables.empty());
-    // C1: all pointers in UnsafeBufferUsageAnalysisResult are unsafe
-    getResult().Reachables.insert(UnsafePtrs.begin(), UnsafePtrs.end());
+      BPG.try_emplace(Id, BoundsPropagationGraph(filterNonConstrainedEdges(
+                              SubGraph, TypeConstraints)));
+    for (auto &[Contributor, EPLs] : UnsafePtrs) {
+      auto FilteredRange = filterNonConstrainedPointers(EPLs, TypeConstraints);
+
+      getResult().Reachables[Contributor].insert(FilteredRange.begin(),
+                                                 FilteredRange.end());
+    }
     return llvm::Error::success();
   }
 
   llvm::Expected<bool> step() override {
-    // result meets C1 & C2:
+    // Compute the reachable EPLs from the C1 unsafe pointers over the
+    // pointer-flow graph; both are already C3-filtered, so the result
+    // satisfies C1, C2, and C3.
     computeReachableUnsafePointers();
-    // result meets C1 & C2 & C3:
-    filterTransformablePointers();
     // This is not an iterative algorithm so stop iteration by retruning false:
     return false;
   }
diff --git 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
index f7ab07f517c03..ef6e32d72ef10 100644
--- 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
+++ 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
@@ -12,7 +12,16 @@
 // RUN: clang-ssaf-analyzer %t/lu.json -o %t/wpa.json \
 // RUN:   -a UnsafeBufferReachableAnalysisResult
 
-// RUN: FileCheck %s --input-file=%t/wpa.json
+// 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_L1 - a reachable-set entry closing as "}, 1 ]", i.e. pointer level 1.
+// RUN: sed -e 's|$NS|{{([^]]\|[[:space:]])+\\],}}|g' \
+// RUN:     -e 's|$WS|{{[[:space:]]+}}|g' \
+// RUN:     -e 's|$PTR_L1|{{[[:space:]]+\\},[[:space:]]+1[[:space:]]+\\]}}|g' \
+// RUN:     %s > %t/checks.txt
+// RUN: FileCheck %t/checks.txt --input-file=%t/wpa.json
 
 
 int main(int argc, char **argv) {
@@ -25,8 +34,8 @@ void foo(int *q) {
   q[5] = 0;
 }
 
-// CHECK-DAG: "id": 
[[Q_ID:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"1",[[:space:]]+"usr": }}"c:@F@foo#*I#"
-// CHECK-DAG: "id": 
[[ARGV_ID:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"2",[[:space:]]+"usr": }}"c:@F@main{{.*}}"
+// CHECK-DAG: "id": [[Q_ID:[0-9]+]],$NS$WS"suffix": "1",$WS"usr": 
"c:@F@foo#*I#"
+// CHECK-DAG: "id": [[ARGV_ID:[0-9]+]],$NS$WS"suffix": "2",$WS"usr": 
"c:@F@main{{.*}}"
 
 // 'argv' is reported as type-constrained.
 // CHECK: "analysis_name": "TypeConstrainedPointersAnalysisResult"
@@ -34,7 +43,7 @@ void foo(int *q) {
 
 // In the reachable result 'q' is present but 'argv' is not.
 // CHECK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
-// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[Q_ID]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
+// CHECK-DAG: "@": [[Q_ID]]$PTR_L1
 // CHECK-NOT: "@": [[ARGV_ID]]
 
 // CHECK: "analysis_name"
diff --git 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
index 6b3d037952f74..41cae10924f3d 100644
--- 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
+++ 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
@@ -14,7 +14,16 @@
 // RUN: clang-ssaf-analyzer %t/lu.json -o %t/wpa.json \
 // RUN:   -a UnsafeBufferReachableAnalysisResult
 
-// RUN: FileCheck %s --input-file=%t/wpa.json
+// 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_L1 - a reachable-set entry closing as "}, 1 ]", i.e. pointer level 1.
+// RUN: sed -e 's|$NS|{{([^]]\|[[:space:]])+\\],}}|g' \
+// RUN:     -e 's|$WS|{{[[:space:]]+}}|g' \
+// RUN:     -e 's|$PTR_L1|{{[[:space:]]+\\},[[:space:]]+1[[:space:]]+\\]}}|g' \
+// RUN:     %s > %t/checks.txt
+// RUN: FileCheck %t/checks.txt --input-file=%t/wpa.json
 
 typedef __SIZE_TYPE__ size_t;
 
@@ -36,24 +45,33 @@ void foo(int *p) {
   void *r = ::operator new(10, p);
   int *q = (int *)r;
 
-  // 'q' is unsafe, it propagates along the path
-  // 'q -> r -> return_new -> new_local -> place'  
-  // All pointers along the path are rechable but return_new and place
-  // are type-constrained.
-  q[5] = 0; 
+  // 'q' is unsafe and the original propagation path is
+  // 'q -> r -> return_new -> new_local -> place'.  However, since 'return_new'
+  // (also 'place') is type-constrained, it is removed from the graph, 
resulting
+  // in no path from 'q' to 'new_local'.
+  q[5] = 0;
   ::operator delete(q);
 }
 
+void delete_caller() {
+  char * foo;
+  delete(foo);
+}
+
 
 // CHECK: "id_table"
-// CHECK-DAG: "id": 
[[NEW_RET:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"0",[[:space:]]+"usr": }}"c:@F@operator new#{{[^#]+}}#*v#"
-// CHECK-DAG: "id": 
[[NEW_PLACE:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"2",[[:space:]]+"usr": }}"c:@F@operator new#{{[^#]+}}#*v#"
-// CHECK-DAG: "id": 
[[DEL_PTR:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"1",[[:space:]]+"usr": }}"c:@F@operator delete#*v#"
+// CHECK-DAG: "id": [[NEW_RET:[0-9]+]],$NS$WS"suffix": "0",$WS"usr": 
"c:@F@operator new#{{[^#]+}}#*v#"
+// CHECK-DAG: "id": [[NEW_PLACE:[0-9]+]],$NS$WS"suffix": "2",$WS"usr": 
"c:@F@operator new#{{[^#]+}}#*v#"
+// CHECK-DAG: "id": [[DEL_PTR:[0-9]+]],$NS$WS"suffix": "1",$WS"usr": 
"c:@F@operator delete#*v#"
+
+// CHECK-DAG: "id": [[FOO_Q:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}foo#*I#@q"
+// CHECK-DAG: "id": [[FOO_R:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}foo#*I#@r"
+// CHECK-DAG: "id": [[NEW_LOCAL:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}operator new#{{[^#]+}}#*v#@new_local"
+// CHECK-DAG: "id": [[DELETE_LOCAL:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}operator delete#*v#@delete_local"
 
-// CHECK-DAG: "id": 
[[FOO_Q:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}foo#*I#@q"
-// CHECK-DAG: "id": 
[[FOO_R:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}foo#*I#@r"
-// CHECK-DAG: "id": 
[[NEW_LOCAL:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}operator new#{{[^#]+}}#*v#@new_local"
-// CHECK-DAG: "id": 
[[DELETE_LOCAL:[0-9]+]],{{([^]]|[[:space:]])+\],[[:space:]]+"suffix": 
"",[[:space:]]+"usr": "[^"]+}}operator delete#*v#@delete_local"
+// Contributor (owner) functions, i.e. the keys of the reachable-result map:
+// CHECK-DAG: "id": [[CONTRIBUTOR_FOO:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"c:@F@foo#*I#"
+// CHECK-DAG: "id": [[CONTRIBUTOR_DELETE_FN:[0-9]+]],$NS$WS"suffix": 
"",$WS"usr": "c:@F@operator delete#*v#"
 
 // The return and every new/delete parameter are reported as type-constrained.
 // CHECK: "analysis_name": "TypeConstrainedPointersAnalysisResult"
@@ -62,13 +80,19 @@ void foo(int *p) {
 // CHECK-DAG: "@": [[DEL_PTR]]
 
 // CHECK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
-// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[FOO_Q]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
-// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[FOO_R]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
-// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[NEW_LOCAL]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
-// CHECK-DAG: {{\{[[:space:]]+}}"@": 
[[DELETE_LOCAL]]{{[[:space:]]+\},[[:space:]]+1[[:space:]]+\]}}
-
-// CHECK-NOT: {{"@": }}[[NEW_RET]]{{[[:space:]]}}
-// CHECK-NOT: {{"@": }}[[NEW_PLACE]]{{[[:space:]]}}
-// CHECK-NOT: {{"@": }}[[DEL_PTR]]{{[[:space:]]}}
+
+// 'foo' reaches its own unsafe pointers 'q' and 'r'.
+// CHECK: "@": [[CONTRIBUTOR_FOO]]$WS},$WS[
+// CHECK-DAG: "@": [[FOO_Q]]$PTR_L1
+// CHECK-DAG: "@": [[FOO_R]]$PTR_L1
+
+// 'operator delete' reaches its own unsafe pointer 'delete_local'.
+// CHECK: "@": [[CONTRIBUTOR_DELETE_FN]]$WS},$WS[
+// CHECK-DAG: "@": [[DELETE_LOCAL]]$PTR_L1
+
+// CHECK-NOT: "@": [[NEW_RET]]$WS
+// CHECK-NOT: "@": [[NEW_PLACE]]$WS
+// CHECK-NOT: "@": [[DEL_PTR]]$WS
+// CHECK-NOT: "@": [[NEW_LOCAL]]$WS
 
 // CHECK: "analysis_name"

>From d4a08d3c8529093903cf4f6c0805811b81f52b25 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <[email protected]>
Date: Sat, 18 Jul 2026 23:39:45 -0700
Subject: [PATCH 5/6] fix tests

---
 ...achable-excludes-type-constrained-main.cpp | 13 +++++++-
 ...e-excludes-type-constrained-new-delete.cpp | 32 +++++++++++++------
 2 files changed, 35 insertions(+), 10 deletions(-)

diff --git 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
index ef6e32d72ef10..95eff21fbb0e5 100644
--- 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
+++ 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-main.cpp
@@ -37,13 +37,24 @@ void foo(int *q) {
 // CHECK-DAG: "id": [[Q_ID:[0-9]+]],$NS$WS"suffix": "1",$WS"usr": 
"c:@F@foo#*I#"
 // CHECK-DAG: "id": [[ARGV_ID:[0-9]+]],$NS$WS"suffix": "2",$WS"usr": 
"c:@F@main{{.*}}"
 
+// Contributor function ids:
+// CHECK-DAG: "id": [[CONTRIBUTOR_FOO:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"c:@F@foo#*I#"
+// CHECK-DAG: "id": [[CONTRIBUTOR_MAIN:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"c:@F@main{{.*}}"
+
 // 'argv' is reported as type-constrained.
 // CHECK: "analysis_name": "TypeConstrainedPointersAnalysisResult"
 // CHECK: "@": [[ARGV_ID]]
 
 // In the reachable result 'q' is present but 'argv' is not.
 // CHECK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
-// CHECK-DAG: "@": [[Q_ID]]$PTR_L1
+
+// 'foo' contributes unsafe pointer 'q'.
+// CHECK: "@": [[CONTRIBUTOR_FOO]]$WS},$WS[
+// CHECK: "@": [[Q_ID]]$PTR_L1
+// CHECK-NOT: "@":
+
+// 'main' contributes nothing: 'argv' is type-constrained and excluded.
+// CHECK: "@": [[CONTRIBUTOR_MAIN]]$WS},$WS[
 // CHECK-NOT: "@": [[ARGV_ID]]
 
 // CHECK: "analysis_name"
diff --git 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
index 41cae10924f3d..798b714e14af2 100644
--- 
a/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
+++ 
b/clang/test/Analysis/Scalable/TypeConstrainedPointers/unsafe-buffer-reachable-excludes-type-constrained-new-delete.cpp
@@ -53,12 +53,16 @@ void foo(int *p) {
   ::operator delete(q);
 }
 
-void delete_caller() {
-  char * foo;
-  delete(foo);
-}
+void bar() {
+  char * x, *y;
 
+  // 'x' cannot be reached from an unsafe pointer because the
+  // parameter of 'delete' is type-constrained:
+  ::operator delete(x);
+  y[5] = 5;
+}
 
+// Save entity IDs in variables:
 // CHECK: "id_table"
 // CHECK-DAG: "id": [[NEW_RET:[0-9]+]],$NS$WS"suffix": "0",$WS"usr": 
"c:@F@operator new#{{[^#]+}}#*v#"
 // CHECK-DAG: "id": [[NEW_PLACE:[0-9]+]],$NS$WS"suffix": "2",$WS"usr": 
"c:@F@operator new#{{[^#]+}}#*v#"
@@ -68,10 +72,13 @@ void delete_caller() {
 // CHECK-DAG: "id": [[FOO_R:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}foo#*I#@r"
 // CHECK-DAG: "id": [[NEW_LOCAL:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}operator new#{{[^#]+}}#*v#@new_local"
 // CHECK-DAG: "id": [[DELETE_LOCAL:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}operator delete#*v#@delete_local"
+// CHECK-DAG: "id": [[BAR_Y:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}bar#@y"
+// CHECK-DAG: "id": [[BAR_X:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"{{[^"]+}}bar#@x"
 
-// Contributor (owner) functions, i.e. the keys of the reachable-result map:
+// Contributor function ids:
+// CHECK-DAG: "id": [[CONTRIBUTOR_BAR:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"c:@F@bar#"
 // CHECK-DAG: "id": [[CONTRIBUTOR_FOO:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"c:@F@foo#*I#"
-// CHECK-DAG: "id": [[CONTRIBUTOR_DELETE_FN:[0-9]+]],$NS$WS"suffix": 
"",$WS"usr": "c:@F@operator delete#*v#"
+// CHECK-DAG: "id": [[CONTRIBUTOR_DELETE:[0-9]+]],$NS$WS"suffix": "",$WS"usr": 
"c:@F@operator delete#*v#"
 
 // The return and every new/delete parameter are reported as type-constrained.
 // CHECK: "analysis_name": "TypeConstrainedPointersAnalysisResult"
@@ -81,15 +88,22 @@ void delete_caller() {
 
 // CHECK: "analysis_name": "UnsafeBufferReachableAnalysisResult"
 
-// 'foo' reaches its own unsafe pointers 'q' and 'r'.
+// 'bar' contributes unsafe pointer 'y' but not 'x':
+// CHECK: "@": [[CONTRIBUTOR_BAR]]$WS},$WS[
+// CHECK: "@": [[BAR_Y]]$PTR_L1
+// CHECK-NOT: "@": [[BAR_X]]$PTR_L1
+
+// 'foo' contributes unsafe pointers 'q' and 'r':
 // CHECK: "@": [[CONTRIBUTOR_FOO]]$WS},$WS[
 // CHECK-DAG: "@": [[FOO_Q]]$PTR_L1
 // CHECK-DAG: "@": [[FOO_R]]$PTR_L1
+// CHECK-NOT: "@":
 
-// 'operator delete' reaches its own unsafe pointer 'delete_local'.
-// CHECK: "@": [[CONTRIBUTOR_DELETE_FN]]$WS},$WS[
+// 'operator delete' contributes unsafe pointer 'delete_local':
+// CHECK: "@": [[CONTRIBUTOR_DELETE]]$WS},$WS[
 // CHECK-DAG: "@": [[DELETE_LOCAL]]$PTR_L1
 
+// The type-constrained pointers never appear in the reachable result:
 // CHECK-NOT: "@": [[NEW_RET]]$WS
 // CHECK-NOT: "@": [[NEW_PLACE]]$WS
 // CHECK-NOT: "@": [[DEL_PTR]]$WS

>From f0c08a4e8b4478a24cf4c34d10e4a560efdf212a Mon Sep 17 00:00:00 2001
From: Ziqing Luo <[email protected]>
Date: Sun, 19 Jul 2026 13:34:07 -0700
Subject: [PATCH 6/6] address comment

---
 .../UnsafeBufferUsageAnalysis.cpp             | 65 ++++++++-----------
 1 file changed, 26 insertions(+), 39 deletions(-)

diff --git 
a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
 
b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
index 11a5d61645e36..b745ecc617c8c 100644
--- 
a/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
+++ 
b/clang/lib/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
@@ -239,51 +239,38 @@ class UnsafeBufferReachableAnalysis
     }
   }
 
-  /// \return a lazy range over the pointers in \p UnsafePtrs whose entity is
-  /// NOT type-constrained (per \p TypeConstraints).
-  auto filterNonConstrainedPointers(
-      const EntityPointerLevelSet &UnsafePtrs,
-      const TypeConstrainedPointersAnalysisResult &TypeConstraints) {
-    return llvm::make_filter_range(
-        UnsafePtrs, [&TypeConstraints](const EntityPointerLevel &EPL) {
-          return !TypeConstraints.contains(EPL.getEntity());
-        });
-  }
-
-  /// \return a copy of \p Edges but with no edge involving a type-constrained
-  /// entity (per \p TypeConstraints).
-  EdgeSet filterNonConstrainedEdges(
-      const EdgeSet &Edges,
-      const TypeConstrainedPointersAnalysisResult &TypeConstraints) {
-    EdgeSet Result;
-
-    for (const auto &[Src, Dsts] : Edges) {
-      if (TypeConstraints.contains(Src.getEntity()))
-        continue;
-
-      EntityPointerLevelSet FilteredDsts;
-
-      for (const EntityPointerLevel &Dst : Dsts)
-        if (!TypeConstraints.contains(Dst.getEntity()))
-          FilteredDsts.insert(Dst);
-      if (!FilteredDsts.empty())
-        Result.emplace(Src, std::move(FilteredDsts));
-    }
-    return Result;
-  }
-
 public:
   llvm::Error
   initialize(const PointerFlowAnalysisResult &PtrFlowGraph,
              const TypeConstrainedPointersAnalysisResult &TypeConstraints,
              const UnsafeBufferUsageAnalysisResult &UnsafePtrs) override {
-    // Remove type-constrained pointers from `PtrFlowGraph` and `UnsafePtrs` so
-    // that the final result satisfies C3.
-    for (auto &[Id, SubGraph] : PtrFlowGraph.Edges)
-      BPG.try_emplace(Id, BoundsPropagationGraph(filterNonConstrainedEdges(
-                              SubGraph, TypeConstraints)));
+    // Filter out edges involving type-constrained pointers from 
`PtrFlowGraph`:
+    for (auto &[Id, SubGraph] : PtrFlowGraph.Edges) {
+      EdgeSet FilteredSubGraph;
+
+      for (const auto &[Src, Dsts] : SubGraph) {
+        if (TypeConstraints.contains(Src.getEntity()))
+          continue;
+
+        auto FilteredDstRange = llvm::make_filter_range(
+            Dsts, [&TypeConstraints](const EntityPointerLevel &EPL) {
+              return !TypeConstraints.contains(EPL.getEntity());
+            });
+
+        if (FilteredDstRange.begin() != FilteredDstRange.end())
+          FilteredSubGraph[Src].insert(FilteredDstRange.begin(),
+                                       FilteredDstRange.end());
+      }
+      if (!FilteredSubGraph.empty())
+        BPG.try_emplace(Id, std::move(FilteredSubGraph));
+    }
+
+    // Filter out type-constrained pointers from `UnsafePtrs`:
     for (auto &[Contributor, EPLs] : UnsafePtrs) {
-      auto FilteredRange = filterNonConstrainedPointers(EPLs, TypeConstraints);
+      auto FilteredRange = llvm::make_filter_range(
+          EPLs, [&TypeConstraints](const EntityPointerLevel &EPL) {
+            return !TypeConstraints.contains(EPL.getEntity());
+          });
 
       getResult().Reachables[Contributor].insert(FilteredRange.begin(),
                                                  FilteredRange.end());

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to