[clang] Thread Safety Analysis: Fix value-based comparison of literals in TIL (PR #179041)

via cfe-commits cfe-commits at lists.llvm.org
Sat Jan 31 06:49:54 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Marco Elver (melver)

<details>
<summary>Changes</summary>

Previously, til::Literal::compare() always returned true, effectively treating all literals (such as array indices 0 and 1) as identical. This led to false positives when different elements of an array were accessed or locked.

Fix it by adding compareLiterals() to perform value-based comparison for common literal types. This ensures that distinct literals are correctly distinguished.

Reported-by: Bart Van Assche <bvanassche@<!-- -->acm.org>

---
Full diff: https://github.com/llvm/llvm-project/pull/179041.diff


3 Files Affected:

- (modified) clang/include/clang/Analysis/Analyses/ThreadSafetyTIL.h (+9-2) 
- (modified) clang/lib/Analysis/ThreadSafetyTIL.cpp (+29) 
- (modified) clang/test/SemaCXX/warn-thread-safety-analysis.cpp (+26) 


``````````diff
diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafetyTIL.h b/clang/include/clang/Analysis/Analyses/ThreadSafetyTIL.h
index 14c5b679428a3..718fc342bd9cc 100644
--- a/clang/include/clang/Analysis/Analyses/ThreadSafetyTIL.h
+++ b/clang/include/clang/Analysis/Analyses/ThreadSafetyTIL.h
@@ -556,13 +556,20 @@ class Literal : public SExpr {
 
   template <class C>
   typename C::CType compare(const Literal* E, C& Cmp) const {
-    // TODO: defer actual comparison to LiteralT
-    return Cmp.trueResult();
+    // If the expressions are different AST nodes, try to compare their values.
+    if (Cexpr && E->Cexpr && Cexpr != E->Cexpr) {
+      if (compareLiterals(Cexpr, E->Cexpr))
+        return Cmp.trueResult();
+    }
+    return Cmp.comparePointers(Cexpr, E->Cexpr);
   }
 
 private:
   const ValueType ValType;
   const Expr *Cexpr = nullptr;
+
+  // Compare two literal expressions for value equality.
+  static bool compareLiterals(const Expr *E1, const Expr *E2);
 };
 
 // Derived class for literal values, which stores the actual value.
diff --git a/clang/lib/Analysis/ThreadSafetyTIL.cpp b/clang/lib/Analysis/ThreadSafetyTIL.cpp
index dcee891222a33..b032e389ddf1a 100644
--- a/clang/lib/Analysis/ThreadSafetyTIL.cpp
+++ b/clang/lib/Analysis/ThreadSafetyTIL.cpp
@@ -7,6 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Analysis/Analyses/ThreadSafetyTIL.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/Basic/LLVM.h"
 #include <cassert>
 #include <cstddef>
@@ -54,6 +56,33 @@ SExpr* Future::force() {
   return Result;
 }
 
+bool Literal::compareLiterals(const Expr *E1, const Expr *E2) {
+  if (E1->getStmtClass() != E2->getStmtClass())
+    return false;
+  switch (E1->getStmtClass()) {
+  case Stmt::IntegerLiteralClass:
+    return cast<IntegerLiteral>(E1)->getValue() ==
+           cast<IntegerLiteral>(E2)->getValue();
+  case Stmt::CharacterLiteralClass:
+    return cast<CharacterLiteral>(E1)->getValue() ==
+           cast<CharacterLiteral>(E2)->getValue();
+  case Stmt::CXXBoolLiteralExprClass:
+    return cast<CXXBoolLiteralExpr>(E1)->getValue() ==
+           cast<CXXBoolLiteralExpr>(E2)->getValue();
+  case Stmt::StringLiteralClass:
+    return cast<StringLiteral>(E1)->getString() ==
+           cast<StringLiteral>(E2)->getString();
+  case Stmt::FloatingLiteralClass:
+    return cast<FloatingLiteral>(E1)->getValue().bitwiseIsEqual(
+        cast<FloatingLiteral>(E2)->getValue());
+  case Stmt::CXXNullPtrLiteralExprClass:
+  case Stmt::GNUNullExprClass:
+    return true;
+  default:
+    return false;
+  }
+}
+
 unsigned BasicBlock::addPredecessor(BasicBlock *Pred) {
   unsigned Idx = Predecessors.size();
   Predecessors.reserveCheck(1, Arena);
diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 466135a1d9cef..0caee7ccbe5c4 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -7536,6 +7536,32 @@ void testNestedAcquire(Container *c) EXCLUSIVE_LOCK_FUNCTION(&c->foo.mu) {
   buf->mu.Lock();
 }
 
+// Test that array subscripts are not ignored.
+void testArrayOfContainers1() {
+  Container array[10];
+
+  Foo *ptr1 = &array[0].foo;
+  Foo *ptr2 = &array[1].foo;
+  ptr1->mu.Lock();
+  ptr2->mu.Lock();
+  array[0].foo.data = 0;
+  array[1].foo.data = 1;
+  ptr2->mu.Unlock();
+  ptr1->mu.Unlock();
+}
+
+// Test that we don't confuse different indices or constants.
+void testArrayOfContainers2() {
+  Container array[2];
+
+  Foo *ptr = &array[0].foo;
+  ptr->mu.Lock();
+  array[0].foo.data = 0;
+  array[1].foo.data = 1;  // expected-warning{{writing variable 'data' requires holding mutex 'array[1].foo.mu'}} \
+                          // expected-note{{found near match 'array[0].foo.mu'}}
+  ptr->mu.Unlock();
+}
+
 struct ContainerOfPtr {
   Foo *foo_ptr;
   ContainerOfPtr *next;

``````````

</details>


https://github.com/llvm/llvm-project/pull/179041


More information about the cfe-commits mailing list