[clang] 1429240 - [clang][dataflow] Add convenience function for analysing and `FunctionDecl` and diagnosing it.
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 26 10:12:36 PDT 2023
Author: Yitzhak Mandelbaum
Date: 2023-07-26T17:12:29Z
New Revision: 1429240ed63b05991e175e623b3fa5b72e59dc5b
URL: https://github.com/llvm/llvm-project/commit/1429240ed63b05991e175e623b3fa5b72e59dc5b
DIFF: https://github.com/llvm/llvm-project/commit/1429240ed63b05991e175e623b3fa5b72e59dc5b.diff
LOG: [clang][dataflow] Add convenience function for analysing and `FunctionDecl` and diagnosing it.
The convenience function captures running the analysis and then collecting
diagnostics based on a `Diagnoser` object. This pattern is valuable to
clang-tidy checks which analyze a function at a time, though it could be more
generally useful for analysis clients.
Differential Revision: https://reviews.llvm.org/D156254
Added:
Modified:
clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h
clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
clang/include/clang/Analysis/FlowSensitive/NoopAnalysis.h
clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h
index 33eb42897b8a1a..43656c8ded2298 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysis.h
@@ -25,9 +25,13 @@
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
#include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "llvm/ADT/Any.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
namespace clang {
@@ -238,6 +242,60 @@ runDataflowAnalysis(
return std::move(BlockStates);
}
+/// Runs a dataflow analysis over the given function and then runs `Diagnoser`
+/// over the results. Returns a list of diagnostics for `FuncDecl` or an
+/// error. Currently, errors can occur (at least) because the analysis requires
+/// too many iterations over the CFG or the SAT solver times out.
+///
+/// The default value of `MaxSATIterations` was chosen based on the following
+/// observations:
+/// - Non-pathological calls to the solver typically require only a few hundred
+/// iterations.
+/// - This limit is still low enough to keep runtimes acceptable (on typical
+/// machines) in cases where we hit the limit.
+template <typename AnalysisT, typename Diagnostic>
+llvm::Expected<std::vector<Diagnostic>> diagnoseFunction(
+ const FunctionDecl &FuncDecl, ASTContext &ASTCtx,
+ llvm::function_ref<std::vector<Diagnostic>(
+ const CFGElement &, ASTContext &,
+ const TransferStateForDiagnostics<typename AnalysisT::Lattice> &)>
+ Diagnoser,
+ std::int64_t MaxSATIterations = 1'000'000'000) {
+ llvm::Expected<ControlFlowContext> Context =
+ ControlFlowContext::build(FuncDecl);
+ if (!Context)
+ return Context.takeError();
+
+ auto OwnedSolver = std::make_unique<WatchedLiteralsSolver>(MaxSATIterations);
+ const WatchedLiteralsSolver *Solver = OwnedSolver.get();
+ DataflowAnalysisContext AnalysisContext(std::move(OwnedSolver));
+ Environment Env(AnalysisContext, FuncDecl);
+ AnalysisT Analysis(ASTCtx);
+ std::vector<Diagnostic> Diagnostics;
+ if (llvm::Error Err =
+ runTypeErasedDataflowAnalysis(
+ *Context, Analysis, Env,
+ [&ASTCtx, &Diagnoser, &Diagnostics](
+ const CFGElement &Elt,
+ const TypeErasedDataflowAnalysisState &State) mutable {
+ auto EltDiagnostics = Diagnoser(
+ Elt, ASTCtx,
+ TransferStateForDiagnostics<typename AnalysisT::Lattice>(
+ llvm::any_cast<const typename AnalysisT::Lattice &>(
+ State.Lattice.Value),
+ State.Env));
+ llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
+ })
+ .takeError())
+ return Err;
+
+ if (Solver->reachedLimit())
+ return llvm::createStringError(llvm::errc::interrupted,
+ "SAT solver timed out");
+
+ return Diagnostics;
+}
+
/// 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.
diff --git a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
index 9a298478c51000..085308f7db54e9 100644
--- a/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
+++ b/clang/include/clang/Analysis/FlowSensitive/MatchSwitch.h
@@ -48,6 +48,10 @@ template <typename LatticeT> struct TransferState {
};
/// A read-only version of TransferState.
+///
+/// FIXME: this type is being used as a general (typed) view type for untyped
+/// dataflow analysis state, rather than strictly for transfer-function
+/// purposes. Move it (and rename it) to DataflowAnalysis.h.
template <typename LatticeT> struct TransferStateForDiagnostics {
TransferStateForDiagnostics(const LatticeT &Lattice, const Environment &Env)
: Lattice(Lattice), Env(Env) {}
diff --git a/clang/include/clang/Analysis/FlowSensitive/NoopAnalysis.h b/clang/include/clang/Analysis/FlowSensitive/NoopAnalysis.h
index e28a7f902faf16..016b6b3ba01454 100644
--- a/clang/include/clang/Analysis/FlowSensitive/NoopAnalysis.h
+++ b/clang/include/clang/Analysis/FlowSensitive/NoopAnalysis.h
@@ -24,6 +24,9 @@ namespace dataflow {
class NoopAnalysis : public DataflowAnalysis<NoopAnalysis, NoopLattice> {
public:
+ NoopAnalysis(ASTContext &Context)
+ : DataflowAnalysis<NoopAnalysis, NoopLattice>(Context) {}
+
/// Deprecated. Use the `DataflowAnalysisOptions` constructor instead.
NoopAnalysis(ASTContext &Context, bool ApplyBuiltinTransfer)
: DataflowAnalysis<NoopAnalysis, NoopLattice>(Context,
diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
index 462ffb5eb389ce..44d6fec962ab5e 100644
--- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
@@ -47,6 +47,7 @@ using namespace test;
using namespace ast_matchers;
using llvm::IsStringMapEntry;
using ::testing::DescribeMatcher;
+using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::NotNull;
using ::testing::Test;
@@ -84,6 +85,30 @@ TEST(DataflowAnalysisTest, NoopAnalysis) {
EXPECT_TRUE(BlockStates[1].has_value());
}
+// Basic test that `diagnoseFunction` calls the Diagnoser function for the
+// number of elements expected.
+TEST(DataflowAnalysisTest, DiagnoseFunctionDiagnoserCalledOnEachElement) {
+ std::string Code = R"(void target() { int x = 0; ++x; })";
+ std::unique_ptr<ASTUnit> AST =
+ tooling::buildASTFromCodeWithArgs(Code, {"-std=c++11"});
+
+ auto *Func =
+ cast<FunctionDecl>(findValueDecl(AST->getASTContext(), "target"));
+ auto Diagnoser = [](const CFGElement &Elt, ASTContext &,
+ const TransferStateForDiagnostics<NoopLattice> &) {
+ std::vector<std::string> Diagnostics(1);
+ llvm::raw_string_ostream OS(Diagnostics.front());
+ Elt.dumpToStream(OS);
+ return Diagnostics;
+ };
+ auto Result = diagnoseFunction<NoopAnalysis, std::string>(
+ *Func, AST->getASTContext(), Diagnoser);
+ // `diagnoseFunction` provides no guarantees about the order in which elements
+ // are visited, so we use `UnorderedElementsAre`.
+ EXPECT_THAT_EXPECTED(Result, llvm::HasValue(UnorderedElementsAre(
+ "0\n", "int x = 0;\n", "x\n", "++x\n")));
+}
+
struct NonConvergingLattice {
int State;
More information about the cfe-commits
mailing list