Skip to content

Conversation

Prabhuk
Copy link
Contributor

@Prabhuk Prabhuk commented Sep 5, 2025

Before emitting warning about locks being held at the end of function
scope check if the underlying mutex is function scoped or not.

Issue: #156760

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:analysis labels Sep 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 5, 2025

@llvm/pr-subscribers-clang-static-analyzer-1
@llvm/pr-subscribers-libcxx

@llvm/pr-subscribers-clang

Author: Prabhu Rajasekaran (Prabhuk)

Changes

Before emitting warning about locks being held at the end of function
scope check if the underlying mutex is function scoped or not.


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

1 Files Affected:

  • (modified) clang/lib/Analysis/ThreadSafety.cpp (+16)
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index 131170df9976e..b215a6e6d74cc 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -2443,6 +2443,22 @@ void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &EntrySet,
       if (join(FactMan[*EntryIt], ExitFact, JoinLoc, EntryLEK))
         *EntryIt = Fact;
     } else if (!ExitFact.managed() || EntryLEK == LEK_LockedAtEndOfFunction) {
+      if (EntryLEK == LEK_LockedAtEndOfFunction) {
+        const til::SExpr *Sexp = ExitFact.sexpr();
+        const VarDecl *Var = nullptr;
+
+        if (const auto *Proj = dyn_cast<til::Project>(Sexp)) {
+          if (const auto *Base = dyn_cast<til::LiteralPtr>(Proj->record()))
+            Var = dyn_cast_or_null<VarDecl>(Base->clangDecl());
+        } else if (const auto *LP = dyn_cast<til::LiteralPtr>(Sexp)) {
+          Var = dyn_cast_or_null<VarDecl>(LP->clangDecl());
+        }
+
+        if (Var && Var->getStorageDuration() == SD_Automatic &&
+            Var->getDeclContext() == CurrentFunction) {
+          continue;
+        }
+      }
       ExitFact.handleRemovalFromIntersection(ExitSet, FactMan, JoinLoc,
                                              EntryLEK, Handler);
     }

@llvmbot
Copy link
Member

llvmbot commented Sep 5, 2025

@llvm/pr-subscribers-clang-analysis

Author: Prabhu Rajasekaran (Prabhuk)

Changes

Before emitting warning about locks being held at the end of function
scope check if the underlying mutex is function scoped or not.


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

1 Files Affected:

  • (modified) clang/lib/Analysis/ThreadSafety.cpp (+16)
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index 131170df9976e..b215a6e6d74cc 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -2443,6 +2443,22 @@ void ThreadSafetyAnalyzer::intersectAndWarn(FactSet &EntrySet,
       if (join(FactMan[*EntryIt], ExitFact, JoinLoc, EntryLEK))
         *EntryIt = Fact;
     } else if (!ExitFact.managed() || EntryLEK == LEK_LockedAtEndOfFunction) {
+      if (EntryLEK == LEK_LockedAtEndOfFunction) {
+        const til::SExpr *Sexp = ExitFact.sexpr();
+        const VarDecl *Var = nullptr;
+
+        if (const auto *Proj = dyn_cast<til::Project>(Sexp)) {
+          if (const auto *Base = dyn_cast<til::LiteralPtr>(Proj->record()))
+            Var = dyn_cast_or_null<VarDecl>(Base->clangDecl());
+        } else if (const auto *LP = dyn_cast<til::LiteralPtr>(Sexp)) {
+          Var = dyn_cast_or_null<VarDecl>(LP->clangDecl());
+        }
+
+        if (Var && Var->getStorageDuration() == SD_Automatic &&
+            Var->getDeclContext() == CurrentFunction) {
+          continue;
+        }
+      }
       ExitFact.handleRemovalFromIntersection(ExitSet, FactMan, JoinLoc,
                                              EntryLEK, Handler);
     }

@Prabhuk Prabhuk requested a review from PiJoules September 5, 2025 20:37
Before emitting warning about locks being held at the end of function
scope check if the underlying mutex is function scoped or not.
@Prabhuk Prabhuk requested a review from a team as a code owner September 5, 2025 20:46
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Sep 5, 2025
@Prabhuk Prabhuk requested a review from aaronpuchert September 5, 2025 21:08
Copy link
Contributor

@PiJoules PiJoules left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and can confirm this fixes #156760. Could you add that repro as a regression test here.

@Prabhuk
Copy link
Contributor Author

Prabhuk commented Sep 5, 2025

LGTM and can confirm this fixes #156760. Could you add that repro as a regression test here.

Can you check if the regression tests added to clang/test/PCH/thread-safety-attrs.cpp covers the cases?

@PiJoules
Copy link
Contributor

PiJoules commented Sep 5, 2025

LGTM and can confirm this fixes #156760. Could you add that repro as a regression test here.

Can you check if the regression tests added to clang/test/PCH/thread-safety-attrs.cpp covers the cases?

That test passes mutexes to std::lock but the example in the bug has unique_locks passed to std::lock

@ilovepi
Copy link
Contributor

ilovepi commented Sep 5, 2025

Is there anything in this that requires pch? Seems like the other thread safety tests would make more sense.

@Prabhuk
Copy link
Contributor Author

Prabhuk commented Sep 5, 2025

Is there anything in this that requires pch? Seems like the other thread safety tests would make more sense.

Extracted regression tests into clang/test/Analysis directory into a separate file. PTAL.

Copy link
Member

@aaronpuchert aaronpuchert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there is some confusion around the concepts here: unique_lock is not a lockable but a scoped_lockable. The latter should be automatically released if you annotate the destructor. The former should in my view be manually released before they're destroyed. I'll comment on the original issue.

Mutex local_m0;
Mutex local_m1;
LockMutexes(local_m0, local_m1);
} // No warnings expected at end of function scope as the mutexes are function local.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not sure I agree here. I think mutexes should be properly released even if they go out of scope.

void no_local_unique_locks_held_warning() {
unique_lock<Mutex> ul0(m0);
unique_lock<Mutex> ul1(m1);
LockMutexes(ul0, ul1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several issues here:

  • unique_lock isn't annotated as a mutex (lockable) or scoped lockable class. I guess we don't warn about it because it's a template, but I'd have to check.
  • Once you have a scoped lockable class with properly annotated destructor, this should already be handled by the destructor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we're still not able to emit attribute warnings on template instantiation. The reason is that we're doing Sema on ParsedAttr, but template instantiation has a regular attribute. I don't have a good idea how to extend the warning there.

Comment on lines +2447 to +2455
const til::SExpr *Sexp = ExitFact.sexpr();
const VarDecl *MutexVar = nullptr;

if (const auto *Proj = dyn_cast<til::Project>(Sexp)) {
if (const auto *Base = dyn_cast<til::LiteralPtr>(Proj->record()))
MutexVar = dyn_cast_or_null<VarDecl>(Base->clangDecl());
} else if (const auto *LP = dyn_cast<til::LiteralPtr>(Sexp)) {
MutexVar = dyn_cast_or_null<VarDecl>(LP->clangDecl());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scoped lockables should be unlocked by the automatic destructor. I don't think we should add this code.

@aaronpuchert
Copy link
Member

Commented on #156760: the issues is that we don't support scoped lockable arguments for the attributes. (That is, we support a different syntax since #110523, but we can't use that for std::lock which accepts both lockable and scoped lockable arguments, possibly even mixed.)

@Prabhuk Prabhuk closed this Sep 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:analysis clang:static analyzer clang Clang issues not falling into any other category libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants