mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 19:08:21 +08:00
[LifetimeSafety] Reorganize code into modular components (#162474)
Restructure the C++ Lifetime Safety Analysis into modular components
with clear separation of concerns.
This PR reorganizes the C++ Lifetime Safety Analysis code by:
1. Breaking up the monolithic `LifetimeSafety.cpp` (1500+ lines) into
multiple smaller, focused files
2. Creating a dedicated `LifetimeSafety` directory with a clean
component structure
3. Introducing header files for each component with proper documentation
4. Moving existing code into the appropriate component files:
- `Checker.h/cpp`: Core lifetime checking logic
- `Dataflow.h`: Generic dataflow analysis framework
- `Facts.h`: Lifetime-relevant events and fact management
- `FactsGenerator.h/cpp`: AST traversal for fact generation
- `LiveOrigins.h/cpp`: Backward dataflow analysis for origin liveness
- `LoanPropagation.h/cpp`: Forward dataflow analysis for loan tracking
- `Loans.h`: Loan and access path definitions
- `Origins.h`: Origin management
- `Reporter.h`: Interface for reporting lifetime violations
- `Utils.h`: Common utilities for the analysis
The code functionality remains the same, but is now better organized
with clearer interfaces between components.
This commit is contained in:
4
.github/new-prs-labeler.yml
vendored
4
.github/new-prs-labeler.yml
vendored
@@ -1096,8 +1096,8 @@ clang:openmp:
|
||||
- llvm/test/Transforms/OpenMP/**
|
||||
|
||||
clang:temporal-safety:
|
||||
- clang/include/clang/Analysis/Analyses/LifetimeSafety*
|
||||
- clang/lib/Analysis/LifetimeSafety*
|
||||
- clang/include/clang/Analysis/Analyses/LifetimeSafety/**
|
||||
- clang/lib/Analysis/LifetimeSafety/**
|
||||
- clang/unittests/Analysis/LifetimeSafety*
|
||||
- clang/test/Sema/*lifetime-safety*
|
||||
- clang/test/Sema/*lifetime-analysis*
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
//===- LifetimeSafety.h - C++ Lifetime Safety Analysis -*----------- 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 defines the entry point for a dataflow-based static analysis
|
||||
// that checks for C++ lifetime violations.
|
||||
//
|
||||
// The analysis is based on the concepts of "origins" and "loans" to track
|
||||
// pointer lifetimes and detect issues like use-after-free and dangling
|
||||
// pointers. See the RFC for more details:
|
||||
// https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "llvm/ADT/DenseMapInfo.h"
|
||||
#include "llvm/ADT/ImmutableMap.h"
|
||||
#include "llvm/ADT/ImmutableSet.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include <memory>
|
||||
|
||||
namespace clang::lifetimes {
|
||||
|
||||
/// Enum to track the confidence level of a potential error.
|
||||
enum class Confidence : uint8_t {
|
||||
None,
|
||||
Maybe, // Reported as a potential error (-Wlifetime-safety-strict)
|
||||
Definite // Reported as a definite error (-Wlifetime-safety-permissive)
|
||||
};
|
||||
|
||||
enum class LivenessKind : uint8_t {
|
||||
Dead, // Not alive
|
||||
Maybe, // Live on some path but not all paths (may-be-live)
|
||||
Must // Live on all paths (must-be-live)
|
||||
};
|
||||
|
||||
class LifetimeSafetyReporter {
|
||||
public:
|
||||
LifetimeSafetyReporter() = default;
|
||||
virtual ~LifetimeSafetyReporter() = default;
|
||||
|
||||
virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
|
||||
SourceLocation FreeLoc,
|
||||
Confidence Confidence) {}
|
||||
};
|
||||
|
||||
/// The main entry point for the analysis.
|
||||
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter);
|
||||
|
||||
namespace internal {
|
||||
// Forward declarations of internal types.
|
||||
class Fact;
|
||||
class FactManager;
|
||||
class LoanPropagationAnalysis;
|
||||
class ExpiredLoansAnalysis;
|
||||
class LiveOriginAnalysis;
|
||||
struct LifetimeFactory;
|
||||
|
||||
/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
|
||||
/// Used for giving ID to loans and origins.
|
||||
template <typename Tag> struct ID {
|
||||
uint32_t Value = 0;
|
||||
|
||||
bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; }
|
||||
bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); }
|
||||
bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; }
|
||||
ID<Tag> operator++(int) {
|
||||
ID<Tag> Tmp = *this;
|
||||
++Value;
|
||||
return Tmp;
|
||||
}
|
||||
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
|
||||
IDBuilder.AddInteger(Value);
|
||||
}
|
||||
};
|
||||
|
||||
using LoanID = ID<struct LoanTag>;
|
||||
using OriginID = ID<struct OriginTag>;
|
||||
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
|
||||
return OS << ID.Value;
|
||||
}
|
||||
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
|
||||
return OS << ID.Value;
|
||||
}
|
||||
|
||||
// Using LLVM's immutable collections is efficient for dataflow analysis
|
||||
// as it avoids deep copies during state transitions.
|
||||
// TODO(opt): Consider using a bitset to represent the set of loans.
|
||||
using LoanSet = llvm::ImmutableSet<LoanID>;
|
||||
using OriginSet = llvm::ImmutableSet<OriginID>;
|
||||
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
|
||||
|
||||
/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
|
||||
/// `Fact`. identified by a lifetime-related event (`Fact`).
|
||||
///
|
||||
/// A `ProgramPoint` has "after" semantics: it represents the location
|
||||
/// immediately after its corresponding `Fact`.
|
||||
using ProgramPoint = const Fact *;
|
||||
|
||||
/// Running the lifetime safety analysis and querying its results. It
|
||||
/// encapsulates the various dataflow analyses.
|
||||
class LifetimeSafetyAnalysis {
|
||||
public:
|
||||
LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter);
|
||||
~LifetimeSafetyAnalysis();
|
||||
|
||||
void run();
|
||||
|
||||
/// Returns the set of loans an origin holds at a specific program point.
|
||||
LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
|
||||
|
||||
/// Returns the set of origins that are live at a specific program point,
|
||||
/// along with the confidence level of their liveness.
|
||||
///
|
||||
/// An origin is considered live if there are potential future uses of that
|
||||
/// origin after the given program point. The confidence level indicates
|
||||
/// whether the origin is definitely live (Definite) due to being domintated
|
||||
/// by a set of uses or only possibly live (Maybe) only on some but not all
|
||||
/// control flow paths.
|
||||
std::vector<std::pair<OriginID, LivenessKind>>
|
||||
getLiveOriginsAtPoint(ProgramPoint PP) const;
|
||||
|
||||
/// Finds the OriginID for a given declaration.
|
||||
/// Returns a null optional if not found.
|
||||
std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const;
|
||||
|
||||
/// Finds the LoanID's for the loan created with the specific variable as
|
||||
/// their Path.
|
||||
std::vector<LoanID> getLoanIDForVar(const VarDecl *VD) const;
|
||||
|
||||
/// Retrieves program points that were specially marked in the source code
|
||||
/// for testing.
|
||||
///
|
||||
/// The analysis recognizes special function calls of the form
|
||||
/// `void("__lifetime_test_point_<name>")` as test points. This method returns
|
||||
/// a map from the annotation string (<name>) to the corresponding
|
||||
/// `ProgramPoint`. This allows test harnesses to query the analysis state at
|
||||
/// user-defined locations in the code.
|
||||
/// \note This is intended for testing only.
|
||||
llvm::StringMap<ProgramPoint> getTestPoints() const;
|
||||
|
||||
private:
|
||||
AnalysisDeclContext &AC;
|
||||
LifetimeSafetyReporter *Reporter;
|
||||
std::unique_ptr<LifetimeFactory> Factory;
|
||||
std::unique_ptr<FactManager> FactMgr;
|
||||
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
|
||||
std::unique_ptr<LiveOriginAnalysis> LiveOrigins;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace clang::lifetimes
|
||||
|
||||
namespace llvm {
|
||||
template <typename Tag>
|
||||
struct DenseMapInfo<clang::lifetimes::internal::ID<Tag>> {
|
||||
using ID = clang::lifetimes::internal::ID<Tag>;
|
||||
|
||||
static inline ID getEmptyKey() {
|
||||
return {DenseMapInfo<uint32_t>::getEmptyKey()};
|
||||
}
|
||||
|
||||
static inline ID getTombstoneKey() {
|
||||
return {DenseMapInfo<uint32_t>::getTombstoneKey()};
|
||||
}
|
||||
|
||||
static unsigned getHashValue(const ID &Val) {
|
||||
return DenseMapInfo<uint32_t>::getHashValue(Val.Value);
|
||||
}
|
||||
|
||||
static bool isEqual(const ID &LHS, const ID &RHS) { return LHS == RHS; }
|
||||
};
|
||||
} // namespace llvm
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
|
||||
@@ -0,0 +1,35 @@
|
||||
//===- Checker.h - C++ Lifetime Safety Analysis -*----------- 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 defines and enforces the lifetime safety policy. It detects
|
||||
// use-after-free errors by examining loan expiration points and checking if
|
||||
// any live origins hold the expired loans.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
/// Runs the lifetime checker, which detects use-after-free errors by
|
||||
/// examining loan expiration points and checking if any live origins hold
|
||||
/// the expired loan.
|
||||
void runLifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
|
||||
const LiveOriginsAnalysis &LiveOrigins,
|
||||
const FactManager &FactMgr, AnalysisDeclContext &ADC,
|
||||
LifetimeSafetyReporter *Reporter);
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_CHECKER_H
|
||||
232
clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
Normal file
232
clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
Normal file
@@ -0,0 +1,232 @@
|
||||
//===- Facts.h - Lifetime Analysis Facts and Fact Manager ------*- 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 defines Facts, which are atomic lifetime-relevant events (such as
|
||||
// loan issuance, loan expiration, origin flow, and use), and the FactManager,
|
||||
// which manages the storage and retrieval of facts for each CFG block.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
/// An abstract base class for a single, atomic lifetime-relevant event.
|
||||
class Fact {
|
||||
|
||||
public:
|
||||
enum class Kind : uint8_t {
|
||||
/// A new loan is issued from a borrow expression (e.g., &x).
|
||||
Issue,
|
||||
/// A loan expires as its underlying storage is freed (e.g., variable goes
|
||||
/// out of scope).
|
||||
Expire,
|
||||
/// An origin is propagated from a source to a destination (e.g., p = q).
|
||||
/// This can also optionally kill the destination origin before flowing into
|
||||
/// it. Otherwise, the source's loan set is merged into the destination's
|
||||
/// loan set.
|
||||
OriginFlow,
|
||||
/// An origin escapes the function by flowing into the return value.
|
||||
ReturnOfOrigin,
|
||||
/// An origin is used (eg. appears as l-value expression like DeclRefExpr).
|
||||
Use,
|
||||
/// A marker for a specific point in the code, for testing.
|
||||
TestPoint,
|
||||
};
|
||||
|
||||
private:
|
||||
Kind K;
|
||||
|
||||
protected:
|
||||
Fact(Kind K) : K(K) {}
|
||||
|
||||
public:
|
||||
virtual ~Fact() = default;
|
||||
Kind getKind() const { return K; }
|
||||
|
||||
template <typename T> const T *getAs() const {
|
||||
if (T::classof(this))
|
||||
return static_cast<const T *>(this);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &) const;
|
||||
};
|
||||
|
||||
/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
|
||||
/// `Fact`. identified by a lifetime-related event (`Fact`).
|
||||
///
|
||||
/// A `ProgramPoint` has "after" semantics: it represents the location
|
||||
/// immediately after its corresponding `Fact`.
|
||||
using ProgramPoint = const Fact *;
|
||||
|
||||
class IssueFact : public Fact {
|
||||
LoanID LID;
|
||||
OriginID OID;
|
||||
|
||||
public:
|
||||
static bool classof(const Fact *F) { return F->getKind() == Kind::Issue; }
|
||||
|
||||
IssueFact(LoanID LID, OriginID OID) : Fact(Kind::Issue), LID(LID), OID(OID) {}
|
||||
LoanID getLoanID() const { return LID; }
|
||||
OriginID getOriginID() const { return OID; }
|
||||
void dump(llvm::raw_ostream &OS, const LoanManager &LM,
|
||||
const OriginManager &OM) const override;
|
||||
};
|
||||
|
||||
class ExpireFact : public Fact {
|
||||
LoanID LID;
|
||||
SourceLocation ExpiryLoc;
|
||||
|
||||
public:
|
||||
static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; }
|
||||
|
||||
ExpireFact(LoanID LID, SourceLocation ExpiryLoc)
|
||||
: Fact(Kind::Expire), LID(LID), ExpiryLoc(ExpiryLoc) {}
|
||||
|
||||
LoanID getLoanID() const { return LID; }
|
||||
SourceLocation getExpiryLoc() const { return ExpiryLoc; }
|
||||
|
||||
void dump(llvm::raw_ostream &OS, const LoanManager &LM,
|
||||
const OriginManager &) const override;
|
||||
};
|
||||
|
||||
class OriginFlowFact : public Fact {
|
||||
OriginID OIDDest;
|
||||
OriginID OIDSrc;
|
||||
// True if the destination origin should be killed (i.e., its current loans
|
||||
// cleared) before the source origin's loans are flowed into it.
|
||||
bool KillDest;
|
||||
|
||||
public:
|
||||
static bool classof(const Fact *F) {
|
||||
return F->getKind() == Kind::OriginFlow;
|
||||
}
|
||||
|
||||
OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
|
||||
: Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc),
|
||||
KillDest(KillDest) {}
|
||||
|
||||
OriginID getDestOriginID() const { return OIDDest; }
|
||||
OriginID getSrcOriginID() const { return OIDSrc; }
|
||||
bool getKillDest() const { return KillDest; }
|
||||
|
||||
void dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &OM) const override;
|
||||
};
|
||||
|
||||
class ReturnOfOriginFact : public Fact {
|
||||
OriginID OID;
|
||||
|
||||
public:
|
||||
static bool classof(const Fact *F) {
|
||||
return F->getKind() == Kind::ReturnOfOrigin;
|
||||
}
|
||||
|
||||
ReturnOfOriginFact(OriginID OID) : Fact(Kind::ReturnOfOrigin), OID(OID) {}
|
||||
OriginID getReturnedOriginID() const { return OID; }
|
||||
void dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &OM) const override;
|
||||
};
|
||||
|
||||
class UseFact : public Fact {
|
||||
const Expr *UseExpr;
|
||||
// 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) : Fact(Kind::Use), UseExpr(UseExpr) {}
|
||||
|
||||
OriginID getUsedOrigin(const OriginManager &OM) const {
|
||||
// TODO: Remove const cast and make OriginManager::get as const.
|
||||
return const_cast<OriginManager &>(OM).get(*UseExpr);
|
||||
}
|
||||
const Expr *getUseExpr() const { return UseExpr; }
|
||||
void markAsWritten() { IsWritten = true; }
|
||||
bool isWritten() const { return IsWritten; }
|
||||
|
||||
void dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &OM) const override;
|
||||
};
|
||||
|
||||
/// A dummy-fact used to mark a specific point in the code for testing.
|
||||
/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
|
||||
class TestPointFact : public Fact {
|
||||
StringRef Annotation;
|
||||
|
||||
public:
|
||||
static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; }
|
||||
|
||||
explicit TestPointFact(StringRef Annotation)
|
||||
: Fact(Kind::TestPoint), Annotation(Annotation) {}
|
||||
|
||||
StringRef getAnnotation() const { return Annotation; }
|
||||
|
||||
void dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &) const override;
|
||||
};
|
||||
|
||||
class FactManager {
|
||||
public:
|
||||
llvm::ArrayRef<const Fact *> getFacts(const CFGBlock *B) const {
|
||||
auto It = BlockToFactsMap.find(B);
|
||||
if (It != BlockToFactsMap.end())
|
||||
return It->second;
|
||||
return {};
|
||||
}
|
||||
|
||||
void addBlockFacts(const CFGBlock *B, llvm::ArrayRef<Fact *> NewFacts) {
|
||||
if (!NewFacts.empty())
|
||||
BlockToFactsMap[B].assign(NewFacts.begin(), NewFacts.end());
|
||||
}
|
||||
|
||||
template <typename FactType, typename... Args>
|
||||
FactType *createFact(Args &&...args) {
|
||||
void *Mem = FactAllocator.Allocate<FactType>();
|
||||
return new (Mem) FactType(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void dump(const CFG &Cfg, AnalysisDeclContext &AC) const;
|
||||
|
||||
/// Retrieves program points that were specially marked in the source code
|
||||
/// for testing.
|
||||
///
|
||||
/// The analysis recognizes special function calls of the form
|
||||
/// `void("__lifetime_test_point_<name>")` as test points. This method returns
|
||||
/// a map from the annotation string (<name>) to the corresponding
|
||||
/// `ProgramPoint`. This allows test harnesses to query the analysis state at
|
||||
/// user-defined locations in the code.
|
||||
/// \note This is intended for testing only.
|
||||
llvm::StringMap<ProgramPoint> getTestPoints() const;
|
||||
|
||||
LoanManager &getLoanMgr() { return LoanMgr; }
|
||||
const LoanManager &getLoanMgr() const { return LoanMgr; }
|
||||
OriginManager &getOriginMgr() { return OriginMgr; }
|
||||
const OriginManager &getOriginMgr() const { return OriginMgr; }
|
||||
|
||||
private:
|
||||
LoanManager LoanMgr;
|
||||
OriginManager OriginMgr;
|
||||
llvm::DenseMap<const clang::CFGBlock *, llvm::SmallVector<const Fact *>>
|
||||
BlockToFactsMap;
|
||||
llvm::BumpPtrAllocator FactAllocator;
|
||||
};
|
||||
} // namespace clang::lifetimes::internal
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTS_H
|
||||
@@ -0,0 +1,106 @@
|
||||
//===- FactsGenerator.h - Lifetime Facts Generation -------------*- 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 defines the FactsGenerator, which traverses the AST to generate
|
||||
// lifetime-relevant facts (such as loan issuance, expiration, origin flow,
|
||||
// and use) from CFG statements. These facts are used by the dataflow analyses
|
||||
// to track pointer lifetimes and detect use-after-free errors.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTSGENERATOR_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTSGENERATOR_H
|
||||
|
||||
#include "clang/AST/StmtVisitor.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
|
||||
using Base = ConstStmtVisitor<FactsGenerator>;
|
||||
|
||||
public:
|
||||
FactsGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
|
||||
: FactMgr(FactMgr), AC(AC) {}
|
||||
|
||||
void run();
|
||||
|
||||
void VisitDeclStmt(const DeclStmt *DS);
|
||||
void VisitDeclRefExpr(const DeclRefExpr *DRE);
|
||||
void VisitCXXConstructExpr(const CXXConstructExpr *CCE);
|
||||
void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE);
|
||||
void VisitCallExpr(const CallExpr *CE);
|
||||
void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N);
|
||||
void VisitImplicitCastExpr(const ImplicitCastExpr *ICE);
|
||||
void VisitUnaryOperator(const UnaryOperator *UO);
|
||||
void VisitReturnStmt(const ReturnStmt *RS);
|
||||
void VisitBinaryOperator(const BinaryOperator *BO);
|
||||
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE);
|
||||
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE);
|
||||
void VisitInitListExpr(const InitListExpr *ILE);
|
||||
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
|
||||
|
||||
private:
|
||||
void handleDestructor(const CFGAutomaticObjDtor &DtorOpt);
|
||||
|
||||
void handleGSLPointerConstruction(const CXXConstructExpr *CCE);
|
||||
|
||||
/// Checks if a call-like expression creates a borrow by passing a value to a
|
||||
/// reference parameter, creating an IssueFact if it does.
|
||||
/// \param IsGslConstruction True if this is a GSL construction where all
|
||||
/// argument origins should flow to the returned origin.
|
||||
void handleFunctionCall(const Expr *Call, const FunctionDecl *FD,
|
||||
ArrayRef<const Expr *> Args,
|
||||
bool IsGslConstruction = false);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/// 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).
|
||||
void handleUse(const DeclRefExpr *DRE);
|
||||
|
||||
void markUseAsWrite(const DeclRefExpr *DRE);
|
||||
|
||||
FactManager &FactMgr;
|
||||
AnalysisDeclContext &AC;
|
||||
llvm::SmallVector<Fact *> CurrentBlockFacts;
|
||||
// To distinguish between reads and writes for use-after-free checks, this map
|
||||
// stores the `UseFact` for each `DeclRefExpr`. We initially identify all
|
||||
// `DeclRefExpr`s as "read" uses. When an assignment is processed, the use
|
||||
// corresponding to the left-hand side is updated to be a "write", thereby
|
||||
// exempting it from the check.
|
||||
llvm::DenseMap<const DeclRefExpr *, UseFact *> UseFacts;
|
||||
};
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_FACTSGENERATOR_H
|
||||
@@ -12,8 +12,7 @@
|
||||
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
|
||||
namespace clang {
|
||||
namespace lifetimes {
|
||||
namespace clang ::lifetimes {
|
||||
|
||||
/// Returns the most recent declaration of the method to ensure all
|
||||
/// lifetime-bound attributes from redeclarations are considered.
|
||||
@@ -38,7 +37,7 @@ bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD);
|
||||
/// lifetimebound, either due to an explicit lifetimebound attribute on the
|
||||
/// method or because it's a normal assignment operator.
|
||||
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD);
|
||||
} // namespace lifetimes
|
||||
} // namespace clang
|
||||
|
||||
} // namespace clang::lifetimes
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H
|
||||
@@ -0,0 +1,87 @@
|
||||
//===- LifetimeSafety.h - C++ Lifetime Safety Analysis -*----------- 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 defines the main entry point and orchestrator for the C++ Lifetime
|
||||
// Safety Analysis. It coordinates the entire analysis pipeline: fact
|
||||
// generation, loan propagation, live origins analysis, and enforcement of
|
||||
// lifetime safety policy.
|
||||
//
|
||||
// The analysis is based on the concepts of "origins" and "loans" to track
|
||||
// pointer lifetimes and detect issues like use-after-free and dangling
|
||||
// pointers. See the RFC for more details:
|
||||
// https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_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/AnalysisDeclContext.h"
|
||||
|
||||
namespace clang::lifetimes {
|
||||
|
||||
/// Enum to track the confidence level of a potential error.
|
||||
enum class Confidence : uint8_t {
|
||||
None,
|
||||
Maybe, // Reported as a potential error (-Wlifetime-safety-strict)
|
||||
Definite // Reported as a definite error (-Wlifetime-safety-permissive)
|
||||
};
|
||||
|
||||
class LifetimeSafetyReporter {
|
||||
public:
|
||||
LifetimeSafetyReporter() = default;
|
||||
virtual ~LifetimeSafetyReporter() = default;
|
||||
|
||||
virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
|
||||
SourceLocation FreeLoc,
|
||||
Confidence Confidence) {}
|
||||
};
|
||||
|
||||
/// The main entry point for the analysis.
|
||||
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter);
|
||||
|
||||
namespace internal {
|
||||
/// An object to hold the factories for immutable collections, ensuring
|
||||
/// that all created states share the same underlying memory management.
|
||||
struct LifetimeFactory {
|
||||
OriginLoanMap::Factory OriginMapFactory{/*canonicalize=*/false};
|
||||
LoanSet::Factory LoanSetFactory{/*canonicalize=*/false};
|
||||
LivenessMap::Factory LivenessMapFactory{/*canonicalize=*/false};
|
||||
};
|
||||
|
||||
/// Running the lifetime safety analysis and querying its results. It
|
||||
/// encapsulates the various dataflow analyses.
|
||||
class LifetimeSafetyAnalysis {
|
||||
public:
|
||||
LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter);
|
||||
|
||||
void run();
|
||||
|
||||
/// \note These are provided only for testing purposes.
|
||||
LoanPropagationAnalysis &getLoanPropagation() const {
|
||||
return *LoanPropagation;
|
||||
}
|
||||
LiveOriginsAnalysis &getLiveOrigins() const { return *LiveOrigins; }
|
||||
FactManager &getFactManager() { return FactMgr; }
|
||||
|
||||
private:
|
||||
AnalysisDeclContext &AC;
|
||||
LifetimeSafetyReporter *Reporter;
|
||||
LifetimeFactory Factory;
|
||||
FactManager FactMgr;
|
||||
std::unique_ptr<LiveOriginsAnalysis> LiveOrigins;
|
||||
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace clang::lifetimes
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
|
||||
@@ -0,0 +1,97 @@
|
||||
//===- LiveOrigins.h - Live Origins Analysis -------------------*- 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 defines the LiveOriginAnalysis, a backward dataflow analysis that
|
||||
// determines which origins are "live" at each program point. An origin is
|
||||
// "live" at a program point if there's a potential future use of a pointer it
|
||||
// is associated with. Liveness is "generated" by a use of an origin (e.g., a
|
||||
// `UseFact` from a read of a pointer) and is "killed" (i.e., it stops being
|
||||
// live) when the origin is replaced by flowing a different origin into it
|
||||
// (e.g., an OriginFlow from an assignment that kills the destination).
|
||||
//
|
||||
// This information is used for detecting use-after-free errors, as it allows us
|
||||
// to check if a live origin holds a loan to an object that has already expired.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LIVE_ORIGINS_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LIVE_ORIGINS_H
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "llvm/ADT/FoldingSet.h"
|
||||
#include "llvm/ADT/ImmutableMap.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
enum class LivenessKind : uint8_t {
|
||||
Dead, // Not alive
|
||||
Maybe, // Live on some path but not all paths (may-be-live)
|
||||
Must // Live on all paths (must-be-live)
|
||||
};
|
||||
|
||||
/// Information about why an origin is live at a program point.
|
||||
struct LivenessInfo {
|
||||
/// The use that makes the origin live. If liveness is propagated from
|
||||
/// multiple uses along different paths, this will point to the use appearing
|
||||
/// earlier in the translation unit.
|
||||
/// This is 'null' when the origin is not live.
|
||||
const UseFact *CausingUseFact;
|
||||
|
||||
/// The kind of liveness of the origin.
|
||||
/// `Must`: The origin is live on all control-flow paths from the current
|
||||
/// point to the function's exit (i.e. the current point is dominated by a set
|
||||
/// of uses).
|
||||
/// `Maybe`: indicates it is live on some but not all paths.
|
||||
///
|
||||
/// This determines the diagnostic's confidence level.
|
||||
/// `Must`-be-alive at expiration implies a definite use-after-free,
|
||||
/// while `Maybe`-be-alive suggests a potential one on some paths.
|
||||
LivenessKind Kind;
|
||||
|
||||
LivenessInfo() : CausingUseFact(nullptr), Kind(LivenessKind::Dead) {}
|
||||
LivenessInfo(const UseFact *UF, LivenessKind K)
|
||||
: CausingUseFact(UF), Kind(K) {}
|
||||
|
||||
bool operator==(const LivenessInfo &Other) const {
|
||||
return CausingUseFact == Other.CausingUseFact && Kind == Other.Kind;
|
||||
}
|
||||
bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); }
|
||||
|
||||
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
|
||||
IDBuilder.AddPointer(CausingUseFact);
|
||||
IDBuilder.Add(Kind);
|
||||
}
|
||||
};
|
||||
|
||||
using LivenessMap = llvm::ImmutableMap<OriginID, LivenessInfo>;
|
||||
|
||||
class LiveOriginsAnalysis {
|
||||
public:
|
||||
LiveOriginsAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
|
||||
LivenessMap::Factory &SF);
|
||||
~LiveOriginsAnalysis();
|
||||
|
||||
/// Returns the set of origins that are live at a specific program point,
|
||||
/// along with the the details of the liveness.
|
||||
LivenessMap getLiveOriginsAt(ProgramPoint P) const;
|
||||
|
||||
// Dump liveness values on all test points in the program.
|
||||
void dump(llvm::raw_ostream &OS,
|
||||
llvm::StringMap<ProgramPoint> TestPoints) const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> PImpl;
|
||||
};
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LIVE_ORIGINS_H
|
||||
@@ -0,0 +1,48 @@
|
||||
//===- LoanPropagation.h - Loan Propagation Analysis -----------*- 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 defines the LoanPropagationAnalysis, a forward dataflow analysis
|
||||
// that tracks which loans each origin holds at each program point. Loans
|
||||
// represent borrows of storage locations and are propagated through the
|
||||
// program as pointers are copied or assigned.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "llvm/ADT/ImmutableMap.h"
|
||||
#include "llvm/ADT/ImmutableSet.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
// Using LLVM's immutable collections is efficient for dataflow analysis
|
||||
// as it avoids deep copies during state transitions.
|
||||
// TODO(opt): Consider using a bitset to represent the set of loans.
|
||||
using LoanSet = llvm::ImmutableSet<LoanID>;
|
||||
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
|
||||
|
||||
class LoanPropagationAnalysis {
|
||||
public:
|
||||
LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
|
||||
OriginLoanMap::Factory &OriginLoanMapFactory,
|
||||
LoanSet::Factory &LoanSetFactory);
|
||||
~LoanPropagationAnalysis();
|
||||
|
||||
LoanSet getLoans(OriginID OID, ProgramPoint P) const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> PImpl;
|
||||
};
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H
|
||||
80
clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
Normal file
80
clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
Normal file
@@ -0,0 +1,80 @@
|
||||
//===- Loans.h - Loan and Access Path Definitions --------------*- 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 defines the Loan and AccessPath structures, which represent
|
||||
// borrows of storage locations, and the LoanManager, which manages the
|
||||
// creation and retrieval of loans during lifetime analysis.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H
|
||||
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
using LoanID = utils::ID<struct LoanTag>;
|
||||
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
|
||||
return OS << ID.Value;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
struct AccessPath {
|
||||
const clang::ValueDecl *D;
|
||||
|
||||
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 {
|
||||
/// TODO: Represent opaque loans.
|
||||
/// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it
|
||||
/// is represented as empty LoanSet
|
||||
LoanID ID;
|
||||
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) {}
|
||||
|
||||
void dump(llvm::raw_ostream &OS) const;
|
||||
};
|
||||
|
||||
/// 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();
|
||||
}
|
||||
|
||||
const Loan &getLoan(LoanID ID) const {
|
||||
assert(ID.Value < AllLoans.size());
|
||||
return AllLoans[ID.Value];
|
||||
}
|
||||
llvm::ArrayRef<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;
|
||||
};
|
||||
} // namespace clang::lifetimes::internal
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H
|
||||
@@ -0,0 +1,91 @@
|
||||
//===- Origins.h - Origin and Origin Management ----------------*- 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 defines Origins, which represent the set of possible loans a
|
||||
// pointer-like object could hold, and the OriginManager, which manages the
|
||||
// creation, storage, and retrieval of origins for variables and expressions.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_ORIGINS_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_ORIGINS_H
|
||||
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/Expr.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
using OriginID = utils::ID<struct OriginTag>;
|
||||
|
||||
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
|
||||
return OS << ID.Value;
|
||||
}
|
||||
|
||||
/// 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
|
||||
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) {}
|
||||
|
||||
const clang::ValueDecl *getDecl() const {
|
||||
return Ptr.dyn_cast<const clang::ValueDecl *>();
|
||||
}
|
||||
const clang::Expr *getExpr() const {
|
||||
return Ptr.dyn_cast<const clang::Expr *>();
|
||||
}
|
||||
};
|
||||
|
||||
/// 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);
|
||||
|
||||
const Origin &getOrigin(OriginID ID) const;
|
||||
|
||||
llvm::ArrayRef<Origin> getOrigins() const { return AllOrigins; }
|
||||
|
||||
OriginID getOrCreate(const ValueDecl &D);
|
||||
|
||||
void dump(OriginID OID, llvm::raw_ostream &OS) const;
|
||||
|
||||
private:
|
||||
OriginID getNextOriginID() { return NextOriginID++; }
|
||||
|
||||
OriginID NextOriginID{0};
|
||||
/// TODO(opt): Profile and evaluate the usefullness of small buffer
|
||||
/// optimisation.
|
||||
llvm::SmallVector<Origin> AllOrigins;
|
||||
llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
|
||||
llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
|
||||
};
|
||||
} // namespace clang::lifetimes::internal
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_ORIGINS_H
|
||||
118
clang/include/clang/Analysis/Analyses/LifetimeSafety/Utils.h
Normal file
118
clang/include/clang/Analysis/Analyses/LifetimeSafety/Utils.h
Normal file
@@ -0,0 +1,118 @@
|
||||
//===- Utils.h - Utility Functions for Lifetime Safety --------*- 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 provides utilities for the lifetime safety analysis, including
|
||||
// join operations for LLVM's immutable data structures.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_UTILS_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_UTILS_H
|
||||
|
||||
#include "llvm/ADT/ImmutableMap.h"
|
||||
#include "llvm/ADT/ImmutableSet.h"
|
||||
|
||||
namespace clang::lifetimes::internal::utils {
|
||||
|
||||
/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
|
||||
/// Used for giving ID to loans and origins.
|
||||
template <typename Tag> struct ID {
|
||||
uint32_t Value = 0;
|
||||
|
||||
bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; }
|
||||
bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); }
|
||||
bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; }
|
||||
ID<Tag> operator++(int) {
|
||||
ID<Tag> Tmp = *this;
|
||||
++Value;
|
||||
return Tmp;
|
||||
}
|
||||
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
|
||||
IDBuilder.AddInteger(Value);
|
||||
}
|
||||
};
|
||||
|
||||
/// Computes the union of two ImmutableSets.
|
||||
template <typename T>
|
||||
static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
|
||||
llvm::ImmutableSet<T> B,
|
||||
typename llvm::ImmutableSet<T>::Factory &F) {
|
||||
if (A.getHeight() < B.getHeight())
|
||||
std::swap(A, B);
|
||||
for (const T &E : B)
|
||||
A = F.add(A, E);
|
||||
return A;
|
||||
}
|
||||
|
||||
/// Describes the strategy for joining two `ImmutableMap` instances, primarily
|
||||
/// differing in how they handle keys that are unique to one of the maps.
|
||||
///
|
||||
/// A `Symmetric` join is universally correct, while an `Asymmetric` join
|
||||
/// serves as a performance optimization. The latter is applicable only when the
|
||||
/// join operation possesses a left identity element, allowing for a more
|
||||
/// efficient, one-sided merge.
|
||||
enum class JoinKind {
|
||||
/// A symmetric join applies the `JoinValues` operation to keys unique to
|
||||
/// either map, ensuring that values from both maps contribute to the result.
|
||||
Symmetric,
|
||||
/// An asymmetric join preserves keys unique to the first map as-is, while
|
||||
/// applying the `JoinValues` operation only to keys unique to the second map.
|
||||
Asymmetric,
|
||||
};
|
||||
|
||||
/// Computes the key-wise union of two ImmutableMaps.
|
||||
// TODO(opt): This key-wise join is a performance bottleneck. A more
|
||||
// efficient merge could be implemented using a Patricia Trie or HAMT
|
||||
// instead of the current AVL-tree-based ImmutableMap.
|
||||
template <typename K, typename V, typename Joiner>
|
||||
static llvm::ImmutableMap<K, V>
|
||||
join(const llvm::ImmutableMap<K, V> &A, const llvm::ImmutableMap<K, V> &B,
|
||||
typename llvm::ImmutableMap<K, V>::Factory &F, Joiner JoinValues,
|
||||
JoinKind Kind) {
|
||||
if (A.getHeight() < B.getHeight())
|
||||
return join(B, A, F, JoinValues, Kind);
|
||||
|
||||
// For each element in B, join it with the corresponding element in A
|
||||
// (or with an empty value if it doesn't exist in A).
|
||||
llvm::ImmutableMap<K, V> Res = A;
|
||||
for (const auto &Entry : B) {
|
||||
const K &Key = Entry.first;
|
||||
const V &ValB = Entry.second;
|
||||
Res = F.add(Res, Key, JoinValues(A.lookup(Key), &ValB));
|
||||
}
|
||||
if (Kind == JoinKind::Symmetric) {
|
||||
for (const auto &Entry : A) {
|
||||
const K &Key = Entry.first;
|
||||
const V &ValA = Entry.second;
|
||||
if (!B.contains(Key))
|
||||
Res = F.add(Res, Key, JoinValues(&ValA, nullptr));
|
||||
}
|
||||
}
|
||||
return Res;
|
||||
}
|
||||
} // namespace clang::lifetimes::internal::utils
|
||||
|
||||
namespace llvm {
|
||||
template <typename Tag>
|
||||
struct DenseMapInfo<clang::lifetimes::internal::utils::ID<Tag>> {
|
||||
using ID = clang::lifetimes::internal::utils::ID<Tag>;
|
||||
|
||||
static inline ID getEmptyKey() {
|
||||
return {DenseMapInfo<uint32_t>::getEmptyKey()};
|
||||
}
|
||||
|
||||
static inline ID getTombstoneKey() {
|
||||
return {DenseMapInfo<uint32_t>::getTombstoneKey()};
|
||||
}
|
||||
|
||||
static unsigned getHashValue(const ID &Val) {
|
||||
return DenseMapInfo<uint32_t>::getHashValue(Val.Value);
|
||||
}
|
||||
|
||||
static bool isEqual(const ID &LHS, const ID &RHS) { return LHS == RHS; }
|
||||
};
|
||||
} // namespace llvm
|
||||
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_UTILS_H
|
||||
@@ -21,8 +21,6 @@ add_clang_library(clangAnalysis
|
||||
FixitUtil.cpp
|
||||
IntervalPartition.cpp
|
||||
IssueHash.cpp
|
||||
LifetimeAnnotations.cpp
|
||||
LifetimeSafety.cpp
|
||||
LiveVariables.cpp
|
||||
MacroExpansionContext.cpp
|
||||
ObjCNoReturn.cpp
|
||||
@@ -51,3 +49,4 @@ add_clang_library(clangAnalysis
|
||||
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(FlowSensitive)
|
||||
add_subdirectory(LifetimeSafety)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
12
clang/lib/Analysis/LifetimeSafety/CMakeLists.txt
Normal file
12
clang/lib/Analysis/LifetimeSafety/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
add_clang_library(clangAnalysisLifetimeSafety
|
||||
Checker.cpp
|
||||
Facts.cpp
|
||||
FactsGenerator.cpp
|
||||
LifetimeAnnotations.cpp
|
||||
LifetimeSafety.cpp
|
||||
LiveOrigins.cpp
|
||||
Loans.cpp
|
||||
LoanPropagation.cpp
|
||||
Origins.cpp
|
||||
)
|
||||
|
||||
130
clang/lib/Analysis/LifetimeSafety/Checker.cpp
Normal file
130
clang/lib/Analysis/LifetimeSafety/Checker.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
//===- 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 "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.
|
||||
const Expr *UseExpr; // Where the origin holding this loan was used.
|
||||
Confidence ConfidenceLevel;
|
||||
};
|
||||
|
||||
class LifetimeChecker {
|
||||
private:
|
||||
llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
|
||||
const LoanPropagationAnalysis &LoanPropagation;
|
||||
const LiveOriginsAnalysis &LiveOrigins;
|
||||
const FactManager &FactMgr;
|
||||
LifetimeSafetyReporter *Reporter;
|
||||
|
||||
public:
|
||||
LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
|
||||
const LiveOriginsAnalysis &LiveOrigins, const FactManager &FM,
|
||||
AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter)
|
||||
: LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM),
|
||||
Reporter(Reporter) {
|
||||
for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
|
||||
for (const Fact *F : FactMgr.getFacts(B))
|
||||
if (const auto *EF = F->getAs<ExpireFact>())
|
||||
checkExpiry(EF);
|
||||
issuePendingWarnings();
|
||||
}
|
||||
|
||||
/// Checks for use-after-free 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;
|
||||
const UseFact *BadUse = 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;
|
||||
BadUse = LiveInfo.CausingUseFact;
|
||||
}
|
||||
}
|
||||
if (!BadUse)
|
||||
return;
|
||||
// We have a use-after-free.
|
||||
Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel;
|
||||
if (LastConf >= CurConfidence)
|
||||
return;
|
||||
FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
|
||||
/*UseExpr=*/BadUse->getUseExpr(),
|
||||
/*ConfidenceLevel=*/CurConfidence};
|
||||
}
|
||||
|
||||
void issuePendingWarnings() {
|
||||
if (!Reporter)
|
||||
return;
|
||||
for (const auto &[LID, Warning] : FinalWarningsMap) {
|
||||
const Loan &L = FactMgr.getLoanMgr().getLoan(LID);
|
||||
const Expr *IssueExpr = L.IssueExpr;
|
||||
Reporter->reportUseAfterFree(IssueExpr, Warning.UseExpr,
|
||||
Warning.ExpiryLoc, Warning.ConfidenceLevel);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // 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
|
||||
188
clang/lib/Analysis/LifetimeSafety/Dataflow.h
Normal file
188
clang/lib/Analysis/LifetimeSafety/Dataflow.h
Normal file
@@ -0,0 +1,188 @@
|
||||
//===- Dataflow.h - Generic Dataflow Analysis Framework --------*- 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 defines a generic, policy-based driver for dataflow analyses.
|
||||
// It provides a flexible framework that combines the dataflow runner and
|
||||
// transfer functions, allowing derived classes to implement specific analyses
|
||||
// by defining their lattice, join, and transfer functions.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/TimeProfiler.h"
|
||||
#include <optional>
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
enum class Direction { Forward, Backward };
|
||||
|
||||
/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
|
||||
/// `Fact`. identified by a lifetime-related event (`Fact`).
|
||||
///
|
||||
/// A `ProgramPoint` has "after" semantics: it represents the location
|
||||
/// immediately after its corresponding `Fact`.
|
||||
using ProgramPoint = const Fact *;
|
||||
|
||||
/// A generic, policy-based driver for dataflow analyses. It combines
|
||||
/// the dataflow runner and the transferer logic into a single class hierarchy.
|
||||
///
|
||||
/// The derived class is expected to provide:
|
||||
/// - A `Lattice` type.
|
||||
/// - `StringRef getAnalysisName() const`
|
||||
/// - `Lattice getInitialState();` The initial state of the analysis.
|
||||
/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths.
|
||||
/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single
|
||||
/// lifetime-relevant `Fact` transforms the lattice state. Only overloads
|
||||
/// for facts relevant to the analysis need to be implemented.
|
||||
///
|
||||
/// \tparam Derived The CRTP derived class that implements the specific
|
||||
/// analysis.
|
||||
/// \tparam LatticeType The dataflow lattice used by the analysis.
|
||||
/// \tparam Dir The direction of the analysis (Forward or Backward).
|
||||
/// TODO: Maybe use the dataflow framework! The framework might need changes
|
||||
/// to support the current comparison done at block-entry.
|
||||
template <typename Derived, typename LatticeType, Direction Dir>
|
||||
class DataflowAnalysis {
|
||||
public:
|
||||
using Lattice = LatticeType;
|
||||
using Base = DataflowAnalysis<Derived, Lattice, Dir>;
|
||||
|
||||
private:
|
||||
const CFG &Cfg;
|
||||
AnalysisDeclContext &AC;
|
||||
|
||||
/// The dataflow state before a basic block is processed.
|
||||
llvm::DenseMap<const CFGBlock *, Lattice> InStates;
|
||||
/// The dataflow state after a basic block is processed.
|
||||
llvm::DenseMap<const CFGBlock *, Lattice> OutStates;
|
||||
/// The dataflow state at a Program Point.
|
||||
/// In a forward analysis, this is the state after the Fact at that point has
|
||||
/// been applied, while in a backward analysis, it is the state before.
|
||||
llvm::DenseMap<ProgramPoint, Lattice> PerPointStates;
|
||||
|
||||
static constexpr bool isForward() { return Dir == Direction::Forward; }
|
||||
|
||||
protected:
|
||||
FactManager &FactMgr;
|
||||
|
||||
explicit DataflowAnalysis(const CFG &Cfg, AnalysisDeclContext &AC,
|
||||
FactManager &FactMgr)
|
||||
: Cfg(Cfg), AC(AC), FactMgr(FactMgr) {}
|
||||
|
||||
public:
|
||||
void run() {
|
||||
Derived &D = static_cast<Derived &>(*this);
|
||||
llvm::TimeTraceScope Time(D.getAnalysisName());
|
||||
|
||||
using Worklist =
|
||||
std::conditional_t<Dir == Direction::Forward, ForwardDataflowWorklist,
|
||||
BackwardDataflowWorklist>;
|
||||
Worklist W(Cfg, AC);
|
||||
|
||||
const CFGBlock *Start = isForward() ? &Cfg.getEntry() : &Cfg.getExit();
|
||||
InStates[Start] = D.getInitialState();
|
||||
W.enqueueBlock(Start);
|
||||
|
||||
while (const CFGBlock *B = W.dequeue()) {
|
||||
Lattice StateIn = *getInState(B);
|
||||
Lattice StateOut = transferBlock(B, StateIn);
|
||||
OutStates[B] = StateOut;
|
||||
for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) {
|
||||
if (!AdjacentB)
|
||||
continue;
|
||||
std::optional<Lattice> OldInState = getInState(AdjacentB);
|
||||
Lattice NewInState =
|
||||
!OldInState ? StateOut : D.join(*OldInState, StateOut);
|
||||
// Enqueue the adjacent block if its in-state has changed or if we have
|
||||
// never seen it.
|
||||
if (!OldInState || NewInState != *OldInState) {
|
||||
InStates[AdjacentB] = NewInState;
|
||||
W.enqueueBlock(AdjacentB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); }
|
||||
|
||||
std::optional<Lattice> getInState(const CFGBlock *B) const {
|
||||
auto It = InStates.find(B);
|
||||
if (It == InStates.end())
|
||||
return std::nullopt;
|
||||
return It->second;
|
||||
}
|
||||
|
||||
Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); }
|
||||
|
||||
void dump() const {
|
||||
const Derived *D = static_cast<const Derived *>(this);
|
||||
llvm::dbgs() << "==========================================\n";
|
||||
llvm::dbgs() << D->getAnalysisName() << " results:\n";
|
||||
llvm::dbgs() << "==========================================\n";
|
||||
const CFGBlock &B = isForward() ? Cfg.getExit() : Cfg.getEntry();
|
||||
getOutState(&B).dump(llvm::dbgs());
|
||||
}
|
||||
|
||||
private:
|
||||
/// Computes the state at one end of a block by applying all its facts
|
||||
/// sequentially to a given state from the other end.
|
||||
Lattice transferBlock(const CFGBlock *Block, Lattice State) {
|
||||
auto Facts = FactMgr.getFacts(Block);
|
||||
if constexpr (isForward()) {
|
||||
for (const Fact *F : Facts) {
|
||||
State = transferFact(State, F);
|
||||
PerPointStates[F] = State;
|
||||
}
|
||||
} else {
|
||||
for (const Fact *F : llvm::reverse(Facts)) {
|
||||
// In backward analysis, capture the state before applying the fact.
|
||||
PerPointStates[F] = State;
|
||||
State = transferFact(State, F);
|
||||
}
|
||||
}
|
||||
return State;
|
||||
}
|
||||
|
||||
Lattice transferFact(Lattice In, const Fact *F) {
|
||||
assert(F);
|
||||
Derived *D = static_cast<Derived *>(this);
|
||||
switch (F->getKind()) {
|
||||
case Fact::Kind::Issue:
|
||||
return D->transfer(In, *F->getAs<IssueFact>());
|
||||
case Fact::Kind::Expire:
|
||||
return D->transfer(In, *F->getAs<ExpireFact>());
|
||||
case Fact::Kind::OriginFlow:
|
||||
return D->transfer(In, *F->getAs<OriginFlowFact>());
|
||||
case Fact::Kind::ReturnOfOrigin:
|
||||
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
|
||||
case Fact::Kind::Use:
|
||||
return D->transfer(In, *F->getAs<UseFact>());
|
||||
case Fact::Kind::TestPoint:
|
||||
return D->transfer(In, *F->getAs<TestPointFact>());
|
||||
}
|
||||
llvm_unreachable("Unknown fact kind");
|
||||
}
|
||||
|
||||
public:
|
||||
Lattice transfer(Lattice In, const IssueFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const UseFact &) { return In; }
|
||||
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
|
||||
};
|
||||
} // namespace clang::lifetimes::internal
|
||||
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
|
||||
102
clang/lib/Analysis/LifetimeSafety/Facts.cpp
Normal file
102
clang/lib/Analysis/LifetimeSafety/Facts.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
//===- Facts.cpp - Lifetime Analysis Facts Implementation -------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
void Fact::dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &) const {
|
||||
OS << "Fact (Kind: " << static_cast<int>(K) << ")\n";
|
||||
}
|
||||
|
||||
void IssueFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
|
||||
const OriginManager &OM) const {
|
||||
OS << "Issue (";
|
||||
LM.getLoan(getLoanID()).dump(OS);
|
||||
OS << ", ToOrigin: ";
|
||||
OM.dump(getOriginID(), OS);
|
||||
OS << ")\n";
|
||||
}
|
||||
|
||||
void ExpireFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
|
||||
const OriginManager &) const {
|
||||
OS << "Expire (";
|
||||
LM.getLoan(getLoanID()).dump(OS);
|
||||
OS << ")\n";
|
||||
}
|
||||
|
||||
void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &OM) const {
|
||||
OS << "OriginFlow (Dest: ";
|
||||
OM.dump(getDestOriginID(), OS);
|
||||
OS << ", Src: ";
|
||||
OM.dump(getSrcOriginID(), OS);
|
||||
OS << (getKillDest() ? "" : ", Merge");
|
||||
OS << ")\n";
|
||||
}
|
||||
|
||||
void ReturnOfOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &OM) const {
|
||||
OS << "ReturnOfOrigin (";
|
||||
OM.dump(getReturnedOriginID(), OS);
|
||||
OS << ")\n";
|
||||
}
|
||||
|
||||
void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &OM) const {
|
||||
OS << "Use (";
|
||||
OM.dump(getUsedOrigin(OM), OS);
|
||||
OS << ", " << (isWritten() ? "Write" : "Read") << ")\n";
|
||||
}
|
||||
|
||||
void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &,
|
||||
const OriginManager &) const {
|
||||
OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
|
||||
}
|
||||
|
||||
llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const {
|
||||
llvm::StringMap<ProgramPoint> AnnotationToPointMap;
|
||||
for (const CFGBlock *Block : BlockToFactsMap.keys()) {
|
||||
for (const Fact *F : getFacts(Block)) {
|
||||
if (const auto *TPF = F->getAs<TestPointFact>()) {
|
||||
StringRef PointName = TPF->getAnnotation();
|
||||
assert(AnnotationToPointMap.find(PointName) ==
|
||||
AnnotationToPointMap.end() &&
|
||||
"more than one test points with the same name");
|
||||
AnnotationToPointMap[PointName] = F;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AnnotationToPointMap;
|
||||
}
|
||||
|
||||
void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
|
||||
llvm::dbgs() << "==========================================\n";
|
||||
llvm::dbgs() << " Lifetime Analysis Facts:\n";
|
||||
llvm::dbgs() << "==========================================\n";
|
||||
if (const Decl *D = AC.getDecl())
|
||||
if (const auto *ND = dyn_cast<NamedDecl>(D))
|
||||
llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n";
|
||||
// Print blocks in the order as they appear in code for a stable ordering.
|
||||
for (const CFGBlock *B : *AC.getAnalysis<PostOrderCFGView>()) {
|
||||
llvm::dbgs() << " Block B" << B->getBlockID() << ":\n";
|
||||
auto It = BlockToFactsMap.find(B);
|
||||
if (It != BlockToFactsMap.end()) {
|
||||
for (const Fact *F : It->second) {
|
||||
llvm::dbgs() << " ";
|
||||
F->dump(llvm::dbgs(), LoanMgr, OriginMgr);
|
||||
}
|
||||
}
|
||||
llvm::dbgs() << " End of Block\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
348
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
Normal file
348
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
//===- FactsGenerator.cpp - Lifetime Facts Generation -----------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
|
||||
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
|
||||
#include "llvm/Support/TimeProfiler.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
static bool isGslPointerType(QualType QT) {
|
||||
if (const auto *RD = QT->getAsCXXRecordDecl()) {
|
||||
// We need to check the template definition for specializations.
|
||||
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
|
||||
return CTSD->getSpecializedTemplate()
|
||||
->getTemplatedDecl()
|
||||
->hasAttr<PointerAttr>();
|
||||
return RD->hasAttr<PointerAttr>();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isPointerType(QualType QT) {
|
||||
return QT->isPointerOrReferenceType() || isGslPointerType(QT);
|
||||
}
|
||||
// Check if a type has an origin.
|
||||
static bool hasOrigin(const Expr *E) {
|
||||
return E->isGLValue() || isPointerType(E->getType());
|
||||
}
|
||||
|
||||
static bool hasOrigin(const VarDecl *VD) {
|
||||
return isPointerType(VD->getType());
|
||||
}
|
||||
|
||||
/// Creates a loan for the storage path of a given declaration reference.
|
||||
/// 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) {
|
||||
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 nullptr;
|
||||
}
|
||||
|
||||
void FactsGenerator::run() {
|
||||
llvm::TimeTraceScope TimeProfile("FactGenerator");
|
||||
// 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();
|
||||
for (unsigned I = 0; I < Block->size(); ++I) {
|
||||
const CFGElement &Element = Block->Elements[I];
|
||||
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
|
||||
Visit(CS->getStmt());
|
||||
else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
|
||||
Element.getAs<CFGAutomaticObjDtor>())
|
||||
handleDestructor(*DtorOpt);
|
||||
}
|
||||
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) {
|
||||
for (const Decl *D : DS->decls())
|
||||
if (const auto *VD = dyn_cast<VarDecl>(D))
|
||||
if (hasOrigin(VD))
|
||||
if (const Expr *InitExpr = VD->getInit())
|
||||
killAndFlowOrigin(*VD, *InitExpr);
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
|
||||
handleUse(DRE);
|
||||
// For non-pointer/non-view types, a reference to the variable's storage
|
||||
// is a borrow. We create a loan for it.
|
||||
// For pointer/view types, we stick to the existing model for now and do
|
||||
// not create an extra origin for the l-value expression itself.
|
||||
|
||||
// TODO: A single origin for a `DeclRefExpr` for a pointer or view type is
|
||||
// not sufficient to model the different levels of indirection. The current
|
||||
// single-origin model cannot distinguish between a loan to the variable's
|
||||
// storage and a loan to what it points to. A multi-origin model would be
|
||||
// required for this.
|
||||
if (!isPointerType(DRE->getType())) {
|
||||
if (const Loan *L = createLoan(FactMgr, DRE)) {
|
||||
OriginID ExprOID = FactMgr.getOriginMgr().getOrCreate(*DRE);
|
||||
CurrentBlockFacts.push_back(
|
||||
FactMgr.createFact<IssueFact>(L->ID, ExprOID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
|
||||
if (isGslPointerType(CCE->getType())) {
|
||||
handleGSLPointerConstruction(CCE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
|
||||
// Specifically for conversion operators,
|
||||
// like `std::string_view p = std::string{};`
|
||||
if (isGslPointerType(MCE->getType()) &&
|
||||
isa<CXXConversionDecl>(MCE->getCalleeDecl())) {
|
||||
// The argument is the implicit object itself.
|
||||
handleFunctionCall(MCE, MCE->getMethodDecl(),
|
||||
{MCE->getImplicitObjectArgument()},
|
||||
/*IsGslConstruction=*/true);
|
||||
}
|
||||
if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
|
||||
// Construct the argument list, with the implicit 'this' object as the
|
||||
// first argument.
|
||||
llvm::SmallVector<const Expr *, 4> Args;
|
||||
Args.push_back(MCE->getImplicitObjectArgument());
|
||||
Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs());
|
||||
|
||||
handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitCallExpr(const CallExpr *CE) {
|
||||
handleFunctionCall(CE, CE->getDirectCallee(),
|
||||
{CE->getArgs(), CE->getNumArgs()});
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitCXXNullPtrLiteralExpr(
|
||||
const CXXNullPtrLiteralExpr *N) {
|
||||
/// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
|
||||
/// pointers can use the same type of loan.
|
||||
FactMgr.getOriginMgr().getOrCreate(*N);
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
|
||||
if (!hasOrigin(ICE))
|
||||
return;
|
||||
// An ImplicitCastExpr node itself gets an origin, which flows from the
|
||||
// origin of its sub-expression (after stripping its own parens/casts).
|
||||
killAndFlowOrigin(*ICE, *ICE->getSubExpr());
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) {
|
||||
if (UO->getOpcode() == UO_AddrOf) {
|
||||
const Expr *SubExpr = UO->getSubExpr();
|
||||
// Taking address of a pointer-type expression is not yet supported and
|
||||
// will be supported in multi-origin model.
|
||||
if (isPointerType(SubExpr->getType()))
|
||||
return;
|
||||
// The origin of an address-of expression (e.g., &x) is the origin of
|
||||
// its sub-expression (x). This fact will cause the dataflow analysis
|
||||
// to propagate any loans held by the sub-expression's origin to the
|
||||
// origin of this UnaryOperator expression.
|
||||
killAndFlowOrigin(*UO, *SubExpr);
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
|
||||
if (const Expr *RetExpr = RS->getRetValue()) {
|
||||
if (hasOrigin(RetExpr)) {
|
||||
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr);
|
||||
CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
|
||||
if (BO->isAssignmentOp())
|
||||
handleAssignment(BO->getLHS(), BO->getRHS());
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
|
||||
// Assignment operators have special "kill-then-propagate" semantics
|
||||
// and are handled separately.
|
||||
if (OCE->isAssignmentOp() && OCE->getNumArgs() == 2) {
|
||||
handleAssignment(OCE->getArg(0), OCE->getArg(1));
|
||||
return;
|
||||
}
|
||||
handleFunctionCall(OCE, OCE->getDirectCallee(),
|
||||
{OCE->getArgs(), OCE->getNumArgs()},
|
||||
/*IsGslConstruction=*/false);
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitCXXFunctionalCastExpr(
|
||||
const CXXFunctionalCastExpr *FCE) {
|
||||
// Check if this is a test point marker. If so, we are done with this
|
||||
// expression.
|
||||
if (handleTestPoint(FCE))
|
||||
return;
|
||||
if (isGslPointerType(FCE->getType()))
|
||||
killAndFlowOrigin(*FCE, *FCE->getSubExpr());
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
|
||||
if (!hasOrigin(ILE))
|
||||
return;
|
||||
// For list initialization with a single element, like `View{...}`, the
|
||||
// origin of the list itself is the origin of its single element.
|
||||
if (ILE->getNumInits() == 1)
|
||||
killAndFlowOrigin(*ILE, *ILE->getInit(0));
|
||||
}
|
||||
|
||||
void FactsGenerator::VisitMaterializeTemporaryExpr(
|
||||
const MaterializeTemporaryExpr *MTE) {
|
||||
if (!hasOrigin(MTE))
|
||||
return;
|
||||
// A temporary object's origin is the same as the origin of the
|
||||
// expression that initializes it.
|
||||
killAndFlowOrigin(*MTE, *MTE->getSubExpr());
|
||||
}
|
||||
|
||||
void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
|
||||
/// TODO: Also handle trivial destructors (e.g., for `int`
|
||||
/// variables) which will never have a CFGAutomaticObjDtor node.
|
||||
/// TODO: Handle loans to temporaries.
|
||||
/// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the
|
||||
/// lifetime ends.
|
||||
const VarDecl *DestructedVD = DtorOpt.getVarDecl();
|
||||
if (!DestructedVD)
|
||||
return;
|
||||
// Iterate through all loans to see if any expire.
|
||||
/// TODO(opt): Do better than a linear search to find loans associated with
|
||||
/// 'DestructedVD'.
|
||||
for (const Loan &L : FactMgr.getLoanMgr().getLoans()) {
|
||||
const AccessPath &LoanPath = L.Path;
|
||||
// Check if the loan is for a stack variable and if that variable
|
||||
// is the one being destructed.
|
||||
if (LoanPath.D == DestructedVD)
|
||||
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
|
||||
L.ID, DtorOpt.getTriggerStmt()->getEndLoc()));
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
|
||||
assert(isGslPointerType(CCE->getType()));
|
||||
if (CCE->getNumArgs() != 1)
|
||||
return;
|
||||
if (hasOrigin(CCE->getArg(0)))
|
||||
killAndFlowOrigin(*CCE, *CCE->getArg(0));
|
||||
else
|
||||
// This could be a new borrow.
|
||||
handleFunctionCall(CCE, CCE->getConstructor(),
|
||||
{CCE->getArgs(), CCE->getNumArgs()},
|
||||
/*IsGslConstruction=*/true);
|
||||
}
|
||||
|
||||
/// Checks if a call-like expression creates a borrow by passing a value to a
|
||||
/// reference parameter, creating an IssueFact if it does.
|
||||
/// \param IsGslConstruction True if this is a GSL construction where all
|
||||
/// argument origins should flow to the returned origin.
|
||||
void FactsGenerator::handleFunctionCall(const Expr *Call,
|
||||
const FunctionDecl *FD,
|
||||
ArrayRef<const Expr *> Args,
|
||||
bool IsGslConstruction) {
|
||||
// Ignore functions returning values with no origin.
|
||||
if (!FD || !hasOrigin(Call))
|
||||
return;
|
||||
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
|
||||
const ParmVarDecl *PVD = nullptr;
|
||||
if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
|
||||
Method && Method->isInstance()) {
|
||||
if (I == 0)
|
||||
// For the 'this' argument, the attribute is on the method itself.
|
||||
return implicitObjectParamIsLifetimeBound(Method);
|
||||
if ((I - 1) < Method->getNumParams())
|
||||
// For explicit arguments, find the corresponding parameter
|
||||
// declaration.
|
||||
PVD = Method->getParamDecl(I - 1);
|
||||
} else if (I < FD->getNumParams())
|
||||
// For free functions or static methods.
|
||||
PVD = FD->getParamDecl(I);
|
||||
return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
|
||||
};
|
||||
if (Args.empty())
|
||||
return;
|
||||
bool killedSrc = false;
|
||||
for (unsigned I = 0; I < Args.size(); ++I)
|
||||
if (IsGslConstruction || IsArgLifetimeBound(I)) {
|
||||
if (!killedSrc) {
|
||||
killedSrc = true;
|
||||
killAndFlowOrigin(*Call, *Args[I]);
|
||||
} else
|
||||
flowOrigin(*Call, *Args[I]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
|
||||
/// If so, creates a `TestPointFact` and returns true.
|
||||
bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) {
|
||||
if (!FCE->getType()->isVoidType())
|
||||
return false;
|
||||
|
||||
const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
|
||||
if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) {
|
||||
llvm::StringRef LiteralValue = SL->getString();
|
||||
const std::string Prefix = "__lifetime_test_point_";
|
||||
|
||||
if (LiteralValue.starts_with(Prefix)) {
|
||||
StringRef Annotation = LiteralValue.drop_front(Prefix.length());
|
||||
CurrentBlockFacts.push_back(
|
||||
FactMgr.createFact<TestPointFact>(Annotation));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FactsGenerator::handleAssignment(const Expr *LHSExpr,
|
||||
const Expr *RHSExpr) {
|
||||
if (!hasOrigin(LHSExpr))
|
||||
return;
|
||||
// Find the underlying variable declaration for the left-hand side.
|
||||
if (const auto *DRE_LHS =
|
||||
dyn_cast<DeclRefExpr>(LHSExpr->IgnoreParenImpCasts())) {
|
||||
markUseAsWrite(DRE_LHS);
|
||||
if (const auto *VD_LHS = dyn_cast<ValueDecl>(DRE_LHS->getDecl())) {
|
||||
// Kill the old loans of the destination origin and flow the new loans
|
||||
// from the source origin.
|
||||
killAndFlowOrigin(*VD_LHS, *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).
|
||||
void FactsGenerator::handleUse(const DeclRefExpr *DRE) {
|
||||
if (isPointerType(DRE->getType())) {
|
||||
UseFact *UF = FactMgr.createFact<UseFact>(DRE);
|
||||
CurrentBlockFacts.push_back(UF);
|
||||
assert(!UseFacts.contains(DRE));
|
||||
UseFacts[DRE] = UF;
|
||||
}
|
||||
}
|
||||
|
||||
void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
|
||||
if (!isPointerType(DRE->getType()))
|
||||
return;
|
||||
assert(UseFacts.contains(DRE));
|
||||
UseFacts[DRE]->markAsWritten();
|
||||
}
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
@@ -5,7 +5,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Attr.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
@@ -13,8 +13,7 @@
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/AST/TypeLoc.h"
|
||||
|
||||
namespace clang {
|
||||
namespace lifetimes {
|
||||
namespace clang::lifetimes {
|
||||
|
||||
const FunctionDecl *
|
||||
getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
|
||||
@@ -71,5 +70,4 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
|
||||
return isNormalAssignmentOperator(FD);
|
||||
}
|
||||
|
||||
} // namespace lifetimes
|
||||
} // namespace clang
|
||||
} // namespace clang::lifetimes
|
||||
77
clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
Normal file
77
clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
//===- LifetimeSafety.cpp - C++ Lifetime Safety Analysis -*--------- 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 main LifetimeSafetyAnalysis class, which coordinates
|
||||
// the various components (fact generation, loan propagation, live origins
|
||||
// analysis, and checking) to detect lifetime safety violations in C++ code.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/Expr.h"
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Checker.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "llvm/ADT/FoldingSet.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/TimeProfiler.h"
|
||||
#include <memory>
|
||||
|
||||
namespace clang::lifetimes {
|
||||
namespace internal {
|
||||
|
||||
LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter)
|
||||
: AC(AC), Reporter(Reporter) {}
|
||||
|
||||
void LifetimeSafetyAnalysis::run() {
|
||||
llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
|
||||
|
||||
const CFG &Cfg = *AC.getCFG();
|
||||
DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
|
||||
/*ShowColors=*/true));
|
||||
|
||||
FactsGenerator FactGen(FactMgr, AC);
|
||||
FactGen.run();
|
||||
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
|
||||
|
||||
/// TODO(opt): Consider optimizing individual blocks before running the
|
||||
/// dataflow analysis.
|
||||
/// 1. Expression Origins: These are assigned once and read at most once,
|
||||
/// forming simple chains. These chains can be compressed into a single
|
||||
/// assignment.
|
||||
/// 2. Block-Local Loans: Origins of expressions are never read by other
|
||||
/// blocks; only Decls are visible. Therefore, loans in a block that
|
||||
/// never reach an Origin associated with a Decl can be safely dropped by
|
||||
/// the analysis.
|
||||
/// 3. Collapse ExpireFacts belonging to same source location into a single
|
||||
/// Fact.
|
||||
LoanPropagation = std::make_unique<LoanPropagationAnalysis>(
|
||||
Cfg, AC, FactMgr, Factory.OriginMapFactory, Factory.LoanSetFactory);
|
||||
|
||||
LiveOrigins = std::make_unique<LiveOriginsAnalysis>(
|
||||
Cfg, AC, FactMgr, Factory.LivenessMapFactory);
|
||||
DEBUG_WITH_TYPE("LiveOrigins",
|
||||
LiveOrigins->dump(llvm::dbgs(), FactMgr.getTestPoints()));
|
||||
|
||||
runLifetimeChecker(*LoanPropagation, *LiveOrigins, FactMgr, AC, Reporter);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
|
||||
LifetimeSafetyReporter *Reporter) {
|
||||
internal::LifetimeSafetyAnalysis Analysis(AC, Reporter);
|
||||
Analysis.run();
|
||||
}
|
||||
} // namespace clang::lifetimes
|
||||
180
clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
Normal file
180
clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
//===- LiveOrigins.cpp - Live Origins Analysis -----------------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LiveOrigins.h"
|
||||
#include "Dataflow.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
namespace {
|
||||
|
||||
/// The dataflow lattice for origin liveness analysis.
|
||||
/// It tracks which origins are live, why they're live (which UseFact),
|
||||
/// and the confidence level of that liveness.
|
||||
struct Lattice {
|
||||
LivenessMap LiveOrigins;
|
||||
|
||||
Lattice() : LiveOrigins(nullptr) {};
|
||||
|
||||
explicit Lattice(LivenessMap L) : LiveOrigins(L) {}
|
||||
|
||||
bool operator==(const Lattice &Other) const {
|
||||
return LiveOrigins == Other.LiveOrigins;
|
||||
}
|
||||
|
||||
bool operator!=(const Lattice &Other) const { return !(*this == Other); }
|
||||
|
||||
void dump(llvm::raw_ostream &OS, const OriginManager &OM) const {
|
||||
if (LiveOrigins.isEmpty())
|
||||
OS << " <empty>\n";
|
||||
for (const auto &Entry : LiveOrigins) {
|
||||
OriginID OID = Entry.first;
|
||||
const LivenessInfo &Info = Entry.second;
|
||||
OS << " ";
|
||||
OM.dump(OID, OS);
|
||||
OS << " is ";
|
||||
switch (Info.Kind) {
|
||||
case LivenessKind::Must:
|
||||
OS << "definitely";
|
||||
break;
|
||||
case LivenessKind::Maybe:
|
||||
OS << "maybe";
|
||||
break;
|
||||
case LivenessKind::Dead:
|
||||
llvm_unreachable("liveness kind of live origins should not be dead.");
|
||||
}
|
||||
OS << " live at this point\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// The analysis that tracks which origins are live, with granular information
|
||||
/// about the causing use fact and confidence level. This is a backward
|
||||
/// analysis.
|
||||
class AnalysisImpl
|
||||
: public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Backward> {
|
||||
|
||||
public:
|
||||
AnalysisImpl(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
|
||||
LivenessMap::Factory &SF)
|
||||
: DataflowAnalysis(C, AC, F), FactMgr(F), Factory(SF) {}
|
||||
using DataflowAnalysis<AnalysisImpl, Lattice, Direction::Backward>::transfer;
|
||||
|
||||
StringRef getAnalysisName() const { return "LiveOrigins"; }
|
||||
|
||||
Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); }
|
||||
|
||||
/// Merges two lattices by combining liveness information.
|
||||
/// When the same origin has different confidence levels, we take the lower
|
||||
/// one.
|
||||
Lattice join(Lattice L1, Lattice L2) const {
|
||||
LivenessMap Merged = L1.LiveOrigins;
|
||||
// Take the earliest UseFact to make the join hermetic and commutative.
|
||||
auto CombineUseFact = [](const UseFact &A,
|
||||
const UseFact &B) -> const UseFact * {
|
||||
return A.getUseExpr()->getExprLoc() < B.getUseExpr()->getExprLoc() ? &A
|
||||
: &B;
|
||||
};
|
||||
auto CombineLivenessKind = [](LivenessKind K1,
|
||||
LivenessKind K2) -> LivenessKind {
|
||||
assert(K1 != LivenessKind::Dead && "LivenessKind should not be dead.");
|
||||
assert(K2 != LivenessKind::Dead && "LivenessKind should not be dead.");
|
||||
// Only return "Must" if both paths are "Must", otherwise Maybe.
|
||||
if (K1 == LivenessKind::Must && K2 == LivenessKind::Must)
|
||||
return LivenessKind::Must;
|
||||
return LivenessKind::Maybe;
|
||||
};
|
||||
auto CombineLivenessInfo = [&](const LivenessInfo *L1,
|
||||
const LivenessInfo *L2) -> LivenessInfo {
|
||||
assert((L1 || L2) && "unexpectedly merging 2 empty sets");
|
||||
if (!L1)
|
||||
return LivenessInfo(L2->CausingUseFact, LivenessKind::Maybe);
|
||||
if (!L2)
|
||||
return LivenessInfo(L1->CausingUseFact, LivenessKind::Maybe);
|
||||
return LivenessInfo(
|
||||
CombineUseFact(*L1->CausingUseFact, *L2->CausingUseFact),
|
||||
CombineLivenessKind(L1->Kind, L2->Kind));
|
||||
};
|
||||
return Lattice(utils::join(
|
||||
L1.LiveOrigins, L2.LiveOrigins, Factory, CombineLivenessInfo,
|
||||
// A symmetric join is required here. If an origin is live on one
|
||||
// branch but not the other, its confidence must be demoted to `Maybe`.
|
||||
utils::JoinKind::Symmetric));
|
||||
}
|
||||
|
||||
/// A read operation makes the origin live with definite confidence, as it
|
||||
/// dominates this program point. A write operation kills the liveness of
|
||||
/// the origin since it overwrites the value.
|
||||
Lattice transfer(Lattice In, const UseFact &UF) {
|
||||
OriginID OID = UF.getUsedOrigin(FactMgr.getOriginMgr());
|
||||
// Write kills liveness.
|
||||
if (UF.isWritten())
|
||||
return Lattice(Factory.remove(In.LiveOrigins, OID));
|
||||
// Read makes origin live with definite confidence (dominates this point).
|
||||
return Lattice(Factory.add(In.LiveOrigins, OID,
|
||||
LivenessInfo(&UF, LivenessKind::Must)));
|
||||
}
|
||||
|
||||
/// Issuing a new loan to an origin kills its liveness.
|
||||
Lattice transfer(Lattice In, const IssueFact &IF) {
|
||||
return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID()));
|
||||
}
|
||||
|
||||
/// An OriginFlow kills the liveness of the destination origin if `KillDest`
|
||||
/// is true. Otherwise, it propagates liveness from destination to source.
|
||||
Lattice transfer(Lattice In, const OriginFlowFact &OF) {
|
||||
if (!OF.getKillDest())
|
||||
return In;
|
||||
return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID()));
|
||||
}
|
||||
|
||||
LivenessMap getLiveOriginsAt(ProgramPoint P) const {
|
||||
return getState(P).LiveOrigins;
|
||||
}
|
||||
|
||||
// Dump liveness values on all test points in the program.
|
||||
void dump(llvm::raw_ostream &OS,
|
||||
llvm::StringMap<ProgramPoint> TestPoints) const {
|
||||
llvm::dbgs() << "==========================================\n";
|
||||
llvm::dbgs() << getAnalysisName() << " results:\n";
|
||||
llvm::dbgs() << "==========================================\n";
|
||||
for (const auto &Entry : TestPoints) {
|
||||
OS << "TestPoint: " << Entry.getKey() << "\n";
|
||||
getState(Entry.getValue()).dump(OS, FactMgr.getOriginMgr());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FactManager &FactMgr;
|
||||
LivenessMap::Factory &Factory;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// PImpl wrapper implementation
|
||||
class LiveOriginsAnalysis::Impl : public AnalysisImpl {
|
||||
using AnalysisImpl::AnalysisImpl;
|
||||
};
|
||||
|
||||
LiveOriginsAnalysis::LiveOriginsAnalysis(const CFG &C, AnalysisDeclContext &AC,
|
||||
FactManager &F,
|
||||
LivenessMap::Factory &SF)
|
||||
: PImpl(std::make_unique<Impl>(C, AC, F, SF)) {
|
||||
PImpl->run();
|
||||
}
|
||||
|
||||
LiveOriginsAnalysis::~LiveOriginsAnalysis() = default;
|
||||
|
||||
LivenessMap LiveOriginsAnalysis::getLiveOriginsAt(ProgramPoint P) const {
|
||||
return PImpl->getLiveOriginsAt(P);
|
||||
}
|
||||
|
||||
void LiveOriginsAnalysis::dump(llvm::raw_ostream &OS,
|
||||
llvm::StringMap<ProgramPoint> TestPoints) const {
|
||||
PImpl->dump(OS, TestPoints);
|
||||
}
|
||||
} // namespace clang::lifetimes::internal
|
||||
138
clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
Normal file
138
clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
//===- LoanPropagation.cpp - Loan Propagation Analysis ---------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h"
|
||||
#include "Dataflow.h"
|
||||
#include <memory>
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
namespace {
|
||||
/// Represents the dataflow lattice for loan propagation.
|
||||
///
|
||||
/// This lattice tracks which loans each origin may hold at a given program
|
||||
/// point.The lattice has a finite height: An origin's loan set is bounded by
|
||||
/// the total number of loans in the function.
|
||||
/// TODO(opt): To reduce the lattice size, propagate origins of declarations,
|
||||
/// not expressions, because expressions are not visible across blocks.
|
||||
struct Lattice {
|
||||
/// The map from an origin to the set of loans it contains.
|
||||
OriginLoanMap Origins = OriginLoanMap(nullptr);
|
||||
|
||||
explicit Lattice(const OriginLoanMap &S) : Origins(S) {}
|
||||
Lattice() = default;
|
||||
|
||||
bool operator==(const Lattice &Other) const {
|
||||
return Origins == Other.Origins;
|
||||
}
|
||||
bool operator!=(const Lattice &Other) const { return !(*this == Other); }
|
||||
|
||||
void dump(llvm::raw_ostream &OS) const {
|
||||
OS << "LoanPropagationLattice State:\n";
|
||||
if (Origins.isEmpty())
|
||||
OS << " <empty>\n";
|
||||
for (const auto &Entry : Origins) {
|
||||
if (Entry.second.isEmpty())
|
||||
OS << " Origin " << Entry.first << " contains no loans\n";
|
||||
for (const LoanID &LID : Entry.second)
|
||||
OS << " Origin " << Entry.first << " contains Loan " << LID << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AnalysisImpl
|
||||
: public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Forward> {
|
||||
public:
|
||||
AnalysisImpl(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
|
||||
OriginLoanMap::Factory &OriginLoanMapFactory,
|
||||
LoanSet::Factory &LoanSetFactory)
|
||||
: DataflowAnalysis(C, AC, F), OriginLoanMapFactory(OriginLoanMapFactory),
|
||||
LoanSetFactory(LoanSetFactory) {}
|
||||
|
||||
using Base::transfer;
|
||||
|
||||
StringRef getAnalysisName() const { return "LoanPropagation"; }
|
||||
|
||||
Lattice getInitialState() { return Lattice{}; }
|
||||
|
||||
/// Merges two lattices by taking the union of loans for each origin.
|
||||
// TODO(opt): Keep the state small by removing origins which become dead.
|
||||
Lattice join(Lattice A, Lattice B) {
|
||||
OriginLoanMap JoinedOrigins = utils::join(
|
||||
A.Origins, B.Origins, OriginLoanMapFactory,
|
||||
[&](const LoanSet *S1, const LoanSet *S2) {
|
||||
assert((S1 || S2) && "unexpectedly merging 2 empty sets");
|
||||
if (!S1)
|
||||
return *S2;
|
||||
if (!S2)
|
||||
return *S1;
|
||||
return utils::join(*S1, *S2, LoanSetFactory);
|
||||
},
|
||||
// Asymmetric join is a performance win. For origins present only on one
|
||||
// branch, the loan set can be carried over as-is.
|
||||
utils::JoinKind::Asymmetric);
|
||||
return Lattice(JoinedOrigins);
|
||||
}
|
||||
|
||||
/// A new loan is issued to the origin. Old loans are erased.
|
||||
Lattice transfer(Lattice In, const IssueFact &F) {
|
||||
OriginID OID = F.getOriginID();
|
||||
LoanID LID = F.getLoanID();
|
||||
return Lattice(OriginLoanMapFactory.add(
|
||||
In.Origins, OID,
|
||||
LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID)));
|
||||
}
|
||||
|
||||
/// A flow from source to destination. If `KillDest` is true, this replaces
|
||||
/// the destination's loans with the source's. Otherwise, the source's loans
|
||||
/// are merged into the destination's.
|
||||
Lattice transfer(Lattice In, const OriginFlowFact &F) {
|
||||
OriginID DestOID = F.getDestOriginID();
|
||||
OriginID SrcOID = F.getSrcOriginID();
|
||||
|
||||
LoanSet DestLoans =
|
||||
F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
|
||||
LoanSet SrcLoans = getLoans(In, SrcOID);
|
||||
LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
|
||||
|
||||
return Lattice(OriginLoanMapFactory.add(In.Origins, DestOID, MergedLoans));
|
||||
}
|
||||
|
||||
LoanSet getLoans(OriginID OID, ProgramPoint P) const {
|
||||
return getLoans(getState(P), OID);
|
||||
}
|
||||
|
||||
private:
|
||||
LoanSet getLoans(Lattice L, OriginID OID) const {
|
||||
if (auto *Loans = L.Origins.lookup(OID))
|
||||
return *Loans;
|
||||
return LoanSetFactory.getEmptySet();
|
||||
}
|
||||
|
||||
OriginLoanMap::Factory &OriginLoanMapFactory;
|
||||
LoanSet::Factory &LoanSetFactory;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class LoanPropagationAnalysis::Impl final : public AnalysisImpl {
|
||||
using AnalysisImpl::AnalysisImpl;
|
||||
};
|
||||
|
||||
LoanPropagationAnalysis::LoanPropagationAnalysis(
|
||||
const CFG &C, AnalysisDeclContext &AC, FactManager &F,
|
||||
OriginLoanMap::Factory &OriginLoanMapFactory,
|
||||
LoanSet::Factory &LoanSetFactory)
|
||||
: PImpl(std::make_unique<Impl>(C, AC, F, OriginLoanMapFactory,
|
||||
LoanSetFactory)) {
|
||||
PImpl->run();
|
||||
}
|
||||
|
||||
LoanPropagationAnalysis::~LoanPropagationAnalysis() = default;
|
||||
|
||||
LoanSet LoanPropagationAnalysis::getLoans(OriginID OID, ProgramPoint P) const {
|
||||
return PImpl->getLoans(OID, P);
|
||||
}
|
||||
} // namespace clang::lifetimes::internal
|
||||
18
clang/lib/Analysis/LifetimeSafety/Loans.cpp
Normal file
18
clang/lib/Analysis/LifetimeSafety/Loans.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
//===- Loans.cpp - Loan Implementation --------------------------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
void Loan::dump(llvm::raw_ostream &OS) const {
|
||||
OS << ID << " (Path: ";
|
||||
OS << Path.D->getNameAsString() << ")";
|
||||
}
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
89
clang/lib/Analysis/LifetimeSafety/Origins.cpp
Normal file
89
clang/lib/Analysis/LifetimeSafety/Origins.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
//===- Origins.cpp - Origin Implementation -----------------------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
|
||||
|
||||
namespace clang::lifetimes::internal {
|
||||
|
||||
void OriginManager::dump(OriginID OID, llvm::raw_ostream &OS) const {
|
||||
OS << OID << " (";
|
||||
Origin O = getOrigin(OID);
|
||||
if (const ValueDecl *VD = O.getDecl())
|
||||
OS << "Decl: " << VD->getNameAsString();
|
||||
else if (const Expr *E = O.getExpr())
|
||||
OS << "Expr: " << E->getStmtClassName();
|
||||
else
|
||||
OS << "Unknown";
|
||||
OS << ")";
|
||||
}
|
||||
|
||||
Origin &OriginManager::addOrigin(OriginID ID, const clang::ValueDecl &D) {
|
||||
AllOrigins.emplace_back(ID, &D);
|
||||
return AllOrigins.back();
|
||||
}
|
||||
|
||||
Origin &OriginManager::addOrigin(OriginID ID, const clang::Expr &E) {
|
||||
AllOrigins.emplace_back(ID, &E);
|
||||
return AllOrigins.back();
|
||||
}
|
||||
|
||||
// TODO: Mark this method as const once we remove the call to getOrCreate.
|
||||
OriginID OriginManager::get(const Expr &E) {
|
||||
auto It = ExprToOriginID.find(&E);
|
||||
if (It != ExprToOriginID.end())
|
||||
return It->second;
|
||||
// If the expression itself has no specific origin, and it's a reference
|
||||
// to a declaration, its origin is that of the declaration it refers to.
|
||||
// For pointer types, where we don't pre-emptively create an origin for the
|
||||
// DeclRefExpr itself.
|
||||
if (const auto *DRE = dyn_cast<DeclRefExpr>(&E))
|
||||
return get(*DRE->getDecl());
|
||||
// TODO: This should be an assert(It != ExprToOriginID.end()). The current
|
||||
// implementation falls back to getOrCreate to avoid crashing on
|
||||
// yet-unhandled pointer expressions, creating an empty origin for them.
|
||||
return getOrCreate(E);
|
||||
}
|
||||
|
||||
OriginID OriginManager::get(const ValueDecl &D) {
|
||||
auto It = DeclToOriginID.find(&D);
|
||||
// TODO: This should be an assert(It != DeclToOriginID.end()). The current
|
||||
// implementation falls back to getOrCreate to avoid crashing on
|
||||
// yet-unhandled pointer expressions, creating an empty origin for them.
|
||||
if (It == DeclToOriginID.end())
|
||||
return getOrCreate(D);
|
||||
|
||||
return It->second;
|
||||
}
|
||||
|
||||
OriginID OriginManager::getOrCreate(const Expr &E) {
|
||||
auto It = ExprToOriginID.find(&E);
|
||||
if (It != ExprToOriginID.end())
|
||||
return It->second;
|
||||
|
||||
OriginID NewID = getNextOriginID();
|
||||
addOrigin(NewID, E);
|
||||
ExprToOriginID[&E] = NewID;
|
||||
return NewID;
|
||||
}
|
||||
|
||||
const Origin &OriginManager::getOrigin(OriginID ID) const {
|
||||
assert(ID.Value < AllOrigins.size());
|
||||
return AllOrigins[ID.Value];
|
||||
}
|
||||
|
||||
OriginID OriginManager::getOrCreate(const ValueDecl &D) {
|
||||
auto It = DeclToOriginID.find(&D);
|
||||
if (It != DeclToOriginID.end())
|
||||
return It->second;
|
||||
OriginID NewID = getNextOriginID();
|
||||
addOrigin(NewID, D);
|
||||
DeclToOriginID[&D] = NewID;
|
||||
return NewID;
|
||||
}
|
||||
|
||||
} // namespace clang::lifetimes::internal
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
|
||||
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
|
||||
#include "clang/Analysis/Analyses/Consumed.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
|
||||
#include "clang/Analysis/Analyses/ReachableCode.h"
|
||||
#include "clang/Analysis/Analyses/ThreadSafety.h"
|
||||
#include "clang/Analysis/Analyses/UninitializedValues.h"
|
||||
|
||||
@@ -111,6 +111,7 @@ add_clang_library(clangSema
|
||||
clangAPINotes
|
||||
clangAST
|
||||
clangAnalysis
|
||||
clangAnalysisLifetimeSafety
|
||||
clangBasic
|
||||
clangEdit
|
||||
clangLex
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/Expr.h"
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
|
||||
#include "clang/Basic/DiagnosticSema.h"
|
||||
#include "clang/Sema/Initialization.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/AST/DeclObjC.h"
|
||||
#include "clang/AST/TypeLoc.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeAnnotations.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Lex/Lexer.h"
|
||||
#include "clang/Sema/SemaObjC.h"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety.h"
|
||||
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/ASTMatchers/ASTMatchers.h"
|
||||
#include "clang/Testing/TestAST.h"
|
||||
@@ -63,7 +63,7 @@ public:
|
||||
Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr);
|
||||
Analysis->run();
|
||||
|
||||
AnnotationToPointMap = Analysis->getTestPoints();
|
||||
AnnotationToPointMap = Analysis->getFactManager().getTestPoints();
|
||||
}
|
||||
|
||||
LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; }
|
||||
@@ -98,10 +98,11 @@ public:
|
||||
auto *VD = findDecl<ValueDecl>(VarName);
|
||||
if (!VD)
|
||||
return std::nullopt;
|
||||
auto OID = Analysis.getOriginIDForDecl(VD);
|
||||
if (!OID)
|
||||
ADD_FAILURE() << "Origin for '" << VarName << "' not found.";
|
||||
return OID;
|
||||
// This assumes the OriginManager's `get` can find an existing origin.
|
||||
// We might need a `find` method on OriginManager to avoid `getOrCreate`
|
||||
// logic in a const-query context if that becomes an issue.
|
||||
return const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr())
|
||||
.get(*VD);
|
||||
}
|
||||
|
||||
std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
|
||||
@@ -110,7 +111,10 @@ public:
|
||||
ADD_FAILURE() << "Failed to find VarDecl for '" << VarName << "'";
|
||||
return {};
|
||||
}
|
||||
std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD);
|
||||
std::vector<LoanID> LID;
|
||||
for (const Loan &L : Analysis.getFactManager().getLoanMgr().getLoans())
|
||||
if (L.Path.D == VD)
|
||||
LID.push_back(L.ID);
|
||||
if (LID.empty()) {
|
||||
ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
|
||||
return {};
|
||||
@@ -123,7 +127,7 @@ public:
|
||||
ProgramPoint PP = Runner.getProgramPoint(Annotation);
|
||||
if (!PP)
|
||||
return std::nullopt;
|
||||
return Analysis.getLoansAtPoint(OID, PP);
|
||||
return Analysis.getLoanPropagation().getLoans(OID, PP);
|
||||
}
|
||||
|
||||
std::optional<std::vector<std::pair<OriginID, LivenessKind>>>
|
||||
@@ -131,7 +135,10 @@ public:
|
||||
ProgramPoint PP = Runner.getProgramPoint(Annotation);
|
||||
if (!PP)
|
||||
return std::nullopt;
|
||||
return Analysis.getLiveOriginsAtPoint(PP);
|
||||
std::vector<std::pair<OriginID, LivenessKind>> Result;
|
||||
for (auto &[OID, Info] : Analysis.getLiveOrigins().getLiveOriginsAt(PP))
|
||||
Result.push_back({OID, Info.Kind});
|
||||
return Result;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#include "llvm/ADT/iterator.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
Reference in New Issue
Block a user