Skip to content

[analyzer] Remove redundant bug type DoubleDelete #147542

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 4 commits into from
Jul 9, 2025

Conversation

NagyDonat
Copy link
Contributor

This commit removes the DoubleDelete bug type from MallocChecker.cpp because it's completely redundant with the DoubleFree bug (which is already used for all allocator families, including new/delete).

This simplifies the code of the checker and prevents the potential confusion caused by two semantically equivalent and very similar, but not identical bug report messages.

This commit removes the DoubleDelete bug type from `MallocChecker.cpp`
because it's completely redundant with the `DoubleFree` bug (which is
already used for all allocator families, including new/delete).

This simplifies the code of the checker and prevents the potential
confusion caused by two semantically equivalent and very similar, but
not identical bug report messages.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Jul 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 8, 2025

@llvm/pr-subscribers-clang

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

Author: Donát Nagy (NagyDonat)

Changes

This commit removes the DoubleDelete bug type from MallocChecker.cpp because it's completely redundant with the DoubleFree bug (which is already used for all allocator families, including new/delete).

This simplifies the code of the checker and prevents the potential confusion caused by two semantically equivalent and very similar, but not identical bug report messages.


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

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp (+4-29)
  • (modified) clang/test/Analysis/NewDelete-checker-test.cpp (+2-2)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 0a58d7cc2635a..be8f89853bc96 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -346,10 +346,6 @@ namespace {
   };
 
 BUGTYPE_PROVIDER(DoubleFree, "Double free")
-// TODO: Remove DoubleDelete as a separate bug type and when it would be
-// emitted, emit DoubleFree reports instead. (Note that DoubleFree is already
-// used for all allocation families, not just malloc/free.)
-BUGTYPE_PROVIDER(DoubleDelete, "Double delete")
 
 struct Leak : virtual public CheckerFrontend {
   // Leaks should not be reported if they are post-dominated by a sink:
@@ -410,7 +406,7 @@ class MallocChecker
   DynMemFrontend<DoubleFree, Leak, UseFree, BadFree, FreeAlloca, OffsetFree,
                  UseZeroAllocated>
       MallocChecker;
-  DynMemFrontend<DoubleFree, DoubleDelete, UseFree, BadFree, OffsetFree,
+  DynMemFrontend<DoubleFree, UseFree, BadFree, OffsetFree,
                  UseZeroAllocated>
       NewDeleteChecker;
   DynMemFrontend<Leak> NewDeleteLeaksChecker;
@@ -848,8 +844,6 @@ class MallocChecker
   void HandleDoubleFree(CheckerContext &C, SourceRange Range, bool Released,
                         SymbolRef Sym, SymbolRef PrevSym) const;
 
-  void HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const;
-
   void HandleUseZeroAlloc(CheckerContext &C, SourceRange Range,
                           SymbolRef Sym) const;
 
@@ -2737,7 +2731,8 @@ void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range,
         (Released ? "Attempt to free released memory"
                   : "Attempt to free non-owned memory"),
         N);
-    R->addRange(Range);
+    if (Range.isValid())
+      R->addRange(Range);
     R->markInteresting(Sym);
     if (PrevSym)
       R->markInteresting(PrevSym);
@@ -2746,26 +2741,6 @@ void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range,
   }
 }
 
-void MallocChecker::HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const {
-  const DoubleDelete *Frontend = getRelevantFrontendAs<DoubleDelete>(C, Sym);
-  if (!Frontend)
-    return;
-  if (!Frontend->isEnabled()) {
-    C.addSink();
-    return;
-  }
-
-  if (ExplodedNode *N = C.generateErrorNode()) {
-
-    auto R = std::make_unique<PathSensitiveBugReport>(
-        Frontend->DoubleDeleteBug, "Attempt to delete released memory", N);
-
-    R->markInteresting(Sym);
-    R->addVisitor<MallocBugVisitor>(Sym);
-    C.emitReport(std::move(R));
-  }
-}
-
 void MallocChecker::HandleUseZeroAlloc(CheckerContext &C, SourceRange Range,
                                        SymbolRef Sym) const {
   const UseZeroAllocated *Frontend =
@@ -3324,7 +3299,7 @@ void MallocChecker::checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C,
 bool MallocChecker::checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const {
 
   if (isReleased(Sym, C)) {
-    HandleDoubleDelete(C, Sym);
+    HandleDoubleFree(C, SourceRange(), /*Released=*/true, Sym, nullptr);
     return true;
   }
   return false;
diff --git a/clang/test/Analysis/NewDelete-checker-test.cpp b/clang/test/Analysis/NewDelete-checker-test.cpp
index da0eef7c52bd8..7c3e142d586bb 100644
--- a/clang/test/Analysis/NewDelete-checker-test.cpp
+++ b/clang/test/Analysis/NewDelete-checker-test.cpp
@@ -412,7 +412,7 @@ class DerefClass{
 void testDoubleDeleteClassInstance() {
   DerefClass *foo = new DerefClass();
   delete foo;
-  delete foo; // newdelete-warning {{Attempt to delete released memory}}
+  delete foo; // newdelete-warning {{Attempt to free released memory}}
 }
 
 class EmptyClass{
@@ -424,7 +424,7 @@ class EmptyClass{
 void testDoubleDeleteEmptyClass() {
   EmptyClass *foo = new EmptyClass();
   delete foo;
-  delete foo; // newdelete-warning {{Attempt to delete released memory}}
+  delete foo; // newdelete-warning {{Attempt to free released memory}}
 }
 
 struct Base {

Copy link

github-actions bot commented Jul 8, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@@ -412,7 +412,7 @@ class DerefClass{
void testDoubleDeleteClassInstance() {
DerefClass *foo = new DerefClass();
delete foo;
delete foo; // newdelete-warning {{Attempt to delete released memory}}
delete foo; // newdelete-warning {{Attempt to free released memory}}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not convinced that the message change is an improvement.
Can we somehow still use the delete spelling?

Copy link
Contributor

Choose a reason for hiding this comment

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

One idea is to inspect the Expr against which we report the bug is a DeleteExpr or a callexpr.

Copy link
Contributor Author

@NagyDonat NagyDonat Jul 9, 2025

Choose a reason for hiding this comment

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

There are already many situations where cplusplus.NewDelete encounters repeated delete expressions and produces a DoubleFree bug report with "Attempt to free released memory" as the message, see e.g.

delete p; // expected-warning {{Attempt to free released memory}}

The DoubleDelete bug (which I want to remove / merge into DoubleFree) is only emitted in the special case when a nontrivial (?) destructor of the deleted type would be called, and it was introduced because in this case the body of the destructor is executed before the point where the DoubleFree bug is produced, so if the body of the destructor references a member variable, then (without the DoubleDelete code) we would get a use-after-free report instead of the DoubleFree (which is technically correct, but less elegant).

I think it's important that the warning message produced by delete foo; delete foo; should not depend on whether *foo has a destructor or not, so in this PR I would like to keep the spelling "Attempt to free released memory" (i.e. adjust the message produced by the special DoubleDelete case to match the more common DoubleFree message).

After this, I would gladly accept a separate followup PR that replaces "free" with a more accurate word, e.g. by

  • changing the DoubleFree message to "Attempt to release already released memory" (which would fit all allocator families);
  • or using "delete" in all the DoubleFree reports that involve delete operators (and not just those that come through the DoubleDelete logic).

However, I don't have capacity for implementing this, as I'm already deep in the "also solve this tangentially related issue" mode.

Copy link
Contributor Author

@NagyDonat NagyDonat Jul 9, 2025

Choose a reason for hiding this comment

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

In my commit 15b8a18 I refactored this area of the code (by inlining checkDoubleDelete to remove a superfluous layer of indirection) and added a comment that describes the role of this "emit a DoubleDelete/DoubleFree when we see a destructor call to preempt the potential use-after-free report" branch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Given that this is the only user-visible change of this PR, I'm not happy to see this wording change, but I'm not interested in blocking this PR. I liked the Attempt to release already released memory phrasing that you suggested.

Copy link
Contributor Author

@NagyDonat NagyDonat Jul 9, 2025

Choose a reason for hiding this comment

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

I see your point and agree that in that particular case the spelling with free is slightly worse than the old message that said delete, but I think that overall the consistently slightly worse message is still better than the inconsistent situation where the checker could emit different messages in almost-identical situations.

I'll try to ensure that in the foreseeable future somebody (e.g. an intern in our team) systemically updates all the messages of the MallocChecker family to use properly generic vocabulary and e.g. say "release" instead of "free".

NagyDonat added 2 commits July 9, 2025 15:15
I decided to inline and remove `checkDoubleDelete` because (1) it was
fairly short and (2) it is not a natural "do what is reasonable in
<a certain situation>" method so we cannot hide its implementation
behind a clear and descriptive name.
@NagyDonat
Copy link
Contributor Author

The Windows CI failure is irrelevant:

llvm-lit.py: C:_work\llvm-project\llvm-project\llvm\utils\lit\lit\llvm\config.py:280: fatal: Could not run process ['c:\_work\llvm-project\llvm-project\build\bin\clang.exe', '-print-file-name=include']
[6110/6127] Linking CXX executable bin\llvm-nm.exe
FAILED: tools/clang/tools/extra/CMakeFiles/check-clang-tools

@NagyDonat NagyDonat merged commit cd193f4 into llvm:main Jul 9, 2025
8 of 9 checks passed
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.

3 participants