|
| 1 | +//===- LifetimeSafety.h - C++ Lifetime Safety Analysis -*----------- C++-*-===// |
| 2 | +// |
| 3 | +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | +// See https://llvm.org/LICENSE.txt for license information. |
| 5 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | +// |
| 7 | +//===----------------------------------------------------------------------===// |
| 8 | +// |
| 9 | +// This file defines the entry point for a dataflow-based static analysis |
| 10 | +// that checks for C++ lifetime violations. |
| 11 | +// |
| 12 | +// The analysis is based on the concepts of "origins" and "loans" to track |
| 13 | +// pointer lifetimes and detect issues like use-after-free and dangling |
| 14 | +// pointers. See the RFC for more details: |
| 15 | +// https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291 |
| 16 | +// |
| 17 | +//===----------------------------------------------------------------------===// |
| 18 | +#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H |
| 19 | +#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H |
| 20 | +#include "clang/Analysis/AnalysisDeclContext.h" |
| 21 | +#include "clang/Analysis/CFG.h" |
| 22 | +#include "clang/Basic/SourceLocation.h" |
| 23 | +#include "llvm/ADT/DenseMapInfo.h" |
| 24 | +#include "llvm/ADT/ImmutableMap.h" |
| 25 | +#include "llvm/ADT/ImmutableSet.h" |
| 26 | +#include "llvm/ADT/StringMap.h" |
| 27 | +#include <memory> |
| 28 | + |
| 29 | +namespace clang::lifetimes { |
| 30 | + |
| 31 | +/// Enum to track the confidence level of a potential error. |
| 32 | +enum class Confidence : uint8_t { |
| 33 | + None, |
| 34 | + Maybe, // Reported as a potential error (-Wlifetime-safety-strict) |
| 35 | + Definite // Reported as a definite error (-Wlifetime-safety-permissive) |
| 36 | +}; |
| 37 | + |
| 38 | +enum class LivenessKind : uint8_t { |
| 39 | + Dead, // Not alive |
| 40 | + Maybe, // Live on some path but not all paths (may-be-live) |
| 41 | + Must // Live on all paths (must-be-live) |
| 42 | +}; |
| 43 | + |
| 44 | +class LifetimeSafetyReporter { |
| 45 | +public: |
| 46 | + LifetimeSafetyReporter() = default; |
| 47 | + virtual ~LifetimeSafetyReporter() = default; |
| 48 | + |
| 49 | + virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, |
| 50 | + SourceLocation FreeLoc, |
| 51 | + Confidence Confidence) {} |
| 52 | +}; |
| 53 | + |
| 54 | +/// The main entry point for the analysis. |
| 55 | +void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC, |
| 56 | + LifetimeSafetyReporter *Reporter); |
| 57 | + |
| 58 | +namespace internal { |
| 59 | +// Forward declarations of internal types. |
| 60 | +class Fact; |
| 61 | +class FactManager; |
| 62 | +class LoanPropagationAnalysis; |
| 63 | +class ExpiredLoansAnalysis; |
| 64 | +class LiveOriginAnalysis; |
| 65 | +struct LifetimeFactory; |
| 66 | + |
| 67 | +/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type. |
| 68 | +/// Used for giving ID to loans and origins. |
| 69 | +template <typename Tag> struct ID { |
| 70 | + uint32_t Value = 0; |
| 71 | + |
| 72 | + bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; } |
| 73 | + bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); } |
| 74 | + bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; } |
| 75 | + ID<Tag> operator++(int) { |
| 76 | + ID<Tag> Tmp = *this; |
| 77 | + ++Value; |
| 78 | + return Tmp; |
| 79 | + } |
| 80 | + void Profile(llvm::FoldingSetNodeID &IDBuilder) const { |
| 81 | + IDBuilder.AddInteger(Value); |
| 82 | + } |
| 83 | +}; |
| 84 | + |
| 85 | +using LoanID = ID<struct LoanTag>; |
| 86 | +using OriginID = ID<struct OriginTag>; |
| 87 | +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) { |
| 88 | + return OS << ID.Value; |
| 89 | +} |
| 90 | +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) { |
| 91 | + return OS << ID.Value; |
| 92 | +} |
| 93 | + |
| 94 | +// Using LLVM's immutable collections is efficient for dataflow analysis |
| 95 | +// as it avoids deep copies during state transitions. |
| 96 | +// TODO(opt): Consider using a bitset to represent the set of loans. |
| 97 | +using LoanSet = llvm::ImmutableSet<LoanID>; |
| 98 | +using OriginSet = llvm::ImmutableSet<OriginID>; |
| 99 | +using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>; |
| 100 | + |
| 101 | +/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific |
| 102 | +/// `Fact`. identified by a lifetime-related event (`Fact`). |
| 103 | +/// |
| 104 | +/// A `ProgramPoint` has "after" semantics: it represents the location |
| 105 | +/// immediately after its corresponding `Fact`. |
| 106 | +using ProgramPoint = const Fact *; |
| 107 | + |
| 108 | +/// Running the lifetime safety analysis and querying its results. It |
| 109 | +/// encapsulates the various dataflow analyses. |
| 110 | +class LifetimeSafetyAnalysis { |
| 111 | +public: |
| 112 | + LifetimeSafetyAnalysis(AnalysisDeclContext &AC, |
| 113 | + LifetimeSafetyReporter *Reporter); |
| 114 | + ~LifetimeSafetyAnalysis(); |
| 115 | + |
| 116 | + void run(); |
| 117 | + |
| 118 | + /// Returns the set of loans an origin holds at a specific program point. |
| 119 | + LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const; |
| 120 | + |
| 121 | + /// Returns the set of origins that are live at a specific program point, |
| 122 | + /// along with the confidence level of their liveness. |
| 123 | + /// |
| 124 | + /// An origin is considered live if there are potential future uses of that |
| 125 | + /// origin after the given program point. The confidence level indicates |
| 126 | + /// whether the origin is definitely live (Definite) due to being domintated |
| 127 | + /// by a set of uses or only possibly live (Maybe) only on some but not all |
| 128 | + /// control flow paths. |
| 129 | + std::vector<std::pair<OriginID, LivenessKind>> |
| 130 | + getLiveOriginsAtPoint(ProgramPoint PP) const; |
| 131 | + |
| 132 | + /// Finds the OriginID for a given declaration. |
| 133 | + /// Returns a null optional if not found. |
| 134 | + std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const; |
| 135 | + |
| 136 | + /// Finds the LoanID's for the loan created with the specific variable as |
| 137 | + /// their Path. |
| 138 | + std::vector<LoanID> getLoanIDForVar(const VarDecl *VD) const; |
| 139 | + |
| 140 | + /// Retrieves program points that were specially marked in the source code |
| 141 | + /// for testing. |
| 142 | + /// |
| 143 | + /// The analysis recognizes special function calls of the form |
| 144 | + /// `void("__lifetime_test_point_<name>")` as test points. This method returns |
| 145 | + /// a map from the annotation string (<name>) to the corresponding |
| 146 | + /// `ProgramPoint`. This allows test harnesses to query the analysis state at |
| 147 | + /// user-defined locations in the code. |
| 148 | + /// \note This is intended for testing only. |
| 149 | + llvm::StringMap<ProgramPoint> getTestPoints() const; |
| 150 | + |
| 151 | +private: |
| 152 | + AnalysisDeclContext &AC; |
| 153 | + LifetimeSafetyReporter *Reporter; |
| 154 | + std::unique_ptr<LifetimeFactory> Factory; |
| 155 | + std::unique_ptr<FactManager> FactMgr; |
| 156 | + std::unique_ptr<LoanPropagationAnalysis> LoanPropagation; |
| 157 | + std::unique_ptr<LiveOriginAnalysis> LiveOrigins; |
| 158 | +}; |
| 159 | +} // namespace internal |
| 160 | +} // namespace clang::lifetimes |
| 161 | + |
| 162 | +namespace llvm { |
| 163 | +template <typename Tag> |
| 164 | +struct DenseMapInfo<clang::lifetimes::internal::ID<Tag>> { |
| 165 | + using ID = clang::lifetimes::internal::ID<Tag>; |
| 166 | + |
| 167 | + static inline ID getEmptyKey() { |
| 168 | + return {DenseMapInfo<uint32_t>::getEmptyKey()}; |
| 169 | + } |
| 170 | + |
| 171 | + static inline ID getTombstoneKey() { |
| 172 | + return {DenseMapInfo<uint32_t>::getTombstoneKey()}; |
| 173 | + } |
| 174 | + |
| 175 | + static unsigned getHashValue(const ID &Val) { |
| 176 | + return DenseMapInfo<uint32_t>::getHashValue(Val.Value); |
| 177 | + } |
| 178 | + |
| 179 | + static bool isEqual(const ID &LHS, const ID &RHS) { return LHS == RHS; } |
| 180 | +}; |
| 181 | +} // namespace llvm |
| 182 | + |
| 183 | +#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H |
0 commit comments