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
13 changes: 7 additions & 6 deletions clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,18 +155,19 @@ class OriginEscapesFact : public Fact {

class UseFact : public Fact {
const Expr *UseExpr;
OriginID OID;
// The origins of the expression being used.
llvm::SmallVector<OriginID, 1> OIDs;
// True if this use is a write operation (e.g., left-hand side of assignment).
// Write operations are exempted from use-after-free checks.
bool IsWritten = false;

public:
static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }

UseFact(const Expr *UseExpr, OriginManager &OM)
: Fact(Kind::Use), UseExpr(UseExpr), OID(OM.get(*UseExpr)) {}
UseFact(const Expr *UseExpr, llvm::ArrayRef<OriginID> OIDs)
: Fact(Kind::Use), UseExpr(UseExpr), OIDs(OIDs.begin(), OIDs.end()) {}

OriginID getUsedOrigin() const { return OID; }
llvm::ArrayRef<OriginID> getUsedOrigins() const { return OIDs; }
const Expr *getUseExpr() const { return UseExpr; }
void markAsWritten() { IsWritten = true; }
bool isWritten() const { return IsWritten; }
Expand Down Expand Up @@ -194,8 +195,8 @@ class TestPointFact : public Fact {

class FactManager {
public:
void init(const CFG &Cfg) {
assert(BlockToFacts.empty() && "FactManager already initialized");
FactManager(const AnalysisDeclContext &AC, const CFG &Cfg)
: OriginMgr(AC.getASTContext()) {
BlockToFacts.resize(Cfg.getNumBlockIDs());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);

private:
OriginTree *getTree(const ValueDecl &D);
OriginTree *getTree(const Expr &E);

void flow(OriginTree *Dst, OriginTree *Src, bool Kill);

void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);

void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
Expand All @@ -64,26 +69,18 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {

template <typename Destination, typename Source>
void flowOrigin(const Destination &D, const Source &S) {
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
OriginID SrcOID = FactMgr.getOriginMgr().get(S);
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
DestOID, SrcOID, /*KillDest=*/false));
flow(getTree(D), getTree(S), /*Kill=*/false);
}

template <typename Destination, typename Source>
void killAndFlowOrigin(const Destination &D, const Source &S) {
OriginID DestOID = FactMgr.getOriginMgr().getOrCreate(D);
OriginID SrcOID = FactMgr.getOriginMgr().get(S);
CurrentBlockFacts.push_back(
FactMgr.createFact<OriginFlowFact>(DestOID, SrcOID, /*KillDest=*/true));
flow(getTree(D), getTree(S), /*Kill=*/true);
}

/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
/// If so, creates a `TestPointFact` and returns true.
bool handleTestPoint(const CXXFunctionalCastExpr *FCE);

void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);

// A DeclRefExpr will be treated as a use of the referenced decl. It will be
// checked for use-after-free unless it is later marked as being written to
// (e.g. on the left-hand side of an assignment).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ class LifetimeSafetyAnalysis {
return *LoanPropagation;
}
LiveOriginsAnalysis &getLiveOrigins() const { return *LiveOrigins; }
FactManager &getFactManager() { return FactMgr; }
FactManager &getFactManager() { return *FactMgr; }

private:
AnalysisDeclContext &AC;
LifetimeSafetyReporter *Reporter;
LifetimeFactory Factory;
FactManager FactMgr;
std::unique_ptr<FactManager> FactMgr;
std::unique_ptr<LiveOriginsAnalysis> LiveOrigins;
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
};
Expand Down
128 changes: 104 additions & 24 deletions clang/include/clang/Analysis/Analyses/LifetimeSafety/Origins.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/TypeBase.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"

namespace clang::lifetimes::internal {
Expand All @@ -28,21 +29,30 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {

/// An Origin is a symbolic identifier that represents the set of possible
/// loans a pointer-like object could hold at any given time.
/// TODO: Enhance the origin model to handle complex types, pointer
/// indirection and reborrowing. The plan is to move from a single origin per
/// variable/expression to a "list of origins" governed by the Type.
/// For example, the type 'int**' would have two origins.
/// See discussion:
/// https://github.com/llvm/llvm-project/pull/142313/commits/0cd187b01e61b200d92ca0b640789c1586075142#r2137644238
///
/// Each Origin corresponds to a single level of indirection. For complex types
/// with multiple levels of indirection (e.g., `int**`), multiple Origins are
/// organized into an OriginTree structure (see below).
struct Origin {
OriginID ID;
/// A pointer to the AST node that this origin represents. This union
/// distinguishes between origins from declarations (variables or parameters)
/// and origins from expressions.
llvm::PointerUnion<const clang::ValueDecl *, const clang::Expr *> Ptr;

Origin(OriginID ID, const clang::ValueDecl *D) : ID(ID), Ptr(D) {}
Origin(OriginID ID, const clang::Expr *E) : ID(ID), Ptr(E) {}
/// The type at this indirection level.
///
/// For `int** pp`:
/// Root origin: QT = `int**` (what pp points to)
/// Pointee origin: QT = `int*` (what *pp points to)
///
/// Null for synthetic lvalue origins (e.g., outer origin of DeclRefExpr).
QualType QT;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the qualifier important for the loans? If not, I wonder if having a const Type* directly would be better.


Origin(OriginID ID, const clang::ValueDecl *D, QualType QT)
: ID(ID), Ptr(D), QT(QT) {}
Origin(OriginID ID, const clang::Expr *E, QualType QT)
: ID(ID), Ptr(E), QT(QT) {}

const clang::ValueDecl *getDecl() const {
return Ptr.dyn_cast<const clang::ValueDecl *>();
Expand All @@ -52,41 +62,111 @@ struct Origin {
}
};

/// A tree of origins representing levels of indirection for pointer-like types.
///
/// Each node in the tree contains an OriginID representing a level of
/// indirection. The tree structure captures the multi-level nature of
/// pointer and reference types in the lifetime analysis.
///
/// Examples:
/// - For `int& x`, the tree has depth 2:
/// * Root: origin for the reference storage itself (the lvalue `x`)
/// * Pointee: origin for what `x` refers to
///
/// - For `int* p`, the tree has depth 2:
/// * Root: origin for the pointer variable `p`
/// * Pointee: origin for what `p` points to
///
/// - For `View v` (where View is gsl::Pointer), the tree has depth 2:
/// * Root: origin for the view object itself
/// * Pointee: origin for what the view refers to
///
/// - For `int** pp`, the tree has depth 3:
/// * Root: origin for `pp` itself
/// * Pointee: origin for `*pp` (what `pp` points to)
/// * Pointee->Pointee: origin for `**pp` (what `*pp` points to)
///
/// The tree structure enables the analysis to track how loans flow through
/// different levels of indirection when assignments and dereferences occur.
struct OriginTree {
OriginID OID;
OriginTree *Pointee = nullptr;

OriginTree(OriginID OID) : OID(OID) {}

size_t getDepth() const {
size_t Depth = 1;
const OriginTree *T = this;
while (T->Pointee) {
T = T->Pointee;
Depth++;
}
return Depth;
}
};

bool hasOrigins(QualType QT);
bool hasOrigins(const Expr *E);
bool doesDeclHaveStorage(const ValueDecl *D);

/// Manages the creation, storage, and retrieval of origins for pointer-like
/// variables and expressions.
class OriginManager {
public:
OriginManager() = default;

Origin &addOrigin(OriginID ID, const clang::ValueDecl &D);
Origin &addOrigin(OriginID ID, const clang::Expr &E);

// TODO: Mark this method as const once we remove the call to getOrCreate.
OriginID get(const Expr &E);

OriginID get(const ValueDecl &D);

OriginID getOrCreate(const Expr &E);
explicit OriginManager(ASTContext &AST) : AST(AST) {}

/// Gets or creates the OriginTree for a given ValueDecl.
///
/// Creates a tree structure mirroring the levels of indirection in the
/// declaration's type (e.g., `int** p` creates depth 2).
///
/// \returns The OriginTree, or nullptr if the type is not pointer-like.
OriginTree *getOrCreateTree(const ValueDecl *D);

/// Gets or creates the OriginTree for a given Expr.
///
/// Creates a tree based on the expression's type and value category:
/// - Lvalues get an implicit reference level (modeling addressability)
/// - Rvalues of non-pointer type return nullptr (no trackable origin)
/// - DeclRefExpr may reuse the underlying declaration's tree
///
/// \returns The OriginTree, or nullptr for non-pointer rvalues.
OriginTree *getOrCreateTree(const Expr *E, size_t Depth = 0);

const Origin &getOrigin(OriginID ID) const;

llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }

OriginID getOrCreate(const ValueDecl &D);

unsigned getNumOrigins() const { return NextOriginID.Value; }

void dump(OriginID OID, llvm::raw_ostream &OS) const;

private:
OriginID getNextOriginID() { return NextOriginID++; }

OriginTree *createNode(const ValueDecl *D, QualType QT) {
OriginID NewID = getNextOriginID();
AllOrigins.emplace_back(NewID, D, QT);
return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
}

OriginTree *createNode(const Expr *E, QualType QT) {
OriginID NewID = getNextOriginID();
AllOrigins.emplace_back(NewID, E, QT);
return new (TreeAllocator.Allocate<OriginTree>()) OriginTree(NewID);
}

template <typename T>
OriginTree *buildTreeForType(QualType QT, const T *Node);

ASTContext &AST;
OriginID NextOriginID{0};
/// TODO(opt): Profile and evaluate the usefullness of small buffer
/// TODO(opt): Profile and evaluate the usefulness of small buffer
/// optimisation.
llvm::SmallVector<Origin> AllOrigins;
llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
llvm::BumpPtrAllocator TreeAllocator;
llvm::DenseMap<const clang::ValueDecl *, OriginTree *> DeclToTree;
llvm::DenseMap<const clang::Expr *, OriginTree *> ExprToTree;
};
} // namespace clang::lifetimes::internal

Expand Down
15 changes: 11 additions & 4 deletions clang/lib/Analysis/LifetimeSafety/Facts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,

void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const {
OS << "OriginFlow (Dest: ";
OS << "OriginFlow: \n";
OS << "\tDest: ";
OM.dump(getDestOriginID(), OS);
OS << ", Src: ";
OS << "\n";
OS << "\tSrc: ";
OM.dump(getSrcOriginID(), OS);
OS << (getKillDest() ? "" : ", Merge");
OS << ")\n";
OS << "\n";
}

void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
Expand All @@ -53,7 +55,12 @@ void OriginEscapesFact::dump(llvm::raw_ostream &OS, const LoanManager &,
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM) const {
OS << "Use (";
OM.dump(getUsedOrigin(), OS);
size_t NumUsedOrigins = getUsedOrigins().size();
for (size_t I = 0; I < NumUsedOrigins; ++I) {
OM.dump(getUsedOrigins()[I], OS);
if (I < NumUsedOrigins - 1)
OS << ", ";
}
OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
}

Expand Down
Loading