Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "clang/Analysis/FlowSensitive/Value.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/Support/ExtensibleRTTI.h"

namespace clang {
namespace dataflow {
Expand All @@ -45,9 +46,15 @@ namespace dataflow {
/// use(s.getFoo().value()); // unsafe (invalidate cache for s)
/// }
/// }
template <typename Base> class CachedConstAccessorsLattice : public Base {
template <typename Base>
class CachedConstAccessorsLattice
: public llvm::RTTIExtends<CachedConstAccessorsLattice<Base>, Base> {
public:
using Base::Base; // inherit all constructors
using Parent = llvm::RTTIExtends<CachedConstAccessorsLattice, Base>;
using Parent::Parent; // inherit all constructors

/// For RTTI.
inline static char ID = 0;

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

LatticeJoinEffect join(const CachedConstAccessorsLattice &Other);
std::unique_ptr<DataflowLattice> clone() override {
return std::make_unique<CachedConstAccessorsLattice>(*this);
}

bool isEqual(const DataflowLattice &Other) const override {
return llvm::isa<const CachedConstAccessorsLattice>(Other) &&
*this == llvm::cast<const CachedConstAccessorsLattice>(Other);
}

LatticeEffect join(const DataflowLattice &Other) override;

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

template <typename Base>
LatticeEffect CachedConstAccessorsLattice<Base>::join(
const CachedConstAccessorsLattice<Base> &Other) {
LatticeEffect
CachedConstAccessorsLattice<Base>::join(const DataflowLattice &L) {
LatticeEffect Effect = Base::join(L);

LatticeEffect Effect = Base::join(Other);
const auto &Other = llvm::cast<const CachedConstAccessorsLattice<Base>>(L);

// For simplicity, we only retain values that are identical, but not ones that
// are non-identical but equivalent. This is likely to be sufficient in
Expand Down
147 changes: 6 additions & 141 deletions clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

#include <iterator>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>

Expand All @@ -37,138 +36,6 @@
namespace clang {
namespace dataflow {

/// Base class template for dataflow analyses built on a single lattice type.
///
/// Requirements:
///
/// `Derived` must be derived from a specialization of this class template and
/// must provide the following public members:
/// * `LatticeT initialElement()` - returns a lattice element that models the
/// initial state of a basic block;
/// * `void transfer(const CFGElement &, LatticeT &, Environment &)` - applies
/// the analysis transfer function for a given CFG element and lattice
/// element.
///
/// `Derived` can optionally provide the following members:
/// * `void transferBranch(bool Branch, const Stmt *Stmt, TypeErasedLattice &E,
/// Environment &Env)` - applies the analysis transfer
/// function for a given edge from a CFG block of a conditional statement.
///
/// `Derived` can optionally override the virtual functions in the
/// `Environment::ValueModel` interface (which is an indirect base class of
/// this class).
///
/// `LatticeT` is a bounded join-semilattice that is used by `Derived` and must
/// provide the following public members:
/// * `LatticeJoinEffect join(const LatticeT &)` - joins the object and the
/// argument by computing their least upper bound, modifies the object if
/// necessary, and returns an effect indicating whether any changes were
/// made to it;
/// FIXME: make it `static LatticeT join(const LatticeT&, const LatticeT&)`
/// * `bool operator==(const LatticeT &) const` - returns true if and only if
/// the object is equal to the argument.
///
/// `LatticeT` can optionally provide the following members:
/// * `LatticeJoinEffect widen(const LatticeT &Previous)` - replaces the
/// lattice element with an approximation that can reach a fixed point more
/// quickly than iterated application of the transfer function alone. The
/// previous value is provided to inform the choice of widened value. The
/// function must also serve as a comparison operation, by indicating whether
/// the widened value is equivalent to the previous value with the returned
/// `LatticeJoinEffect`.
template <typename Derived, typename LatticeT>
class DataflowAnalysis : public TypeErasedDataflowAnalysis {
public:
/// Bounded join-semilattice that is used in the analysis.
using Lattice = LatticeT;

explicit DataflowAnalysis(ASTContext &Context) : Context(Context) {}

explicit DataflowAnalysis(ASTContext &Context,
DataflowAnalysisOptions Options)
: TypeErasedDataflowAnalysis(Options), Context(Context) {}

ASTContext &getASTContext() final { return Context; }

TypeErasedLattice typeErasedInitialElement() final {
return {static_cast<Derived *>(this)->initialElement()};
}

TypeErasedLattice joinTypeErased(const TypeErasedLattice &E1,
const TypeErasedLattice &E2) final {
// FIXME: change the signature of join() to avoid copying here.
Lattice L1 = llvm::any_cast<const Lattice &>(E1.Value);
const Lattice &L2 = llvm::any_cast<const Lattice &>(E2.Value);
L1.join(L2);
return {std::move(L1)};
}

LatticeJoinEffect widenTypeErased(TypeErasedLattice &Current,
const TypeErasedLattice &Previous) final {
Lattice &C = llvm::any_cast<Lattice &>(Current.Value);
const Lattice &P = llvm::any_cast<const Lattice &>(Previous.Value);
return widenInternal(Rank0{}, C, P);
}

bool isEqualTypeErased(const TypeErasedLattice &E1,
const TypeErasedLattice &E2) final {
const Lattice &L1 = llvm::any_cast<const Lattice &>(E1.Value);
const Lattice &L2 = llvm::any_cast<const Lattice &>(E2.Value);
return L1 == L2;
}

void transferTypeErased(const CFGElement &Element, TypeErasedLattice &E,
Environment &Env) final {
Lattice &L = llvm::any_cast<Lattice &>(E.Value);
static_cast<Derived *>(this)->transfer(Element, L, Env);
}

void transferBranchTypeErased(bool Branch, const Stmt *Stmt,
TypeErasedLattice &E, Environment &Env) final {
transferBranchInternal(Rank0{}, *static_cast<Derived *>(this), Branch, Stmt,
E, Env);
}

private:
// These `Rank` structs are used for template metaprogramming to choose
// between overloads.
struct Rank1 {};
struct Rank0 : Rank1 {};

// The first-choice implementation: use `widen` when it is available.
template <typename T>
static auto widenInternal(Rank0, T &Current, const T &Prev)
-> decltype(Current.widen(Prev)) {
return Current.widen(Prev);
}

// The second-choice implementation: `widen` is unavailable. Widening is
// merged with equality checking, so when widening is unimplemented, we
// default to equality checking.
static LatticeJoinEffect widenInternal(Rank1, const Lattice &Current,
const Lattice &Prev) {
return Prev == Current ? LatticeJoinEffect::Unchanged
: LatticeJoinEffect::Changed;
}

// The first-choice implementation: `transferBranch` is implemented.
template <typename Analysis>
static auto transferBranchInternal(Rank0, Analysis &A, bool Branch,
const Stmt *Stmt, TypeErasedLattice &L,
Environment &Env)
-> std::void_t<decltype(A.transferBranch(
Branch, Stmt, std::declval<LatticeT &>(), Env))> {
A.transferBranch(Branch, Stmt, llvm::any_cast<Lattice &>(L.Value), Env);
}

// The second-choice implementation: `transferBranch` is unimplemented. No-op.
template <typename Analysis>
static void transferBranchInternal(Rank1, Analysis &A, bool, const Stmt *,
TypeErasedLattice &, Environment &) {}

ASTContext &Context;
};

// Model of the program at a given program point.
template <typename LatticeT> struct DataflowAnalysisState {
// Model of a program property.
Expand Down Expand Up @@ -241,7 +108,7 @@ runDataflowAnalysis(const AdornedCFG &ACFG, AnalysisT &Analysis,
[&PostAnalysisCallbacks](const CFGElement &Element,
const TypeErasedDataflowAnalysisState &State) {
auto *Lattice =
llvm::any_cast<typename AnalysisT::Lattice>(&State.Lattice.Value);
llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get());
// FIXME: we should not be copying the environment here!
// Ultimately the `CFGEltCallback` only gets a const reference anyway.
PostAnalysisCallbacks.Before(
Expand All @@ -254,7 +121,7 @@ runDataflowAnalysis(const AdornedCFG &ACFG, AnalysisT &Analysis,
[&PostAnalysisCallbacks](const CFGElement &Element,
const TypeErasedDataflowAnalysisState &State) {
auto *Lattice =
llvm::any_cast<typename AnalysisT::Lattice>(&State.Lattice.Value);
llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get());
// FIXME: we should not be copying the environment here!
// Ultimately the `CFGEltCallback` only gets a const reference anyway.
PostAnalysisCallbacks.After(
Expand All @@ -278,8 +145,8 @@ runDataflowAnalysis(const AdornedCFG &ACFG, AnalysisT &Analysis,
return llvm::transformOptional(
std::move(OptState), [](TypeErasedDataflowAnalysisState &&State) {
return DataflowAnalysisState<typename AnalysisT::Lattice>{
llvm::any_cast<typename AnalysisT::Lattice>(
std::move(State.Lattice.Value)),
std::move(*llvm::cast<typename AnalysisT::Lattice>(
State.Lattice.get())),
std::move(State.Env)};
});
});
Expand Down Expand Up @@ -341,8 +208,7 @@ diagnoseFunction(const FunctionDecl &FuncDecl, ASTContext &ASTCtx,
auto EltDiagnostics = Diagnoser.Before(
Elt, ASTCtx,
TransferStateForDiagnostics<typename AnalysisT::Lattice>(
llvm::any_cast<const typename AnalysisT::Lattice &>(
State.Lattice.Value),
*llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get()),
State.Env));
llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
};
Expand All @@ -355,8 +221,7 @@ diagnoseFunction(const FunctionDecl &FuncDecl, ASTContext &ASTCtx,
auto EltDiagnostics = Diagnoser.After(
Elt, ASTCtx,
TransferStateForDiagnostics<typename AnalysisT::Lattice>(
llvm::any_cast<const typename AnalysisT::Lattice &>(
State.Lattice.Value),
*llvm::cast<typename AnalysisT::Lattice>(State.Lattice.get()),
State.Env));
llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
};
Expand Down
46 changes: 46 additions & 0 deletions clang/include/clang/Analysis/FlowSensitive/DataflowLattice.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWLATTICE_H
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWLATTICE_H

#include "llvm/Support/Casting.h"
#include "llvm/Support/ExtensibleRTTI.h"
#include <memory>

namespace clang {
namespace dataflow {

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

class DataflowLattice
: public llvm::RTTIExtends<DataflowLattice, llvm::RTTIRoot> {
public:
/// For RTTI.
inline static char ID = 0;

DataflowLattice() = default;

/// Joins the object with `Other` by computing their least upper bound,
/// modifies the object if necessary, and returns an effect indicating whether
/// any changes were made to it.
virtual LatticeEffect join(const DataflowLattice &Other) = 0;

virtual std::unique_ptr<DataflowLattice> clone() = 0;

/// Replaces this lattice element with one that approximates it, given the
/// previous element at the current program point. The approximation should
/// be chosen so that the analysis can reach a fixed point more quickly than
/// iterated application of the transfer function alone. The previous value is
/// provided to inform the choice of widened value. The function must also
/// serve as a comparison operation, by indicating whether the widened value
/// is equivalent to the previous value with the returned `LatticeJoinEffect`.
///
/// Overriding `widen` is optional -- it is only needed to either accelerate
/// convergence (for lattices with non-trivial height) or guarantee
/// convergence (for lattices with infinite height). The default
/// implementation simply checks for equality.
///
/// Returns an indication of whether any changes were made to the object. This
/// saves a separate call to `isEqual` after the widening.
virtual LatticeEffect widen(const DataflowLattice &Previous) {
return isEqual(Previous) ? LatticeEffect::Unchanged
: LatticeEffect::Changed;
}

/// Returns true if and only if the two given type-erased lattice elements are
/// equal.
virtual bool isEqual(const DataflowLattice &) const = 0;
};

using DataflowLatticePtr = std::unique_ptr<DataflowLattice>;

} // namespace dataflow
} // namespace clang

Expand Down
5 changes: 2 additions & 3 deletions clang/include/clang/Analysis/FlowSensitive/Logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
namespace clang::dataflow {
// Forward declarations so we can use Logger anywhere in the framework.
class AdornedCFG;
class TypeErasedDataflowAnalysis;
class DataflowAnalysis;
struct TypeErasedDataflowAnalysisState;

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

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

// At any time during the analysis, we're computing the state for some target
Expand Down
26 changes: 20 additions & 6 deletions clang/include/clang/Analysis/FlowSensitive/MapLattice.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@ namespace dataflow {
///
/// Requirements on `ElementLattice`:
/// * Provides standard declarations of a bounded semi-lattice.
template <typename Key, typename ElementLattice> class MapLattice {
template <typename Key, typename ElementLattice>
class MapLattice : public llvm::RTTIExtends<MapLattice<Key, ElementLattice>,
DataflowLattice> {
using Container = llvm::DenseMap<Key, ElementLattice>;
Container C;

public:
inline static char ID = 0;

using key_type = Key;
using mapped_type = ElementLattice;
using value_type = typename Container::value_type;
Expand Down Expand Up @@ -89,18 +93,28 @@ template <typename Key, typename ElementLattice> class MapLattice {

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

DataflowLatticePtr clone() override {
return std::make_unique<MapLattice>(*this);
}

bool isEqual(const DataflowLattice &Other) const override {
return llvm::isa<const MapLattice>(Other) &&
*this == llvm::cast<const MapLattice>(Other);
}

/// If an entry exists in one map but not the other, the missing entry is
/// treated as implicitly mapping to `bottom`. So, the joined map contains the
/// entry as it was in the source map.
LatticeJoinEffect join(const MapLattice &Other) {
LatticeJoinEffect Effect = LatticeJoinEffect::Unchanged;
LatticeEffect join(const DataflowLattice &L) override {
const auto &Other = llvm::cast<MapLattice>(L);
LatticeEffect Effect = LatticeEffect::Unchanged;
for (const auto &O : Other.C) {
auto It = C.find(O.first);
if (It == C.end()) {
C.insert(O);
Effect = LatticeJoinEffect::Changed;
} else if (It->second.join(O.second) == LatticeJoinEffect::Changed)
Effect = LatticeJoinEffect::Changed;
Effect = LatticeEffect::Changed;
} else if (It->second.join(O.second) == LatticeEffect::Changed)
Effect = LatticeEffect::Changed;
}
return Effect;
}
Expand Down
Loading
Loading