[clang] [LifetimeSafety] Trace assignment history for use-after-scope errors (PR #188467)
Yuan Suo via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 8 20:57:51 PDT 2026
================
@@ -0,0 +1,380 @@
+//===- AssignmentQuery.cpp - C++ Lifetime Safety Checker --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the LifetimeChecker, which detects use-after-free
+// errors by checking if live origins hold loans that have expired.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Analyses/LifetimeSafety/AssignmentQuery.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include <cstddef>
+
+namespace {
+
+using namespace clang;
+using namespace clang::lifetimes;
+using namespace clang::lifetimes::internal;
+
+std::optional<const Expr *> GetPureSrcExpr(const Expr *TargetExpr) {
+ if (!TargetExpr)
+ return std::nullopt;
+ const Expr *SExpr = TargetExpr->IgnoreParenCasts();
+ if (!SExpr)
+ return std::nullopt;
+
+ if (llvm::isa<DeclRefExpr, CXXTemporaryObjectExpr, ConditionalOperator,
+ CXXConstructExpr>(SExpr) &&
+ !SExpr->getExprLoc().isInvalid())
+ return SExpr;
+
+ if (const auto *SCExpr = llvm::dyn_cast<CallExpr>(SExpr);
+ SCExpr && !SCExpr->getExprLoc().isInvalid() &&
+ !SCExpr->getCallee()->IgnoreParenCasts()->getExprLoc().isInvalid())
+ return SCExpr;
+
+ if (const auto *SMExpr = llvm::dyn_cast<MemberExpr>(SExpr))
+ return GetPureSrcExpr(SMExpr->getBase());
+ if (const auto *SCExpr = llvm::dyn_cast<CXXMemberCallExpr>(SExpr))
+ return GetPureSrcExpr(SCExpr->getCallee());
+ if (const auto *SUOExpr = llvm::dyn_cast<UnaryOperator>(SExpr))
+ return GetPureSrcExpr(SUOExpr->getSubExpr());
+ if (const auto *SCBExpr = llvm::dyn_cast<CXXBindTemporaryExpr>(SExpr))
+ return GetPureSrcExpr(SCBExpr->getSubExpr());
+
+ return std::nullopt;
+}
+
+/// Specifically handles assignments involving a FieldDecl.
+///
+/// Since we currently only store the FieldDecl without its corresponding
+/// LHS expression, this function attempts to recover or resolve the LHS
+/// context by analyzing the RHS.
+const MemberExpr *getFieldFromAssignmentExpr(const Expr *RHS,
+ const ParentMap &CurrParentMap) {
+
+ const Stmt *CurrStmt = CurrParentMap.getParent(RHS);
+ if (!CurrStmt)
+ return nullptr;
+ if (const auto *BinaryOp = llvm::dyn_cast<BinaryOperator>(CurrStmt))
+ return llvm::dyn_cast<MemberExpr>(BinaryOp->getLHS());
+ if (const auto *CXXOp = llvm::dyn_cast<CXXOperatorCallExpr>(CurrStmt);
+ CXXOp && CXXOp->getOperator() == OO_Equal && CXXOp->getNumArgs() == 2)
+ return llvm::dyn_cast<MemberExpr>(CXXOp->getArg(0));
+ return nullptr;
+}
+
+AliasAssignmentSearchResult getAliasListCore(
+ const AssignmentQueryContext &Context, const CFGBlock *Block,
+ const LoanID EndLoanID, OriginID *TargetOID,
+ const std::optional<OriginDestExpr> LastDestDecl = std::nullopt,
+ const std::optional<OriginID> LastOriginID = std::nullopt) {
+ std::optional<OriginID> CurrOrigin = std::nullopt;
+ std::optional<OriginDestExpr> DestDecl = LastDestDecl;
+ std::optional<const Expr *> SrcExpr = std::nullopt;
+ llvm::SmallVector<AssignmentPair> AliasStmts;
+ const auto Facts = Context.FactMgr.getFacts(Block);
+ bool FetchLoan = false;
+ auto IssueOriginID = LastOriginID;
+
+ for (const auto &F : llvm::reverse(Facts)) {
+ if (const auto *OFF = F->getAs<OriginFlowFact>()) {
+ if (IssueOriginID.has_value() &&
+ OFF->getDestOriginID() == IssueOriginID.value())
+ FetchLoan = true;
+
+ if (OFF->getDestOriginID() == *TargetOID) {
+ const auto HeldLoans =
+ Context.LoanPropagation.getLoans(OFF->getSrcOriginID(), OFF);
+
+ if (HeldLoans.contains(EndLoanID)) {
+ const auto TargetOrigin =
+ Context.FactMgr.getOriginMgr().getOrigin(OFF->getDestOriginID());
+
+ if (!DestDecl.has_value()) {
+ if (const ValueDecl *DVecl = TargetOrigin.getDecl();
+ DVecl && !DVecl->getLocation().isInvalid()) {
+ if (llvm::isa<FieldDecl>(DVecl)) {
+ const auto *CurrExpr = Context.FactMgr.getOriginMgr()
+ .getOrigin(OFF->getSrcOriginID())
+ .getExpr();
+ if (CurrExpr)
+ DestDecl = getFieldFromAssignmentExpr(
+ CurrExpr, Context.ADC.getParentMap());
+ } else {
+ CurrOrigin = *TargetOID;
+ DestDecl = DVecl;
+ }
+ }
+ } else {
+ auto SExpr = GetPureSrcExpr(TargetOrigin.getExpr());
+ if (!SExpr.has_value()) {
+ const auto SrcOrigin = Context.FactMgr.getOriginMgr().getOrigin(
+ OFF->getSrcOriginID());
+ SExpr = GetPureSrcExpr(SrcOrigin.getExpr());
+ }
+
+ if (SExpr.has_value()) {
+ AliasStmts.push_back({DestDecl.value(), SExpr.value()});
+ SrcExpr = SExpr.value();
+ DestDecl = std::nullopt;
+ CurrOrigin = std::nullopt;
+ }
+ }
+ *TargetOID = OFF->getSrcOriginID();
+ }
+ }
+ } else if (const auto *IF = F->getAs<IssueFact>()) {
+ if (IF->getLoanID() == EndLoanID) {
+ IssueOriginID = IF->getOriginID();
+ }
+ } else if (const auto *UF = F->getAs<UseFact>()) {
+ if (CurrOrigin.has_value()) {
+ for (const OriginList *Cur = UF->getUsedOrigins(); Cur;
+ Cur = Cur->peelOuterOrigin()) {
+ if (Cur->getOuterOriginID() == CurrOrigin.value() &&
+ UF->isWritten()) {
+ const auto UExpr = GetPureSrcExpr(UF->getUseExpr());
+ if (UExpr.has_value()) {
+ if (const auto *UDExpr =
+ llvm::dyn_cast<DeclRefExpr>(UExpr.value())) {
+ DestDecl = UDExpr;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (FetchLoan) {
+ return {AliasStmts, true, DestDecl, IssueOriginID};
+ }
+ }
+ return {AliasStmts, false, DestDecl, IssueOriginID};
+}
+
+std::optional<llvm::SmallVector<AssignmentPair>>
+getAliasListInMultiBlock(const AssignmentQueryContext &Context,
+ const CFGBlock *StartBlock, const LoanID EndLoanID,
+ OriginID *StartOID) {
+ std::optional<OriginDestExpr> LastDestDecl = std::nullopt;
+ llvm::SmallVector<const CFGBlock *> PendingBlocks;
+ std::optional<AssignmentPair> StartStmt = std::nullopt;
+ std::optional<AssignmentPair> EndStmt = std::nullopt;
+ std::optional<OriginID> LastOriginID = std::nullopt;
+ llvm::SmallPtrSet<const CFGBlock *, 32> VistedBlocks;
+ llvm::DenseMap<AssignmentPair, AssignmentPair> VistedExprs;
+
+ const auto AliasStmtFilter = [&VistedExprs](const AssignmentPair StartStmt,
+ const AssignmentPair EndStmt) {
+ llvm::SmallVector<AssignmentPair> AliasStmts;
+ for (auto Stmt = StartStmt; Stmt != EndStmt; Stmt = VistedExprs.at(Stmt))
+ AliasStmts.push_back(Stmt);
+ AliasStmts.push_back(EndStmt);
+ return AliasStmts;
+ };
+
+ PendingBlocks.push_back(StartBlock);
+
+ for (size_t i = 0; i < PendingBlocks.size(); ++i) {
+ const CFGBlock *CurrBlock = PendingBlocks[i];
+
+ const auto [BlockAliasList, Success, CurrLastDestDecl, CurrLastOriginID] =
+ getAliasListCore(Context, CurrBlock, EndLoanID, StartOID, LastDestDecl,
+ LastOriginID);
+ if (CurrLastDestDecl)
+ LastDestDecl = CurrLastDestDecl;
+ if (CurrLastOriginID.has_value())
+ LastOriginID = CurrLastOriginID;
+
+ if (!BlockAliasList.empty()) {
+ if (VistedExprs.empty())
+ StartStmt = BlockAliasList[0];
+
+ for (size_t i = 0; i < BlockAliasList.size() - 1; ++i)
+ VistedExprs.insert({BlockAliasList[i], BlockAliasList[i + 1]});
+
+ if (EndStmt.has_value())
+ VistedExprs.insert({EndStmt.value(), BlockAliasList[0]});
+
+ EndStmt = BlockAliasList[BlockAliasList.size() - 1];
+ }
+
+ if (Success && StartStmt.has_value() && EndStmt.has_value())
+ return AliasStmtFilter(StartStmt.value(), EndStmt.value());
+
+ for (const auto &Block : CurrBlock->preds())
+ if (Block && VistedBlocks.insert(Block).second)
+ PendingBlocks.push_back(Block);
+
+ if (VistedBlocks.size() >= 32 && StartStmt.has_value() &&
----------------
suoyuan666 wrote:
This is just a temporary solution. I should have written a comment here.
Because I didn't think the search should go on indefinitely, I wanted to set a boundary, but I hadn't figured out a suitable condition, so I stopped after searching 32 CFGBlocks.
https://github.com/llvm/llvm-project/pull/188467
More information about the cfe-commits
mailing list