[clang] [analyzer] Fix null-pointer dereference in PthreadLockChecker (PR #210912)

Arseniy Zaostrovnykh via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 22 05:32:17 PDT 2026


https://github.com/necto updated https://github.com/llvm/llvm-project/pull/210912

>From e9e747a0cad3ff0ca74a2b035fca1eacd492f168 Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Mon, 20 Jul 2026 13:20:00 +0200
Subject: [PATCH 1/2] Crashing test case

---
 clang/test/Analysis/pthreadlock.c | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/clang/test/Analysis/pthreadlock.c b/clang/test/Analysis/pthreadlock.c
index e931569c45ab8..426070ae3012a 100644
--- a/clang/test/Analysis/pthreadlock.c
+++ b/clang/test/Analysis/pthreadlock.c
@@ -1,8 +1,8 @@
 // RUN: %clang_analyze_cc1 \
-// RUN:   -analyzer-checker=alpha.unix.PthreadLock \
+// RUN:   -analyzer-checker=alpha.unix.PthreadLock,debug.ExprInspection \
 // RUN:   -verify %s
 // RUN: %clang_analyze_cc1 \
-// RUN:   -analyzer-checker=alpha.unix.PthreadLock \
+// RUN:   -analyzer-checker=alpha.unix.PthreadLock,debug.ExprInspection \
 // RUN:   -analyzer-config alpha.unix.PthreadLock:WarnOnLockOrderReversal=true \
 // RUN:   -verify=expected,lor %s
 
@@ -18,6 +18,28 @@ lck_rw_t rw;
 
 #define NULL 0
 
+void clang_analyzer_warnIfReached(void);
+long global_var;
+void noCrash(void) {
+  // Produce a complicated self-contradictory constraint
+  if (((global_var & 137) == 2) &&
+      ((global_var & 137) & 8)) {
+    // This branch is actually dead, but the analyzer does not realize that yet.
+    clang_analyzer_warnIfReached();
+    pthread_mutex_lock(&mtx1); // no-warning
+  }
+}
+
+void noCrashTryLock(void) {
+  // Produce a complicated self-contradictory constraint
+  if (((global_var & 137) == 2) &&
+      ((global_var & 137) & 8)) {
+    // This branch is actually dead, but the analyzer does not realize that yet.
+    clang_analyzer_warnIfReached();
+    pthread_mutex_trylock(&mtx1); // no-warning
+  }
+}
+
 void
 ok1(void)
 {

>From 36eca8abce0f944aab2b3cd5b7b40b60764d2eab Mon Sep 17 00:00:00 2001
From: Arseniy Zaostrovnykh <necto.ne at gmail.com>
Date: Tue, 21 Jul 2026 10:39:09 +0200
Subject: [PATCH 2/2] Fix PthreadLockChecker

---
 .../Checkers/PthreadLockChecker.cpp             | 17 ++++++++++++++---
 clang/test/Analysis/pthreadlock.c               |  4 ++--
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 6a1c7a93fb773..c3540ddc32e3a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -493,8 +493,11 @@ void PthreadLockChecker::AcquireLockAux(const CallEvent &Call,
       default:
         llvm_unreachable("Unknown tryLock locking semantics");
       }
-      assert(lockFail && lockSucc);
-      C.addTransition(lockFail);
+      // The state where the lock failed can be infeasible if the constraint
+      // solver only now discovers a contradiction in the accumulated
+      // constraints; only take that transition when it is feasible.
+      if (lockFail)
+        C.addTransition(lockFail);
     }
     // We might want to handle the case when the mutex lock function was inlined
     // and returned an Unknown or Undefined value.
@@ -505,7 +508,9 @@ void PthreadLockChecker::AcquireLockAux(const CallEvent &Call,
       // FIXME: If the lock function was inlined and returned true,
       // we need to behave sanely - at least generate sink.
       lockSucc = state->assume(*DefinedRetVal, false);
-      assert(lockSucc);
+      // `lockSucc` can be null here if the constraint solver only now detects
+      // a contradiction in the accumulated constraints; the shared guard below
+      // prunes this infeasible path.
     }
     // We might want to handle the case when the mutex lock function was inlined
     // and returned an Unknown or Undefined value.
@@ -515,6 +520,12 @@ void PthreadLockChecker::AcquireLockAux(const CallEvent &Call,
     lockSucc = state;
   }
 
+  // If the constraint solver determined the lock-acquired path is infeasible
+  // (which can surface here when it only now detects a contradiction in the
+  // accumulated constraints), prune this path.
+  if (!lockSucc)
+    return;
+
   // Record that the lock was acquired.
   lockSucc = lockSucc->add<LockSet>(lockR);
   lockSucc = lockSucc->set<LockMap>(lockR, LockState::getLocked());
diff --git a/clang/test/Analysis/pthreadlock.c b/clang/test/Analysis/pthreadlock.c
index 426070ae3012a..49b83cab031a0 100644
--- a/clang/test/Analysis/pthreadlock.c
+++ b/clang/test/Analysis/pthreadlock.c
@@ -25,7 +25,7 @@ void noCrash(void) {
   if (((global_var & 137) == 2) &&
       ((global_var & 137) & 8)) {
     // This branch is actually dead, but the analyzer does not realize that yet.
-    clang_analyzer_warnIfReached();
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
     pthread_mutex_lock(&mtx1); // no-warning
   }
 }
@@ -35,7 +35,7 @@ void noCrashTryLock(void) {
   if (((global_var & 137) == 2) &&
       ((global_var & 137) & 8)) {
     // This branch is actually dead, but the analyzer does not realize that yet.
-    clang_analyzer_warnIfReached();
+    clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}}
     pthread_mutex_trylock(&mtx1); // no-warning
   }
 }



More information about the cfe-commits mailing list