Skip to content

Commit db20651

Browse files
committed
Introduce virtual interface for lattices and remove dependency on llvm::Any.
This PR has 2 related goals: *Remove dependency on `llvm::Any`*. For some platforms, use of `llvm::Any` forces users to explicitly define internal details related to `Any`. Aside from aesthetics, this places an obscure requirement on lattice implementers. We prefer to use LLVM's (explicit) RTTI framework instead. *Introduce virtual interface for lattices*. Currently, we implicitly define the interface for lattices, because the interface to analyses is fully captured in templates. This PR moves to an explicit, virtual interface. We combine these two changes in a single PR because they are closely related: we use the new lattice interface as the basis of an open type hierarchy that embeds RTTI for safe interfacing with the dataflow engine. As a side-effect, we are able to greatly simplify the interface of `DataflowAnalysis` and collapse the distinction between it and `TypeErasedDataflowAnalysis`.
1 parent 11676da commit db20651

24 files changed

+451
-437
lines changed

clang/include/clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "clang/Analysis/FlowSensitive/Value.h"
2121
#include "llvm/ADT/DenseMap.h"
2222
#include "llvm/ADT/STLFunctionalExtras.h"
23+
#include "llvm/Support/ExtensibleRTTI.h"
2324

2425
namespace clang {
2526
namespace dataflow {
@@ -45,9 +46,15 @@ namespace dataflow {
4546
/// use(s.getFoo().value()); // unsafe (invalidate cache for s)
4647
/// }
4748
/// }
48-
template <typename Base> class CachedConstAccessorsLattice : public Base {
49+
template <typename Base>
50+
class CachedConstAccessorsLattice
51+
: public llvm::RTTIExtends<CachedConstAccessorsLattice<Base>, Base> {
4952
public:
50-
using Base::Base; // inherit all constructors
53+
using Parent = llvm::RTTIExtends<CachedConstAccessorsLattice, Base>;
54+
using Parent::Parent; // inherit all constructors
55+
56+
/// For RTTI.
57+
inline static char ID = 0;
5158

5259
/// Creates or returns a previously created `Value` associated with a const
5360
/// method call `obj.getFoo()` where `RecordLoc` is the
@@ -88,7 +95,16 @@ template <typename Base> class CachedConstAccessorsLattice : public Base {
8895
return Base::operator==(Other);
8996
}
9097

91-
LatticeJoinEffect join(const CachedConstAccessorsLattice &Other);
98+
std::unique_ptr<DataflowLattice> clone() override {
99+
return std::make_unique<CachedConstAccessorsLattice>(*this);
100+
}
101+
102+
bool isEqual(const DataflowLattice &Other) const override {
103+
return llvm::isa<const CachedConstAccessorsLattice>(Other) &&
104+
*this == llvm::cast<const CachedConstAccessorsLattice>(Other);
105+
}
106+
107+
LatticeEffect join(const DataflowLattice &Other) override;
92108

93109
private:
94110
// Maps a record storage location and const method to the value to return
@@ -145,10 +161,11 @@ joinConstMethodMap(
145161
} // namespace internal
146162

147163
template <typename Base>
148-
LatticeEffect CachedConstAccessorsLattice<Base>::join(
149-
const CachedConstAccessorsLattice<Base> &Other) {
164+
LatticeEffect
165+
CachedConstAccessorsLattice<Base>::join(const DataflowLattice &L) {
166+
LatticeEffect Effect = Base::join(L);
150167

151-
LatticeEffect Effect = Base::join(Other);
168+
const auto &Other = llvm::cast<const CachedConstAccessorsLattice<Base>>(L);
152169

153170
// For simplicity, we only retain values that are identical, but not ones that
154171
// are non-identical but equivalent. This is likely to be sufficient in

clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h

Lines changed: 6 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
#include <iterator>
1818
#include <optional>
19-
#include <type_traits>
2019
#include <utility>
2120
#include <vector>
2221

@@ -37,138 +36,6 @@
3736
namespace clang {
3837
namespace dataflow {
3938

40-
/// Base class template for dataflow analyses built on a single lattice type.
41-
///
42-
/// Requirements:
43-
///
44-
/// `Derived` must be derived from a specialization of this class template and
45-
/// must provide the following public members:
46-
/// * `LatticeT initialElement()` - returns a lattice element that models the
47-
/// initial state of a basic block;
48-
/// * `void transfer(const CFGElement &, LatticeT &, Environment &)` - applies
49-
/// the analysis transfer function for a given CFG element and lattice
50-
/// element.
51-
///
52-
/// `Derived` can optionally provide the following members:
53-
/// * `void transferBranch(bool Branch, const Stmt *Stmt, TypeErasedLattice &E,
54-
/// Environment &Env)` - applies the analysis transfer
55-
/// function for a given edge from a CFG block of a conditional statement.
56-
///
57-
/// `Derived` can optionally override the virtual functions in the
58-
/// `Environment::ValueModel` interface (which is an indirect base class of
59-
/// this class).
60-
///
61-
/// `LatticeT` is a bounded join-semilattice that is used by `Derived` and must
62-
/// provide the following public members:
63-
/// * `LatticeJoinEffect join(const LatticeT &)` - joins the object and the
64-
/// argument by computing their least upper bound, modifies the object if
65-
/// necessary, and returns an effect indicating whether any changes were
66-
/// made to it;
67-
/// FIXME: make it `static LatticeT join(const LatticeT&, const LatticeT&)`
68-
/// * `bool operator==(const LatticeT &) const` - returns true if and only if
69-
/// the object is equal to the argument.
70-
///
71-
/// `LatticeT` can optionally provide the following members:
72-
/// * `LatticeJoinEffect widen(const LatticeT &Previous)` - replaces the
73-
/// lattice element with an approximation that can reach a fixed point more
74-
/// quickly than iterated application of the transfer function alone. The
75-
/// previous value is provided to inform the choice of widened value. The
76-
/// function must also serve as a comparison operation, by indicating whether
77-
/// the widened value is equivalent to the previous value with the returned
78-
/// `LatticeJoinEffect`.
79-
template <typename Derived, typename LatticeT>
80-
class DataflowAnalysis : public TypeErasedDataflowAnalysis {
81-
public:
82-
/// Bounded join-semilattice that is used in the analysis.
83-
using Lattice = LatticeT;
84-
85-
explicit DataflowAnalysis(ASTContext &Context) : Context(Context) {}
86-
87-
explicit DataflowAnalysis(ASTContext &Context,
88-
DataflowAnalysisOptions Options)
89-
: TypeErasedDataflowAnalysis(Options), Context(Context) {}
90-
91-
ASTContext &getASTContext() final { return Context; }
92-
93-
TypeErasedLattice typeErasedInitialElement() final {
94-
return {static_cast<Derived *>(this)->initialElement()};
95-
}
96-
97-
TypeErasedLattice joinTypeErased(const TypeErasedLattice &E1,
98-
const TypeErasedLattice &E2) final {
99-
// FIXME: change the signature of join() to avoid copying here.
100-
Lattice L1 = llvm::any_cast<const Lattice &>(E1.Value);
101-
const Lattice &L2 = llvm::any_cast<const Lattice &>(E2.Value);
102-
L1.join(L2);
103-
return {std::move(L1)};
104-
}
105-
106-
LatticeJoinEffect widenTypeErased(TypeErasedLattice &Current,
107-
const TypeErasedLattice &Previous) final {
108-
Lattice &C = llvm::any_cast<Lattice &>(Current.Value);
109-
const Lattice &P = llvm::any_cast<const Lattice &>(Previous.Value);
110-
return widenInternal(Rank0{}, C, P);
111-
}
112-
113-
bool isEqualTypeErased(const TypeErasedLattice &E1,
114-
const TypeErasedLattice &E2) final {
115-
const Lattice &L1 = llvm::any_cast<const Lattice &>(E1.Value);
116-
const Lattice &L2 = llvm::any_cast<const Lattice &>(E2.Value);
117-
return L1 == L2;
118-
}
119-
120-
void transferTypeErased(const CFGElement &Element, TypeErasedLattice &E,
121-
Environment &Env) final {
122-
Lattice &L = llvm::any_cast<Lattice &>(E.Value);
123-
static_cast<Derived *>(this)->transfer(Element, L, Env);
124-
}
125-
126-
void transferBranchTypeErased(bool Branch, const Stmt *Stmt,
127-
TypeErasedLattice &E, Environment &Env) final {
128-
transferBranchInternal(Rank0{}, *static_cast<Derived *>(this), Branch, Stmt,
129-
E, Env);
130-
}
131-
132-
private:
133-
// These `Rank` structs are used for template metaprogramming to choose
134-
// between overloads.
135-
struct Rank1 {};
136-
struct Rank0 : Rank1 {};
137-
138-
// The first-choice implementation: use `widen` when it is available.
139-
template <typename T>
140-
static auto widenInternal(Rank0, T &Current, const T &Prev)
141-
-> decltype(Current.widen(Prev)) {
142-
return Current.widen(Prev);
143-
}
144-
145-
// The second-choice implementation: `widen` is unavailable. Widening is
146-
// merged with equality checking, so when widening is unimplemented, we
147-
// default to equality checking.
148-
static LatticeJoinEffect widenInternal(Rank1, const Lattice &Current,
149-
const Lattice &Prev) {
150-
return Prev == Current ? LatticeJoinEffect::Unchanged
151-
: LatticeJoinEffect::Changed;
152-
}
153-
154-
// The first-choice implementation: `transferBranch` is implemented.
155-
template <typename Analysis>
156-
static auto transferBranchInternal(Rank0, Analysis &A, bool Branch,
157-
const Stmt *Stmt, TypeErasedLattice &L,
158-
Environment &Env)
159-
-> std::void_t<decltype(A.transferBranch(
160-
Branch, Stmt, std::declval<LatticeT &>(), Env))> {
161-
A.transferBranch(Branch, Stmt, llvm::any_cast<Lattice &>(L.Value), Env);
162-
}
163-
164-
// The second-choice implementation: `transferBranch` is unimplemented. No-op.
165-
template <typename Analysis>
166-
static void transferBranchInternal(Rank1, Analysis &A, bool, const Stmt *,
167-
TypeErasedLattice &, Environment &) {}
168-
169-
ASTContext &Context;
170-
};
171-
17239
// Model of the program at a given program point.
17340
template <typename LatticeT> struct DataflowAnalysisState {
17441
// Model of a program property.
@@ -241,7 +108,7 @@ runDataflowAnalysis(const AdornedCFG &ACFG, AnalysisT &Analysis,
241108
[&PostAnalysisCallbacks](const CFGElement &Element,
242109
const TypeErasedDataflowAnalysisState &State) {
243110
auto *Lattice =
244-
llvm::any_cast<typename AnalysisT::Lattice>(&State.Lattice.Value);
111+
llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get());
245112
// FIXME: we should not be copying the environment here!
246113
// Ultimately the `CFGEltCallback` only gets a const reference anyway.
247114
PostAnalysisCallbacks.Before(
@@ -254,7 +121,7 @@ runDataflowAnalysis(const AdornedCFG &ACFG, AnalysisT &Analysis,
254121
[&PostAnalysisCallbacks](const CFGElement &Element,
255122
const TypeErasedDataflowAnalysisState &State) {
256123
auto *Lattice =
257-
llvm::any_cast<typename AnalysisT::Lattice>(&State.Lattice.Value);
124+
llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get());
258125
// FIXME: we should not be copying the environment here!
259126
// Ultimately the `CFGEltCallback` only gets a const reference anyway.
260127
PostAnalysisCallbacks.After(
@@ -278,8 +145,8 @@ runDataflowAnalysis(const AdornedCFG &ACFG, AnalysisT &Analysis,
278145
return llvm::transformOptional(
279146
std::move(OptState), [](TypeErasedDataflowAnalysisState &&State) {
280147
return DataflowAnalysisState<typename AnalysisT::Lattice>{
281-
llvm::any_cast<typename AnalysisT::Lattice>(
282-
std::move(State.Lattice.Value)),
148+
std::move(*llvm::cast<typename AnalysisT::Lattice>(
149+
State.Lattice.get())),
283150
std::move(State.Env)};
284151
});
285152
});
@@ -341,8 +208,7 @@ diagnoseFunction(const FunctionDecl &FuncDecl, ASTContext &ASTCtx,
341208
auto EltDiagnostics = Diagnoser.Before(
342209
Elt, ASTCtx,
343210
TransferStateForDiagnostics<typename AnalysisT::Lattice>(
344-
llvm::any_cast<const typename AnalysisT::Lattice &>(
345-
State.Lattice.Value),
211+
*llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get()),
346212
State.Env));
347213
llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
348214
};
@@ -355,8 +221,7 @@ diagnoseFunction(const FunctionDecl &FuncDecl, ASTContext &ASTCtx,
355221
auto EltDiagnostics = Diagnoser.After(
356222
Elt, ASTCtx,
357223
TransferStateForDiagnostics<typename AnalysisT::Lattice>(
358-
llvm::any_cast<const typename AnalysisT::Lattice &>(
359-
State.Lattice.Value),
224+
*llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get()),
360225
State.Env));
361226
llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
362227
};

clang/include/clang/Analysis/FlowSensitive/DataflowLattice.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWLATTICE_H
1515
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWLATTICE_H
1616

17+
#include "llvm/Support/Casting.h"
18+
#include "llvm/Support/ExtensibleRTTI.h"
19+
#include <memory>
20+
1721
namespace clang {
1822
namespace dataflow {
1923

@@ -25,6 +29,48 @@ enum class LatticeEffect {
2529
// DEPRECATED. Use `LatticeEffect`.
2630
using LatticeJoinEffect = LatticeEffect;
2731

32+
class DataflowLattice
33+
: public llvm::RTTIExtends<DataflowLattice, llvm::RTTIRoot> {
34+
public:
35+
/// For RTTI.
36+
inline static char ID = 0;
37+
38+
DataflowLattice() = default;
39+
40+
/// Joins the object with `Other` by computing their least upper bound,
41+
/// modifies the object if necessary, and returns an effect indicating whether
42+
/// any changes were made to it.
43+
virtual LatticeEffect join(const DataflowLattice &Other) = 0;
44+
45+
virtual std::unique_ptr<DataflowLattice> clone() = 0;
46+
47+
/// Replaces this lattice element with one that approximates it, given the
48+
/// previous element at the current program point. The approximation should
49+
/// be chosen so that the analysis can reach a fixed point more quickly than
50+
/// iterated application of the transfer function alone. The previous value is
51+
/// provided to inform the choice of widened value. The function must also
52+
/// serve as a comparison operation, by indicating whether the widened value
53+
/// is equivalent to the previous value with the returned `LatticeJoinEffect`.
54+
///
55+
/// Overriding `widen` is optional -- it is only needed to either accelerate
56+
/// convergence (for lattices with non-trivial height) or guarantee
57+
/// convergence (for lattices with infinite height). The default
58+
/// implementation simply checks for equality.
59+
///
60+
/// Returns an indication of whether any changes were made to the object. This
61+
/// saves a separate call to `isEqual` after the widening.
62+
virtual LatticeEffect widen(const DataflowLattice &Previous) {
63+
return isEqual(Previous) ? LatticeEffect::Unchanged
64+
: LatticeEffect::Changed;
65+
}
66+
67+
/// Returns true if and only if the two given type-erased lattice elements are
68+
/// equal.
69+
virtual bool isEqual(const DataflowLattice &) const = 0;
70+
};
71+
72+
using DataflowLatticePtr = std::unique_ptr<DataflowLattice>;
73+
2874
} // namespace dataflow
2975
} // namespace clang
3076

clang/include/clang/Analysis/FlowSensitive/Logger.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
namespace clang::dataflow {
1717
// Forward declarations so we can use Logger anywhere in the framework.
1818
class AdornedCFG;
19-
class TypeErasedDataflowAnalysis;
19+
class DataflowAnalysis;
2020
struct TypeErasedDataflowAnalysisState;
2121

2222
/// A logger is notified as the analysis progresses.
@@ -40,8 +40,7 @@ class Logger {
4040

4141
/// Called by the framework as we start analyzing a new function or statement.
4242
/// Forms a pair with endAnalysis().
43-
virtual void beginAnalysis(const AdornedCFG &, TypeErasedDataflowAnalysis &) {
44-
}
43+
virtual void beginAnalysis(const AdornedCFG &, DataflowAnalysis &) {}
4544
virtual void endAnalysis() {}
4645

4746
// At any time during the analysis, we're computing the state for some target

clang/include/clang/Analysis/FlowSensitive/MapLattice.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ namespace dataflow {
3636
///
3737
/// Requirements on `ElementLattice`:
3838
/// * Provides standard declarations of a bounded semi-lattice.
39-
template <typename Key, typename ElementLattice> class MapLattice {
39+
template <typename Key, typename ElementLattice>
40+
class MapLattice : public llvm::RTTIExtends<MapLattice<Key, ElementLattice>,
41+
DataflowLattice> {
4042
using Container = llvm::DenseMap<Key, ElementLattice>;
4143
Container C;
4244

4345
public:
46+
inline static char ID = 0;
47+
4448
using key_type = Key;
4549
using mapped_type = ElementLattice;
4650
using value_type = typename Container::value_type;
@@ -89,18 +93,28 @@ template <typename Key, typename ElementLattice> class MapLattice {
8993

9094
mapped_type &operator[](const key_type &K) { return C[K]; }
9195

96+
DataflowLatticePtr clone() override {
97+
return std::make_unique<MapLattice>(*this);
98+
}
99+
100+
bool isEqual(const DataflowLattice &Other) const override {
101+
return llvm::isa<const MapLattice>(Other) &&
102+
*this == llvm::cast<const MapLattice>(Other);
103+
}
104+
92105
/// If an entry exists in one map but not the other, the missing entry is
93106
/// treated as implicitly mapping to `bottom`. So, the joined map contains the
94107
/// entry as it was in the source map.
95-
LatticeJoinEffect join(const MapLattice &Other) {
96-
LatticeJoinEffect Effect = LatticeJoinEffect::Unchanged;
108+
LatticeEffect join(const DataflowLattice &L) override {
109+
const auto &Other = llvm::cast<MapLattice>(L);
110+
LatticeEffect Effect = LatticeEffect::Unchanged;
97111
for (const auto &O : Other.C) {
98112
auto It = C.find(O.first);
99113
if (It == C.end()) {
100114
C.insert(O);
101-
Effect = LatticeJoinEffect::Changed;
102-
} else if (It->second.join(O.second) == LatticeJoinEffect::Changed)
103-
Effect = LatticeJoinEffect::Changed;
115+
Effect = LatticeEffect::Changed;
116+
} else if (It->second.join(O.second) == LatticeEffect::Changed)
117+
Effect = LatticeEffect::Changed;
104118
}
105119
return Effect;
106120
}

0 commit comments

Comments
 (0)