[clang] [Clang SA]: add support for mismatched ownership_returns+ownership_takes calls for custom allocation classes (PR #98941)
Pavel Skripkin via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 15 10:46:47 PDT 2024
https://github.com/pskrgag created https://github.com/llvm/llvm-project/pull/98941
Add support for checking mismatched ownership_returns/ownership_takes attributes.
Closes #76861
>From e08dd7946051202d35199bf181602f88589f8ed9 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Sun, 14 Jul 2024 01:24:20 +0300
Subject: [PATCH 1/3] csa/MallocChecker: add support for custom allocation
classes
Add support for custom allocation classes that could be specified by
ownership_returns attribute.
This patch adds basic support, so new class is not contructed anywhere
and handled as an error everywhere.
---
.../StaticAnalyzer/Checkers/MallocChecker.cpp | 184 ++++++++++++------
1 file changed, 122 insertions(+), 62 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index fe202c79ed620..9d170b6f424d3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -103,14 +103,46 @@ using namespace std::placeholders;
namespace {
// Used to check correspondence between allocators and deallocators.
-enum AllocationFamily {
+enum AllocationFamilyKind {
AF_None,
AF_Malloc,
AF_CXXNew,
AF_CXXNewArray,
AF_IfNameIndex,
AF_Alloca,
- AF_InnerBuffer
+ AF_InnerBuffer,
+ AF_Custom,
+};
+
+class AllocationFamily {
+public:
+ AllocationFamily(AllocationFamilyKind kind,
+ std::optional<StringRef> name = std::nullopt)
+ : _kind(kind), _customName(name) {
+ assert(kind != AF_Custom || name != std::nullopt);
+ }
+
+ bool operator==(const AllocationFamily &Other) const {
+ if (Other.kind() == this->kind()) {
+ return this->kind() == AF_Custom ? this->_customName == Other._customName
+ : true;
+ } else {
+ return false;
+ }
+ }
+
+ bool operator==(const AllocationFamilyKind &kind) {
+ return this->kind() == kind;
+ }
+
+ bool operator!=(const AllocationFamilyKind &kind) { return !(*this == kind); }
+
+ std::optional<StringRef> name() const { return _customName; }
+ AllocationFamilyKind kind() const { return _kind; }
+
+private:
+ AllocationFamilyKind _kind;
+ std::optional<StringRef> _customName;
};
} // end of anonymous namespace
@@ -194,7 +226,7 @@ class RefState {
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(K);
ID.AddPointer(S);
- ID.AddInteger(Family);
+ ID.AddInteger(Family.kind());
}
LLVM_DUMP_METHOD void dump(raw_ostream &OS) const {
@@ -1918,26 +1950,52 @@ static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E) {
static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) {
- switch(Family) {
- case AF_Malloc: os << "malloc()"; return;
- case AF_CXXNew: os << "'new'"; return;
- case AF_CXXNewArray: os << "'new[]'"; return;
- case AF_IfNameIndex: os << "'if_nameindex()'"; return;
- case AF_InnerBuffer: os << "container-specific allocator"; return;
- case AF_Alloca:
- case AF_None: llvm_unreachable("not a deallocation expression");
+ switch (Family.kind()) {
+ case AF_Malloc:
+ os << "malloc()";
+ return;
+ case AF_CXXNew:
+ os << "'new'";
+ return;
+ case AF_CXXNewArray:
+ os << "'new[]'";
+ return;
+ case AF_IfNameIndex:
+ os << "'if_nameindex()'";
+ return;
+ case AF_InnerBuffer:
+ os << "container-specific allocator";
+ return;
+ case AF_Alloca:
+ case AF_None:
+ llvm_unreachable("not a deallocation expression");
+ case AF_Custom:
+ llvm_unreachable("not a deallocation expression");
}
}
static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) {
- switch(Family) {
- case AF_Malloc: os << "free()"; return;
- case AF_CXXNew: os << "'delete'"; return;
- case AF_CXXNewArray: os << "'delete[]'"; return;
- case AF_IfNameIndex: os << "'if_freenameindex()'"; return;
- case AF_InnerBuffer: os << "container-specific deallocator"; return;
- case AF_Alloca:
- case AF_None: llvm_unreachable("suspicious argument");
+ switch (Family.kind()) {
+ case AF_Malloc:
+ os << "free()";
+ return;
+ case AF_CXXNew:
+ os << "'delete'";
+ return;
+ case AF_CXXNewArray:
+ os << "'delete[]'";
+ return;
+ case AF_IfNameIndex:
+ os << "'if_freenameindex()'";
+ return;
+ case AF_InnerBuffer:
+ os << "container-specific deallocator";
+ return;
+ case AF_Alloca:
+ case AF_None:
+ llvm_unreachable("suspicious argument");
+ case AF_Custom:
+ llvm_unreachable("suspicious argument");
}
}
@@ -2119,7 +2177,7 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr,
std::optional<MallocChecker::CheckKind>
MallocChecker::getCheckIfTracked(AllocationFamily Family,
bool IsALeakCheck) const {
- switch (Family) {
+ switch (Family.kind()) {
case AF_Malloc:
case AF_Alloca:
case AF_IfNameIndex: {
@@ -2144,6 +2202,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
return CK_InnerPointerChecker;
return std::nullopt;
}
+ case AF_Custom:
case AF_None: {
llvm_unreachable("no family");
}
@@ -3483,53 +3542,54 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
Sym, "Returned allocated memory");
} else if (isReleased(RSCurr, RSPrev, S)) {
const auto Family = RSCurr->getAllocationFamily();
- switch (Family) {
- case AF_Alloca:
- case AF_Malloc:
- case AF_CXXNew:
- case AF_CXXNewArray:
- case AF_IfNameIndex:
- Msg = "Memory is released";
+ switch (Family.kind()) {
+ case AF_Alloca:
+ case AF_Malloc:
+ case AF_CXXNew:
+ case AF_CXXNewArray:
+ case AF_IfNameIndex:
+ Msg = "Memory is released";
+ StackHint = std::make_unique<StackHintGeneratorForSymbol>(
+ Sym, "Returning; memory was released");
+ break;
+ case AF_InnerBuffer: {
+ const MemRegion *ObjRegion =
+ allocation_state::getContainerObjRegion(statePrev, Sym);
+ const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
+ QualType ObjTy = TypedRegion->getValueType();
+ OS << "Inner buffer of '" << ObjTy << "' ";
+
+ if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) {
+ OS << "deallocated by call to destructor";
StackHint = std::make_unique<StackHintGeneratorForSymbol>(
- Sym, "Returning; memory was released");
- break;
- case AF_InnerBuffer: {
- const MemRegion *ObjRegion =
- allocation_state::getContainerObjRegion(statePrev, Sym);
- const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
- QualType ObjTy = TypedRegion->getValueType();
- OS << "Inner buffer of '" << ObjTy << "' ";
-
- if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) {
- OS << "deallocated by call to destructor";
- StackHint = std::make_unique<StackHintGeneratorForSymbol>(
- Sym, "Returning; inner buffer was deallocated");
- } else {
- OS << "reallocated by call to '";
- const Stmt *S = RSCurr->getStmt();
- if (const auto *MemCallE = dyn_cast<CXXMemberCallExpr>(S)) {
- OS << MemCallE->getMethodDecl()->getDeclName();
- } else if (const auto *OpCallE = dyn_cast<CXXOperatorCallExpr>(S)) {
- OS << OpCallE->getDirectCallee()->getDeclName();
- } else if (const auto *CallE = dyn_cast<CallExpr>(S)) {
- auto &CEMgr = BRC.getStateManager().getCallEventManager();
- CallEventRef<> Call =
- CEMgr.getSimpleCall(CallE, state, CurrentLC, {nullptr, 0});
- if (const auto *D = dyn_cast_or_null<NamedDecl>(Call->getDecl()))
- OS << D->getDeclName();
- else
- OS << "unknown";
- }
- OS << "'";
- StackHint = std::make_unique<StackHintGeneratorForSymbol>(
- Sym, "Returning; inner buffer was reallocated");
+ Sym, "Returning; inner buffer was deallocated");
+ } else {
+ OS << "reallocated by call to '";
+ const Stmt *S = RSCurr->getStmt();
+ if (const auto *MemCallE = dyn_cast<CXXMemberCallExpr>(S)) {
+ OS << MemCallE->getMethodDecl()->getDeclName();
+ } else if (const auto *OpCallE = dyn_cast<CXXOperatorCallExpr>(S)) {
+ OS << OpCallE->getDirectCallee()->getDeclName();
+ } else if (const auto *CallE = dyn_cast<CallExpr>(S)) {
+ auto &CEMgr = BRC.getStateManager().getCallEventManager();
+ CallEventRef<> Call =
+ CEMgr.getSimpleCall(CallE, state, CurrentLC, {nullptr, 0});
+ if (const auto *D = dyn_cast_or_null<NamedDecl>(Call->getDecl()))
+ OS << D->getDeclName();
+ else
+ OS << "unknown";
}
- Msg = OS.str();
- break;
+ OS << "'";
+ StackHint = std::make_unique<StackHintGeneratorForSymbol>(
+ Sym, "Returning; inner buffer was reallocated");
+ }
+ Msg = OS.str();
+ break;
}
+ case AF_Custom:
case AF_None:
llvm_unreachable("Unhandled allocation family!");
- }
+ }
// See if we're releasing memory while inlining a destructor
// (or one of its callees). This turns on various common
>From 4655163bdde2decf8c5a7db08c01e51b2574c6d8 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Mon, 15 Jul 2024 00:54:31 +0300
Subject: [PATCH 2/3] csa/MismatchedDeallocator: handle custom allocation
clasess
Patch introduces support for custom allocation classes in
MismatchedDeallocator.
Patch preserves previous behavior, in which 'malloc' class was handled
as AF_Malloc type.
---
.../StaticAnalyzer/Checkers/MallocChecker.cpp | 70 ++++++++++++++-----
1 file changed, 53 insertions(+), 17 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 9d170b6f424d3..821e0c8da5d2d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -1702,15 +1702,18 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call,
if (!State)
return nullptr;
- if (Att->getModule()->getName() != "malloc")
- return nullptr;
+ // Preseve previous behavior when "malloc" class means AF_Malloc
+ auto attrClassName = Att->getModule()->getName();
+ auto AF = attrClassName == "malloc"
+ ? AF_Malloc
+ : AllocationFamily(AF_Custom, attrClassName);
if (!Att->args().empty()) {
return MallocMemAux(C, Call,
Call.getArgExpr(Att->args_begin()->getASTIndex()),
- UndefinedVal(), State, AF_Malloc);
+ UndefinedVal(), State, AF);
}
- return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, AF_Malloc);
+ return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, AF);
}
ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
@@ -1862,16 +1865,18 @@ ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C,
if (!State)
return nullptr;
- if (Att->getModule()->getName() != "malloc")
- return nullptr;
+ // Preseve previous behavior when "malloc" class means AF_Malloc
+ auto attrClassName = Att->getModule()->getName();
+ auto AF = attrClassName == "malloc"
+ ? AF_Malloc
+ : AllocationFamily(AF_Custom, attrClassName);
bool IsKnownToBeAllocated = false;
for (const auto &Arg : Att->args()) {
- ProgramStateRef StateI =
- FreeMemAux(C, Call, State, Arg.getASTIndex(),
- Att->getOwnKind() == OwnershipAttr::Holds,
- IsKnownToBeAllocated, AF_Malloc);
+ ProgramStateRef StateI = FreeMemAux(
+ C, Call, State, Arg.getASTIndex(),
+ Att->getOwnKind() == OwnershipAttr::Holds, IsKnownToBeAllocated, AF);
if (StateI)
State = StateI;
}
@@ -1909,6 +1914,33 @@ static bool didPreviousFreeFail(ProgramStateRef State,
return false;
}
+static void printOwnershipTakesList(raw_ostream &os, CheckerContext &C,
+ const Expr *E) {
+ if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
+ const FunctionDecl *FD = CE->getDirectCallee();
+ if (!FD)
+ return;
+
+ if (FD->hasAttrs()) {
+ bool attrPrinted = false;
+
+ for (const auto *I : FD->specific_attrs<OwnershipAttr>()) {
+ OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind();
+
+ if (OwnKind == OwnershipAttr::Takes) {
+ if (attrPrinted)
+ os << ", ";
+ else
+ os << ", which takes ownership of ";
+
+ os << '\'' << I->getModule()->getName() << "'";
+ attrPrinted = true;
+ }
+ }
+ }
+ }
+}
+
static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E) {
if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
// FIXME: This doesn't handle indirect calls.
@@ -1966,11 +1998,12 @@ static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) {
case AF_InnerBuffer:
os << "container-specific allocator";
return;
+ case AF_Custom:
+ os << Family.name().value();
+ return;
case AF_Alloca:
case AF_None:
llvm_unreachable("not a deallocation expression");
- case AF_Custom:
- llvm_unreachable("not a deallocation expression");
}
}
@@ -1991,11 +2024,12 @@ static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) {
case AF_InnerBuffer:
os << "container-specific deallocator";
return;
+ case AF_Custom:
+ os << "function that takes ownership of '" << Family.name().value() << "\'";
+ return;
case AF_Alloca:
case AF_None:
llvm_unreachable("suspicious argument");
- case AF_Custom:
- llvm_unreachable("suspicious argument");
}
}
@@ -2180,6 +2214,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
switch (Family.kind()) {
case AF_Malloc:
case AF_Alloca:
+ case AF_Custom:
case AF_IfNameIndex: {
if (ChecksEnabled[CK_MallocChecker])
return CK_MallocChecker;
@@ -2202,7 +2237,6 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
return CK_InnerPointerChecker;
return std::nullopt;
}
- case AF_Custom:
case AF_None: {
llvm_unreachable("no family");
}
@@ -2432,6 +2466,8 @@ void MallocChecker::HandleMismatchedDealloc(CheckerContext &C,
if (printMemFnName(DeallocOs, C, DeallocExpr))
os << ", not " << DeallocOs.str();
+
+ printOwnershipTakesList(os, C, DeallocExpr);
}
auto R = std::make_unique<PathSensitiveBugReport>(*BT_MismatchedDealloc,
@@ -3545,6 +3581,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
switch (Family.kind()) {
case AF_Alloca:
case AF_Malloc:
+ case AF_Custom:
case AF_CXXNew:
case AF_CXXNewArray:
case AF_IfNameIndex:
@@ -3585,8 +3622,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
}
Msg = OS.str();
break;
- }
- case AF_Custom:
+ }
case AF_None:
llvm_unreachable("Unhandled allocation family!");
}
>From 0df5e138706e0db69f5c405b9712d3f26a7199c1 Mon Sep 17 00:00:00 2001
From: Pavel Skripkin <paskripkin at gmail.com>
Date: Mon, 15 Jul 2024 00:57:21 +0300
Subject: [PATCH 3/3] csa/tests: add test suite for custom allocation classes
---
.../MismatchedDeallocator-checker-test.mm | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
index 013d677e515cf..ef87951c0131e 100644
--- a/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
+++ b/clang/test/Analysis/MismatchedDeallocator-checker-test.mm
@@ -14,6 +14,13 @@
void free(void *);
void __attribute((ownership_takes(malloc, 1))) my_free(void *);
+void __attribute((ownership_returns(malloc1))) *my_malloc1(size_t);
+void __attribute((ownership_takes(malloc1, 1))) my_free1(void *);
+
+void __attribute((ownership_returns(malloc2))) *my_malloc2(size_t);
+void __attribute((ownership_returns(malloc2))) *my_malloc3(size_t);
+void __attribute((ownership_takes(malloc2, 1))) __attribute((ownership_takes(malloc3, 1))) my_free23(void *);
+
//---------------------------------------------------------------
// Test if an allocation function matches deallocation function
//---------------------------------------------------------------
@@ -60,6 +67,41 @@ void testMalloc8() {
operator delete[](p); // expected-warning{{Memory allocated by malloc() should be deallocated by free(), not operator delete[]}}
}
+void testMalloc9() {
+ int *p = (int *)my_malloc(sizeof(int));
+ my_free(p); // no warning
+}
+
+void testMalloc10() {
+ int *p = (int *)my_malloc1(sizeof(int));
+ my_free1(p); // no warning
+}
+
+void testMalloc11() {
+ int *p = (int *)my_malloc2(sizeof(int));
+ my_free23(p); // no warning
+}
+
+void testMalloc12() {
+ int *p = (int *)my_malloc1(sizeof(int));
+ my_free(p); // expected-warning{{Memory allocated by my_malloc1() should be deallocated by function that takes ownership of 'malloc1', not my_free(), which takes ownership of 'malloc'}}
+}
+
+void testMalloc13() {
+ int *p = (int *)my_malloc2(sizeof(int));
+ my_free1(p); // expected-warning{{Memory allocated by my_malloc2() should be deallocated by function that takes ownership of 'malloc2', not my_free1(), which takes ownership of 'malloc1'}}
+}
+
+void testMalloc14() {
+ int *p = (int *)my_malloc1(sizeof(int));
+ my_free23(p); // expected-warning{{Memory allocated by my_malloc1() should be deallocated by function that takes ownership of 'malloc1', not my_free23(), which takes ownership of 'malloc2', 'malloc3'}}
+}
+
+void testMalloc15() {
+ int *p = (int *)my_malloc1(sizeof(int));
+ free(p); // expected-warning{{Memory allocated by my_malloc1() should be deallocated by function that takes ownership of 'malloc1', not free()}}
+}
+
void testAlloca() {
int *p = (int *)__builtin_alloca(sizeof(int));
delete p; // expected-warning{{Memory allocated by alloca() should not be deallocated}}
More information about the cfe-commits
mailing list