[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:
parent
b1aa6a4506
commit
8a61e8f816
@ -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; }
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user