[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:44:23 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-temporal-safety

Author: NeKon69

<details>
<summary>Changes</summary>

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

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


4 Files Affected:

- (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+21-8) 
- (modified) clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp (+20) 
- (modified) clang/test/Sema/LifetimeSafety/Inputs/lifetime-analysis.h (+5) 
- (modified) clang/test/Sema/LifetimeSafety/safety.cpp (+24-2) 


``````````diff
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 {
 

``````````

</details>


https://github.com/llvm/llvm-project/pull/213443


More information about the llvm-branch-commits mailing list