mboehme created this revision.
Herald added subscribers: martong, xazax.hun.
Herald added a reviewer: NoQ.
Herald added a project: All.
mboehme requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

The locations of some glvalue expressions are discarded or are merely used in
a `LValueToRValue` cast. However, the corresponding entries in `ExprToLoc` can
currently prevent convergence.

As the framework itself does not query these locations, and a particular
analysis is unlikely to query them either, we ignore them for the purpose of
determining whether a block has converged (in `Environment::equivalentTo()`).

This patch adds two tests that do not converge without the other changes in the
patch.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D156856

Files:
  clang/include/clang/Analysis/FlowSensitive/ControlFlowContext.h
  clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
  clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp
  clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
  clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Index: clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
===================================================================
--- clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
+++ clang/unittests/Analysis/FlowSensitive/TransferTest.cpp
@@ -3836,6 +3836,68 @@
       });
 }
 
+TEST(TransferTest, LoopDereferencingChangingPointerConverges) {
+  std::string Code = R"cc(
+    bool some_condition();
+
+    void target(int i1, int i2) {
+      int *p = &i1;
+      while (true) {
+        // Dereference the pointer, producing an lvalue, in various ways where
+        // the location of the lvalue is not important.
+        *p;
+        (void)*p;
+        int unused = *p;
+
+        if (some_condition())
+          p = &i1;
+        else
+          p = &i2;
+      }
+    }
+  )cc";
+  // The key property that we are verifying is implicit in `runDataflow` --
+  // namely, that the analysis succeeds, rather than hitting the maximum number
+  // of iterations.
+  runDataflow(
+      Code,
+      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+         ASTContext &ASTCtx) {});
+}
+
+TEST(TransferTest, LoopDereferencingChangingRecordPointerConverges) {
+  std::string Code = R"cc(
+    struct Lookup {
+      int x;
+    };
+
+    bool some_condition();
+
+    void target(Lookup l1, Lookup l2) {
+      Lookup *l = &l1;
+      while (true) {
+        // Access the member variable, producing an lvalue, in various ways
+        // where the location of the lvalue is not important.
+        l->x;
+        (void)l->x;
+        int unused = l->x;
+
+        if (some_condition())
+          l = &l1;
+        else
+          l = &l2;
+      }
+    }
+  )cc";
+  // The key property that we are verifying is implicit in `runDataflow` --
+  // namely, that the analysis succeeds, rather than hitting the maximum number
+  // of iterations.
+  runDataflow(
+      Code,
+      [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+         ASTContext &ASTCtx) {});
+}
+
 TEST(TransferTest, DoesNotCrashOnUnionThisExpr) {
   std::string Code = R"(
     union Union {
Index: clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
===================================================================
--- clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -414,6 +414,87 @@
   }
 }
 
+/// Returns whether locations `Loc1` and `Loc2` associated with expression `E`
+/// in two different environments `Env1` and `Env2` should be considered
+/// equivalent.
+/// The idea is that the location of certain expressions is almost certainly not
+/// relevant for the result of the block, but changes in these locations may
+/// nevertheless hinder convergence.
+static bool exprLocsEquivalent(const Expr *E, StorageLocation *Loc1,
+                               StorageLocation *Loc2, const Environment &Env1,
+                               const Environment &Env2,
+                               Environment::ValueModel &Model,
+                               const ControlFlowContext *CFCtx) {
+  // prvalues have stable storage locations, so we don't need to compare them.
+  if (E->isPRValue())
+    return true;
+
+  // If we have no `ControlFlowContext`, we can't determine the parent of `E`,
+  // so we have to assume that location matters.
+  if (CFCtx == nullptr)
+    return Loc1 == Loc2;
+
+  const Stmt *Parent = CFCtx->getParent(E);
+  // An expression should always have a parent within the body of the function.
+  assert(Parent != nullptr);
+  if (Parent == nullptr)
+    return Loc1 == Loc2;
+
+  // If this an expression statement, the location is discarded.
+  if (isa<CompoundStmt>(Parent))
+    return true;
+
+  if (auto *Cast = dyn_cast<CastExpr>(Parent)) {
+    // If the expression is cast to void, its location is discarded.
+    if (Cast->getCastKind() == CK_ToVoid)
+      return true;
+
+    // If the expression is cast from an lvalue to an rvalue, its location is
+    // unlikely to matter. Instead, just compare the values.
+    if (Cast->getCastKind() == CK_LValueToRValue) {
+      if (Loc1 == nullptr || Loc2 == nullptr)
+          return Loc1 == Loc2;
+
+      Value *Val1 = Env1.getValue(*Loc1);
+      Value *Val2 = Env2.getValue(*Loc2);
+
+      if (Val1 == nullptr || Val2 == nullptr)
+          return Val1 == Val2;
+
+      return areEquivalentValues(*Val1, *Val2) ||
+             compareDistinctValues(E->getType(), *Val1, Env1, *Val2, Env2,
+                                   Model);
+    }
+  }
+
+  return Loc1 == Loc2;
+}
+
+/// Returns whether two `ExprToLoc` maps should be considered equivalent. The
+/// comparison ignores certain classes of expressions; see comments in
+/// `exprLocsEquivalent()`.
+static bool exprToLocEquivalent(
+    const llvm::DenseMap<const Expr *, StorageLocation *> &ExprToLoc1,
+    const llvm::DenseMap<const Expr *, StorageLocation *> &ExprToLoc2,
+    const Environment &Env1, const Environment &Env2,
+    Environment::ValueModel &Model) {
+  if (ExprToLoc1.size() != ExprToLoc2.size())
+    return false;
+
+  const ControlFlowContext *CFCtx = nullptr;
+  if (const FunctionDecl *Func = Env1.getCurrentFunc())
+    CFCtx = Env1.getDataflowAnalysisContext().getControlFlowContext(Func);
+
+  for (auto [E, Loc1] : ExprToLoc1) {
+    StorageLocation *Loc2 = ExprToLoc2.lookup(E);
+
+    if (!exprLocsEquivalent(E, Loc1, Loc2, Env1, Env2, Model, CFCtx))
+      return false;
+  }
+
+  return true;
+}
+
 bool Environment::equivalentTo(const Environment &Other,
                                Environment::ValueModel &Model) const {
   assert(DACtx == Other.DACtx);
@@ -430,7 +511,7 @@
   if (DeclToLoc != Other.DeclToLoc)
     return false;
 
-  if (ExprToLoc != Other.ExprToLoc)
+  if (!exprToLocEquivalent(ExprToLoc, Other.ExprToLoc, *this, Other, Model))
     return false;
 
   // Compare the contents for the intersection of their domains.
Index: clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp
===================================================================
--- clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp
+++ clang/lib/Analysis/FlowSensitive/ControlFlowContext.cpp
@@ -67,6 +67,27 @@
   return BlockReachable;
 }
 
+static llvm::DenseMap<const Stmt *, const Stmt *>
+buildStmtToParentMap(const Stmt &S) {
+  llvm::DenseMap<const Stmt *, const Stmt *> Result;
+
+  llvm::SmallVector<const Stmt *> StmtsToVisit = {&S};
+  while (!StmtsToVisit.empty()) {
+    const Stmt *Parent = StmtsToVisit.back();
+    StmtsToVisit.pop_back();
+
+    for (const Stmt *Child : Parent->children()) {
+      if (Child == nullptr)
+        continue;
+
+      StmtsToVisit.push_back(Child);
+      Result[Child] = Parent;
+    }
+  }
+
+  return Result;
+}
+
 llvm::Expected<ControlFlowContext>
 ControlFlowContext::build(const FunctionDecl &Func) {
   if (!Func.hasBody())
@@ -106,7 +127,7 @@
   llvm::BitVector BlockReachable = findReachableBlocks(*Cfg);
 
   return ControlFlowContext(&D, std::move(Cfg), std::move(StmtToBlock),
-                            std::move(BlockReachable));
+                            std::move(BlockReachable), buildStmtToParentMap(S));
 }
 
 llvm::Expected<ControlFlowContext>
Index: clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
===================================================================
--- clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -573,12 +573,16 @@
 
   /// Returns the `DeclContext` of the block being analysed, if any. Otherwise,
   /// returns null.
-  const DeclContext *getDeclCtx() const { return CallStack.back(); }
+  const DeclContext *getDeclCtx() const {
+    if (CallStack.empty())
+      return nullptr;
+    return CallStack.back();
+  }
 
   /// Returns the function currently being analyzed, or null if the code being
   /// analyzed isn't part of a function.
   const FunctionDecl *getCurrentFunc() const {
-    return dyn_cast<FunctionDecl>(getDeclCtx());
+    return dyn_cast_or_null<FunctionDecl>(getDeclCtx());
   }
 
   /// Returns whether this `Environment` can be extended to analyze the given
Index: clang/include/clang/Analysis/FlowSensitive/ControlFlowContext.h
===================================================================
--- clang/include/clang/Analysis/FlowSensitive/ControlFlowContext.h
+++ clang/include/clang/Analysis/FlowSensitive/ControlFlowContext.h
@@ -63,21 +63,28 @@
     return BlockReachable[B.getBlockID()];
   }
 
+  /// Returns the parent of `S` or null if the parent could not be found.
+  /// `S` must be a descendant of the function or statement passed to `build()`.
+  const Stmt *getParent(const Stmt *S) const { return StmtToParent.lookup(S); }
+
 private:
   // FIXME: Once the deprecated `build` method is removed, mark `D` as "must not
   // be null" and add an assertion.
   ControlFlowContext(const Decl *D, std::unique_ptr<CFG> Cfg,
                      llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock,
-                     llvm::BitVector BlockReachable)
+                     llvm::BitVector BlockReachable,
+                     llvm::DenseMap<const Stmt *, const Stmt *> StmtToParent)
       : ContainingDecl(D), Cfg(std::move(Cfg)),
         StmtToBlock(std::move(StmtToBlock)),
-        BlockReachable(std::move(BlockReachable)) {}
+        BlockReachable(std::move(BlockReachable)),
+        StmtToParent(std::move(StmtToParent)) {}
 
   /// The `Decl` containing the statement used to construct the CFG.
   const Decl *ContainingDecl;
   std::unique_ptr<CFG> Cfg;
   llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock;
   llvm::BitVector BlockReachable;
+  llvm::DenseMap<const Stmt *, const Stmt *> StmtToParent;
 };
 
 } // namespace dataflow
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D156856: [clang][dat... Martin Böhme via Phabricator via cfe-commits

Reply via email to