[clang][dataflow] Extend transfer functions for other `CFGElement`s
authorWei Yi Tee <wyt@google.com>
Wed, 31 Aug 2022 08:41:32 +0000 (08:41 +0000)
committerWei Yi Tee <wyt@google.com>
Wed, 31 Aug 2022 10:23:53 +0000 (10:23 +0000)
Previously, the transfer function `void transfer(const Stmt *, ...)` overriden by users is restricted to apply only on `CFGStmt`s and its contained `Stmt`.

By using a transfer function (`void transfer(const CFGElement *, ...)`) that takes a `CFGElement` as input, this patch extends user-defined analysis to all kinds of `CFGElement`. For example, users can now handle `CFGInitializer`s where `CXXCtorInitializer` AST nodes are contained.

Reviewed By: gribozavr2, sgatev

Differential Revision: https://reviews.llvm.org/D131614

clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h
clang/include/clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h
clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp
clang/unittests/Analysis/FlowSensitive/SingleVarConstantPropagationTest.cpp
clang/unittests/Analysis/FlowSensitive/TestingSupport.h

index 4d1f524..4e084d5 100644 (file)
 #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_DATAFLOWANALYSIS_H
 
 #include <iterator>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Stmt.h"
+#include "clang/Analysis/CFG.h"
 #include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
 #include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
 namespace clang {
 namespace dataflow {
 
+template <typename AnalysisT, typename LatticeT, typename InputT,
+          typename = std::void_t<>>
+struct HasTransferFor : std::false_type {};
+
+template <typename AnalysisT, typename LatticeT, typename InputT>
+struct HasTransferFor<
+    AnalysisT, LatticeT, InputT,
+    std::void_t<decltype(std::declval<AnalysisT>().transfer(
+        std::declval<const InputT *>(), std::declval<LatticeT &>(),
+        std::declval<Environment &>()))>> : std::true_type {};
+
 /// Base class template for dataflow analyses built on a single lattice type.
 ///
 /// Requirements:
@@ -39,8 +52,9 @@ namespace dataflow {
 ///  must provide the following public members:
 ///   * `LatticeT initialElement()` - returns a lattice element that models the
 ///     initial state of a basic block;
-///   * `void transfer(const Stmt *, LatticeT &, Environment &)` - applies the
-///     analysis transfer function for a given statement and lattice element.
+///   * `void transfer(const CFGElement *, LatticeT &, Environment &)` - applies
+///     the analysis transfer function for a given CFG element and lattice
+///     element.
 ///
 ///  `Derived` can optionally override the following members:
 ///   * `bool merge(QualType, const Value &, const Value &, Value &,
@@ -93,10 +107,20 @@ public:
     return L1 == L2;
   }
 
-  void transferTypeErased(const Stmt *Stmt, TypeErasedLattice &E,
+  void transferTypeErased(const CFGElement *Element, TypeErasedLattice &E,
                           Environment &Env) final {
     Lattice &L = llvm::any_cast<Lattice &>(E.Value);
-    static_cast<Derived *>(this)->transfer(Stmt, L, Env);
+    if constexpr (HasTransferFor<Derived, LatticeT, CFGElement>::value) {
+      static_cast<Derived *>(this)->transfer(Element, L, Env);
+    }
+
+    // FIXME: Remove after users have been updated to implement `transfer` on
+    // `CFGElement`.
+    if constexpr (HasTransferFor<Derived, LatticeT, Stmt>::value) {
+      if (auto Stmt = Element->getAs<CFGStmt>()) {
+        static_cast<Derived *>(this)->transfer(Stmt->getStmt(), L, Env);
+      }
+    }
   }
 
 private:
@@ -112,37 +136,41 @@ template <typename LatticeT> struct DataflowAnalysisState {
   Environment Env;
 };
 
+// FIXME: Rename to `runDataflowAnalysis` after usages of the overload that
+// applies to `CFGStmt` have been replaced.
+//
 /// Performs dataflow analysis and returns a mapping from basic block IDs to
 /// dataflow analysis states that model the respective basic blocks. The
 /// returned vector, if any, will have the same size as the number of CFG
 /// blocks, with indices corresponding to basic block IDs. Returns an error if
 /// the dataflow analysis cannot be performed successfully. Otherwise, calls
-/// `PostVisitStmt` on each statement with the final analysis results at that
+/// `PostVisitCFG` on each CFG element with the final analysis results at that
 /// program point.
 template <typename AnalysisT>
 llvm::Expected<std::vector<
     llvm::Optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>>
-runDataflowAnalysis(
+runDataflowAnalysisOnCFG(
     const ControlFlowContext &CFCtx, AnalysisT &Analysis,
     const Environment &InitEnv,
-    std::function<void(const CFGStmt &, const DataflowAnalysisState<
-                                            typename AnalysisT::Lattice> &)>
-        PostVisitStmt = nullptr) {
-  std::function<void(const CFGStmt &, const TypeErasedDataflowAnalysisState &)>
-      PostVisitStmtClosure = nullptr;
-  if (PostVisitStmt != nullptr) {
-    PostVisitStmtClosure = [&PostVisitStmt](
-                               const CFGStmt &Stmt,
-                               const TypeErasedDataflowAnalysisState &State) {
+    std::function<void(const CFGElement &, const DataflowAnalysisState<
+                                               typename AnalysisT::Lattice> &)>
+        PostVisitCFG = nullptr) {
+  std::function<void(const CFGElement &,
+                     const TypeErasedDataflowAnalysisState &)>
+      PostVisitCFGClosure = nullptr;
+  if (PostVisitCFG) {
+    PostVisitCFGClosure = [&PostVisitCFG](
+                              const CFGElement &Element,
+                              const TypeErasedDataflowAnalysisState &State) {
       auto *Lattice =
           llvm::any_cast<typename AnalysisT::Lattice>(&State.Lattice.Value);
-      PostVisitStmt(Stmt, DataflowAnalysisState<typename AnalysisT::Lattice>{
-                              *Lattice, State.Env});
+      PostVisitCFG(Element, DataflowAnalysisState<typename AnalysisT::Lattice>{
+                                *Lattice, State.Env});
     };
   }
 
   auto TypeErasedBlockStates = runTypeErasedDataflowAnalysis(
-      CFCtx, Analysis, InitEnv, PostVisitStmtClosure);
+      CFCtx, Analysis, InitEnv, PostVisitCFGClosure);
   if (!TypeErasedBlockStates)
     return TypeErasedBlockStates.takeError();
 
@@ -163,6 +191,41 @@ runDataflowAnalysis(
   return BlockStates;
 }
 
+/// Deprecated. Use `runDataflowAnalysisOnCFG` instead.
+///
+/// Performs dataflow analysis and returns a mapping from basic block IDs to
+/// dataflow analysis states that model the respective basic blocks. The
+/// returned vector, if any, will have the same size as the number of CFG
+/// blocks, with indices corresponding to basic block IDs. Returns an error if
+/// the dataflow analysis cannot be performed successfully. Otherwise, calls
+/// `PostVisitStmt` on each statement with the final analysis results at that
+/// program point.
+template <typename AnalysisT>
+llvm::Expected<std::vector<
+    llvm::Optional<DataflowAnalysisState<typename AnalysisT::Lattice>>>>
+runDataflowAnalysis(
+    const ControlFlowContext &CFCtx, AnalysisT &Analysis,
+    const Environment &InitEnv,
+    std::function<void(const CFGStmt &, const DataflowAnalysisState<
+                                            typename AnalysisT::Lattice> &)>
+        PostVisitStmt = nullptr) {
+  std::function<void(
+      const CFGElement &,
+      const DataflowAnalysisState<typename AnalysisT::Lattice> &)>
+      PostVisitCFG = nullptr;
+  if (PostVisitStmt) {
+    PostVisitCFG =
+        [&PostVisitStmt](
+            const CFGElement &Element,
+            const DataflowAnalysisState<typename AnalysisT::Lattice> &State) {
+          if (auto Stmt = Element.getAs<CFGStmt>()) {
+            PostVisitStmt(*Stmt, State);
+          }
+        };
+  }
+  return runDataflowAnalysisOnCFG(CFCtx, Analysis, InitEnv, PostVisitCFG);
+}
+
 /// Abstract base class for dataflow "models": reusable analysis components that
 /// model a particular aspect of program semantics in the `Environment`. For
 /// example, a model may capture a type and its related functions.
index 58acda7..c7dccc3 100644 (file)
@@ -79,9 +79,9 @@ public:
   virtual bool isEqualTypeErased(const TypeErasedLattice &,
                                  const TypeErasedLattice &) = 0;
 
-  /// Applies the analysis transfer function for a given statement and
-  /// type-erased lattice element.
-  virtual void transferTypeErased(const Stmt *, TypeErasedLattice &,
+  /// Applies the analysis transfer function for a given control flow graph
+  /// element and type-erased lattice element.
+  virtual void transferTypeErased(const CFGElement *, TypeErasedLattice &,
                                   Environment &) = 0;
 
   /// If the built-in transfer functions (which model the heap and stack in the
@@ -104,10 +104,10 @@ struct TypeErasedDataflowAnalysisState {
       : Lattice(std::move(Lattice)), Env(std::move(Env)) {}
 };
 
-/// Transfers the state of a basic block by evaluating each of its statements in
+/// Transfers the state of a basic block by evaluating each of its elements in
 /// the context of `Analysis` and the states of its predecessors that are
-/// available in `BlockStates`. `HandleTransferredStmt` (if provided) will be
-/// applied to each statement in the block, after it is evaluated.
+/// available in `BlockStates`. `PostVisitCFG` (if provided) will be applied to
+/// each element in the block, after it is evaluated.
 ///
 /// Requirements:
 ///
@@ -119,23 +119,23 @@ TypeErasedDataflowAnalysisState transferBlock(
     llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates,
     const CFGBlock &Block, const Environment &InitEnv,
     TypeErasedDataflowAnalysis &Analysis,
-    std::function<void(const CFGStmt &,
+    std::function<void(const CFGElement &,
                        const TypeErasedDataflowAnalysisState &)>
-        HandleTransferredStmt = nullptr);
+        PostVisitCFG = nullptr);
 
 /// Performs dataflow analysis and returns a mapping from basic block IDs to
 /// dataflow analysis states that model the respective basic blocks. Indices of
 /// the returned vector correspond to basic block IDs. Returns an error if the
 /// dataflow analysis cannot be performed successfully. Otherwise, calls
-/// `PostVisitStmt` on each statement with the final analysis results at that
+/// `PostVisitCFG` on each CFG element with the final analysis results at that
 /// program point.
 llvm::Expected<std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>>
 runTypeErasedDataflowAnalysis(
     const ControlFlowContext &CFCtx, TypeErasedDataflowAnalysis &Analysis,
     const Environment &InitEnv,
-    std::function<void(const CFGStmt &,
+    std::function<void(const CFGElement &,
                        const TypeErasedDataflowAnalysisState &)>
-        PostVisitStmt = nullptr);
+        PostVisitCFG = nullptr);
 
 } // namespace dataflow
 } // namespace clang
index dc2ecef..b85a72a 100644 (file)
@@ -154,19 +154,37 @@ private:
   TransferOptions TransferOpts;
 };
 
+/// Holds data structures required for running dataflow analysis.
+struct AnalysisContext {
+  AnalysisContext(
+      const ControlFlowContext &CFCtx, TypeErasedDataflowAnalysis &Analysis,
+      const Environment &InitEnv,
+      llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>>
+          BlockStates)
+      : CFCtx(CFCtx), Analysis(Analysis), InitEnv(InitEnv),
+        BlockStates(BlockStates) {}
+
+  /// Contains the CFG being analyzed.
+  const ControlFlowContext &CFCtx;
+  /// The analysis to be run.
+  TypeErasedDataflowAnalysis &Analysis;
+  /// Initial state to start the analysis.
+  const Environment &InitEnv;
+  /// Stores the state of a CFG block if it has been evaluated by the analysis.
+  /// The indices correspond to the block IDs.
+  llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates;
+};
+
 /// Computes the input state for a given basic block by joining the output
 /// states of its predecessors.
 ///
 /// Requirements:
 ///
 ///   All predecessors of `Block` except those with loop back edges must have
-///   already been transferred. States in `BlockStates` that are set to
+///   already been transferred. States in `AC.BlockStates` that are set to
 ///   `llvm::None` represent basic blocks that are not evaluated yet.
-static TypeErasedDataflowAnalysisState computeBlockInputState(
-    const ControlFlowContext &CFCtx,
-    llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates,
-    const CFGBlock &Block, const Environment &InitEnv,
-    TypeErasedDataflowAnalysis &Analysis) {
+static TypeErasedDataflowAnalysisState
+computeBlockInputState(const CFGBlock &Block, AnalysisContext &AC) {
   llvm::DenseSet<const CFGBlock *> Preds;
   Preds.insert(Block.pred_begin(), Block.pred_end());
   if (Block.getTerminator().isTemporaryDtorsBranch()) {
@@ -193,13 +211,16 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
     //
     // See `NoreturnDestructorTest` for concrete examples.
     if (Block.succ_begin()->getReachableBlock()->hasNoReturnElement()) {
-      auto StmtBlock = CFCtx.getStmtToBlock().find(Block.getTerminatorStmt());
-      assert(StmtBlock != CFCtx.getStmtToBlock().end());
+      auto &StmtToBlock = AC.CFCtx.getStmtToBlock();
+      auto StmtBlock = StmtToBlock.find(Block.getTerminatorStmt());
+      assert(StmtBlock != StmtToBlock.end());
       Preds.erase(StmtBlock->getSecond());
     }
   }
 
   llvm::Optional<TypeErasedDataflowAnalysisState> MaybeState;
+
+  auto &Analysis = AC.Analysis;
   auto BuiltinTransferOpts = Analysis.builtinTransferOptions();
 
   for (const CFGBlock *Pred : Preds) {
@@ -210,14 +231,14 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
     // Skip if `Pred` was not evaluated yet. This could happen if `Pred` has a
     // loop back edge to `Block`.
     const llvm::Optional<TypeErasedDataflowAnalysisState> &MaybePredState =
-        BlockStates[Pred->getBlockID()];
+        AC.BlockStates[Pred->getBlockID()];
     if (!MaybePredState)
       continue;
 
     TypeErasedDataflowAnalysisState PredState = MaybePredState.value();
     if (BuiltinTransferOpts) {
       if (const Stmt *PredTerminatorStmt = Pred->getTerminatorStmt()) {
-        const StmtToEnvMapImpl StmtToEnv(CFCtx, BlockStates);
+        const StmtToEnvMapImpl StmtToEnv(AC.CFCtx, AC.BlockStates);
         TerminatorVisitor(StmtToEnv, PredState.Env,
                           blockIndexInPredecessor(*Pred, Block),
                           *BuiltinTransferOpts)
@@ -236,107 +257,125 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
     // FIXME: Consider passing `Block` to `Analysis.typeErasedInitialElement()`
     // to enable building analyses like computation of dominators that
     // initialize the state of each basic block differently.
-    MaybeState.emplace(Analysis.typeErasedInitialElement(), InitEnv);
+    MaybeState.emplace(Analysis.typeErasedInitialElement(), AC.InitEnv);
   }
   return *MaybeState;
 }
 
-/// Transfers `State` by evaluating `CfgStmt` in the context of `Analysis`.
-/// `HandleTransferredStmt` (if provided) will be applied to `CfgStmt`, after it
-/// is evaluated.
-static void transferCFGStmt(
-    const ControlFlowContext &CFCtx,
-    llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates,
-    const CFGStmt &CfgStmt, TypeErasedDataflowAnalysis &Analysis,
-    TypeErasedDataflowAnalysisState &State,
-    std::function<void(const CFGStmt &,
-                       const TypeErasedDataflowAnalysisState &)>
-        HandleTransferredStmt) {
-  const Stmt *S = CfgStmt.getStmt();
+/// Built-in transfer function for `CFGStmt`.
+void builtinTransferStatement(const CFGStmt &Elt,
+                              TypeErasedDataflowAnalysisState &InputState,
+                              AnalysisContext &AC) {
+  const Stmt *S = Elt.getStmt();
   assert(S != nullptr);
-
-  auto BuiltinTransferOpts = Analysis.builtinTransferOptions();
-  if (BuiltinTransferOpts)
-    transfer(StmtToEnvMapImpl(CFCtx, BlockStates), *S, State.Env,
-             *BuiltinTransferOpts);
-  Analysis.transferTypeErased(S, State.Lattice, State.Env);
-
-  if (HandleTransferredStmt != nullptr)
-    HandleTransferredStmt(CfgStmt, State);
+  transfer(StmtToEnvMapImpl(AC.CFCtx, AC.BlockStates), *S, InputState.Env,
+           *AC.Analysis.builtinTransferOptions());
 }
 
-/// Transfers `State` by evaluating `CfgInit`.
-static void transferCFGInitializer(const CFGInitializer &CfgInit,
-                                   TypeErasedDataflowAnalysisState &State) {
-  const auto &ThisLoc = *cast<AggregateStorageLocation>(
-      State.Env.getThisPointeeStorageLocation());
+/// Built-in transfer function for `CFGInitializer`.
+void builtinTransferInitializer(const CFGInitializer &Elt,
+                                TypeErasedDataflowAnalysisState &InputState) {
+  const CXXCtorInitializer *Init = Elt.getInitializer();
+  assert(Init != nullptr);
 
-  const CXXCtorInitializer *Initializer = CfgInit.getInitializer();
-  assert(Initializer != nullptr);
+  auto &Env = InputState.Env;
+  const auto &ThisLoc =
+      *cast<AggregateStorageLocation>(Env.getThisPointeeStorageLocation());
 
-  const FieldDecl *Member = Initializer->getMember();
+  const FieldDecl *Member = Init->getMember();
   if (Member == nullptr)
     // Not a field initializer.
     return;
 
-  auto *InitStmt = Initializer->getInit();
+  auto *InitStmt = Init->getInit();
   assert(InitStmt != nullptr);
 
-  auto *InitStmtLoc =
-      State.Env.getStorageLocation(*InitStmt, SkipPast::Reference);
+  auto *InitStmtLoc = Env.getStorageLocation(*InitStmt, SkipPast::Reference);
   if (InitStmtLoc == nullptr)
     return;
 
-  auto *InitStmtVal = State.Env.getValue(*InitStmtLoc);
+  auto *InitStmtVal = Env.getValue(*InitStmtLoc);
   if (InitStmtVal == nullptr)
     return;
 
   if (Member->getType()->isReferenceType()) {
     auto &MemberLoc = ThisLoc.getChild(*Member);
-    State.Env.setValue(MemberLoc,
-                       State.Env.takeOwnership(
-                           std::make_unique<ReferenceValue>(*InitStmtLoc)));
+    Env.setValue(MemberLoc, Env.takeOwnership(std::make_unique<ReferenceValue>(
+                                *InitStmtLoc)));
   } else {
     auto &MemberLoc = ThisLoc.getChild(*Member);
-    State.Env.setValue(MemberLoc, *InitStmtVal);
+    Env.setValue(MemberLoc, *InitStmtVal);
+  }
+}
+
+void builtinTransfer(const CFGElement &Elt,
+                     TypeErasedDataflowAnalysisState &State,
+                     AnalysisContext &AC) {
+  switch (Elt.getKind()) {
+  case CFGElement::Statement: {
+    builtinTransferStatement(Elt.castAs<CFGStmt>(), State, AC);
+    break;
+  }
+  case CFGElement::Initializer: {
+    builtinTransferInitializer(Elt.castAs<CFGInitializer>(), State);
+    break;
+  }
+  default:
+    // FIXME: Evaluate other kinds of `CFGElement`.
+    break;
   }
 }
 
+/// Transfers `State` by evaluating each element in the `Block` based on the
+/// `AC.Analysis` specified.
+///
+/// Built-in transfer functions (if the option for `ApplyBuiltinTransfer` is set
+/// by the analysis) will be applied to the element before evaluation by the
+/// user-specified analysis.
+/// `PostVisitCFG` (if provided) will be applied to the element after evaluation
+/// by the user-specified analysis.
+TypeErasedDataflowAnalysisState
+transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC,
+                 std::function<void(const CFGElement &,
+                                    const TypeErasedDataflowAnalysisState &)>
+                     PostVisitCFG = nullptr) {
+  auto State = computeBlockInputState(Block, AC);
+  for (const auto &Element : Block) {
+    // Built-in analysis
+    if (AC.Analysis.builtinTransferOptions()) {
+      builtinTransfer(Element, State, AC);
+    }
+
+    // User-provided analysis
+    AC.Analysis.transferTypeErased(&Element, State.Lattice, State.Env);
+
+    // Post processing
+    if (PostVisitCFG) {
+      PostVisitCFG(Element, State);
+    }
+  }
+  return State;
+}
+
 TypeErasedDataflowAnalysisState transferBlock(
     const ControlFlowContext &CFCtx,
     llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates,
     const CFGBlock &Block, const Environment &InitEnv,
     TypeErasedDataflowAnalysis &Analysis,
-    std::function<void(const CFGStmt &,
+    std::function<void(const CFGElement &,
                        const TypeErasedDataflowAnalysisState &)>
-        HandleTransferredStmt) {
-  TypeErasedDataflowAnalysisState State =
-      computeBlockInputState(CFCtx, BlockStates, Block, InitEnv, Analysis);
-  for (const CFGElement &Element : Block) {
-    switch (Element.getKind()) {
-    case CFGElement::Statement:
-      transferCFGStmt(CFCtx, BlockStates, *Element.getAs<CFGStmt>(), Analysis,
-                      State, HandleTransferredStmt);
-      break;
-    case CFGElement::Initializer:
-      if (Analysis.builtinTransferOptions())
-        transferCFGInitializer(*Element.getAs<CFGInitializer>(), State);
-      break;
-    default:
-      // FIXME: Evaluate other kinds of `CFGElement`.
-      break;
-    }
-  }
-  return State;
+        PostVisitCFG) {
+  AnalysisContext AC(CFCtx, Analysis, InitEnv, BlockStates);
+  return transferCFGBlock(Block, AC, PostVisitCFG);
 }
 
 llvm::Expected<std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>>
 runTypeErasedDataflowAnalysis(
     const ControlFlowContext &CFCtx, TypeErasedDataflowAnalysis &Analysis,
     const Environment &InitEnv,
-    std::function<void(const CFGStmt &,
+    std::function<void(const CFGElement &,
                        const TypeErasedDataflowAnalysisState &)>
-        PostVisitStmt) {
+        PostVisitCFG) {
   PostOrderCFGView POV(&CFCtx.getCFG());
   ForwardDataflowWorklist Worklist(CFCtx.getCFG(), &POV);
 
@@ -349,6 +388,8 @@ runTypeErasedDataflowAnalysis(
                                      InitEnv};
   Worklist.enqueueSuccessors(&Entry);
 
+  AnalysisContext AC(CFCtx, Analysis, InitEnv, BlockStates);
+
   // Bugs in lattices and transfer functions can prevent the analysis from
   // converging. To limit the damage (infinite loops) that these bugs can cause,
   // limit the number of iterations.
@@ -373,7 +414,7 @@ runTypeErasedDataflowAnalysis(
     const llvm::Optional<TypeErasedDataflowAnalysisState> &OldBlockState =
         BlockStates[Block->getBlockID()];
     TypeErasedDataflowAnalysisState NewBlockState =
-        transferBlock(CFCtx, BlockStates, *Block, InitEnv, Analysis);
+        transferCFGBlock(*Block, AC);
 
     if (OldBlockState &&
         Analysis.isEqualTypeErased(OldBlockState.value().Lattice,
@@ -395,14 +436,12 @@ runTypeErasedDataflowAnalysis(
   // FIXME: Consider evaluating unreachable basic blocks (those that have a
   // state set to `llvm::None` at this point) to also analyze dead code.
 
-  if (PostVisitStmt) {
+  if (PostVisitCFG) {
     for (const CFGBlock *Block : CFCtx.getCFG()) {
       // Skip blocks that were not evaluated.
       if (!BlockStates[Block->getBlockID()])
         continue;
-
-      transferBlock(CFCtx, BlockStates, *Block, InitEnv, Analysis,
-                    PostVisitStmt);
+      transferCFGBlock(*Block, AC, PostVisitCFG);
     }
   }
 
index 3c95ead..6649500 100644 (file)
@@ -194,8 +194,8 @@ void RunDataflow(llvm::StringRef Code, Matcher Expectations) {
           },
           [&Expectations](
               llvm::ArrayRef<std::pair<
-                  std::string, DataflowAnalysisState<
-                                    ConstantPropagationAnalysis::Lattice>>>
+                  std::string,
+                  DataflowAnalysisState<ConstantPropagationAnalysis::Lattice>>>
                   Results,
               ASTContext &) { EXPECT_THAT(Results, Expectations); },
           {"-fsyntax-only", "-std=c++17"}),
index f09d5f9..cdcf61e 100644 (file)
@@ -71,14 +71,25 @@ struct AnalysisData {
   std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates;
 };
 
+// FIXME: Rename to `checkDataflow` after usages of the overload that applies to
+// `CFGStmt` have been replaced.
+//
+/// Runs dataflow analysis (specified from `MakeAnalysis`) and the
+/// `PostVisitCFG` function (if provided) on the body of the function that
+/// matches `TargetFuncMatcher` in code snippet `Code`. `VerifyResults` checks
+/// that the results from the analysis are correct.
+///
+/// Requirements:
+///
+///  `AnalysisT` contains a type `Lattice`.
 template <typename AnalysisT>
-llvm::Error checkDataflow(
+llvm::Error checkDataflowOnCFG(
     llvm::StringRef Code,
     ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher,
     std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysis,
-    std::function<void(ASTContext &, const CFGStmt &,
+    std::function<void(ASTContext &, const CFGElement &,
                        const TypeErasedDataflowAnalysisState &)>
-        PostVisitStmt,
+        PostVisitCFG,
     std::function<void(AnalysisData)> VerifyResults, ArrayRef<std::string> Args,
     const tooling::FileContentMappings &VirtualMappedFiles = {}) {
   llvm::Annotations AnnotatedCode(Code);
@@ -112,13 +123,14 @@ llvm::Error checkDataflow(
   Environment Env(DACtx, *F);
   auto Analysis = MakeAnalysis(Context, Env);
 
-  std::function<void(const CFGStmt &, const TypeErasedDataflowAnalysisState &)>
-      PostVisitStmtClosure = nullptr;
-  if (PostVisitStmt != nullptr) {
-    PostVisitStmtClosure = [&PostVisitStmt, &Context](
-                               const CFGStmt &Stmt,
-                               const TypeErasedDataflowAnalysisState &State) {
-      PostVisitStmt(Context, Stmt, State);
+  std::function<void(const CFGElement &,
+                     const TypeErasedDataflowAnalysisState &)>
+      PostVisitCFGClosure = nullptr;
+  if (PostVisitCFG) {
+    PostVisitCFGClosure = [&PostVisitCFG, &Context](
+                              const CFGElement &Element,
+                              const TypeErasedDataflowAnalysisState &State) {
+      PostVisitCFG(Context, Element, State);
     };
   }
 
@@ -130,7 +142,7 @@ llvm::Error checkDataflow(
 
   llvm::Expected<std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>>>
       MaybeBlockStates = runTypeErasedDataflowAnalysis(*CFCtx, Analysis, Env,
-                                                       PostVisitStmtClosure);
+                                                       PostVisitCFGClosure);
   if (!MaybeBlockStates)
     return MaybeBlockStates.takeError();
   auto &BlockStates = *MaybeBlockStates;
@@ -141,6 +153,32 @@ llvm::Error checkDataflow(
   return llvm::Error::success();
 }
 
+template <typename AnalysisT>
+llvm::Error checkDataflow(
+    llvm::StringRef Code,
+    ast_matchers::internal::Matcher<FunctionDecl> TargetFuncMatcher,
+    std::function<AnalysisT(ASTContext &, Environment &)> MakeAnalysis,
+    std::function<void(ASTContext &, const CFGStmt &,
+                       const TypeErasedDataflowAnalysisState &)>
+        PostVisitStmt,
+    std::function<void(AnalysisData)> VerifyResults, ArrayRef<std::string> Args,
+    const tooling::FileContentMappings &VirtualMappedFiles = {}) {
+  std::function<void(ASTContext & Context, const CFGElement &,
+                     const TypeErasedDataflowAnalysisState &)>
+      PostVisitCFG = nullptr;
+  if (PostVisitStmt) {
+    PostVisitCFG =
+        [&PostVisitStmt](ASTContext &Context, const CFGElement &Element,
+                         const TypeErasedDataflowAnalysisState &State) {
+          if (auto Stmt = Element.getAs<CFGStmt>()) {
+            PostVisitStmt(Context, *Stmt, State);
+          }
+        };
+  }
+  return checkDataflowOnCFG(Code, TargetFuncMatcher, MakeAnalysis, PostVisitCFG,
+                            VerifyResults, Args, VirtualMappedFiles);
+}
+
 // Runs dataflow on the body of the function that matches `TargetFuncMatcher` in
 // code snippet `Code`. Requires: `AnalysisT` contains a type `Lattice`.
 template <typename AnalysisT>
@@ -157,9 +195,9 @@ llvm::Error checkDataflow(
     const tooling::FileContentMappings &VirtualMappedFiles = {}) {
   using StateT = DataflowAnalysisState<typename AnalysisT::Lattice>;
 
-  return checkDataflow(
+  return checkDataflowOnCFG(
       Code, std::move(TargetFuncMatcher), std::move(MakeAnalysis),
-      /*PostVisitStmt=*/nullptr,
+      /*PostVisitCFG=*/nullptr,
       [&VerifyResults](AnalysisData AnalysisData) {
         if (AnalysisData.BlockStates.empty()) {
           VerifyResults({}, AnalysisData.ASTCtx);
@@ -180,9 +218,13 @@ llvm::Error checkDataflow(
               AnalysisData.CFCtx, AnalysisData.BlockStates, *Block,
               AnalysisData.Env, AnalysisData.Analysis,
               [&Results,
-               &Annotations](const clang::CFGStmt &Stmt,
+               &Annotations](const clang::CFGElement &Element,
                              const TypeErasedDataflowAnalysisState &State) {
-                auto It = Annotations.find(Stmt.getStmt());
+                // FIXME: Extend testing annotations to non statement constructs
+                auto Stmt = Element.getAs<CFGStmt>();
+                if (!Stmt)
+                  return;
+                auto It = Annotations.find(Stmt->getStmt());
                 if (It == Annotations.end())
                   return;
                 auto *Lattice = llvm::any_cast<typename AnalysisT::Lattice>(