Skip to content

Commit 9d359f6

Browse files
SzelethusKristóf Umann
authored andcommitted
[analyzer] MallocChecker: Add notes from NoOwnershipChangeVisitor only when a function "intents", but doesn't change ownership, enable by default
D105819 Added NoOwnershipChangeVisitor, but it is only registered when an off-by-default, hidden checker option was enabled. The reason behind this was that it grossly overestimated the set of functions that really needed a note: std::string getTrainName(const Train *T) { return T->name; } // note: Retuning without changing the ownership of or deallocating memory // Umm... I mean duh? Nor would I expect this function to do anything like that... void foo() { Train *T = new Train("Land Plane"); print(getTrainName(T)); // note: calling getTrainName / returning from getTrainName } // warn: Memory leak This patch adds a heuristic that guesses that any function that has an explicit operator delete call could have be responsible for deallocating the memory that ended up leaking. This is waaaay too conservative (see the TODOs in the new function), but it safer to err on the side of too little than too much, and would allow us to enable the option by default *now*, and add refinements one-by-one. Differential Revision: https://reviews.llvm.org/D108753
1 parent 9691851 commit 9d359f6

File tree

4 files changed

+30
-11
lines changed

4 files changed

+30
-11
lines changed

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,8 @@ def DynamicMemoryModeling: Checker<"DynamicMemoryModeling">,
493493
"that neither deallocated it, or have taken responsibility "
494494
"of the ownership are noted, similarly to "
495495
"NoStoreFuncVisitor.",
496-
"false",
497-
InAlpha,
496+
"true",
497+
Released,
498498
Hide>
499499
]>,
500500
Dependencies<[CStringModeling]>,

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
#include "clang/AST/Expr.h"
5353
#include "clang/AST/ExprCXX.h"
5454
#include "clang/AST/ParentMap.h"
55+
#include "clang/ASTMatchers/ASTMatchFinder.h"
56+
#include "clang/ASTMatchers/ASTMatchers.h"
5557
#include "clang/Analysis/ProgramPoint.h"
5658
#include "clang/Basic/LLVM.h"
5759
#include "clang/Basic/SourceManager.h"
@@ -791,9 +793,28 @@ class NoOwnershipChangeVisitor final : public NoStateChangeFuncVisitor {
791793
return "";
792794
}
793795

796+
bool doesFnIntendToHandleOwnership(const Decl *Callee, ASTContext &ACtx) {
797+
using namespace clang::ast_matchers;
798+
const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);
799+
if (!FD)
800+
return false;
801+
// TODO: Operator delete is hardly the only deallocator -- Can we reuse
802+
// isFreeingCall() or something thats already here?
803+
auto Deallocations = match(
804+
stmt(hasDescendant(cxxDeleteExpr().bind("delete"))
805+
), *FD->getBody(), ACtx);
806+
// TODO: Ownership my change with an attempt to store the allocated memory.
807+
return !Deallocations.empty();
808+
}
809+
794810
virtual bool
795811
wasModifiedInFunction(const ExplodedNode *CallEnterN,
796812
const ExplodedNode *CallExitEndN) override {
813+
if (!doesFnIntendToHandleOwnership(
814+
CallExitEndN->getFirstPred()->getLocationContext()->getDecl(),
815+
CallExitEndN->getState()->getAnalysisManager().getASTContext()))
816+
return true;
817+
797818
if (CallEnterN->getState()->get<RegionState>(Sym) !=
798819
CallExitEndN->getState()->get<RegionState>(Sym))
799820
return true;

clang/test/Analysis/NewDeleteLeaks.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@
2020

2121
bool coin();
2222

23+
// TODO: AST analysis of sink would reveal that it doesn't intent to free the
24+
// allocated memory, but in this instance, its also the only function with
25+
// the ability to do so, we should see a note here.
2326
namespace memory_allocated_in_fn_call {
2427

2528
void sink(int *P) {
26-
} // ownership-note {{Returning without deallocating memory or storing the pointer for later deallocation}}
29+
}
2730

2831
void foo() {
2932
sink(new int(5)); // expected-note {{Memory is allocated}}
30-
// ownership-note@-1 {{Calling 'sink'}}
31-
// ownership-note@-2 {{Returning from 'sink'}}
3233
} // expected-warning {{Potential memory leak [cplusplus.NewDeleteLeaks]}}
3334
// expected-note@-1 {{Potential memory leak}}
3435

@@ -109,17 +110,14 @@ void foo() {
109110

110111
} // namespace memory_shared_with_ptr_of_same_lifetime
111112

112-
// TODO: We don't want a note here. sink() doesn't seem like a function that
113-
// even attempts to take care of any memory ownership problems.
114113
namespace memory_passed_into_fn_that_doesnt_intend_to_free {
115114

116115
void sink(int *P) {
117-
} // ownership-note {{Returning without deallocating memory or storing the pointer for later deallocation}}
116+
}
118117

119118
void foo() {
120119
int *ptr = new int(5); // expected-note {{Memory is allocated}}
121-
sink(ptr); // ownership-note {{Calling 'sink'}}
122-
// ownership-note@-1 {{Returning from 'sink'}}
120+
sink(ptr);
123121
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [cplusplus.NewDeleteLeaks]}}
124122
// expected-note@-1 {{Potential leak}}
125123

clang/test/Analysis/analyzer-config.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
// CHECK-NEXT: suppress-null-return-paths = true
118118
// CHECK-NEXT: track-conditions = true
119119
// CHECK-NEXT: track-conditions-debug = false
120-
// CHECK-NEXT: unix.DynamicMemoryModeling:AddNoOwnershipChangeNotes = false
120+
// CHECK-NEXT: unix.DynamicMemoryModeling:AddNoOwnershipChangeNotes = true
121121
// CHECK-NEXT: unix.DynamicMemoryModeling:Optimistic = false
122122
// CHECK-NEXT: unroll-loops = false
123123
// CHECK-NEXT: verbose-report-filename = false

0 commit comments

Comments
 (0)