Author: Benedek Kaibas
Date: 2026-08-14T17:02:23+02:00
New Revision: 17460a5bad231004a5747aecae589abda06c537d

URL: 
https://github.com/llvm/llvm-project/commit/17460a5bad231004a5747aecae589abda06c537d
DIFF: 
https://github.com/llvm/llvm-project/commit/17460a5bad231004a5747aecae589abda06c537d.diff

LOG: [analyzer] Only report the first dereference of the same variable in 
DanglingPtrDeref (#215409)

If the same variable is dereferenced multiple times then the
`DanglingPtrDeref` checker should only emit a single wanring for it
instead of multiple ones. If there are multiple variables dereferenced
then for each variable there should be one warning emitted. For that
reason I have implemented the `markAsReported` function which returns
the updated state with the memory region of the given variable marked as
reported if the memory region is seen for the first time.

Added: 
    

Modified: 
    clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
    clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
    clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
    clang/test/Analysis/dangling-ptr-deref.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp 
b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
index bd4cd864cb768..cd690328ebc47 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
@@ -69,6 +69,11 @@ void DanglingPtrDeref::checkPostCall(const CallEvent &Call,
 void DanglingPtrDeref::reportUseAfterScope(const MemRegion *Region,
                                            const Stmt *S, ExplodedNode *N,
                                            CheckerContext &C) const {
+  ProgramStateRef ReportedState =
+      lifetime_modeling::markAsReported(N->getState(), Region);
+  if (!ReportedState)
+    return;
+
   auto BR = std::make_unique<PathSensitiveBugReport>(
       BugMsg,
       (llvm::Twine("Use of ") + lifetime_modeling::getRegionName(Region) +
@@ -79,6 +84,7 @@ void DanglingPtrDeref::reportUseAfterScope(const MemRegion 
*Region,
     if (const Expr *DerefExpr = bugreporter::getDerefExpr(S))
       bugreporter::trackExpressionValue(N, DerefExpr, *BR);
   }
+  C.addTransition(ReportedState, N);
   C.emitReport(std::move(BR));
 }
 

diff  --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 2fab20b199f01..7074e715d789b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -15,6 +15,7 @@ REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, 
const MemRegion *)
 REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
 
 REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
+REGISTER_SET_WITH_PROGRAMSTATE(ReportedDeadRegions, const MemRegion *)
 
 namespace {
 
@@ -86,6 +87,14 @@ bool lifetime_modeling::isDeallocated(ProgramStateRef State,
   return State->contains<DeallocatedSourceSet>(Region->getBaseRegion());
 }
 
+ProgramStateRef lifetime_modeling::markAsReported(ProgramStateRef State,
+                                                  const MemRegion *Region) {
+  ProgramStateRef NewState =
+      State->add<ReportedDeadRegions>(Region->getBaseRegion());
+
+  return (NewState != State) ? NewState : nullptr;
+}
+
 static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
                                   const MemRegion *Source) {
   LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
@@ -165,6 +174,7 @@ void LifetimeModeling::checkDeadSymbols(SymbolReaper 
&SymReaper,
   ProgramStateRef State = C.getState();
   LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
   DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
+  ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>();
 
   for (SVal Val : llvm::make_first_range(LBMap)) {
     if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(R))
@@ -181,20 +191,32 @@ void LifetimeModeling::checkDeadSymbols(SymbolReaper 
&SymReaper,
     if (!SymReaper.isLiveRegion(Region))
       State = State->remove<DeallocatedSourceSet>(Region);
   }
+
+  for (const MemRegion *Region : Reported) {
+    if (!SymReaper.isLiveRegion(Region))
+      State = State->remove<ReportedDeadRegions>(Region);
+  }
   C.addTransition(State);
 }
 
 void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
                                   const char *NL, const char *Sep) const {
   auto LBMap = State->get<LifetimeBoundMap>();
+  ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>();
 
-  if (LBMap.isEmpty())
-    return;
+  if (!LBMap.isEmpty()) {
+    Out << Sep << "LifetimeBound bindings:" << NL;
+    for (auto &&[OriginSym, SourceSet] : LBMap) {
+      for (const auto *Region : SourceSet)
+        Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
+    }
+  }
 
-  Out << Sep << "LifetimeBound bindings:" << NL;
-  for (auto &&[OriginSym, SourceSet] : LBMap) {
-    for (const auto *Region : SourceSet)
-      Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
+  if (!Reported.isEmpty()) {
+    Out << Sep << "Reported regions: " << NL;
+    for (const auto *Region : Reported) {
+      Out << " " << Region << NL;
+    }
   }
 }
 

diff  --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
index 8d6c8e4882d1c..18f3014cfa6e9 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -22,6 +22,11 @@ bool isBoundToLifetimeSource(ProgramStateRef State, SVal 
Val);
 /// Returns the descriptive name of the memory region or a placeholder if a
 /// descriptive name cannot be constructed for it.
 std::string getRegionName(const MemRegion *Reg);
+
+/// Returns the updated \p State with \p R marked as reported if \p R is seen
+/// the first time. Returns nullptr if \p R was already reported.
+ProgramStateRef markAsReported(ProgramStateRef State, const MemRegion *Region);
+
 } // namespace clang::ento::lifetime_modeling
 
 #endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H

diff  --git a/clang/test/Analysis/dangling-ptr-deref.cpp 
b/clang/test/Analysis/dangling-ptr-deref.cpp
index 7f13c241dadf0..55dd5eadc8ad0 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -331,3 +331,16 @@ void dangling_through_calls() {
   // expected-warning@-1 {{Use of 'local' after its lifetime ended}}
   // expected-note@-2    {{Use of 'local' after its lifetime ended}}
 }
+
+// If the same variable is dereferenced multiple times then only
+// report for the first dereference.
+void multiple_deref() {
+  int *ptr = nullptr;
+  {
+    int a = 5; // expected-note {{'a' initialized to 5}}
+    ptr = &a; // expected-note  {{Value assigned to 'ptr'}}
+  } // expected-note            {{'a' is destroyed here}}
+  *ptr = 6; // expected-note    {{Use of 'a' after its lifetime ended}}
+  // expected-warning@-1        {{Use of 'a' after its lifetime ended}}
+  *ptr = 7; // no-warning: Already reported this base region.
+}


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

Reply via email to