Skip to content

[analyzer] Fix false positive for mutexes inheriting mutex_base #106240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 28, 2024

Conversation

necto
Copy link
Contributor

@necto necto commented Aug 27, 2024

If a mutex interface is split in inheritance chain, e.g. struct mutex has unlock and inherits lock from __mutex_base then calls m.lock() and m.unlock() have different "this" targets: m and the __mutex_base of m, which used to confuse the ActiveCritSections list.

Taking base region canonicalizes the region used to identify a critical section and enables search in ActiveCritSections list regardless of which class the callee is the member of.

This possibly fixes #104241

CPP-5541

If a mutex interface is split in inheritance chain, e.g. struct mutex
has `unlock` and inherits `lock` from __mutex_base then calls m.lock()
and m.unlock() have different "this" targets: m and the __mutex_base of
m, which used to confuse the `ActiveCritSections` list.

Taking base region canonicalizes the region used to identify a critical
section and enables search in ActiveCritSections list regardless of
which class the callee is the member of.

This possibly fixes llvm#104241

CPP-5541
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Aug 27, 2024
@llvmbot
Copy link
Member

llvmbot commented Aug 27, 2024

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

@llvm/pr-subscribers-clang

Author: Arseniy Zaostrovnykh (necto)

Changes

If a mutex interface is split in inheritance chain, e.g. struct mutex has unlock and inherits lock from __mutex_base then calls m.lock() and m.unlock() have different "this" targets: m and the __mutex_base of m, which used to confuse the ActiveCritSections list.

Taking base region canonicalizes the region used to identify a critical section and enables search in ActiveCritSections list regardless of which class the callee is the member of.

This possibly fixes #104241

CPP-5541


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

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp (+4-2)
  • (modified) clang/test/Analysis/block-in-critical-section-inheritance.cpp (+10)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 4cd2f2802f30cd..52ff639c6b8022 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -245,8 +245,10 @@ static const MemRegion *getRegion(const CallEvent &Call,
                                   const MutexDescriptor &Descriptor,
                                   bool IsLock) {
   return std::visit(
-      [&Call, IsLock](auto &&Descriptor) {
-        return Descriptor.getRegion(Call, IsLock);
+      [&Call, IsLock](auto &Descr) -> const MemRegion * {
+        if (const MemRegion *Reg = Descr.getRegion(Call, IsLock))
+          return Reg->getBaseRegion();
+        return nullptr;
       },
       Descriptor);
 }
diff --git a/clang/test/Analysis/block-in-critical-section-inheritance.cpp b/clang/test/Analysis/block-in-critical-section-inheritance.cpp
index db20df8c60a5c9..c60ba2632cee25 100644
--- a/clang/test/Analysis/block-in-critical-section-inheritance.cpp
+++ b/clang/test/Analysis/block-in-critical-section-inheritance.cpp
@@ -29,3 +29,13 @@ void gh_99628() {
   // expected-note@-2 {{Call to blocking function 'sleep' inside of critical section}}
   m.unlock();
 }
+
+void no_false_positive_gh_104241() {
+  std::mutex m;
+  m.lock();
+  // If inheritance not handled properly, this unlock might not match the lock
+  // above because technically they act on different memory regions:
+  // __mutex_base and mutex.
+  m.unlock();
+  sleep(10); // no-warning
+}

Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

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

LGTM, nice catch!

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

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

Uh that FN seems really bad. Have you measured this change?
Can we relax the canonicalization to only unwrap base class regions, or only apply to classes within the stdlib?

@necto
Copy link
Contributor Author

necto commented Aug 28, 2024

Uh that FN seems really bad. Have you measured this change? Can we relax the canonicalization to only unwrap base class regions, or only apply to classes within the stdlib?

relaxed in 744272e

@steakhal
Copy link
Contributor

Could you also add this test case?

namespace std {
struct __mutex_base {
  void lock();
};
struct mutex : __mutex_base {
  void unlock();
  bool try_lock();
};
template <class... MutexTypes> struct scoped_lock {
  explicit scoped_lock(MutexTypes&... m);
  ~scoped_lock();
};
template <class MutexType> class scoped_lock<MutexType> {
public:
  explicit scoped_lock(MutexType& m) : m(m) { m.lock(); }
  ~scoped_lock() { m.unlock(); }
private:
  MutexType& m;
};
} // namespace std

extern "C" unsigned int sleep(unsigned int seconds);

int magic_number;
std::mutex m;

void fixed() {
  int current;
  for (int items_processed = 0; items_processed < 100; ++items_processed) {
    {
      std::scoped_lock guard(m);
      current = magic_number;
    }
    sleep(current); // expected no warning
  }
}

Or is it already implied by other tests?

@necto
Copy link
Contributor Author

necto commented Aug 28, 2024

Could you also add this test case?

Or is it already implied by other tests?

My first test

void no_false_positive_gh_104241() {
  std::mutex m;
  m.lock();
  // If inheritance not handled properly, this unlock might not match the lock
  // above because technically they act on different memory regions:
  // __mutex_base and mutex.
  m.unlock();
  sleep(10); // no-warning
}

Was derived from your example, so I guess it is implied, but I added your example as is with 0d95583 to make it extra clear.

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

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

Thanks, I'm lovin it!

@necto necto requested a review from steakhal August 28, 2024 07:24
@necto
Copy link
Contributor Author

necto commented Aug 28, 2024

@steakhal I added a fix for multiple-inheritance fn, please take another look

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

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

Still looks good.

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

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

Let's merge this.

@steakhal steakhal merged commit 82e314e into llvm:main Aug 28, 2024
8 checks passed
@necto necto deleted the az/CPP-5541-crit-section branch August 28, 2024 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang][analyzer] Possible false positive from unix.BlockInCriticalSection
4 participants