Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {

void markUseAsWrite(const DeclRefExpr *DRE);

llvm::SmallVector<Fact *> issuePlaceholderLoans();
FactManager &FactMgr;
AnalysisDeclContext &AC;
llvm::SmallVector<Fact *> CurrentBlockFacts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class LifetimeSafetyReporter {
const Expr *EscapeExpr,
SourceLocation ExpiryLoc,
Confidence Confidence) {}

virtual void suggestAnnotation(const ParmVarDecl *PVD,
const Expr *EscapeExpr) {}
};

/// The main entry point for the analysis.
Expand Down
92 changes: 78 additions & 14 deletions clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,46 +34,110 @@ struct AccessPath {
AccessPath(const clang::ValueDecl *D) : D(D) {}
};

/// Information about a single borrow, or "Loan". A loan is created when a
/// reference or pointer is created.
struct Loan {
/// An abstract base class for a single borrow, or "Loan".
class Loan {
/// TODO: Represent opaque loans.
/// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it
/// is represented as empty LoanSet
LoanID ID;
public:
enum class Kind : uint8_t {
/// A regular borrow of a variable within the function that has a path and
/// can expire.
Borrow,
/// A non-expiring placeholder loan for a parameter, representing a borrow
/// from the function's caller.
Placeholder
};

Loan(Kind K, LoanID ID) : K(K), ID(ID) {}
virtual ~Loan() = default;

Kind getKind() const { return K; }
LoanID getID() const { return ID; }

virtual void dump(llvm::raw_ostream &OS) const = 0;

private:
const Kind K;
const LoanID ID;
};

/// Information about a single borrow, or "Loan". A loan is created when a
/// reference or pointer is created.
class BorrowLoan : public Loan {
AccessPath Path;
/// The expression that creates the loan, e.g., &x.
const Expr *IssueExpr;

Loan(LoanID id, AccessPath path, const Expr *IssueExpr)
: ID(id), Path(path), IssueExpr(IssueExpr) {}
public:
BorrowLoan(LoanID ID, AccessPath Path, const Expr *IssueExpr)
: Loan(Kind::Borrow, ID), Path(Path), IssueExpr(IssueExpr) {}

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::Borrow; }
};

/// A placeholder loan held by a function parameter, representing a borrow from
/// the caller's scope.
///
/// Created at function entry for each pointer or reference parameter with an
/// origin. Unlike BorrowLoan, 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 should be marked [[clang::lifetimebound]], enabling
/// lifetime annotation suggestions.
class PlaceholderLoan : public Loan {
/// The function parameter that holds this placeholder loan.
const ParmVarDecl *PVD;

void dump(llvm::raw_ostream &OS) const;
public:
PlaceholderLoan(LoanID ID, const ParmVarDecl *PVD)
: Loan(Kind::Placeholder, ID), PVD(PVD) {}

const ParmVarDecl *getParmVarDecl() const { return PVD; }

void dump(llvm::raw_ostream &OS) const override;

static bool classof(const Loan *L) {
return L->getKind() == Kind::Placeholder;
}
};

/// Manages the creation, storage and retrieval of loans.
class LoanManager {
public:
LoanManager() = default;

Loan &addLoan(AccessPath Path, const Expr *IssueExpr) {
AllLoans.emplace_back(getNextLoanID(), Path, IssueExpr);
return AllLoans.back();
template <typename LoanType, typename... Args>
LoanType *createLoan(Args &&...args) {
void *Mem = LoanAllocator.Allocate<LoanType>();
auto *NewLoan =
new (Mem) LoanType(getNextLoanID(), std::forward<Args>(args)...);
AllLoans.push_back(NewLoan);
return NewLoan;
}

const Loan &getLoan(LoanID ID) const {
const Loan *getLoan(LoanID ID) const {
assert(ID.Value < AllLoans.size());
return AllLoans[ID.Value];
}
llvm::ArrayRef<Loan> getLoans() const { return AllLoans; }
llvm::ArrayRef<const Loan *> getLoans() const { return AllLoans; }

private:
LoanID getNextLoanID() { return NextLoanID++; }

LoanID NextLoanID{0};
/// TODO(opt): Profile and evaluate the usefullness of small buffer
/// optimisation.
llvm::SmallVector<Loan> AllLoans;
llvm::SmallVector<const Loan *> AllLoans;
llvm::BumpPtrAllocator LoanAllocator;
};
} // namespace clang::lifetimes::internal

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,8 @@ def LifetimeSafety : DiagGroup<"experimental-lifetime-safety",
Experimental warnings to detect use-after-free and related temporal safety bugs based on lifetime safety analysis.
}];
}
def LifetimeSafetySuggestions
: DiagGroup<"experimental-lifetime-safety-suggestions">;

def DistributedObjectModifiers : DiagGroup<"distributed-object-modifiers">;
def DllexportExplicitInstantiationDecl : DiagGroup<"dllexport-explicit-instantiation-decl">;
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -10778,6 +10778,13 @@ def note_lifetime_safety_used_here : Note<"later used here">;
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
def note_lifetime_safety_returned_here : Note<"returned here">;

def warn_lifetime_safety_suggest_lifetimebound
: Warning<"param should be marked [[clang::lifetimebound]]">,
InGroup<LifetimeSafetySuggestions>,
DefaultIgnore;

def note_lifetime_safety_suggestion_returned_here : Note<"param returned here">;

// For non-floating point, expressions of the form x == x or x != x
// should result in a warning, since these always evaluate to a constant.
// Array comparisons have similar warnings
Expand Down
32 changes: 30 additions & 2 deletions clang/lib/Analysis/LifetimeSafety/Checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct PendingWarning {
class LifetimeChecker {
private:
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
llvm::DenseMap<const ParmVarDecl *, const Expr *> AnnotationWarningsMap;
const LoanPropagationAnalysis &LoanPropagation;
const LiveOriginsAnalysis &LiveOrigins;
const FactManager &FactMgr;
Expand All @@ -65,7 +66,26 @@ class LifetimeChecker {
for (const Fact *F : FactMgr.getFacts(B))
if (const auto *EF = F->getAs<ExpireFact>())
checkExpiry(EF);
else if (const auto *OEF = F->getAs<OriginEscapesFact>())
checkAnnotations(OEF);
issuePendingWarnings();
suggestAnnotations();
}

/// Checks if an escaping origin holds a placeholder loan, indicating a
/// missing [[clang::lifetimebound]] annotation.
void checkAnnotations(const OriginEscapesFact *OEF) {
OriginID EscapedOID = OEF->getEscapedOriginID();
LoanSet EscapedLoans = LoanPropagation.getLoans(EscapedOID, OEF);
for (LoanID LID : EscapedLoans) {
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
if (const auto *PL = dyn_cast<PlaceholderLoan>(L)) {
const ParmVarDecl *PVD = PL->getParmVarDecl();
if (PVD->hasAttr<LifetimeBoundAttr>())
continue;
AnnotationWarningsMap.try_emplace(PVD, OEF->getEscapeExpr());
}
}
}

/// Checks for use-after-free & use-after-return errors when a loan expires.
Expand Down Expand Up @@ -114,8 +134,9 @@ class LifetimeChecker {
if (!Reporter)
return;
for (const auto &[LID, Warning] : FinalWarningsMap) {
const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
const Expr *IssueExpr = L.IssueExpr;
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
const auto *BL = cast<BorrowLoan>(L);
const Expr *IssueExpr = BL->getIssueExpr();
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
CausingFact = Warning.CausingFact;
Confidence Confidence = Warning.ConfidenceLevel;
Expand All @@ -132,6 +153,13 @@ class LifetimeChecker {
llvm_unreachable("Unhandled CausingFact type");
}
}

void suggestAnnotations() {
if (!Reporter)
return;
for (const auto &[PVD, EscapeExpr] : AnnotationWarningsMap)
Reporter->suggestAnnotation(PVD, EscapeExpr);
}
};
} // namespace

Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Analysis/LifetimeSafety/Facts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void Fact::dump(llvm::raw_ostream &OS, const LoanManager &,
void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
const OriginManager &OM) const {
OS << "Issue (";
LM.getLoan(getLoanID()).dump(OS);
LM.getLoan(getLoanID())->dump(OS);
OS << ", ToOrigin: ";
OM.dump(getOriginID(), OS);
OS << ")\n";
Expand All @@ -29,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 &) const {
OS << "Expire (";
LM.getLoan(getLoanID()).dump(OS);
LM.getLoan(getLoanID())->dump(OS);
OS << ")\n";
}

Expand Down
47 changes: 37 additions & 10 deletions clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,28 @@ static bool hasOrigin(const VarDecl *VD) {
/// 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 Loan *createLoan(FactManager &FactMgr, const DeclRefExpr *DRE) {
static const BorrowLoan *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().addLoan(Path, DRE);
return FactMgr.getLoanMgr().createLoan<BorrowLoan>(Path, DRE);
}
return nullptr;
}

void FactsGenerator::run() {
llvm::TimeTraceScope TimeProfile("FactGenerator");
const CFG &Cfg = *AC.getCFG();
llvm::SmallVector<Fact *> PlaceholderLoanFacts = issuePlaceholderLoans();
// Iterate through the CFG blocks in reverse post-order to ensure that
// initializations and destructions are processed in the correct sequence.
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
CurrentBlockFacts.clear();
EscapesInCurrentBlock.clear();
if (Block == &Cfg.getEntry())
CurrentBlockFacts.append(PlaceholderLoanFacts.begin(),
PlaceholderLoanFacts.end());
for (unsigned I = 0; I < Block->size(); ++I) {
const CFGElement &Element = Block->Elements[I];
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
Expand Down Expand Up @@ -85,7 +91,7 @@ void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
if (const Loan *L = createLoan(FactMgr, DRE)) {
OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE);
CurrentBlockFacts.push_back(
FactMgr.createFact<IssueFact>(L->ID, ExprOID));
FactMgr.createFact<IssueFact>(L->getID(), ExprOID));
}
}
}
Expand Down Expand Up @@ -223,13 +229,14 @@ void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
if (!LifetimeEndsVD)
return;
// Iterate through all loans to see if any expire.
for (const auto &Loan : FactMgr.getLoanMgr().getLoans()) {
const AccessPath &LoanPath = Loan.Path;
// Check if the loan is for a stack variable and if that variable
// is the one being destructed.
if (LoanPath.D == LifetimeEndsVD)
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
Loan.ID, LifetimeEnds.getTriggerStmt()->getEndLoc()));
for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
if (const auto *BL = dyn_cast<BorrowLoan>(Loan)) {
// Check if the loan is for a stack variable and if that variable
// is the one being destructed.
if (BL->getAccessPath().D == LifetimeEndsVD)
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
BL->getID(), LifetimeEnds.getTriggerStmt()->getEndLoc()));
}
}
}

Expand Down Expand Up @@ -342,4 +349,24 @@ void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
UseFacts[DRE]->markAsWritten();
}

// Creates an IssueFact for a new placeholder loan for each pointer or reference
// parameter at the function's entry.
llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
const auto *FD = dyn_cast<FunctionDecl>(AC.getDecl());
if (!FD)
return {};

llvm::SmallVector<Fact *> PlaceholderLoanFacts;
for (const ParmVarDecl *PVD : FD->parameters()) {
if (hasOrigin(PVD)) {
const PlaceholderLoan *L =
FactMgr.getLoanMgr().createLoan<PlaceholderLoan>(PVD);
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*PVD);
PlaceholderLoanFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), OID));
}
}
return PlaceholderLoanFacts;
}

} // namespace clang::lifetimes::internal
8 changes: 6 additions & 2 deletions clang/lib/Analysis/LifetimeSafety/Loans.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@

namespace clang::lifetimes::internal {

void Loan::dump(llvm::raw_ostream &OS) const {
OS << ID << " (Path: ";
void BorrowLoan::dump(llvm::raw_ostream &OS) const {
OS << getID() << " (Path: ";
OS << Path.D->getNameAsString() << ")";
}

void PlaceholderLoan::dump(llvm::raw_ostream &OS) const {
OS << getID() << " (Placeholder loan)";
}

} // namespace clang::lifetimes::internal
13 changes: 13 additions & 0 deletions clang/lib/Sema/AnalysisBasedWarnings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2884,6 +2884,19 @@ class LifetimeSafetyReporterImpl : public LifetimeSafetyReporter {
<< EscapeExpr->getEndLoc();
}

void suggestAnnotation(const ParmVarDecl *PVD,
const Expr *EscapeExpr) override {
SourceLocation InsertionPoint = Lexer::getLocForEndOfToken(
PVD->getEndLoc(), 0, S.getSourceManager(), S.getLangOpts());
S.Diag(PVD->getBeginLoc(), diag::warn_lifetime_safety_suggest_lifetimebound)
<< PVD->getSourceRange()
<< FixItHint::CreateInsertion(InsertionPoint,
" [[clang::lifetimebound]]");
S.Diag(EscapeExpr->getBeginLoc(),
diag::note_lifetime_safety_suggestion_returned_here)
<< EscapeExpr->getSourceRange();
}

private:
Sema &S;
};
Expand Down
Loading