[llvm-branch-commits] [clang] [LifetimeSafety] Support allocating/freeing builtins (PR #213443)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Aug 1 06:18:24 PDT 2026
https://github.com/NeKon69 created https://github.com/llvm/llvm-project/pull/213443
Adds support for plain malloc-style builtins as allocating/freeing functions, without needing any attributes.
`malloc`/`calloc`/`realloc`/`free` and their `__builtin_` spellings are now treated as allocating/freeing functions.
Closes #213435
Assisted-by: DeepSeek V4 for writing most of the code
>From 1be2288960563eae3a7c4bc3b6cb80a8273db257 Mon Sep 17 00:00:00 2001
From: NeKon69 <nobodqwe at gmail.com>
Date: Sat, 1 Aug 2026 15:52:51 +0300
Subject: [PATCH] [LifetimeSafety] Support allocating/freeing builtins
---
.../LifetimeSafety/FactsGenerator.cpp | 29 ++++++++++++++-----
.../LifetimeSafety/LifetimeAnnotations.cpp | 20 +++++++++++++
.../LifetimeSafety/Inputs/lifetime-analysis.h | 5 ++++
clang/test/Sema/LifetimeSafety/safety.cpp | 26 +++++++++++++++--
4 files changed, 70 insertions(+), 10 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 1d8eca0d92eae..a91673c0bc5dc 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -1086,22 +1086,35 @@ void FactsGenerator::handleFreeingCall(const Expr *Call, const FunctionDecl *FD,
// arguments, so the index mapping would be off by one.
if (isa<CXXConstructorDecl>(FD))
return;
+ if (!isFreeingFunction(*FD))
+ return;
+ auto InvalidateArg = [&](unsigned ArgIndex) {
+ if (ArgIndex >= Args.size())
+ return;
+ OriginList *ArgList = getOriginsList(*Args[ArgIndex]);
+ if (!ArgList)
+ return;
+ CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>(
+ ArgList->getOuterOriginID(), Call));
+ };
+
+ bool HasAttr = false;
for (const OwnershipAttr *Attr : FD->specific_attrs<OwnershipAttr>()) {
if (Attr->getOwnKind() != OwnershipAttr::Takes)
continue;
+ HasAttr = true;
for (const ParamIdx &Idx : Attr->args()) {
// `getLLVMIndex` encodes zero-origin indices including any implicit
// 'this' parameter, matching the layout of the Args array.
- unsigned ArgIndex = Idx.getLLVMIndex();
- if (ArgIndex >= Args.size())
- continue;
- OriginList *ArgList = getOriginsList(*Args[ArgIndex]);
- if (!ArgList)
- continue;
- CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>(
- ArgList->getOuterOriginID(), Call));
+ InvalidateArg(Idx.getLLVMIndex());
}
}
+ // Allocating/freeing builtins (e.g. `free`, `realloc`) free their first
+ // argument.
+ if (!HasAttr) {
+ assert(!Args.empty() && "freeing builtins take at least one argument");
+ InvalidateArg(0);
+ }
}
void FactsGenerator::handleAllocatingCall(const Expr *Call,
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 62f178618701b..fa8beda3de745 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -454,6 +454,17 @@ bool destructsFirstArg(const FunctionDecl &FD) {
}
bool isAllocatingFunction(const FunctionDecl &FD) {
+ switch (FD.getBuiltinID()) {
+ case Builtin::BImalloc:
+ case Builtin::BI__builtin_malloc:
+ case Builtin::BIcalloc:
+ case Builtin::BI__builtin_calloc:
+ case Builtin::BIrealloc:
+ case Builtin::BI__builtin_realloc:
+ return true;
+ default:
+ break;
+ }
return llvm::any_of(FD.specific_attrs<OwnershipAttr>(),
[](const OwnershipAttr *Attr) {
return Attr->getOwnKind() == OwnershipAttr::Returns;
@@ -461,6 +472,15 @@ bool isAllocatingFunction(const FunctionDecl &FD) {
}
bool isFreeingFunction(const FunctionDecl &FD) {
+ switch (FD.getBuiltinID()) {
+ case Builtin::BIfree:
+ case Builtin::BI__builtin_free:
+ case Builtin::BIrealloc:
+ case Builtin::BI__builtin_realloc:
+ return true;
+ default:
+ break;
+ }
return llvm::any_of(FD.specific_attrs<OwnershipAttr>(),
[](const OwnershipAttr *Attr) {
return Attr->getOwnKind() == OwnershipAttr::Takes;
diff --git a/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h b/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
index 024c3c2bc51b7..146756db6985a 100644
--- a/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h
@@ -362,3 +362,8 @@ void *operator new[](std::size_t, void *) noexcept;
void *operator new(std::size_t, const std::nothrow_t &) noexcept;
void *operator new(std::size_t, std::align_val_t,
const std::nothrow_t &) noexcept;
+
+extern "C" void *malloc(unsigned long);
+extern "C" void free(void *);
+extern "C" void *calloc(unsigned long, unsigned long);
+extern "C" void *realloc(void *, unsigned long);
diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp
index f52a3a0710914..f68de8f83006d 100644
--- a/clang/test/Sema/LifetimeSafety/safety.cpp
+++ b/clang/test/Sema/LifetimeSafety/safety.cpp
@@ -3379,12 +3379,14 @@ void allocate_void_ptr() {
} // namespace new_allocation
-namespace ownership_functions {
+namespace allocating_freeing_functions {
// Allocating/freeing functions are annotated with the ownership attributes:
// * ownership_returns: the result is a fresh allocation with a new loan.
// * ownership_takes: the annotated arguments are freed, invalidating their
// origins.
+// Plain malloc/free-style builtins are modeled the same way without any
+// attributes.
__attribute__((ownership_returns(malloc))) int *myalloc(void);
__attribute__((ownership_takes(malloc, 1))) void myfree(int *p);
@@ -3511,7 +3513,27 @@ void ownership_returns_member_realloc() {
*p = 1; // expected-note {{later used here}}
}
-} // namespace ownership_functions
+void builtin_malloc_free_uaf() {
+ int *p = (int *)malloc(sizeof(int)); // expected-warning {{object allocated by 'malloc' does not live long enough}}
+ free(p); // expected-note {{object allocated by 'malloc' is freed here}}
+ *p = 1; // expected-note {{later used here}}
+}
+
+void builtin_malloc_prefixed_uaf() {
+ int *p = (int *)__builtin_malloc(sizeof(int)); // expected-warning {{object allocated by '__builtin_malloc' does not live long enough}}
+ __builtin_free(p); // expected-note {{object allocated by '__builtin_malloc' is freed here}}
+ *p = 1; // expected-note {{later used here}}
+}
+
+void builtin_realloc() {
+ int *p = (int *)malloc(sizeof(int));
+ p = (int *)realloc(p, sizeof(int) * 2); // expected-warning {{object allocated by 'realloc' does not live long enough}}
+ *p = 1;
+ free(p); // expected-note {{object allocated by 'realloc' is freed here}}
+ *p = 1; // expected-note {{later used here}}
+}
+
+} // namespace allocating_freeing_functions
namespace placement_new {
More information about the llvm-branch-commits
mailing list