[clang] [LifetimeSafety] Trace assignment history for use-after-scope errors (PR #188467)
Yuan Suo via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 14 04:26:07 PDT 2026
================
@@ -0,0 +1,365 @@
+//===- 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 "clang/Analysis/CFG.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;
+}
+
+const DeclRefExpr *getLHSExpr(const UseFact *UF, const OriginID OID) {
+ for (const OriginList *Cur = UF->getUsedOrigins(); Cur;
+ Cur = Cur->peelOuterOrigin()) {
+ if (Cur->getOuterOriginID() != OID || !UF->isWritten())
+ continue;
+ std::optional<const Expr *> UExpr = GetPureSrcExpr(UF->getUseExpr());
+ if (UExpr) {
+ if (const auto *UDExpr = llvm::dyn_cast<DeclRefExpr>(UExpr.value())) {
+ return UDExpr;
+ }
+ }
+ }
+ return nullptr;
+}
+
+std::optional<OriginDestExpr>
+getLHSDeclOrExpr(const AssignmentQueryContext &Context,
+ const OriginFlowFact *OFF) {
+ const Origin TargetOrigin =
+ Context.FactMgr.getOriginMgr().getOrigin(OFF->getDestOriginID());
+ if (const ValueDecl *DVecl = TargetOrigin.getDecl();
+ DVecl && !DVecl->getLocation().isInvalid()) {
+ if (llvm::isa<FieldDecl>(DVecl)) {
+ const Expr *CurrExpr = Context.FactMgr.getOriginMgr()
+ .getOrigin(OFF->getSrcOriginID())
+ .getExpr();
+ if (CurrExpr)
+ return getFieldFromAssignmentExpr(CurrExpr, Context.ADC.getParentMap());
+ } else {
+ return DVecl;
+ }
+ }
+ return std::nullopt;
+}
+
+std::optional<const Expr *>
+getRHSDeclOrExpr(const AssignmentQueryContext &Context,
+ const OriginFlowFact *OFF) {
+ const Origin TargetOrigin =
+ Context.FactMgr.getOriginMgr().getOrigin(OFF->getDestOriginID());
+ std::optional<const Expr *> SExpr = GetPureSrcExpr(TargetOrigin.getExpr());
+ if (!SExpr) {
+ const Origin SrcOrigin =
+ Context.FactMgr.getOriginMgr().getOrigin(OFF->getSrcOriginID());
+ SExpr = GetPureSrcExpr(SrcOrigin.getExpr());
+ }
+
+ return SExpr;
+}
+
+AliasAssignmentSearchResult getAliasListCore(
+ const AssignmentQueryContext &Context,
+ llvm::SmallVectorImpl<AssignmentPair> &AssignmentList,
+ const CFGBlock *Block, const LoanID EndLoanID, OriginID *TargetOID,
+ const std::optional<OriginDestExpr> LastDestExpr = std::nullopt,
+ const std::optional<OriginID> LastOriginID = std::nullopt) {
+ llvm::ArrayRef<const Fact *> Facts = Context.FactMgr.getFacts(Block);
+ std::optional<OriginID> IssueOriginID = LastOriginID;
+ std::optional<OriginDestExpr> CurrDestExpr = LastDestExpr;
+ std::optional<OriginID> CurrOrigin = std::nullopt;
+
+ const auto InsertAssignmentList = [&](const OriginFlowFact *OFF) {
+ if (!CurrDestExpr) {
+ std::optional<OriginDestExpr> DestExpr = getLHSDeclOrExpr(Context, OFF);
+ if (DestExpr) {
+ if (llvm::isa<const ValueDecl *>(DestExpr.value()))
+ CurrOrigin = *TargetOID;
+ CurrDestExpr = DestExpr;
+ }
+ } else {
+ std::optional<const Expr *> CurrSrcExpr = getRHSDeclOrExpr(Context, OFF);
+ if (CurrSrcExpr) {
+ AssignmentList.push_back({CurrDestExpr.value(), CurrSrcExpr.value()});
+ CurrDestExpr = std::nullopt;
+ CurrOrigin = std::nullopt;
+ }
+ }
+ };
+
+ for (const Fact *F : llvm::reverse(Facts)) {
+ if (const auto *OFF = F->getAs<OriginFlowFact>()) {
+ if (IssueOriginID && OFF->getDestOriginID() == IssueOriginID.value())
+ return {true, CurrDestExpr, IssueOriginID};
+ if (OFF->getDestOriginID() == *TargetOID &&
+ Context.LoanPropagation.getLoans(OFF->getSrcOriginID(), OFF)
+ .contains(EndLoanID)) {
+ InsertAssignmentList(OFF);
+ *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) {
+ const DeclRefExpr *LHSExpr = getLHSExpr(UF, CurrOrigin.value());
+ if (LHSExpr)
+ CurrDestExpr = LHSExpr;
+ }
+ }
+ }
+
+ return {false, CurrDestExpr, IssueOriginID};
+}
+
+void getAliasListInMultiBlock(
+ const AssignmentQueryContext &Context,
+ llvm::SmallVectorImpl<AssignmentPair> &AssignmentList,
+ 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,
+ &AssignmentList](const AssignmentPair StartStmt,
+ const AssignmentPair EndStmt) {
+ llvm::SmallVector<AssignmentPair> AliasStmts;
+ for (AssignmentPair Stmt = StartStmt; Stmt != EndStmt;
+ Stmt = VistedExprs.at(Stmt))
+ AssignmentList.push_back(Stmt);
+ AssignmentList.push_back(EndStmt);
+ return AliasStmts;
+ };
+
+ PendingBlocks.push_back(StartBlock);
+
+ for (size_t i = 0; i < PendingBlocks.size(); ++i) {
----------------
suoyuan666 wrote:
Yes, I intend to use BFS, and after reviewing my implementation, I realized there were indeed issues. Both the deduplication logic and the way previous search states were preserved were flawed.
I have now refactored this part to fix those issues.
https://github.com/llvm/llvm-project/pull/188467
More information about the cfe-commits
mailing list