[LifetimeSafety] Introduce AccessPath-based expiry (#187708)

Refactored the loan system to use access paths instead of loan IDs for
expiry tracking, consolidating PathLoan and PlaceholderLoan into a
unified Loan class.

This is a non-functional refactoring to move towards more granular
paths. This also removes a quadratic complexity of `handleLifetimeEnds`
where we iterated over all loans to find which loans expired.
This commit is contained in:
Utkarsh Saxena 2026-03-24 14:21:52 +01:00 committed by GitHub
parent b1aa6a4506
commit 8a61e8f816
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 203 additions and 265 deletions

View File

@ -102,8 +102,13 @@ public:
const OriginManager &OM) const override;
};
/// When an AccessPath expires (e.g., a variable goes out of scope), all loans
/// that are associated with this path expire. For example, if `x` expires, then
/// the loan to `x` expires.
class ExpireFact : public Fact {
LoanID LID;
// The access path that expires.
AccessPath AP;
// Expired origin (e.g., its variable goes out of scope).
std::optional<OriginID> OID;
SourceLocation ExpiryLoc;
@ -111,11 +116,11 @@ class ExpireFact : public Fact {
public:
static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; }
ExpireFact(LoanID LID, SourceLocation ExpiryLoc,
ExpireFact(AccessPath AP, SourceLocation ExpiryLoc,
std::optional<OriginID> OID = std::nullopt)
: Fact(Kind::Expire), LID(LID), OID(OID), ExpiryLoc(ExpiryLoc) {}
: Fact(Kind::Expire), AP(AP), OID(OID), ExpiryLoc(ExpiryLoc) {}
LoanID getLoanID() const { return LID; }
const AccessPath &getAccessPath() const { return AP; }
std::optional<OriginID> getOriginID() const { return OID; }
SourceLocation getExpiryLoc() const { return ExpiryLoc; }

View File

@ -28,119 +28,101 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
}
/// Represents the storage location being borrowed, e.g., a specific stack
/// variable.
/// TODO: Model access paths of other types, e.g., s.field, heap and globals.
/// variable or a field within it: var.field.*
///
/// An AccessPath consists of a root which is one of:
/// - ValueDecl: a local variable or global
/// - MaterializeTemporaryExpr: a temporary object
/// - ParmVarDecl: a function parameter (placeholder)
/// - CXXMethodDecl: the implicit 'this' object (placeholder)
///
/// Placeholder paths never expire within the function scope, as they represent
/// storage from the caller's scope.
///
/// TODO: Model access paths of other types, e.g. field, array subscript, heap
/// and globals.
class AccessPath {
// An access path can be:
// - ValueDecl * , to represent the storage location corresponding to the
// variable declared in ValueDecl.
// - MaterializeTemporaryExpr * , to represent the storage location of the
// temporary object materialized via this MaterializeTemporaryExpr.
const llvm::PointerUnion<const clang::ValueDecl *,
const clang::MaterializeTemporaryExpr *>
P;
public:
AccessPath(const clang::ValueDecl *D) : P(D) {}
AccessPath(const clang::MaterializeTemporaryExpr *MTE) : P(MTE) {}
const clang::ValueDecl *getAsValueDecl() const {
return P.dyn_cast<const clang::ValueDecl *>();
}
const clang::MaterializeTemporaryExpr *getAsMaterializeTemporaryExpr() const {
return P.dyn_cast<const clang::MaterializeTemporaryExpr *>();
}
bool operator==(const AccessPath &RHS) const { return P == RHS.P; }
};
/// An abstract base class for a single "Loan" which represents lending a
/// storage in memory.
class Loan {
/// TODO: Represent opaque loans.
/// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it
/// is represented as empty LoanSet
public:
enum class Kind : uint8_t {
/// A loan with an access path to a storage location.
Path,
/// A non-expiring placeholder loan for a parameter, representing a borrow
/// from the function's caller.
Placeholder
ValueDecl,
MaterializeTemporary,
PlaceholderParam,
PlaceholderThis
};
Loan(Kind K, LoanID ID) : K(K), ID(ID) {}
virtual ~Loan() = default;
private:
Kind K;
const llvm::PointerUnion<const clang::ValueDecl *,
const clang::MaterializeTemporaryExpr *,
const ParmVarDecl *, const CXXMethodDecl *>
Root;
public:
AccessPath(const clang::ValueDecl *D) : K(Kind::ValueDecl), Root(D) {}
AccessPath(const clang::MaterializeTemporaryExpr *MTE)
: K(Kind::MaterializeTemporary), Root(MTE) {}
static AccessPath Placeholder(const ParmVarDecl *PVD) {
return AccessPath(Kind::PlaceholderParam, PVD);
}
static AccessPath Placeholder(const CXXMethodDecl *MD) {
return AccessPath(Kind::PlaceholderThis, MD);
}
AccessPath(const AccessPath &Other) : K(Other.K), Root(Other.Root) {}
Kind getKind() const { return K; }
LoanID getID() const { return ID; }
virtual void dump(llvm::raw_ostream &OS) const = 0;
const clang::ValueDecl *getAsValueDecl() const {
return K == Kind::ValueDecl ? Root.dyn_cast<const clang::ValueDecl *>()
: nullptr;
}
const clang::MaterializeTemporaryExpr *getAsMaterializeTemporaryExpr() const {
return K == Kind::MaterializeTemporary
? Root.dyn_cast<const clang::MaterializeTemporaryExpr *>()
: nullptr;
}
const ParmVarDecl *getAsPlaceholderParam() const {
return K == Kind::PlaceholderParam ? Root.dyn_cast<const ParmVarDecl *>()
: nullptr;
}
const CXXMethodDecl *getAsPlaceholderThis() const {
return K == Kind::PlaceholderThis ? Root.dyn_cast<const CXXMethodDecl *>()
: nullptr;
}
bool operator==(const AccessPath &RHS) const {
return K == RHS.K && Root == RHS.Root;
}
bool operator!=(const AccessPath &RHS) const { return !(*this == RHS); }
void dump(llvm::raw_ostream &OS) const;
private:
const Kind K;
AccessPath(Kind K, const ParmVarDecl *PVD) : K(K), Root(PVD) {}
AccessPath(Kind K, const CXXMethodDecl *MD) : K(K), Root(MD) {}
};
/// Represents lending a storage location.
///
/// A loan tracks the borrowing relationship created by operations like
/// taking a pointer/reference (&x), creating a view (std::string_view sv = s),
/// or receiving a parameter.
///
/// Examples:
/// - `int* p = &x;` creates a loan to `x`
/// - Parameter loans have no IssueExpr (created at function entry)
class Loan {
const LoanID ID;
};
/// PathLoan represents lending a storage location that is visible within the
/// function's scope (e.g., a local variable on stack).
class PathLoan : public Loan {
AccessPath Path;
/// The expression that creates the loan, e.g., &x.
const Expr *IssueExpr;
const AccessPath Path;
/// The expression that creates the loan, e.g., &x. Null for placeholder
/// loans.
const Expr *IssuingExpr;
public:
PathLoan(LoanID ID, AccessPath Path, const Expr *IssueExpr)
: Loan(Kind::Path, ID), Path(Path), IssueExpr(IssueExpr) {}
Loan(LoanID ID, AccessPath Path, const Expr *IssuingExpr)
: ID(ID), Path(Path), IssuingExpr(IssuingExpr) {}
LoanID getID() const { return ID; }
const AccessPath &getAccessPath() const { return Path; }
const Expr *getIssueExpr() const { return IssueExpr; }
void dump(llvm::raw_ostream &OS) const override;
static bool classof(const Loan *L) { return L->getKind() == Kind::Path; }
};
/// A placeholder loan held by a function parameter or an implicit 'this'
/// object, representing a borrow from the caller's scope.
///
/// Created at function entry for each pointer or reference parameter or for
/// the implicit 'this' parameter of instance methods, with an
/// origin. Unlike PathLoan, placeholder loans:
/// - Have no IssueExpr (created at function entry, not at a borrow site)
/// - Have no AccessPath (the borrowed object is not visible to the function)
/// - Do not currently expire, but may in the future when modeling function
/// invalidations (e.g., vector::push_back)
///
/// When a placeholder loan escapes the function (e.g., via return), it
/// indicates the parameter or method should be marked [[clang::lifetimebound]],
/// enabling lifetime annotation suggestions.
class PlaceholderLoan : public Loan {
/// The function parameter or method (representing 'this') that holds this
/// placeholder loan.
llvm::PointerUnion<const ParmVarDecl *, const CXXMethodDecl *> ParamOrMethod;
public:
PlaceholderLoan(LoanID ID, const ParmVarDecl *PVD)
: Loan(Kind::Placeholder, ID), ParamOrMethod(PVD) {}
PlaceholderLoan(LoanID ID, const CXXMethodDecl *MD)
: Loan(Kind::Placeholder, ID), ParamOrMethod(MD) {}
const ParmVarDecl *getParmVarDecl() const {
return ParamOrMethod.dyn_cast<const ParmVarDecl *>();
}
const CXXMethodDecl *getMethodDecl() const {
return ParamOrMethod.dyn_cast<const CXXMethodDecl *>();
}
void dump(llvm::raw_ostream &OS) const override;
static bool classof(const Loan *L) {
return L->getKind() == Kind::Placeholder;
}
const Expr *getIssuingExpr() const { return IssuingExpr; }
void dump(llvm::raw_ostream &OS) const;
};
/// Manages the creation, storage and retrieval of loans.
@ -148,15 +130,9 @@ class LoanManager {
public:
LoanManager() = default;
template <typename LoanType, typename... Args>
LoanType *createLoan(Args &&...args) {
static_assert(
std::is_same_v<LoanType, PathLoan> ||
std::is_same_v<LoanType, PlaceholderLoan>,
"createLoan can only be used with PathLoan or PlaceholderLoan");
void *Mem = LoanAllocator.Allocate<LoanType>();
auto *NewLoan =
new (Mem) LoanType(getNextLoanID(), std::forward<Args>(args)...);
Loan *createLoan(AccessPath Path, const Expr *IssueExpr) {
void *Mem = LoanAllocator.Allocate<Loan>();
auto *NewLoan = new (Mem) Loan(getNextLoanID(), Path, IssueExpr);
AllLoans.push_back(NewLoan);
return NewLoan;
}
@ -165,6 +141,7 @@ public:
assert(ID.Value < AllLoans.size());
return AllLoans[ID.Value];
}
llvm::ArrayRef<const Loan *> getLoans() const { return AllLoans; }
private:

View File

@ -139,60 +139,45 @@ public:
};
for (LoanID LID : EscapedLoans) {
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
const auto *PL = dyn_cast<PlaceholderLoan>(L);
if (!PL)
continue;
if (const auto *PVD = PL->getParmVarDecl())
const AccessPath &AP = L->getAccessPath();
if (const auto *PVD = AP.getAsPlaceholderParam())
CheckParam(PVD);
else if (const auto *MD = PL->getMethodDecl())
else if (const auto *MD = AP.getAsPlaceholderThis())
CheckImplicitThis(MD);
}
}
/// Checks for use-after-free & use-after-return errors when a loan expires.
/// Checks for use-after-free & use-after-return errors when an access path
/// expires (e.g., a variable goes out of scope).
///
/// This method examines all live origins at the expiry point and determines
/// if any of them hold the expiring loan. If so, it creates a pending
/// warning.
///
/// Note: This implementation considers only the confidence of origin
/// liveness. Future enhancements could also consider the confidence of loan
/// propagation (e.g., a loan may only be held on some execution paths).
/// When a path expires, all loans having this path expires.
/// This method examines all live origins and reports warnings for loans they
/// hold that are prefixed by the expired path.
void checkExpiry(const ExpireFact *EF) {
LoanID ExpiredLoan = EF->getLoanID();
const Expr *MovedExpr = nullptr;
if (auto *ME = MovedLoans.getMovedLoans(EF).lookup(ExpiredLoan))
MovedExpr = *ME;
const AccessPath &ExpiredPath = EF->getAccessPath();
LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
bool CurDomination = false;
// The UseFact or OriginEscapesFact most indicative of a lifetime error,
// prioritized by earlier source location.
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact =
nullptr;
for (auto &[OID, LiveInfo] : Origins) {
LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
if (!HeldLoans.contains(ExpiredLoan))
continue;
// Loan is defaulted.
if (!CurDomination || causingFactDominatesExpiry(LiveInfo.Kind))
CausingFact = LiveInfo.CausingFact;
for (LoanID HeldLoanID : HeldLoans) {
const Loan *HeldLoan = FactMgr.getLoanMgr().getLoan(HeldLoanID);
if (ExpiredPath != HeldLoan->getAccessPath())
continue;
// HeldLoan is expired because its AccessPath is expired.
PendingWarning &CurWarning = FinalWarningsMap[HeldLoan->getID()];
const Expr *MovedExpr = nullptr;
if (auto *ME = MovedLoans.getMovedLoans(EF).lookup(HeldLoanID))
MovedExpr = *ME;
// Skip if we already have a dominating causing fact.
if (CurWarning.CausingFactDominatesExpiry)
continue;
if (causingFactDominatesExpiry(LiveInfo.Kind))
CurWarning.CausingFactDominatesExpiry = true;
CurWarning.CausingFact = LiveInfo.CausingFact;
CurWarning.ExpiryLoc = EF->getExpiryLoc();
CurWarning.MovedExpr = MovedExpr;
CurWarning.InvalidatedByExpr = nullptr;
}
}
if (!CausingFact)
return;
bool LastDomination =
FinalWarningsMap.lookup(ExpiredLoan).CausingFactDominatesExpiry;
if (LastDomination)
return;
FinalWarningsMap[ExpiredLoan] = {
/*ExpiryLoc=*/EF->getExpiryLoc(),
/*CausingFact=*/CausingFact,
/*MovedExpr=*/MovedExpr,
/*InvalidatedByExpr=*/nullptr,
/*CausingFactDominatesExpiry=*/CurDomination};
}
/// Checks for use-after-invalidation errors when a container is modified.
@ -206,18 +191,9 @@ public:
LoanSet DirectlyInvalidatedLoans =
LoanPropagation.getLoans(InvalidatedOrigin, IOF);
auto IsInvalidated = [&](const Loan *L) {
auto *PathL = dyn_cast<PathLoan>(L);
auto *PlaceholderL = dyn_cast<PlaceholderLoan>(L);
for (LoanID InvalidID : DirectlyInvalidatedLoans) {
const Loan *L = FactMgr.getLoanMgr().getLoan(InvalidID);
auto *InvalidPathL = dyn_cast<PathLoan>(L);
auto *InvalidPlaceholderL = dyn_cast<PlaceholderLoan>(L);
if (PathL && InvalidPathL &&
PathL->getAccessPath() == InvalidPathL->getAccessPath())
return true;
if (PlaceholderL && InvalidPlaceholderL &&
PlaceholderL->getParmVarDecl() ==
InvalidPlaceholderL->getParmVarDecl())
const Loan *InvalidL = FactMgr.getLoanMgr().getLoan(InvalidID);
if (InvalidL->getAccessPath() == L->getAccessPath())
return true;
}
return false;
@ -248,39 +224,41 @@ public:
return;
for (const auto &[LID, Warning] : FinalWarningsMap) {
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
const Expr *IssueExpr = nullptr;
if (const auto *BL = dyn_cast<PathLoan>(L))
IssueExpr = BL->getIssueExpr();
const ParmVarDecl *InvalidatedPVD = nullptr;
if (const auto *PL = dyn_cast<PlaceholderLoan>(L))
InvalidatedPVD = PL->getParmVarDecl();
const Expr *IssueExpr = L->getIssuingExpr();
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
CausingFact = Warning.CausingFact;
const ParmVarDecl *InvalidatedPVD =
L->getAccessPath().getAsPlaceholderParam();
const Expr *MovedExpr = Warning.MovedExpr;
SourceLocation ExpiryLoc = Warning.ExpiryLoc;
if (const auto *UF = CausingFact.dyn_cast<const UseFact *>()) {
if (Warning.InvalidatedByExpr) {
if (IssueExpr)
// Use-after-invalidation of an object on stack.
SemaHelper->reportUseAfterInvalidation(IssueExpr, UF->getUseExpr(),
Warning.InvalidatedByExpr);
if (InvalidatedPVD)
else if (InvalidatedPVD)
// Use-after-invalidation of a parameter.
SemaHelper->reportUseAfterInvalidation(
InvalidatedPVD, UF->getUseExpr(), Warning.InvalidatedByExpr);
} else
// Scope-based expiry (use-after-scope).
SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
ExpiryLoc);
} else if (const auto *OEF =
CausingFact.dyn_cast<const OriginEscapesFact *>()) {
if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
// Return stack address.
SemaHelper->reportUseAfterReturn(
IssueExpr, RetEscape->getReturnExpr(), MovedExpr, ExpiryLoc);
else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF))
// Dangling field.
SemaHelper->reportDanglingField(
IssueExpr, FieldEscape->getFieldDecl(), MovedExpr, ExpiryLoc);
else if (const auto *GlobalEscape = dyn_cast<GlobalEscapeFact>(OEF))
// Global escape.
SemaHelper->reportDanglingGlobal(IssueExpr, GlobalEscape->getGlobal(),
MovedExpr, ExpiryLoc);
else

View File

@ -8,10 +8,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclID.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
#include "llvm/ADT/STLFunctionalExtras.h"
namespace clang::lifetimes::internal {
@ -32,7 +29,7 @@ void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
const OriginManager &OM) const {
OS << "Expire (";
LM.getLoan(getLoanID())->dump(OS);
getAccessPath().dump(OS);
if (auto OID = getOriginID()) {
OS << ", Origin: ";
OM.dump(*OID, OS);

View File

@ -70,12 +70,11 @@ void FactsGenerator::flow(OriginList *Dst, OriginList *Src, bool Kill) {
/// This function should be called whenever a DeclRefExpr represents a borrow.
/// \param DRE The declaration reference expression that initiates the borrow.
/// \return The new Loan on success, nullptr otherwise.
static const PathLoan *createLoan(FactManager &FactMgr,
const DeclRefExpr *DRE) {
static const Loan *createLoan(FactManager &FactMgr, const DeclRefExpr *DRE) {
if (const auto *VD = dyn_cast<ValueDecl>(DRE->getDecl())) {
AccessPath Path(VD);
// The loan is created at the location of the DeclRefExpr.
return FactMgr.getLoanMgr().createLoan<PathLoan>(Path, DRE);
return FactMgr.getLoanMgr().createLoan(Path, DRE);
}
return nullptr;
}
@ -83,10 +82,10 @@ static const PathLoan *createLoan(FactManager &FactMgr,
/// Creates a loan for the storage location of a temporary object.
/// \param MTE The MaterializeTemporaryExpr that represents the temporary
/// binding. \return The new Loan.
static const PathLoan *createLoan(FactManager &FactMgr,
const MaterializeTemporaryExpr *MTE) {
static const Loan *createLoan(FactManager &FactMgr,
const MaterializeTemporaryExpr *MTE) {
AccessPath Path(MTE);
return FactMgr.getLoanMgr().createLoan<PathLoan>(Path, MTE);
return FactMgr.getLoanMgr().createLoan(Path, MTE);
}
void FactsGenerator::run() {
@ -513,38 +512,16 @@ void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
if (!escapesViaReturn(OID))
ExpiredOID = OID;
}
// Iterate through all loans to see if any expire.
for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
if (const auto *BL = dyn_cast<PathLoan>(Loan)) {
// Check if the loan is for a stack variable and if that variable
// is the one being destructed.
const AccessPath AP = BL->getAccessPath();
const ValueDecl *Path = AP.getAsValueDecl();
if (Path == LifetimeEndsVD)
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
BL->getID(), LifetimeEnds.getTriggerStmt()->getEndLoc(),
ExpiredOID));
}
}
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
AccessPath(LifetimeEndsVD), LifetimeEnds.getTriggerStmt()->getEndLoc(),
ExpiredOID));
}
void FactsGenerator::handleFullExprCleanup(
const CFGFullExprCleanup &FullExprCleanup) {
// Iterate through all loans to see if any expire.
for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
if (const auto *PL = dyn_cast<PathLoan>(Loan)) {
// Check if the loan is for a temporary materialization and if that
// storage location is the one being destructed.
const AccessPath &AP = PL->getAccessPath();
const MaterializeTemporaryExpr *Path = AP.getAsMaterializeTemporaryExpr();
if (!Path)
continue;
if (llvm::is_contained(FullExprCleanup.getExpiringMTEs(), Path)) {
CurrentBlockFacts.push_back(
FactMgr.createFact<ExpireFact>(PL->getID(), Path->getEndLoc()));
}
}
}
for (const auto *MTE : FullExprCleanup.getExpiringMTEs())
CurrentBlockFacts.push_back(
FactMgr.createFact<ExpireFact>(AccessPath(MTE), MTE->getEndLoc()));
}
void FactsGenerator::handleExitBlock() {
@ -791,8 +768,9 @@ llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
llvm::SmallVector<Fact *> PlaceholderLoanFacts;
if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins()) {
OriginList *List = *ThisOrigins;
const PlaceholderLoan *L = FactMgr.getLoanMgr().createLoan<PlaceholderLoan>(
cast<CXXMethodDecl>(FD));
const Loan *L = FactMgr.getLoanMgr().createLoan(
AccessPath::Placeholder(cast<CXXMethodDecl>(FD)),
/*IssuingExpr=*/nullptr);
PlaceholderLoanFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
}
@ -800,8 +778,8 @@ llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
OriginList *List = getOriginsList(*PVD);
if (!List)
continue;
const PlaceholderLoan *L =
FactMgr.getLoanMgr().createLoan<PlaceholderLoan>(PVD);
const Loan *L = FactMgr.getLoanMgr().createLoan(
AccessPath::Placeholder(PVD), /*IssuingExpr=*/nullptr);
PlaceholderLoanFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
}

View File

@ -10,21 +10,30 @@
namespace clang::lifetimes::internal {
void PathLoan::dump(llvm::raw_ostream &OS) const {
void AccessPath::dump(llvm::raw_ostream &OS) const {
switch (K) {
case Kind::ValueDecl:
if (const clang::ValueDecl *VD = getAsValueDecl())
OS << VD->getNameAsString();
break;
case Kind::MaterializeTemporary:
if (const clang::MaterializeTemporaryExpr *MTE =
getAsMaterializeTemporaryExpr())
OS << "MaterializeTemporaryExpr at " << MTE;
break;
case Kind::PlaceholderParam:
if (const auto *PVD = getAsPlaceholderParam())
OS << "$" << PVD->getNameAsString();
break;
case Kind::PlaceholderThis:
OS << "$this";
break;
}
}
void Loan::dump(llvm::raw_ostream &OS) const {
OS << getID() << " (Path: ";
if (const clang::ValueDecl *VD = Path.getAsValueDecl())
OS << VD->getNameAsString();
else if (const clang::MaterializeTemporaryExpr *MTE =
Path.getAsMaterializeTemporaryExpr())
// No nice "name" for the temporary, so deferring to LLVM default
OS << "MaterializeTemporaryExpr at " << MTE;
else
llvm_unreachable("access path is not one of any supported types");
Path.dump(OS);
OS << ")";
}
void PlaceholderLoan::dump(llvm::raw_ostream &OS) const {
OS << getID() << " (Placeholder loan)";
}
} // namespace clang::lifetimes::internal

View File

@ -80,10 +80,7 @@ public:
auto IsInvalidated = [&](const AccessPath &Path) {
for (LoanID LID : ImmediatelyMovedLoans) {
const Loan *MovedLoan = LoanMgr.getLoan(LID);
auto *PL = dyn_cast<PathLoan>(MovedLoan);
if (!PL)
continue;
if (PL->getAccessPath() == Path)
if (MovedLoan->getAccessPath() == Path)
return true;
}
return false;
@ -91,10 +88,7 @@ public:
for (auto [O, _] : LiveOrigins.getLiveOriginsAt(&F))
for (LoanID LiveLoan : LoanPropagation.getLoans(O, &F)) {
const Loan *LiveLoanPtr = LoanMgr.getLoan(LiveLoan);
auto *PL = dyn_cast<PathLoan>(LiveLoanPtr);
if (!PL)
continue;
if (IsInvalidated(PL->getAccessPath()))
if (IsInvalidated(LiveLoanPtr->getAccessPath()))
MovedLoans =
MovedLoansMapFactory.add(MovedLoans, LiveLoan, F.getMoveExpr());
}

View File

@ -25,8 +25,8 @@ MyObj* return_local_addr() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr, Type : MyObj *)
// CHECK-NEXT: Src: [[O_P]] (Decl: p, Type : MyObj *)
// CHECK: Expire ([[L_X]] (Path: x))
// CHECK: Expire ({{[0-9]+}} (Path: p), Origin: [[O_P]] (Decl: p, Type : MyObj *))
// CHECK: Expire (x)
// CHECK: Expire (p, Origin: [[O_P]] (Decl: p, Type : MyObj *))
// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr, Type : MyObj *), via Return)
}
@ -43,7 +43,7 @@ void loan_expires_cpp() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pObj, Type : MyObj *)
// CHECK-NEXT: Src: [[O_ADDR_OBJ]] (Expr: UnaryOperator, Type : MyObj *)
// CHECK: Expire ([[L_OBJ]] (Path: obj))
// CHECK: Expire (obj)
}
@ -59,7 +59,7 @@ void loan_expires_trivial() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: pTrivialObj, Type : int *)
// CHECK-NEXT: Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator, Type : int *)
// CHECK: Expire ([[L_TRIVIAL_OBJ]] (Path: trivial_obj))
// CHECK: Expire (trivial_obj)
// CHECK-NEXT: End of Block
}
@ -86,8 +86,8 @@ void overwrite_origin() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *)
// CHECK-NEXT: Src: [[O_ADDR_S2]] (Expr: UnaryOperator, Type : MyObj *)
// CHECK: Expire ([[L_S2]] (Path: s2))
// CHECK: Expire ([[L_S1]] (Path: s1))
// CHECK: Expire (s2)
// CHECK: Expire (s1)
}
// CHECK-LABEL: Function: reassign_to_null
@ -108,7 +108,7 @@ void reassign_to_null() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: [[O_P]] (Decl: p, Type : MyObj *)
// CHECK-NEXT: Src: {{[0-9]+}} (Expr: ImplicitCastExpr, Type : MyObj *)
// CHECK: Expire ([[L_S1]] (Path: s1))
// CHECK: Expire (s1)
}
// FIXME: Have a better representation for nullptr than just an empty origin.
// It should be a separate loan and origin kind.
@ -205,8 +205,8 @@ void test_use_lifetimebound_call() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: r, Type : MyObj *)
// CHECK-NEXT: Src: [[O_CALL_EXPR]] (Expr: CallExpr, Type : MyObj *)
// CHECK: Expire ([[L_Y]] (Path: y))
// CHECK: Expire ([[L_X]] (Path: x))
// CHECK: Expire (y)
// CHECK: Expire (x)
}
// CHECK-LABEL: Function: test_reference_variable
@ -235,5 +235,5 @@ void test_reference_variable() {
// CHECK: OriginFlow:
// CHECK-NEXT: Dest: {{[0-9]+}} (Decl: p, Type : const MyObj *)
// CHECK-NEXT: Src: {{[0-9]+}} (Expr: UnaryOperator, Type : const MyObj *)
// CHECK: Expire ([[L_X]] (Path: x))
// CHECK: Expire (x)
}

View File

@ -328,9 +328,9 @@ void multiple_expiry_of_same_loan(bool cond) {
if (cond) {
p = &unsafe; // expected-warning {{does not live long enough}}
if (cond)
break;
break; // expected-note {{destroyed here}}
}
} // expected-note {{destroyed here}}
}
(void)*p; // expected-note {{later used here}}
p = &safe;
@ -349,8 +349,8 @@ void multiple_expiry_of_same_loan(bool cond) {
if (cond)
p = &unsafe; // expected-warning {{does not live long enough}}
if (cond)
break;
} // expected-note {{destroyed here}}
break; // expected-note {{destroyed here}}
}
(void)*p; // expected-note {{later used here}}
}
@ -717,12 +717,12 @@ View uar_before_uaf(const MyObj& safe, bool c) {
View p;
{
MyObj local_obj;
p = local_obj; // expected-warning {{object whose reference is captured does not live long enough}}
p = local_obj; // expected-warning {{ddress of stack memory is returned later}}
if (c) {
return p;
return p; // expected-note {{returned here}}
}
} // expected-note {{destroyed here}}
p.use(); // expected-note {{later used here}}
}
p.use();
p = safe;
return p;
}

View File

@ -125,9 +125,8 @@ public:
}
std::vector<LoanID> LID;
for (const Loan *L : Analysis.getFactManager().getLoanMgr().getLoans())
if (const auto *BL = dyn_cast<PathLoan>(L))
if (BL->getAccessPath().getAsValueDecl() == VD)
LID.push_back(L->getID());
if (L->getAccessPath().getAsValueDecl() == VD)
LID.push_back(L->getID());
if (LID.empty()) {
ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
return {};
@ -136,11 +135,11 @@ public:
}
bool isLoanToATemporary(LoanID LID) {
const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(LID);
if (const auto *BL = dyn_cast<PathLoan>(L)) {
return BL->getAccessPath().getAsMaterializeTemporaryExpr() != nullptr;
}
return false;
return Analysis.getFactManager()
.getLoanMgr()
.getLoan(LID)
->getAccessPath()
.getAsMaterializeTemporaryExpr() != nullptr;
}
// Gets the set of loans that are live at the given program point. A loan is
@ -169,9 +168,10 @@ public:
const ExpireFact *
getExpireFactFromAllFacts(const llvm::ArrayRef<const Fact *> &FactsInBlock,
const LoanID &loanID) {
const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(loanID);
for (const Fact *F : FactsInBlock) {
if (auto const *CurrentEF = F->getAs<ExpireFact>())
if (CurrentEF->getLoanID() == loanID)
if (CurrentEF->getAccessPath() == L->getAccessPath())
return CurrentEF;
}
return nullptr;