https://github.com/Snape3058 created 
https://github.com/llvm/llvm-project/pull/212432

Closing #160527 (omitting other redundant issues and Bugzilla reports)

* Modifying the CFGCleanupFunction by creating and storing an off-AST pseudo 
CallExpr
  For `T var __attribute__((cleanup(freefunc)));`, creating a `freefunc(&var)` 
CallExpr in CFGCleanupFunction.
* Invoking the cleanup callee via the pseudo CallExpr when handling the 
CFGCleanupFunction, by following the evalCall procedure.
* Extending the liveness of the annotated variable to keep its assignment until 
after the cleanup callee invocation.
* Other necessary adjustments in Diagnostics and CoreEngine for the invocation.

Call for comments on improving this. Test execution on Linux kernel, no crash 
detected. Feel free to add other related reviewers.

>From 8336a10d690d556747237608254aace94644145d Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Fri, 5 Jun 2026 17:02:40 +0200
Subject: [PATCH 1/8] wip-2026-06-19_13:59:00

---
 .../Core/PathSensitive/ExprEngine.h           |  2 ++
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp  | 29 +++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h 
b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index 327d5858e9c6b..9e02a31f9fc5d 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -387,6 +387,8 @@ class ExprEngine {
   void ProcessTemporaryDtor(const CFGTemporaryDtor D,
                             ExplodedNode *Pred, ExplodedNodeSet &Dst);
 
+  void ProcessCleanupFunction(const CFGCleanupFunction D, ExplodedNode *Pred);
+
   /// Called by CoreEngine when processing the entrance of a CFGBlock.
   void processCFGBlockEntrance(const BlockEdge &L, const BlockEntrance &BE,
                                NodeBuilder &Builder, ExplodedNode *Pred);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index cfb294736ee02..a93ca6ecbf258 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -981,6 +981,9 @@ void ExprEngine::processCFGElement(const CFGElement E, 
ExplodedNode *Pred,
                          E.castAs<CFGLifetimeEnds>().getVarDecl(), Pred);
       return;
     case CFGElement::CleanupFunction:
+      ProcessCleanupFunction(E.castAs<CFGCleanupFunction>(), Pred);
+      return;
+    case CFGElement::LifetimeEnds:
     case CFGElement::FullExprCleanup:
     case CFGElement::ScopeBegin:
     case CFGElement::ScopeEnd:
@@ -1610,6 +1613,32 @@ void ExprEngine::ProcessTemporaryDtor(const 
CFGTemporaryDtor D,
                      /*IsBase=*/false, CleanPred, Dst, CallOpts);
 }
 
+void ExprEngine::ProcessCleanupFunction(const CFGCleanupFunction CF, 
ExplodedNode *Pred) {
+  ProgramStateRef State = Pred->getState();
+  const StackFrame *SF = Pred->getStackFrame();
+
+  const FunctionDecl *FD = CF.getFunctionDecl();
+  SVal VF = svalBuilder.getFunctionPointer(FD);
+
+  const VarDecl *VD = CF.getVarDecl();
+  SVal VLoc = State->getLValue(VD, SF);
+
+  const CallExpr *CE = nullptr; // CF.getPseudoCallExpr();
+  // assert(CE->getNumArgs() == 1 && "Invalid cleanup function definition!");
+  const Expr *Callee = nullptr; // CE->getCallee();
+  const Expr *VarAddr = nullptr; // CE->getArg(0);
+  State = State->BindExpr(Callee, SF, VF, false);
+  State = State->BindExpr(VarAddr, SF, VLoc, false);
+
+  // Copied from ExprEngine::VisitCallExpr
+  CallEventManager &CEMgr = getStateManager().getCallEventManager();
+  CallEventRef<> CallTemplate = CEMgr.getSimpleCall(CE, State, SF, CF);
+  ExplodedNodeSet Dst;
+  evalCall(Dst, Pred, *CallTemplate);
+
+  Engine.enqueueStmtNode(Dst, getCurrBlock(), currStmtIdx);
+}
+
 void ExprEngine::processCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE,
                                                ExplodedNode *Pred,
                                                ExplodedNodeSet &Dst,

>From a7d00377e281c4754f421e9544fdcffc0eb97d3e Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Mon, 29 Jun 2026 14:29:09 +0200
Subject: [PATCH 2/8] wip-2026-06-29_14:29:03

---
 clang/include/clang/Analysis/CFG.h           | 22 ++++++++++-------
 clang/lib/Analysis/CFG.cpp                   | 25 ++++++++++++++++++++
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 16 +++++++------
 3 files changed, 48 insertions(+), 15 deletions(-)

diff --git a/clang/include/clang/Analysis/CFG.h 
b/clang/include/clang/Analysis/CFG.h
index 2079f99046021..f50473b539cda 100644
--- a/clang/include/clang/Analysis/CFG.h
+++ b/clang/include/clang/Analysis/CFG.h
@@ -436,19 +436,23 @@ class CFGImplicitDtor : public CFGElement {
 class CFGCleanupFunction final : public CFGElement {
 public:
   CFGCleanupFunction() = default;
-  CFGCleanupFunction(const VarDecl *VD)
-      : CFGElement(Kind::CleanupFunction, VD) {
-    assert(VD->hasAttr<CleanupAttr>());
-  }
+  CFGCleanupFunction(VarDecl *VD)
+      : CFGElement(Kind::CleanupFunction, createPseudoCallExpr(VD)) {}
 
   const VarDecl *getVarDecl() const {
-    return static_cast<const VarDecl *>(Data1.getPointer());
+    return cast<VarDecl>(
+        cast<DeclRefExpr>(
+            cast<UnaryOperator>(getPseudoCallExpr()->getArg(0))->getSubExpr())
+            ->getDecl());
   }
 
   /// Returns the function to be called when cleaning up the var decl.
   const FunctionDecl *getFunctionDecl() const {
-    const CleanupAttr *A = getVarDecl()->getAttr<CleanupAttr>();
-    return A->getFunctionDecl();
+    return getPseudoCallExpr()->getDirectCallee();
+  }
+
+  const CallExpr *getPseudoCallExpr() const {
+    return static_cast<const CallExpr *>(Data1.getPointer());
   }
 
 private:
@@ -457,6 +461,8 @@ class CFGCleanupFunction final : public CFGElement {
   static bool isKind(const CFGElement E) {
     return E.getKind() == Kind::CleanupFunction;
   }
+
+  static const CallExpr *createPseudoCallExpr(VarDecl *VD);
 };
 
 /// Represents C++ object destructor implicitly generated for automatic object
@@ -1222,7 +1228,7 @@ class CFGBlock {
     Elements.push_back(CFGAutomaticObjDtor(VD, S), C);
   }
 
-  void appendCleanupFunction(const VarDecl *VD, BumpVectorContext &C) {
+  void appendCleanupFunction(VarDecl *VD, BumpVectorContext &C) {
     Elements.push_back(CFGCleanupFunction(VD), C);
   }
 
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index bb8e98a894dab..0e379563ac9a2 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -5582,6 +5582,31 @@ CFGImplicitDtor::getDestructorDecl(ASTContext 
&astContext) const {
   llvm_unreachable("getKind() returned bogus value");
 }
 
+static const CallExpr *CFGCleanupFunction::createPseudoCallExpr(VarDecl *VD) {
+  const ASTContext &Ctx = VD->getASTContext();
+  const CleanupAttr *A = VD->getAttr<CleanupAttr>();
+  const FunctionDecl *FD = A->getFunctionDecl();
+
+  DeclRefExpr *RV = new (&Ctx)
+      DeclRefExpr(Ctx, VD, /*RefersToEnclosingVariableOrCapture=*/false,
+                  VD->getType(), VK_LValue, A->getLocation());
+  UnaryOperator *UO =
+      UnaryOperator::create(Ctx, DR, Ctx->getPointerType(VD->getType()),
+                            UO_AddrOf, VK_LValue, OK_Ordinary, 
A->getLocation(),
+                            /*CanOverflow=*/false, FPOptionsOverride());
+  DeclRefExpr *RF = new (&Ctx)
+      DeclRefExpr(Ctx, FD, /*RefersToEnclosingVariableOrCapture=*/false,
+                  FD->getType(), VK_LValue, A->getLocation());
+  ImplictCastExpr *IC = ImplicitCastExpr::create(
+      Ctx, Ctx->getPointeeType(FD->getType()), CK_FunctionToPointerDecay, RF,
+      nullptr, VK_LValue, FPOptionsOverride());
+  Expr *Args[1] = {IC};
+  CallExpr *CE = CallExpr::create(Ctx, Args, FD->getReturnType(), VK_RValue,
+                                  A->getLocation(), FPOptionsOverride());
+
+  return CE;
+}
+
 
//===----------------------------------------------------------------------===//
 // CFGBlock operations.
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index a93ca6ecbf258..c6bc52a9267b3 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1613,7 +1613,8 @@ void ExprEngine::ProcessTemporaryDtor(const 
CFGTemporaryDtor D,
                      /*IsBase=*/false, CleanPred, Dst, CallOpts);
 }
 
-void ExprEngine::ProcessCleanupFunction(const CFGCleanupFunction CF, 
ExplodedNode *Pred) {
+void ExprEngine::ProcessCleanupFunction(const CFGCleanupFunction CF,
+                                        ExplodedNode *Pred) {
   ProgramStateRef State = Pred->getState();
   const StackFrame *SF = Pred->getStackFrame();
 
@@ -1623,12 +1624,13 @@ void ExprEngine::ProcessCleanupFunction(const 
CFGCleanupFunction CF, ExplodedNod
   const VarDecl *VD = CF.getVarDecl();
   SVal VLoc = State->getLValue(VD, SF);
 
-  const CallExpr *CE = nullptr; // CF.getPseudoCallExpr();
-  // assert(CE->getNumArgs() == 1 && "Invalid cleanup function definition!");
-  const Expr *Callee = nullptr; // CE->getCallee();
-  const Expr *VarAddr = nullptr; // CE->getArg(0);
-  State = State->BindExpr(Callee, SF, VF, false);
-  State = State->BindExpr(VarAddr, SF, VLoc, false);
+  const CallExpr *CE = CF.getPseudoCallExpr();
+  const ImplicitCastExpr *Callee = cast<ImplicitCastExpr>(CE->getCallee());
+  const UnaryOperator *VarAddr = cast<UnaryOperator>(CE->getArg(0));
+  State = State->BindExpr(Callee->getSubExpr(), SF, VF);
+  State = State->BindExpr(Callee, SF, VF);
+  State = State->BindExpr(VarAddr->getSubExpr(), SF, VLoc);
+  State = State->BindExpr(VarAddr, SF, VLoc);
 
   // Copied from ExprEngine::VisitCallExpr
   CallEventManager &CEMgr = getStateManager().getCallEventManager();

>From 9916f44386e6daf7231d7a826e45c1558ebecef0 Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Mon, 29 Jun 2026 17:19:17 +0200
Subject: [PATCH 3/8] first land, fix compilation errors

---
 clang/lib/Analysis/CFG.cpp                   | 23 ++++++++++----------
 clang/lib/Analysis/PathDiagnostic.cpp        |  4 +++-
 clang/lib/StaticAnalyzer/Core/CoreEngine.cpp | 12 +++++++---
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 14 +++++-------
 4 files changed, 29 insertions(+), 24 deletions(-)

diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 0e379563ac9a2..b7dba00b4af8e 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -5582,27 +5582,28 @@ CFGImplicitDtor::getDestructorDecl(ASTContext 
&astContext) const {
   llvm_unreachable("getKind() returned bogus value");
 }
 
-static const CallExpr *CFGCleanupFunction::createPseudoCallExpr(VarDecl *VD) {
+const CallExpr *CFGCleanupFunction::createPseudoCallExpr(VarDecl *VD) {
   const ASTContext &Ctx = VD->getASTContext();
   const CleanupAttr *A = VD->getAttr<CleanupAttr>();
-  const FunctionDecl *FD = A->getFunctionDecl();
+  FunctionDecl *FD = A->getFunctionDecl();
 
   DeclRefExpr *RV = new (&Ctx)
       DeclRefExpr(Ctx, VD, /*RefersToEnclosingVariableOrCapture=*/false,
                   VD->getType(), VK_LValue, A->getLocation());
-  UnaryOperator *UO =
-      UnaryOperator::create(Ctx, DR, Ctx->getPointerType(VD->getType()),
-                            UO_AddrOf, VK_LValue, OK_Ordinary, 
A->getLocation(),
-                            /*CanOverflow=*/false, FPOptionsOverride());
+  UnaryOperator *UO = UnaryOperator::Create(
+      Ctx, RV, UO_AddrOf, Ctx.getPointerType(VD->getType()), VK_LValue,
+      OK_Ordinary, A->getLocation(),
+      /*CanOverflow=*/false, FPOptionsOverride());
+  Expr *Args[1] = {UO};
   DeclRefExpr *RF = new (&Ctx)
       DeclRefExpr(Ctx, FD, /*RefersToEnclosingVariableOrCapture=*/false,
                   FD->getType(), VK_LValue, A->getLocation());
-  ImplictCastExpr *IC = ImplicitCastExpr::create(
-      Ctx, Ctx->getPointeeType(FD->getType()), CK_FunctionToPointerDecay, RF,
+  ImplicitCastExpr *IC = ImplicitCastExpr::Create(
+      Ctx, Ctx.getPointerType(FD->getType()), CK_FunctionToPointerDecay, RF,
       nullptr, VK_LValue, FPOptionsOverride());
-  Expr *Args[1] = {IC};
-  CallExpr *CE = CallExpr::create(Ctx, Args, FD->getReturnType(), VK_RValue,
-                                  A->getLocation(), FPOptionsOverride());
+  CallExpr *CE =
+      CallExpr::Create(Ctx, IC, Args, FD->getReturnType(), VK_PRValue,
+                       A->getLocation(), FPOptionsOverride());
 
   return CE;
 }
diff --git a/clang/lib/Analysis/PathDiagnostic.cpp 
b/clang/lib/Analysis/PathDiagnostic.cpp
index a95ef4582be99..51357119f8d8e 100644
--- a/clang/lib/Analysis/PathDiagnostic.cpp
+++ b/clang/lib/Analysis/PathDiagnostic.cpp
@@ -526,6 +526,9 @@ static PathDiagnosticLocation getLocationForCaller(const 
StackFrame *SF,
   case CFGElement::CXXRecordTypedCall:
     return PathDiagnosticLocation(Source.castAs<CFGStmt>().getStmt(), SM,
                                   CallerSF);
+  case CFGElement::CleanupFunction:
+    return PathDiagnosticLocation(
+        Source.castAs<CFGCleanupFunction>().getPseudoCallExpr(), SM, CallerSF);
   case CFGElement::Initializer: {
     const CFGInitializer &Init = Source.castAs<CFGInitializer>();
     return PathDiagnosticLocation(Init.getInitializer()->getInit(), SM,
@@ -561,7 +564,6 @@ static PathDiagnosticLocation getLocationForCaller(const 
StackFrame *SF,
   }
   case CFGElement::ScopeBegin:
   case CFGElement::ScopeEnd:
-  case CFGElement::CleanupFunction:
     llvm_unreachable("not yet implemented!");
   case CFGElement::LifetimeEnds:
   case CFGElement::FullExprCleanup:
diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
index b38dfdfa6f2c4..9007183696817 100644
--- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -609,9 +609,15 @@ void CoreEngine::enqueueStmtNode(ExplodedNode *N,
     return;
   }
 
-  // At this point, we know we're processing a normal statement.
-  CFGStmt CS = (*Block)[Idx].castAs<CFGStmt>();
-  PostStmt Loc(CS.getStmt(), N->getStackFrame());
+  // At this point, we know we're processing a normal statement from a CFGStmt
+  // or the pseudo CallExpr from CFGCleanupFunction.
+  const Stmt *S = nullptr;
+  if (std::optional<CFGStmt> CS = (*Block)[Idx].getAs<CFGStmt>())
+    S = CS->getStmt();
+  else if (std::optional<CFGCleanupFunction> CF =
+               (*Block)[Idx].getAs<CFGCleanupFunction>())
+    S = CF->getPseudoCallExpr();
+  PostStmt Loc(S, N->getStackFrame());
 
   if (Loc == N->getLocation().withTag(nullptr)) {
     // Note: 'N' should be a fresh node because otherwise it shouldn't be
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index c6bc52a9267b3..475a41c68d530 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -983,7 +983,6 @@ void ExprEngine::processCFGElement(const CFGElement E, 
ExplodedNode *Pred,
     case CFGElement::CleanupFunction:
       ProcessCleanupFunction(E.castAs<CFGCleanupFunction>(), Pred);
       return;
-    case CFGElement::LifetimeEnds:
     case CFGElement::FullExprCleanup:
     case CFGElement::ScopeBegin:
     case CFGElement::ScopeEnd:
@@ -1625,20 +1624,17 @@ void ExprEngine::ProcessCleanupFunction(const 
CFGCleanupFunction CF,
   SVal VLoc = State->getLValue(VD, SF);
 
   const CallExpr *CE = CF.getPseudoCallExpr();
-  const ImplicitCastExpr *Callee = cast<ImplicitCastExpr>(CE->getCallee());
-  const UnaryOperator *VarAddr = cast<UnaryOperator>(CE->getArg(0));
-  State = State->BindExpr(Callee->getSubExpr(), SF, VF);
-  State = State->BindExpr(Callee, SF, VF);
-  State = State->BindExpr(VarAddr->getSubExpr(), SF, VLoc);
-  State = State->BindExpr(VarAddr, SF, VLoc);
+  State = State->BindExpr(CE->getCallee(), SF, VF);
+  State = State->BindExpr(CE->getArg(0), SF, VLoc);
 
   // Copied from ExprEngine::VisitCallExpr
   CallEventManager &CEMgr = getStateManager().getCallEventManager();
-  CallEventRef<> CallTemplate = CEMgr.getSimpleCall(CE, State, SF, CF);
+  CallEventRef<> CallTemplate =
+      CEMgr.getSimpleCall(CE, State, SF, getCFGElementRef());
   ExplodedNodeSet Dst;
   evalCall(Dst, Pred, *CallTemplate);
 
-  Engine.enqueueStmtNode(Dst, getCurrBlock(), currStmtIdx);
+  Engine.enqueueStmtNodes(Dst, getCurrBlock(), currStmtIdx);
 }
 
 void ExprEngine::processCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE,

>From 711ccf06ede0ed7eb3a9698cdd7227e129dfebf6 Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Tue, 30 Jun 2026 10:12:02 +0200
Subject: [PATCH 4/8] fix state by creating a new node

---
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 475a41c68d530..63a549a7ade0a 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1632,7 +1632,9 @@ void ExprEngine::ProcessCleanupFunction(const 
CFGCleanupFunction CF,
   CallEventRef<> CallTemplate =
       CEMgr.getSimpleCall(CE, State, SF, getCFGElementRef());
   ExplodedNodeSet Dst;
-  evalCall(Dst, Pred, *CallTemplate);
+  // Create a new node to apply the new state before function call.
+  evalCall(Dst, Engine.makeNode(PreStmt(CE, SF, /*tag=*/nullptr), State, Pred),
+           *CallTemplate);
 
   Engine.enqueueStmtNodes(Dst, getCurrBlock(), currStmtIdx);
 }

>From 436f0a69b20591eefcd6ada565a8c3f83e8d1583 Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Tue, 30 Jun 2026 10:19:10 +0200
Subject: [PATCH 5/8] add the first test case that can pass

---
 clang/test/Analysis/gcc-cleanup-attr.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
 create mode 100644 clang/test/Analysis/gcc-cleanup-attr.c

diff --git a/clang/test/Analysis/gcc-cleanup-attr.c 
b/clang/test/Analysis/gcc-cleanup-attr.c
new file mode 100644
index 0000000000000..9b05a9f64bc01
--- /dev/null
+++ b/clang/test/Analysis/gcc-cleanup-attr.c
@@ -0,0 +1,11 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify 
%s
+
+void clang_analyzer_dump(int);
+
+void defined_cleanup(int *p) {
+  clang_analyzer_dump(*p); // expected-warning {{42}}
+}
+
+void vardecl_defined() {
+  int x __attribute__((cleanup(defined_cleanup))) = 42;
+}

>From 787e995a858e4064ee5a8e133530f4d43ae072f8 Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Thu, 2 Jul 2026 23:52:03 +0200
Subject: [PATCH 6/8] fix liveness issue

---
 clang/lib/Analysis/LiveVariables.cpp   | 10 ++++++++--
 clang/test/Analysis/gcc-cleanup-attr.c |  2 ++
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Analysis/LiveVariables.cpp 
b/clang/lib/Analysis/LiveVariables.cpp
index af3055c3d5f8d..b5da30900d1ac 100644
--- a/clang/lib/Analysis/LiveVariables.cpp
+++ b/clang/lib/Analysis/LiveVariables.cpp
@@ -517,10 +517,16 @@ LiveVariablesImpl::runOnBlock(const CFGBlock *block,
       continue;
     }
 
-    if (!elem.getAs<CFGStmt>())
+    const Stmt *S = nullptr;
+    if (auto StmtElem = elem.getAs<CFGStmt>())
+      S = elem.castAs<CFGStmt>().getStmt();
+    else if (auto CleanupElem = elem.getAs<CFGCleanupFunction>())
+      S = cast<UnaryOperator>(CleanupElem->getPseudoCallExpr()->getArg(0))
+              ->getSubExpr();
+
+    if (!S)
       continue;
 
-    const Stmt *S = elem.castAs<CFGStmt>().getStmt();
     TF.Visit(const_cast<Stmt*>(S));
     stmtsToLiveness[S] = val;
   }
diff --git a/clang/test/Analysis/gcc-cleanup-attr.c 
b/clang/test/Analysis/gcc-cleanup-attr.c
index 9b05a9f64bc01..f2e64c0eaf8d3 100644
--- a/clang/test/Analysis/gcc-cleanup-attr.c
+++ b/clang/test/Analysis/gcc-cleanup-attr.c
@@ -4,8 +4,10 @@ void clang_analyzer_dump(int);
 
 void defined_cleanup(int *p) {
   clang_analyzer_dump(*p); // expected-warning {{42}}
+                           // expected-warning@-1 {{43}}
 }
 
 void vardecl_defined() {
   int x __attribute__((cleanup(defined_cleanup))) = 42;
+  int y __attribute__((cleanup(defined_cleanup))) = 43;
 }

>From 9120431e614451533f8cd1df370bc53b55cec189 Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Fri, 3 Jul 2026 00:33:35 +0200
Subject: [PATCH 7/8] add more tests

---
 clang/test/Analysis/gcc-cleanup-attr.c | 48 ++++++++++++++++++++++----
 1 file changed, 42 insertions(+), 6 deletions(-)

diff --git a/clang/test/Analysis/gcc-cleanup-attr.c 
b/clang/test/Analysis/gcc-cleanup-attr.c
index f2e64c0eaf8d3..0e7d56f2861b6 100644
--- a/clang/test/Analysis/gcc-cleanup-attr.c
+++ b/clang/test/Analysis/gcc-cleanup-attr.c
@@ -1,13 +1,49 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify 
%s
+// RUN: %clang_analyze_cc1 -verify %s -Wno-int-to-void-pointer-cast \
+// RUN:   -analyzer-checker=core,unix,debug.ExprInspection
 
-void clang_analyzer_dump(int);
+void clang_analyzer_dump(const void *);
 
 void defined_cleanup(int *p) {
-  clang_analyzer_dump(*p); // expected-warning {{42}}
-                           // expected-warning@-1 {{43}}
+  clang_analyzer_dump(p); // expected-warning {{&x}}
+                          // expected-warning@-1 {{&y}}
+  clang_analyzer_dump((const void *)*p); // expected-warning {{42}}
+                                         // expected-warning@-1 {{43}}
 }
 
 void vardecl_defined() {
-  int x __attribute__((cleanup(defined_cleanup))) = 42;
-  int y __attribute__((cleanup(defined_cleanup))) = 43;
+  {
+    int x __attribute__((cleanup(defined_cleanup)));
+    x = 42;
+  }
+  int y __attribute__((cleanup(defined_cleanup)));
+  y = 43;
+}
+
+typedef __typeof(sizeof(int)) size_t;
+void *malloc(size_t);
+void free(void *);
+
+// If cleanup function is not defined, fallback to conservative.
+void declared_cleanup(int **p);
+
+void vardecl_declared() {
+  int *p1 __attribute__((cleanup(declared_cleanup))) = malloc(sizeof(int));
+  int *p2 __attribute__((cleanup(declared_cleanup))) = malloc(sizeof(int));
+  *p1 = 42;
+  *p2 = 43;
+}
+
+void malloc_cleanup(int **p) {
+  clang_analyzer_dump((const void *)**p); // expected-warning {{42}}
+                                          // expected-warning@-1 {{43}}
+  free(*p);
+  clang_analyzer_dump(p); // expected-warning {{&p1}}
+                          // expected-warning@-1 {{&p2}}
+}
+
+void vardecl_malloc() {
+  int *p1 __attribute__((cleanup(malloc_cleanup))) = malloc(sizeof(int));
+  int *p2 __attribute__((cleanup(malloc_cleanup))) = malloc(sizeof(int));
+  *p1 = 42;
+  *p2 = 43;
 }

>From 26caefef6457f9fc2f8b023805bbc6e9d9d99842 Mon Sep 17 00:00:00 2001
From: Ella Ma <[email protected]>
Date: Fri, 3 Jul 2026 00:48:07 +0200
Subject: [PATCH 8/8] adjust pseudo CallExpr source location: attr -> attr arg

---
 clang/lib/Analysis/CFG.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index b7dba00b4af8e..d8aeb681647bd 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -5589,21 +5589,21 @@ const CallExpr 
*CFGCleanupFunction::createPseudoCallExpr(VarDecl *VD) {
 
   DeclRefExpr *RV = new (&Ctx)
       DeclRefExpr(Ctx, VD, /*RefersToEnclosingVariableOrCapture=*/false,
-                  VD->getType(), VK_LValue, A->getLocation());
+                  VD->getType(), VK_LValue, A->getArgLoc());
   UnaryOperator *UO = UnaryOperator::Create(
       Ctx, RV, UO_AddrOf, Ctx.getPointerType(VD->getType()), VK_LValue,
-      OK_Ordinary, A->getLocation(),
+      OK_Ordinary, A->getArgLoc(),
       /*CanOverflow=*/false, FPOptionsOverride());
   Expr *Args[1] = {UO};
   DeclRefExpr *RF = new (&Ctx)
       DeclRefExpr(Ctx, FD, /*RefersToEnclosingVariableOrCapture=*/false,
-                  FD->getType(), VK_LValue, A->getLocation());
+                  FD->getType(), VK_LValue, A->getArgLoc());
   ImplicitCastExpr *IC = ImplicitCastExpr::Create(
       Ctx, Ctx.getPointerType(FD->getType()), CK_FunctionToPointerDecay, RF,
       nullptr, VK_LValue, FPOptionsOverride());
   CallExpr *CE =
       CallExpr::Create(Ctx, IC, Args, FD->getReturnType(), VK_PRValue,
-                       A->getLocation(), FPOptionsOverride());
+                       A->getArgLoc(), FPOptionsOverride());
 
   return CE;
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to