blob: ff2650be594f56056159f4ff129580be9742ad40 [file] [edit]
//===- Checker.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/Checker.h"
#include "clang/AST/Expr.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TimeProfiler.h"
namespace clang::lifetimes::internal {
static Confidence livenessKindToConfidence(LivenessKind K) {
switch (K) {
case LivenessKind::Must:
return Confidence::Definite;
case LivenessKind::Maybe:
return Confidence::Maybe;
case LivenessKind::Dead:
return Confidence::None;
}
llvm_unreachable("unknown liveness kind");
}
namespace {
/// Struct to store the complete context for a potential lifetime violation.
struct PendingWarning {
SourceLocation ExpiryLoc; // Where the loan expired.
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> CausingFact;
Confidence ConfidenceLevel;
};
class LifetimeChecker {
private:
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
llvm::DenseMap<const ParmVarDecl *, const Expr *> AnnotationWarningsMap;
const LoanPropagationAnalysis &LoanPropagation;
const LiveOriginsAnalysis &LiveOrigins;
const FactManager &FactMgr;
LifetimeSafetyReporter *Reporter;
ASTContext &AST;
public:
LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM,
AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
: LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
Reporter(Reporter), AST(ADC.getASTContext()) {
for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
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();
// Annotation inference is currently guarded by a frontend flag. In the
// future, this might be replaced by a design that differentiates between
// explicit and inferred findings with separate warning groups.
if (AST.getLangOpts().EnableLifetimeSafetyInference)
inferAnnotations();
}
/// 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.
///
/// 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 with the appropriate confidence level based on the liveness
/// information. The confidence reflects whether the origin is definitely
/// or maybe live at this point.
///
/// 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).
void checkExpiry(const ExpireFact *EF) {
LoanID ExpiredLoan = EF->getLoanID();
LivenessMap Origins = LiveOrigins.getLiveOriginsAt(EF);
Confidence CurConfidence = Confidence::None;
// The UseFact or OriginEscapesFact most indicative of a lifetime error,
// prioritized by earlier source location.
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
BestCausingFact = nullptr;
for (auto &[OID, LiveInfo] : Origins) {
LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
if (!HeldLoans.contains(ExpiredLoan))
continue;
// Loan is defaulted.
Confidence NewConfidence = livenessKindToConfidence(LiveInfo.Kind);
if (CurConfidence < NewConfidence) {
CurConfidence = NewConfidence;
BestCausingFact = LiveInfo.CausingFact;
}
}
if (!BestCausingFact)
return;
// We have a use-after-free.
Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
if (LastConf >= CurConfidence)
return;
FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
/*BestCausingFact=*/BestCausingFact,
/*ConfidenceLevel=*/CurConfidence};
}
void issuePendingWarnings() {
if (!Reporter)
return;
for (const auto &[LID, Warning] : FinalWarningsMap) {
const Loan *L = FactMgr.getLoanMgr().getLoan(LID);
const auto *BL = cast<PathLoan>(L);
const Expr *IssueExpr = BL->getIssueExpr();
llvm::PointerUnion<const UseFact *, const OriginEscapesFact *>
CausingFact = Warning.CausingFact;
Confidence Confidence = Warning.ConfidenceLevel;
SourceLocation ExpiryLoc = Warning.ExpiryLoc;
if (const auto *UF = CausingFact.dyn_cast<const UseFact *>())
Reporter->reportUseAfterFree(IssueExpr, UF->getUseExpr(), ExpiryLoc,
Confidence);
else if (const auto *OEF =
CausingFact.dyn_cast<const OriginEscapesFact *>())
Reporter->reportUseAfterReturn(IssueExpr, OEF->getEscapeExpr(),
ExpiryLoc, Confidence);
else
llvm_unreachable("Unhandled CausingFact type");
}
}
/// Returns the declaration of a function that is visible across translation
/// units, if such a declaration exists and is different from the definition.
static const FunctionDecl *getCrossTUDecl(const ParmVarDecl &PVD,
SourceManager &SM) {
const auto *FD = dyn_cast<FunctionDecl>(PVD.getDeclContext());
if (!FD)
return nullptr;
if (!FD->isExternallyVisible())
return nullptr;
const FileID DefinitionFile = SM.getFileID(FD->getLocation());
for (const FunctionDecl *Redecl : FD->redecls())
if (SM.getFileID(Redecl->getLocation()) != DefinitionFile)
return Redecl;
return nullptr;
}
void suggestAnnotations() {
if (!Reporter)
return;
SourceManager &SM = AST.getSourceManager();
for (const auto &[PVD, EscapeExpr] : AnnotationWarningsMap) {
if (const FunctionDecl *CrossTUDecl = getCrossTUDecl(*PVD, SM))
Reporter->suggestAnnotation(
SuggestionScope::CrossTU,
CrossTUDecl->getParamDecl(PVD->getFunctionScopeIndex()),
EscapeExpr);
else
Reporter->suggestAnnotation(SuggestionScope::IntraTU, PVD, EscapeExpr);
}
}
void inferAnnotations() {
// FIXME: To maximise inference propagation, functions should be analyzed in
// post-order of the call graph, allowing inferred annotations to propagate
// through the call chain
// FIXME: Add the inferred attribute to all redeclarations of the function,
// not just the definition being analyzed.
for (const auto &[ConstPVD, EscapeExpr] : AnnotationWarningsMap) {
ParmVarDecl *PVD = const_cast<ParmVarDecl *>(ConstPVD);
if (!PVD->hasAttr<LifetimeBoundAttr>())
PVD->addAttr(
LifetimeBoundAttr::CreateImplicit(AST, PVD->getLocation()));
}
}
};
} // namespace
void runLifetimeChecker(const LoanPropagationAnalysis &LP,
const LiveOriginsAnalysis &LO,
const FactManager &FactMgr, AnalysisDeclContext &ADC,
LifetimeSafetyReporter *Reporter) {
llvm::TimeTraceScope TimeProfile("LifetimeChecker");
LifetimeChecker Checker(LP, LO, FactMgr, ADC, Reporter);
}
} // namespace clang::lifetimes::internal