[clang] [clang][dataflow] Add support for serialization and deserialization. (PR #152487)
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 15 11:48:35 PDT 2025
https://github.com/ymand updated https://github.com/llvm/llvm-project/pull/152487
>From a39ac4fe8bdd337ca03651ca70879392b00cdfca Mon Sep 17 00:00:00 2001
From: Yitzhak Mandelbaum <yitzhakm at google.com>
Date: Mon, 4 Aug 2025 18:09:28 +0000
Subject: [PATCH 1/3] [clang][dataflow] Add support for serialization and
deserialization.
Adds support for compact serialization of Formulas, and a corresponding parse function. Extends Environment and AnalysisContext with necessary functions for serializing and deserializing all formula-related parts of the environment.
---
.../FlowSensitive/DataflowAnalysisContext.h | 32 +++
.../FlowSensitive/DataflowEnvironment.h | 14 +-
.../clang/Analysis/FlowSensitive/Formula.h | 19 +-
.../FlowSensitive/FormulaSerialization.h | 40 ++++
.../lib/Analysis/FlowSensitive/CMakeLists.txt | 1 +
.../FlowSensitive/DataflowAnalysisContext.cpp | 23 +++
.../FlowSensitive/FormulaSerialization.cpp | 161 +++++++++++++++
.../Analysis/FlowSensitive/CMakeLists.txt | 1 +
.../DataflowAnalysisContextTest.cpp | 189 ++++++++++++++++++
.../Analysis/FlowSensitive/FormulaTest.cpp | 179 +++++++++++++++++
10 files changed, 647 insertions(+), 12 deletions(-)
create mode 100644 clang/include/clang/Analysis/FlowSensitive/FormulaSerialization.h
create mode 100644 clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
create mode 100644 clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
index 5be4a1145f40d..17b80f0d01100 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
@@ -140,6 +140,15 @@ class DataflowAnalysisContext {
/// Adds `Constraint` to the flow condition identified by `Token`.
void addFlowConditionConstraint(Atom Token, const Formula &Constraint);
+ /// Adds `Deps` to the dependencies of the flow condition identified by
+ /// `Token`. Intended for use in deserializing contexts. The formula alone
+ /// doesn't have enough information to indicate its deps.
+ void addFlowConditionDeps(Atom Token, const llvm::DenseSet<Atom> &Deps) {
+ // Avoid creating an entry for `Token` with an empty set.
+ if (!Deps.empty())
+ FlowConditionDeps[Token].insert(Deps.begin(), Deps.end());
+ }
+
/// Creates a new flow condition with the same constraints as the flow
/// condition identified by `Token` and returns its token.
Atom forkFlowCondition(Atom Token);
@@ -177,6 +186,29 @@ class DataflowAnalysisContext {
Arena &arena() { return *A; }
+ const Formula *getInvariant() const { return Invariant; }
+
+ /// Returns null if no constraints are associated with `Token`.
+ const Formula *getFlowConditionConstraints(Atom Token) const {
+ auto ConstraintsIt = FlowConditionConstraints.find(Token);
+ if (ConstraintsIt == FlowConditionConstraints.end())
+ return nullptr;
+ return ConstraintsIt->second;
+ }
+
+ /// Returns null if no deps are found.
+ const llvm::DenseSet<Atom> *getFlowConditionDeps(Atom Token) const {
+ auto DepsIt = FlowConditionDeps.find(Token);
+ if (DepsIt == FlowConditionDeps.end())
+ return nullptr;
+ return &DepsIt->second;
+ }
+
+ /// Computes the transitive closure of reachable atoms from `Tokens`, through
+ /// the dependency graph.
+ llvm::DenseSet<Atom>
+ getTransitiveClosure(const llvm::DenseSet<Atom> &Tokens) const;
+
/// Returns the outcome of satisfiability checking on `Constraints`.
///
/// Flow conditions are not incorporated, so they may need to be manually
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
index 097ff2bdfe7ad..076714462bb2a 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -157,10 +157,18 @@ class Environment {
};
/// Creates an environment that uses `DACtx` to store objects that encompass
- /// the state of a program.
+ /// the state of a program. `FlowConditionToken` sets the flow condition
+ /// associated with the environment. Generally, new environments should be
+ /// initialized with a fresh token, by using one of the other
+ /// constructors. This constructor is for specialized use, including
+ /// deserialization and delegation from other constructors.
+ Environment(DataflowAnalysisContext &DACtx, Atom FlowConditionToken)
+ : DACtx(&DACtx), FlowConditionToken(FlowConditionToken) {}
+
+ /// Creates an environment that uses `DACtx` to store objects that encompass
+ /// the state of a program. Populates a fresh atom as flow condition token.
explicit Environment(DataflowAnalysisContext &DACtx)
- : DACtx(&DACtx),
- FlowConditionToken(DACtx.arena().makeFlowConditionToken()) {}
+ : Environment(DACtx, DACtx.arena().makeFlowConditionToken()) {}
/// Creates an environment that uses `DACtx` to store objects that encompass
/// the state of a program, with `S` as the statement to analyze.
diff --git a/clang/include/clang/Analysis/FlowSensitive/Formula.h b/clang/include/clang/Analysis/FlowSensitive/Formula.h
index 0e6352403a832..3959bc98619b9 100644
--- a/clang/include/clang/Analysis/FlowSensitive/Formula.h
+++ b/clang/include/clang/Analysis/FlowSensitive/Formula.h
@@ -85,21 +85,17 @@ class alignas(const Formula *) Formula {
}
using AtomNames = llvm::DenseMap<Atom, std::string>;
- // Produce a stable human-readable representation of this formula.
- // For example: (V3 | !(V1 & V2))
- // If AtomNames is provided, these override the default V0, V1... names.
+ /// Produces a stable human-readable representation of this formula.
+ /// For example: (V3 | !(V1 & V2))
+ /// If AtomNames is provided, these override the default V0, V1... names.
void print(llvm::raw_ostream &OS, const AtomNames * = nullptr) const;
- // Allocate Formulas using Arena rather than calling this function directly.
+ /// Allocates Formulas using Arena rather than calling this function directly.
static const Formula &create(llvm::BumpPtrAllocator &Alloc, Kind K,
ArrayRef<const Formula *> Operands,
unsigned Value = 0);
-private:
- Formula() = default;
- Formula(const Formula &) = delete;
- Formula &operator=(const Formula &) = delete;
-
+ /// Count of operands (sub-formulas) associated with Formulas of kind `K`.
static unsigned numOperands(Kind K) {
switch (K) {
case AtomRef:
@@ -116,6 +112,11 @@ class alignas(const Formula *) Formula {
llvm_unreachable("Unhandled Formula::Kind enum");
}
+private:
+ Formula() = default;
+ Formula(const Formula &) = delete;
+ Formula &operator=(const Formula &) = delete;
+
Kind FormulaKind;
// Some kinds of formula have scalar values, e.g. AtomRef's atom number.
unsigned Value;
diff --git a/clang/include/clang/Analysis/FlowSensitive/FormulaSerialization.h b/clang/include/clang/Analysis/FlowSensitive/FormulaSerialization.h
new file mode 100644
index 0000000000000..119f93e5d73f6
--- /dev/null
+++ b/clang/include/clang/Analysis/FlowSensitive/FormulaSerialization.h
@@ -0,0 +1,40 @@
+//=== FormulaSerialization.h - Formula De/Serialization support -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_FORMULA_SERIALIZATION_H
+#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_FORMULA_SERIALIZATION_H
+
+#include "clang/Analysis/FlowSensitive/Arena.h"
+#include "clang/Analysis/FlowSensitive/Formula.h"
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cassert>
+#include <string>
+
+namespace clang::dataflow {
+
+/// Prints `F` to `OS` in a compact format, optimized for easy parsing
+/// (deserialization) rather than human use.
+void serializeFormula(const Formula &F, llvm::raw_ostream &OS);
+
+/// Parses `Str` to build a serialized Formula.
+/// @returns error on parse failure or if parsing does not fully consume `Str`.
+/// @param A used to construct the formula components.
+/// @param AtomMap maps serialized Atom identifiers (unsigned ints) to Atoms.
+/// This map is provided by the caller to enable consistency across
+/// multiple formulas in a single file.
+llvm::Expected<const Formula *>
+parseFormula(llvm::StringRef Str, Arena &A,
+ llvm::DenseMap<unsigned, Atom> &AtomMap);
+
+} // namespace clang::dataflow
+#endif
diff --git a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt
index 0c30df8b4b194..97e09c9bce95f 100644
--- a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt
+++ b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt
@@ -6,6 +6,7 @@ add_clang_library(clangAnalysisFlowSensitive
DataflowAnalysisContext.cpp
DataflowEnvironment.cpp
Formula.cpp
+ FormulaSerialization.cpp
HTMLLogger.cpp
Logger.cpp
RecordOps.cpp
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
index 6421ad3883d10..97914023ee79e 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
@@ -208,6 +208,27 @@ bool DataflowAnalysisContext::equivalentFormulas(const Formula &Val1,
return isUnsatisfiable(std::move(Constraints));
}
+llvm::DenseSet<Atom> DataflowAnalysisContext::getTransitiveClosure(
+ const llvm::DenseSet<Atom> &Tokens) const {
+ llvm::DenseSet<Atom> VisitedTokens;
+ // Use a worklist algorithm, with `Remaining` holding the worklist.
+ std::vector<Atom> Remaining(Tokens.begin(), Tokens.end());
+
+ // For each token in `Remaining`, add its dependencies to the worklist.
+ while (!Remaining.empty()) {
+ auto Token = Remaining.back();
+ Remaining.pop_back();
+ if (!VisitedTokens.insert(Token).second)
+ continue;
+ if (auto DepsIt = FlowConditionDeps.find(Token);
+ DepsIt != FlowConditionDeps.end())
+ for (Atom A : DepsIt->second)
+ Remaining.push_back(A);
+ }
+
+ return VisitedTokens;
+}
+
void DataflowAnalysisContext::addTransitiveFlowConditionConstraints(
Atom Token, llvm::SetVector<const Formula *> &Constraints) {
llvm::DenseSet<Atom> AddedTokens;
@@ -224,6 +245,8 @@ void DataflowAnalysisContext::addTransitiveFlowConditionConstraints(
auto ConstraintsIt = FlowConditionConstraints.find(Token);
if (ConstraintsIt == FlowConditionConstraints.end()) {
+ // The flow condition is unconstrained. Just add the atom directly, which
+ // is equivalent to asserting it is true.
Constraints.insert(&arena().makeAtomRef(Token));
} else {
// Bind flow condition token via `iff` to its set of constraints:
diff --git a/clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp b/clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
new file mode 100644
index 0000000000000..56cbf5aba8e55
--- /dev/null
+++ b/clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
@@ -0,0 +1,161 @@
+//===- FormulaSerialization.cpp ---------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/FlowSensitive/FormulaSerialization.h"
+#include "clang/Analysis/FlowSensitive/Arena.h"
+#include "clang/Analysis/FlowSensitive/Formula.h"
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <cassert>
+
+namespace clang::dataflow {
+
+// Returns the leading indicator of operation formulas. `AtomRef` and `Literal`
+// are handled differently.
+static char compactSigil(Formula::Kind K) {
+ switch (K) {
+ case Formula::AtomRef:
+ case Formula::Literal:
+ // No sigil.
+ return '\0';
+ case Formula::Not:
+ return '!';
+ case Formula::And:
+ return '&';
+ case Formula::Or:
+ return '|';
+ case Formula::Implies:
+ return '>';
+ case Formula::Equal:
+ return '=';
+ }
+ llvm_unreachable("unhandled formula kind");
+}
+
+void serializeFormula(const Formula &F, llvm::raw_ostream &OS) {
+ switch (Formula::numOperands(F.kind())) {
+ case 0:
+ switch (F.kind()) {
+ case Formula::AtomRef:
+ OS << F.getAtom();
+ break;
+ case Formula::Literal:
+ OS << (F.literal() ? 'T' : 'F');
+ break;
+ default:
+ llvm_unreachable("unhandled formula kind");
+ }
+ break;
+ case 1:
+ OS << compactSigil(F.kind());
+ serializeFormula(*F.operands()[0], OS);
+ break;
+ case 2:
+ OS << compactSigil(F.kind());
+ serializeFormula(*F.operands()[0], OS);
+ serializeFormula(*F.operands()[1], OS);
+ break;
+ default:
+ llvm_unreachable("unhandled formula arity");
+ }
+}
+
+static llvm::Expected<const Formula *>
+parsePrefix(llvm::StringRef &Str, Arena &A,
+ llvm::DenseMap<unsigned, Atom> &AtomMap) {
+ if (Str.empty())
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "unexpected end of input");
+
+ char Prefix = Str[0];
+ Str = Str.drop_front();
+
+ switch (Prefix) {
+ case 'T':
+ return &A.makeLiteral(true);
+ case 'F':
+ return &A.makeLiteral(false);
+ case 'V': {
+ unsigned AtomID;
+ if (Str.consumeInteger(10, AtomID))
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "expected atom id");
+ auto [It, Inserted] = AtomMap.try_emplace(AtomID, Atom());
+ if (Inserted)
+ It->second = A.makeAtom();
+ return &A.makeAtomRef(It->second);
+ }
+ case '!': {
+ auto OperandOrErr = parsePrefix(Str, A, AtomMap);
+ if (!OperandOrErr)
+ return OperandOrErr.takeError();
+ return &A.makeNot(**OperandOrErr);
+ }
+ case '&': {
+ auto LeftOrErr = parsePrefix(Str, A, AtomMap);
+ if (!LeftOrErr)
+ return LeftOrErr.takeError();
+ auto RightOrErr = parsePrefix(Str, A, AtomMap);
+ if (!RightOrErr)
+ return RightOrErr.takeError();
+ return &A.makeAnd(**LeftOrErr, **RightOrErr);
+ }
+ case '|': {
+ auto LeftOrErr = parsePrefix(Str, A, AtomMap);
+ if (!LeftOrErr)
+ return LeftOrErr.takeError();
+ auto RightOrErr = parsePrefix(Str, A, AtomMap);
+ if (!RightOrErr)
+ return RightOrErr.takeError();
+ return &A.makeOr(**LeftOrErr, **RightOrErr);
+ }
+ case '>': {
+ auto LeftOrErr = parsePrefix(Str, A, AtomMap);
+ if (!LeftOrErr)
+ return LeftOrErr.takeError();
+ auto RightOrErr = parsePrefix(Str, A, AtomMap);
+ if (!RightOrErr)
+ return RightOrErr.takeError();
+ return &A.makeImplies(**LeftOrErr, **RightOrErr);
+ }
+ case '=': {
+ auto LeftOrErr = parsePrefix(Str, A, AtomMap);
+ if (!LeftOrErr)
+ return LeftOrErr.takeError();
+ auto RightOrErr = parsePrefix(Str, A, AtomMap);
+ if (!RightOrErr)
+ return RightOrErr.takeError();
+ return &A.makeEquals(**LeftOrErr, **RightOrErr);
+ }
+ default:
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "unexpected prefix character");
+ }
+}
+
+llvm::Expected<const Formula *>
+parseFormula(llvm::StringRef Str, Arena &A,
+ llvm::DenseMap<unsigned, Atom> &AtomMap) {
+ size_t OriginalSize = Str.size();
+ llvm::Expected<const Formula *> F = parsePrefix(Str, A, AtomMap);
+ if (!F)
+ return F.takeError();
+ if (!Str.empty())
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ ("unexpected suffix of length: " +
+ llvm::Twine(Str.size() - OriginalSize))
+ .str());
+ return F;
+}
+
+} // namespace clang::dataflow
diff --git a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
index 4ac563143cd68..3bd4a6e21bee7 100644
--- a/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
+++ b/clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
@@ -8,6 +8,7 @@ add_clang_unittest(ClangAnalysisFlowSensitiveTests
DataflowEnvironmentTest.cpp
DebugSupportTest.cpp
DeterminismTest.cpp
+ FormulaTest.cpp
LoggerTest.cpp
MapLatticeTest.cpp
MatchSwitchTest.cpp
diff --git a/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp b/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
index 4f7a72c502ccf..07323952b3e67 100644
--- a/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
@@ -17,6 +17,9 @@ namespace {
using namespace clang;
using namespace dataflow;
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAre;
+
class DataflowAnalysisContextTest : public ::testing::Test {
protected:
DataflowAnalysisContextTest()
@@ -77,6 +80,14 @@ TEST_F(DataflowAnalysisContextTest, AddInvariant) {
EXPECT_TRUE(Context.flowConditionImplies(FC, C));
}
+TEST_F(DataflowAnalysisContextTest, GetInvariant) {
+ auto &C = A.makeAtomRef(A.makeAtom());
+ Context.addInvariant(C);
+ const Formula *Inv = Context.getInvariant();
+ ASSERT_NE(Inv, nullptr);
+ EXPECT_TRUE(Context.equivalentFormulas(*Inv, C));
+}
+
TEST_F(DataflowAnalysisContextTest, InvariantAndFCConstraintInteract) {
Atom FC = A.makeFlowConditionToken();
auto &C = A.makeAtomRef(A.makeAtom());
@@ -123,6 +134,28 @@ TEST_F(DataflowAnalysisContextTest, JoinFlowConditions) {
EXPECT_TRUE(Context.flowConditionImplies(FC3, C3));
}
+TEST_F(DataflowAnalysisContextTest, GetFlowConditionConstraints) {
+ auto &C1 = A.makeAtomRef(A.makeAtom());
+ auto &C2 = A.makeAtomRef(A.makeAtom());
+ auto &C3 = A.makeAtomRef(A.makeAtom());
+
+ Atom FC1 = A.makeFlowConditionToken();
+ Context.addFlowConditionConstraint(FC1, C1);
+ Context.addFlowConditionConstraint(FC1, C3);
+
+ Atom FC2 = A.makeFlowConditionToken();
+ Context.addFlowConditionConstraint(FC2, C2);
+ Context.addFlowConditionConstraint(FC2, C3);
+
+ const Formula *CS1 = Context.getFlowConditionConstraints(FC1);
+ ASSERT_NE(CS1, nullptr);
+ EXPECT_TRUE(Context.equivalentFormulas(*CS1, A.makeAnd(C1, C3)));
+
+ const Formula *CS2 = Context.getFlowConditionConstraints(FC2);
+ ASSERT_NE(CS2, nullptr);
+ EXPECT_TRUE(Context.equivalentFormulas(*CS2, A.makeAnd(C2, C3)));
+}
+
TEST_F(DataflowAnalysisContextTest, EquivBoolVals) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &Y = A.makeAtomRef(A.makeAtom());
@@ -171,4 +204,160 @@ TEST_F(DataflowAnalysisContextTest, EquivBoolVals) {
A.makeAnd(X, A.makeAnd(Y, Z))));
}
+using FlowConditionDepsTest = DataflowAnalysisContextTest;
+
+TEST_F(FlowConditionDepsTest, AddEmptyDeps) {
+ Atom FC1 = A.makeFlowConditionToken();
+
+ // Add empty dependencies to FC1.
+ Context.addFlowConditionDeps(FC1, {});
+
+ // Verify that FC1 has no dependencies.
+ EXPECT_EQ(Context.getFlowConditionDeps(FC1), nullptr);
+}
+
+TEST_F(FlowConditionDepsTest, AddAndGetDeps) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+
+ // Add dependencies: FC1 depends on FC2 and FC3.
+ Context.addFlowConditionDeps(FC1, {FC2, FC3});
+
+ // Verify that FC1 depends on FC2 and FC3.
+ const llvm::DenseSet<Atom> *Deps = Context.getFlowConditionDeps(FC1);
+ ASSERT_NE(Deps, nullptr);
+ EXPECT_THAT(*Deps, UnorderedElementsAre(FC2, FC3));
+
+ // Verify that FC2 and FC3 have no dependencies.
+ EXPECT_EQ(Context.getFlowConditionDeps(FC2), nullptr);
+ EXPECT_EQ(Context.getFlowConditionDeps(FC3), nullptr);
+}
+
+TEST_F(FlowConditionDepsTest, AddDepsToExisting) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+
+ // Add initial dependency: FC1 depends on FC2.
+ Context.addFlowConditionDeps(FC1, {FC2});
+
+ // Add more dependencies: FC1 depends on FC2 and FC3.
+ Context.addFlowConditionDeps(FC1, {FC3});
+
+ // Verify that FC1 depends on FC2 and FC3.
+ const llvm::DenseSet<Atom> *Deps = Context.getFlowConditionDeps(FC1);
+ ASSERT_NE(Deps, nullptr);
+ EXPECT_THAT(*Deps, UnorderedElementsAre(FC2, FC3));
+}
+
+using GetTransitiveClosureTest = DataflowAnalysisContextTest;
+
+TEST_F(GetTransitiveClosureTest, EmptySet) {
+ EXPECT_THAT(Context.getTransitiveClosure({}), IsEmpty());
+}
+
+TEST_F(GetTransitiveClosureTest, SingletonSet) {
+ Atom FC1 = A.makeFlowConditionToken();
+ EXPECT_THAT(Context.getTransitiveClosure({FC1}), UnorderedElementsAre(FC1));
+}
+
+TEST_F(GetTransitiveClosureTest, NoDependency) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+ auto &C1 = A.makeAtomRef(A.makeAtom());
+ auto &C2 = A.makeAtomRef(A.makeAtom());
+ auto &C3 = A.makeAtomRef(A.makeAtom());
+
+ Context.addFlowConditionConstraint(FC1, C1);
+ Context.addFlowConditionConstraint(FC2, C2);
+ Context.addFlowConditionConstraint(FC3, C3);
+
+ // FCs are independent.
+ EXPECT_THAT(Context.getTransitiveClosure({FC1}), UnorderedElementsAre(FC1));
+ EXPECT_THAT(Context.getTransitiveClosure({FC2}), UnorderedElementsAre(FC2));
+ EXPECT_THAT(Context.getTransitiveClosure({FC3}), UnorderedElementsAre(FC3));
+}
+
+TEST_F(GetTransitiveClosureTest, SimpleDependencyChain) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+
+ Context.addFlowConditionConstraint(FC1, A.makeAtomRef(FC2));
+ Context.addFlowConditionDeps(FC1, {FC2});
+
+ Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC3));
+ Context.addFlowConditionDeps(FC2, {FC3});
+
+ EXPECT_THAT(Context.getTransitiveClosure({FC1}),
+ UnorderedElementsAre(FC1, FC2, FC3));
+}
+
+TEST_F(GetTransitiveClosureTest, DependencyTree) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+ Atom FC4 = A.makeFlowConditionToken();
+
+ Context.addFlowConditionDeps(FC1, {FC2, FC3});
+ Context.addFlowConditionConstraint(
+ FC1, A.makeAnd(A.makeAtomRef(FC2), A.makeAtomRef(FC3)));
+
+ Context.addFlowConditionDeps(FC2, {FC4});
+ Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC4));
+
+ EXPECT_THAT(Context.getTransitiveClosure({FC1}),
+ UnorderedElementsAre(FC1, FC2, FC3, FC4));
+}
+
+TEST_F(GetTransitiveClosureTest, DependencyDAG) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+ Atom FC4 = A.makeFlowConditionToken();
+
+ Context.addFlowConditionDeps(FC1, {FC2, FC3});
+ Context.addFlowConditionConstraint(
+ FC1, A.makeAnd(A.makeAtomRef(FC2), A.makeAtomRef(FC3)));
+
+ Context.addFlowConditionDeps(FC2, {FC4});
+ Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC4));
+
+ Context.addFlowConditionDeps(FC3, {FC4});
+ Context.addFlowConditionConstraint(FC3, A.makeAtomRef(FC4));
+
+ EXPECT_THAT(Context.getTransitiveClosure({FC1}),
+ UnorderedElementsAre(FC1, FC2, FC3, FC4));
+}
+
+TEST_F(GetTransitiveClosureTest, DependencyCycle) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+
+ Context.addFlowConditionDeps(FC1, {FC2});
+ Context.addFlowConditionConstraint(FC1, A.makeAtomRef(FC2));
+ Context.addFlowConditionDeps(FC2, {FC3});
+ Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC3));
+ Context.addFlowConditionDeps(FC3, {FC1});
+ Context.addFlowConditionConstraint(FC3, A.makeAtomRef(FC1));
+
+ EXPECT_THAT(Context.getTransitiveClosure({FC1}),
+ UnorderedElementsAre(FC1, FC2, FC3));
+}
+
+TEST_F(GetTransitiveClosureTest, MixedDependencies) {
+ Atom FC1 = A.makeFlowConditionToken();
+ Atom FC2 = A.makeFlowConditionToken();
+ Atom FC3 = A.makeFlowConditionToken();
+ Atom FC4 = A.makeFlowConditionToken();
+
+ Context.addFlowConditionDeps(FC1, {FC2});
+ Context.addFlowConditionConstraint(FC1, A.makeAtomRef(FC2));
+
+ EXPECT_THAT(Context.getTransitiveClosure({FC1, FC3, FC4}),
+ UnorderedElementsAre(FC1, FC2, FC3, FC4));
+}
} // namespace
diff --git a/clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp b/clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp
new file mode 100644
index 0000000000000..49c02a8f192f2
--- /dev/null
+++ b/clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp
@@ -0,0 +1,179 @@
+//===- unittests/Analysis/FlowSensitive/FormulaTest.cpp -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/FlowSensitive/Formula.h"
+#include "TestingSupport.h"
+#include "clang/Analysis/FlowSensitive/Arena.h"
+#include "clang/Analysis/FlowSensitive/FormulaSerialization.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using namespace clang;
+using namespace dataflow;
+
+using ::llvm::HasValue;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
+class SerializeFormulaTest : public ::testing::Test {
+protected:
+ Arena A;
+ std::string Out;
+ llvm::raw_string_ostream OS{Out};
+
+ const Formula &A1 = A.makeAtomRef(A.makeAtom());
+ const Formula &A2 = A.makeAtomRef(A.makeAtom());
+};
+
+TEST_F(SerializeFormulaTest, Atom) {
+ serializeFormula(A1, OS);
+ EXPECT_EQ(Out, "V0");
+ Out = "";
+
+ serializeFormula(A2, OS);
+ EXPECT_EQ(Out, "V1");
+}
+
+TEST_F(SerializeFormulaTest, LiteralTrue) {
+ serializeFormula(A.makeLiteral(true), OS);
+ EXPECT_EQ(Out, "T");
+}
+
+TEST_F(SerializeFormulaTest, LiteralFalse) {
+ serializeFormula(A.makeLiteral(false), OS);
+ EXPECT_EQ(Out, "F");
+}
+
+TEST_F(SerializeFormulaTest, Not) {
+ serializeFormula(A.makeNot(A1), OS);
+ EXPECT_EQ(Out, "!V0");
+}
+
+TEST_F(SerializeFormulaTest, Or) {
+ serializeFormula(A.makeOr(A1, A2), OS);
+ EXPECT_EQ(Out, "|V0V1");
+}
+
+TEST_F(SerializeFormulaTest, And) {
+ serializeFormula(A.makeAnd(A1, A2), OS);
+ EXPECT_EQ(Out, "&V0V1");
+}
+
+TEST_F(SerializeFormulaTest, Implies) {
+ serializeFormula(A.makeImplies(A1, A2), OS);
+ EXPECT_EQ(Out, ">V0V1");
+}
+
+TEST_F(SerializeFormulaTest, Equal) {
+ serializeFormula(A.makeEquals(A1, A2), OS);
+ EXPECT_EQ(Out, "=V0V1");
+}
+
+TEST_F(SerializeFormulaTest, NestedBinaryUnary) {
+ serializeFormula(A.makeEquals(A.makeOr(A1, A2), A2), OS);
+ EXPECT_EQ(Out, "=|V0V1V1");
+}
+
+TEST_F(SerializeFormulaTest, NestedBinaryBinary) {
+ serializeFormula(A.makeEquals(A.makeOr(A1, A2), A.makeAnd(A1, A2)), OS);
+ EXPECT_EQ(Out, "=|V0V1&V0V1");
+}
+
+class ParseFormulaTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ AtomMap[0] = Atom1;
+ AtomMap[1] = Atom2;
+ }
+
+ Arena A;
+ std::string Out;
+ llvm::raw_string_ostream OS{Out};
+
+ Atom Atom1 = A.makeAtom();
+ Atom Atom2 = A.makeAtom();
+ const Formula &A1 = A.makeAtomRef(Atom1);
+ const Formula &A2 = A.makeAtomRef(Atom2);
+ llvm::DenseMap<unsigned, Atom> AtomMap;
+};
+
+TEST_F(ParseFormulaTest, Atom) {
+ EXPECT_THAT_EXPECTED(parseFormula("V0", A, AtomMap), HasValue(&A1));
+ EXPECT_THAT_EXPECTED(parseFormula("V1", A, AtomMap), HasValue(&A2));
+}
+
+TEST_F(ParseFormulaTest, LiteralTrue) {
+ EXPECT_THAT_EXPECTED(parseFormula("T", A, AtomMap),
+ HasValue(&A.makeLiteral(true)));
+}
+
+TEST_F(ParseFormulaTest, LiteralFalse) {
+ EXPECT_THAT_EXPECTED(parseFormula("F", A, AtomMap),
+ HasValue(&A.makeLiteral(false)));
+}
+
+TEST_F(ParseFormulaTest, Not) {
+ EXPECT_THAT_EXPECTED(parseFormula("!V0", A, AtomMap),
+ HasValue(&A.makeNot(A1)));
+}
+
+TEST_F(ParseFormulaTest, Or) {
+ EXPECT_THAT_EXPECTED(parseFormula("|V0V1", A, AtomMap),
+ HasValue(&A.makeOr(A1, A2)));
+}
+
+TEST_F(ParseFormulaTest, And) {
+ EXPECT_THAT_EXPECTED(parseFormula("&V0V1", A, AtomMap),
+ HasValue(&A.makeAnd(A1, A2)));
+}
+
+TEST_F(ParseFormulaTest, Implies) {
+ EXPECT_THAT_EXPECTED(parseFormula(">V0V1", A, AtomMap),
+ HasValue(&A.makeImplies(A1, A2)));
+}
+
+TEST_F(ParseFormulaTest, Equal) {
+ EXPECT_THAT_EXPECTED(parseFormula("=V0V1", A, AtomMap),
+ HasValue(&A.makeEquals(A1, A2)));
+}
+
+TEST_F(ParseFormulaTest, NestedBinaryUnary) {
+ EXPECT_THAT_EXPECTED(parseFormula("=|V0V1V1", A, AtomMap),
+ HasValue(&A.makeEquals(A.makeOr(A1, A2), A2)));
+}
+
+TEST_F(ParseFormulaTest, NestedBinaryBinary) {
+ EXPECT_THAT_EXPECTED(
+ parseFormula("=|V0V1&V0V1", A, AtomMap),
+ HasValue(&A.makeEquals(A.makeOr(A1, A2), A.makeAnd(A1, A2))));
+}
+
+// Verifies that parsing generates fresh atoms, if they are not already in the
+// map.
+TEST_F(ParseFormulaTest, GeneratesAtoms) {
+ llvm::DenseMap<unsigned, Atom> FreshAtomMap;
+ ASSERT_THAT_EXPECTED(parseFormula("=V0V1", A, FreshAtomMap),
+ llvm::Succeeded());
+ // The map contains two, unique elements.
+ ASSERT_EQ(FreshAtomMap.size(), 2U);
+ EXPECT_NE(FreshAtomMap[0], FreshAtomMap[1]);
+}
+
+TEST_F(ParseFormulaTest, BadFormulaFails) {
+ EXPECT_THAT_EXPECTED(parseFormula("Hello", A, AtomMap), llvm::Failed());
+}
+
+TEST_F(ParseFormulaTest, FormulaWithSuffixFails) {
+ EXPECT_THAT_EXPECTED(parseFormula("=V0V1Hello", A, AtomMap), llvm::Failed());
+}
+
+} // namespace
>From 08b0b10595561bafca91a88d8ec142d04cf89425 Mon Sep 17 00:00:00 2001
From: Yitzhak Mandelbaum <yitzhakm at google.com>
Date: Sun, 10 Aug 2025 04:00:32 +0000
Subject: [PATCH 2/3] fixup! [clang][dataflow] Add support for serialization
and deserialization.
rework to use explicit serialization functions
---
.../FlowSensitive/DataflowAnalysisContext.h | 48 ++---
.../FlowSensitive/DataflowAnalysisContext.cpp | 59 ++++++
.../DataflowAnalysisContextTest.cpp | 187 +++++-------------
3 files changed, 131 insertions(+), 163 deletions(-)
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
index 17b80f0d01100..a03bfe058061e 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
@@ -42,6 +42,18 @@ struct ContextSensitiveOptions {
unsigned Depth = 2;
};
+/// A simple representation of essential elements of the logical context used in
+/// environments. Designed for import/export for applications requiring
+/// serialization support.
+struct SimpleLogicalContext {
+ // Global invariant that applies for all definitions in the context.
+ const Formula *Invariant;
+ // Flow-condition tokens in the context.
+ llvm::DenseMap<Atom, const Formula *> TokenDefs;
+ // Dependencies between flow-condition definitions.
+ llvm::DenseMap<Atom, llvm::DenseSet<Atom>> TokenDeps;
+};
+
/// Owns objects that encompass the state of a program and stores context that
/// is used during dataflow analysis.
class DataflowAnalysisContext {
@@ -186,29 +198,6 @@ class DataflowAnalysisContext {
Arena &arena() { return *A; }
- const Formula *getInvariant() const { return Invariant; }
-
- /// Returns null if no constraints are associated with `Token`.
- const Formula *getFlowConditionConstraints(Atom Token) const {
- auto ConstraintsIt = FlowConditionConstraints.find(Token);
- if (ConstraintsIt == FlowConditionConstraints.end())
- return nullptr;
- return ConstraintsIt->second;
- }
-
- /// Returns null if no deps are found.
- const llvm::DenseSet<Atom> *getFlowConditionDeps(Atom Token) const {
- auto DepsIt = FlowConditionDeps.find(Token);
- if (DepsIt == FlowConditionDeps.end())
- return nullptr;
- return &DepsIt->second;
- }
-
- /// Computes the transitive closure of reachable atoms from `Tokens`, through
- /// the dependency graph.
- llvm::DenseSet<Atom>
- getTransitiveClosure(const llvm::DenseSet<Atom> &Tokens) const;
-
/// Returns the outcome of satisfiability checking on `Constraints`.
///
/// Flow conditions are not incorporated, so they may need to be manually
@@ -239,6 +228,14 @@ class DataflowAnalysisContext {
return {};
}
+ /// Export the logical-context portions of `AC`, limited to the given target
+ /// flow-condition tokens.
+ SimpleLogicalContext
+ exportLogicalContext(llvm::DenseSet<dataflow::Atom> TargetTokens) const;
+
+ /// Initializes this context's "logical" components with `LC`.
+ void initLogicalContext(SimpleLogicalContext LC);
+
private:
friend class Environment;
@@ -260,6 +257,11 @@ class DataflowAnalysisContext {
DataflowAnalysisContext(Solver &S, std::unique_ptr<Solver> &&OwnedSolver,
Options Opts);
+ /// Computes the transitive closure of reachable atoms from `Tokens`, through
+ /// the dependency graph.
+ llvm::DenseSet<Atom>
+ getTransitiveClosure(const llvm::DenseSet<Atom> &Tokens) const;
+
// Extends the set of modeled field declarations.
void addModeledFields(const FieldSet &Fields);
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
index 97914023ee79e..487f517420995 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
@@ -262,6 +262,65 @@ void DataflowAnalysisContext::addTransitiveFlowConditionConstraints(
}
}
+static void getReferencedAtoms(const Formula &F,
+ llvm::DenseSet<dataflow::Atom> &Refs) {
+ switch (F.kind()) {
+ case Formula::AtomRef:
+ Refs.insert(F.getAtom());
+ break;
+ case Formula::Literal:
+ break;
+ case Formula::Not:
+ getReferencedAtoms(*F.operands()[0], Refs);
+ break;
+ case Formula::And:
+ case Formula::Or:
+ case Formula::Implies:
+ case Formula::Equal:
+ ArrayRef<const Formula *> Operands = F.operands();
+ getReferencedAtoms(*Operands[0], Refs);
+ getReferencedAtoms(*Operands[1], Refs);
+ break;
+ }
+}
+
+SimpleLogicalContext DataflowAnalysisContext::exportLogicalContext(
+ llvm::DenseSet<dataflow::Atom> TargetTokens) const {
+ SimpleLogicalContext LC;
+
+ if (Invariant != nullptr) {
+ LC.Invariant = Invariant;
+ getReferencedAtoms(*Invariant, TargetTokens);
+ }
+
+ llvm::DenseSet<dataflow::Atom> ReachableTokens =
+ getTransitiveClosure(TargetTokens);
+
+ for (dataflow::Atom Token : ReachableTokens) {
+ // Only process the token if it is constrained. Unconstrained tokens don't
+ // have dependencies.
+ auto ConstraintsIt = FlowConditionConstraints.find(Token);
+ if (ConstraintsIt == FlowConditionConstraints.end())
+ continue;
+ LC.TokenDefs[Token] = ConstraintsIt->second;
+
+ if (auto DepsIt = FlowConditionDeps.find(Token);
+ DepsIt != FlowConditionDeps.end())
+ LC.TokenDeps[Token] = DepsIt->second;
+ }
+
+ return LC;
+}
+
+void DataflowAnalysisContext::initLogicalContext(SimpleLogicalContext LC) {
+ Invariant = LC.Invariant;
+ FlowConditionConstraints = std::move(LC.TokenDefs);
+ // TODO: The dependencies in `LC.TokenDeps` can be reconstructed from
+ // `LC.TokenDefs`. Give the caller the option to reconstruct, , rather than
+ // providing them directly, to save caller space (memory/disk).
+ FlowConditionDeps = std::move(LC.TokenDeps);
+}
+
static void printAtomList(const llvm::SmallVector<Atom> &Atoms,
llvm::raw_ostream &OS) {
OS << "(";
diff --git a/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp b/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
index 07323952b3e67..92b687a5a18a4 100644
--- a/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
@@ -80,14 +80,6 @@ TEST_F(DataflowAnalysisContextTest, AddInvariant) {
EXPECT_TRUE(Context.flowConditionImplies(FC, C));
}
-TEST_F(DataflowAnalysisContextTest, GetInvariant) {
- auto &C = A.makeAtomRef(A.makeAtom());
- Context.addInvariant(C);
- const Formula *Inv = Context.getInvariant();
- ASSERT_NE(Inv, nullptr);
- EXPECT_TRUE(Context.equivalentFormulas(*Inv, C));
-}
-
TEST_F(DataflowAnalysisContextTest, InvariantAndFCConstraintInteract) {
Atom FC = A.makeFlowConditionToken();
auto &C = A.makeAtomRef(A.makeAtom());
@@ -134,28 +126,6 @@ TEST_F(DataflowAnalysisContextTest, JoinFlowConditions) {
EXPECT_TRUE(Context.flowConditionImplies(FC3, C3));
}
-TEST_F(DataflowAnalysisContextTest, GetFlowConditionConstraints) {
- auto &C1 = A.makeAtomRef(A.makeAtom());
- auto &C2 = A.makeAtomRef(A.makeAtom());
- auto &C3 = A.makeAtomRef(A.makeAtom());
-
- Atom FC1 = A.makeFlowConditionToken();
- Context.addFlowConditionConstraint(FC1, C1);
- Context.addFlowConditionConstraint(FC1, C3);
-
- Atom FC2 = A.makeFlowConditionToken();
- Context.addFlowConditionConstraint(FC2, C2);
- Context.addFlowConditionConstraint(FC2, C3);
-
- const Formula *CS1 = Context.getFlowConditionConstraints(FC1);
- ASSERT_NE(CS1, nullptr);
- EXPECT_TRUE(Context.equivalentFormulas(*CS1, A.makeAnd(C1, C3)));
-
- const Formula *CS2 = Context.getFlowConditionConstraints(FC2);
- ASSERT_NE(CS2, nullptr);
- EXPECT_TRUE(Context.equivalentFormulas(*CS2, A.makeAnd(C2, C3)));
-}
-
TEST_F(DataflowAnalysisContextTest, EquivBoolVals) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &Y = A.makeAtomRef(A.makeAtom());
@@ -204,65 +174,27 @@ TEST_F(DataflowAnalysisContextTest, EquivBoolVals) {
A.makeAnd(X, A.makeAnd(Y, Z))));
}
-using FlowConditionDepsTest = DataflowAnalysisContextTest;
-
-TEST_F(FlowConditionDepsTest, AddEmptyDeps) {
- Atom FC1 = A.makeFlowConditionToken();
-
- // Add empty dependencies to FC1.
- Context.addFlowConditionDeps(FC1, {});
+using ExportLogicalContextTest = DataflowAnalysisContextTest;
- // Verify that FC1 has no dependencies.
- EXPECT_EQ(Context.getFlowConditionDeps(FC1), nullptr);
+TEST_F(ExportLogicalContextTest, EmptySet) {
+ EXPECT_THAT(Context.exportLogicalContext({}).TokenDefs, IsEmpty());
}
-TEST_F(FlowConditionDepsTest, AddAndGetDeps) {
+// Only constrainted tokens are included in the output.
+TEST_F(ExportLogicalContextTest, UnconstrainedIgnored) {
Atom FC1 = A.makeFlowConditionToken();
- Atom FC2 = A.makeFlowConditionToken();
- Atom FC3 = A.makeFlowConditionToken();
-
- // Add dependencies: FC1 depends on FC2 and FC3.
- Context.addFlowConditionDeps(FC1, {FC2, FC3});
-
- // Verify that FC1 depends on FC2 and FC3.
- const llvm::DenseSet<Atom> *Deps = Context.getFlowConditionDeps(FC1);
- ASSERT_NE(Deps, nullptr);
- EXPECT_THAT(*Deps, UnorderedElementsAre(FC2, FC3));
-
- // Verify that FC2 and FC3 have no dependencies.
- EXPECT_EQ(Context.getFlowConditionDeps(FC2), nullptr);
- EXPECT_EQ(Context.getFlowConditionDeps(FC3), nullptr);
+ EXPECT_THAT(Context.exportLogicalContext({FC1}).TokenDefs, IsEmpty());
}
-TEST_F(FlowConditionDepsTest, AddDepsToExisting) {
+TEST_F(ExportLogicalContextTest, SingletonSet) {
Atom FC1 = A.makeFlowConditionToken();
- Atom FC2 = A.makeFlowConditionToken();
- Atom FC3 = A.makeFlowConditionToken();
-
- // Add initial dependency: FC1 depends on FC2.
- Context.addFlowConditionDeps(FC1, {FC2});
-
- // Add more dependencies: FC1 depends on FC2 and FC3.
- Context.addFlowConditionDeps(FC1, {FC3});
-
- // Verify that FC1 depends on FC2 and FC3.
- const llvm::DenseSet<Atom> *Deps = Context.getFlowConditionDeps(FC1);
- ASSERT_NE(Deps, nullptr);
- EXPECT_THAT(*Deps, UnorderedElementsAre(FC2, FC3));
-}
-
-using GetTransitiveClosureTest = DataflowAnalysisContextTest;
-
-TEST_F(GetTransitiveClosureTest, EmptySet) {
- EXPECT_THAT(Context.getTransitiveClosure({}), IsEmpty());
-}
-
-TEST_F(GetTransitiveClosureTest, SingletonSet) {
- Atom FC1 = A.makeFlowConditionToken();
- EXPECT_THAT(Context.getTransitiveClosure({FC1}), UnorderedElementsAre(FC1));
+ auto &C1 = A.makeAtomRef(A.makeAtom());
+ Context.addFlowConditionConstraint(FC1, C1);
+ EXPECT_THAT(Context.exportLogicalContext({FC1}).TokenDefs.keys(),
+ UnorderedElementsAre(FC1));
}
-TEST_F(GetTransitiveClosureTest, NoDependency) {
+TEST_F(ExportLogicalContextTest, NoDependency) {
Atom FC1 = A.makeFlowConditionToken();
Atom FC2 = A.makeFlowConditionToken();
Atom FC3 = A.makeFlowConditionToken();
@@ -275,89 +207,64 @@ TEST_F(GetTransitiveClosureTest, NoDependency) {
Context.addFlowConditionConstraint(FC3, C3);
// FCs are independent.
- EXPECT_THAT(Context.getTransitiveClosure({FC1}), UnorderedElementsAre(FC1));
- EXPECT_THAT(Context.getTransitiveClosure({FC2}), UnorderedElementsAre(FC2));
- EXPECT_THAT(Context.getTransitiveClosure({FC3}), UnorderedElementsAre(FC3));
+ EXPECT_THAT(Context.exportLogicalContext({FC1}).TokenDefs.keys(),
+ UnorderedElementsAre(FC1));
+ EXPECT_THAT(Context.exportLogicalContext({FC2}).TokenDefs.keys(),
+ UnorderedElementsAre(FC2));
+ EXPECT_THAT(Context.exportLogicalContext({FC3}).TokenDefs.keys(),
+ UnorderedElementsAre(FC3));
}
-TEST_F(GetTransitiveClosureTest, SimpleDependencyChain) {
+TEST_F(ExportLogicalContextTest, SimpleDependencyChain) {
Atom FC1 = A.makeFlowConditionToken();
- Atom FC2 = A.makeFlowConditionToken();
- Atom FC3 = A.makeFlowConditionToken();
-
- Context.addFlowConditionConstraint(FC1, A.makeAtomRef(FC2));
- Context.addFlowConditionDeps(FC1, {FC2});
-
- Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC3));
- Context.addFlowConditionDeps(FC2, {FC3});
+ const Formula &C = A.makeAtomRef(A.makeAtom());
+ Context.addFlowConditionConstraint(FC1, C);
+ Atom FC2 = Context.forkFlowCondition(FC1);
+ Atom FC3 = Context.forkFlowCondition(FC2);
- EXPECT_THAT(Context.getTransitiveClosure({FC1}),
+ EXPECT_THAT(Context.exportLogicalContext({FC3}).TokenDefs.keys(),
UnorderedElementsAre(FC1, FC2, FC3));
}
-TEST_F(GetTransitiveClosureTest, DependencyTree) {
+TEST_F(ExportLogicalContextTest, DependencyTree) {
Atom FC1 = A.makeFlowConditionToken();
- Atom FC2 = A.makeFlowConditionToken();
+ const Formula &C = A.makeAtomRef(A.makeAtom());
+ Context.addFlowConditionConstraint(FC1, C);
+ Atom FC2 = Context.forkFlowCondition(FC1);
Atom FC3 = A.makeFlowConditionToken();
- Atom FC4 = A.makeFlowConditionToken();
-
- Context.addFlowConditionDeps(FC1, {FC2, FC3});
- Context.addFlowConditionConstraint(
- FC1, A.makeAnd(A.makeAtomRef(FC2), A.makeAtomRef(FC3)));
+ Context.addFlowConditionConstraint(FC3, C);
+ Atom FC4 = Context.joinFlowConditions(FC2, FC3);
- Context.addFlowConditionDeps(FC2, {FC4});
- Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC4));
-
- EXPECT_THAT(Context.getTransitiveClosure({FC1}),
+ EXPECT_THAT(Context.exportLogicalContext({FC4}).TokenDefs.keys(),
UnorderedElementsAre(FC1, FC2, FC3, FC4));
}
-TEST_F(GetTransitiveClosureTest, DependencyDAG) {
+TEST_F(ExportLogicalContextTest, DependencyDAG) {
Atom FC1 = A.makeFlowConditionToken();
- Atom FC2 = A.makeFlowConditionToken();
- Atom FC3 = A.makeFlowConditionToken();
- Atom FC4 = A.makeFlowConditionToken();
-
- Context.addFlowConditionDeps(FC1, {FC2, FC3});
- Context.addFlowConditionConstraint(
- FC1, A.makeAnd(A.makeAtomRef(FC2), A.makeAtomRef(FC3)));
-
- Context.addFlowConditionDeps(FC2, {FC4});
- Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC4));
+ const Formula &C = A.makeAtomRef(A.makeAtom());
+ Context.addFlowConditionConstraint(FC1, C);
- Context.addFlowConditionDeps(FC3, {FC4});
- Context.addFlowConditionConstraint(FC3, A.makeAtomRef(FC4));
+ Atom FC2 = Context.forkFlowCondition(FC1);
+ Atom FC3 = Context.forkFlowCondition(FC1);
+ Atom FC4 = Context.joinFlowConditions(FC2, FC3);
- EXPECT_THAT(Context.getTransitiveClosure({FC1}),
+ EXPECT_THAT(Context.exportLogicalContext({FC4}).TokenDefs.keys(),
UnorderedElementsAre(FC1, FC2, FC3, FC4));
}
-TEST_F(GetTransitiveClosureTest, DependencyCycle) {
+TEST_F(ExportLogicalContextTest, MixedDependencies) {
Atom FC1 = A.makeFlowConditionToken();
- Atom FC2 = A.makeFlowConditionToken();
- Atom FC3 = A.makeFlowConditionToken();
-
- Context.addFlowConditionDeps(FC1, {FC2});
- Context.addFlowConditionConstraint(FC1, A.makeAtomRef(FC2));
- Context.addFlowConditionDeps(FC2, {FC3});
- Context.addFlowConditionConstraint(FC2, A.makeAtomRef(FC3));
- Context.addFlowConditionDeps(FC3, {FC1});
- Context.addFlowConditionConstraint(FC3, A.makeAtomRef(FC1));
+ const Formula &C = A.makeAtomRef(A.makeAtom());
+ Context.addFlowConditionConstraint(FC1, C);
- EXPECT_THAT(Context.getTransitiveClosure({FC1}),
- UnorderedElementsAre(FC1, FC2, FC3));
-}
+ Atom FC2 = Context.forkFlowCondition(FC1);
+ Atom FC3 = Context.forkFlowCondition(FC2);
+ (void)FC3; // unused, and we test below that it is not included.
-TEST_F(GetTransitiveClosureTest, MixedDependencies) {
- Atom FC1 = A.makeFlowConditionToken();
- Atom FC2 = A.makeFlowConditionToken();
- Atom FC3 = A.makeFlowConditionToken();
Atom FC4 = A.makeFlowConditionToken();
+ Context.addFlowConditionConstraint(FC4, C);
- Context.addFlowConditionDeps(FC1, {FC2});
- Context.addFlowConditionConstraint(FC1, A.makeAtomRef(FC2));
-
- EXPECT_THAT(Context.getTransitiveClosure({FC1, FC3, FC4}),
- UnorderedElementsAre(FC1, FC2, FC3, FC4));
+ EXPECT_THAT(Context.exportLogicalContext({FC2, FC4}).TokenDefs.keys(),
+ UnorderedElementsAre(FC1, FC2, FC4));
}
} // namespace
>From 235ea87823b9d55f36906e626206c4a23c2759f7 Mon Sep 17 00:00:00 2001
From: Yitzhak Mandelbaum <yitzhakm at google.com>
Date: Fri, 15 Aug 2025 18:47:52 +0000
Subject: [PATCH 3/3] fixup! fixup! [clang][dataflow] Add support for
serialization and deserialization.
respond to reviewer suggested changes
---
.../FlowSensitive/DataflowAnalysisContext.h | 7 +-
.../FlowSensitive/DataflowAnalysisContext.cpp | 16 ++--
.../FlowSensitive/FormulaSerialization.cpp | 77 ++++++++++---------
.../Analysis/FlowSensitive/FormulaTest.cpp | 70 ++++++++++++-----
4 files changed, 101 insertions(+), 69 deletions(-)
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
index a03bfe058061e..613e79719e3e1 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
@@ -257,10 +257,11 @@ class DataflowAnalysisContext {
DataflowAnalysisContext(Solver &S, std::unique_ptr<Solver> &&OwnedSolver,
Options Opts);
- /// Computes the transitive closure of reachable atoms from `Tokens`, through
- /// the dependency graph.
+ /// Computes the transitive closure of dependencies of (flow-condition)
+ /// `Tokens`. That is, the set of flow-condition tokens reachable from
+ /// `Tokens` in the dependency graph.
llvm::DenseSet<Atom>
- getTransitiveClosure(const llvm::DenseSet<Atom> &Tokens) const;
+ collectDependencies(const llvm::DenseSet<Atom> &Tokens) const;
// Extends the set of modeled field declarations.
void addModeledFields(const FieldSet &Fields);
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
index 487f517420995..4d7c1f033799a 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
@@ -208,7 +208,7 @@ bool DataflowAnalysisContext::equivalentFormulas(const Formula &Val1,
return isUnsatisfiable(std::move(Constraints));
}
-llvm::DenseSet<Atom> DataflowAnalysisContext::getTransitiveClosure(
+llvm::DenseSet<Atom> DataflowAnalysisContext::collectDependencies(
const llvm::DenseSet<Atom> &Tokens) const {
llvm::DenseSet<Atom> VisitedTokens;
// Use a worklist algorithm, with `Remaining` holding the worklist.
@@ -293,16 +293,16 @@ SimpleLogicalContext DataflowAnalysisContext::exportLogicalContext(
getReferencedAtoms(*Invariant, TargetTokens);
}
- llvm::DenseSet<dataflow::Atom> ReachableTokens =
- getTransitiveClosure(TargetTokens);
+ llvm::DenseSet<dataflow::Atom> Dependencies =
+ collectDependencies(TargetTokens);
- for (dataflow::Atom Token : ReachableTokens) {
+ for (dataflow::Atom Token : Dependencies) {
// Only process the token if it is constrained. Unconstrained tokens don't
// have dependencies.
- auto ConstraintsIt = FlowConditionConstraints.find(Token);
- if (ConstraintsIt == FlowConditionConstraints.end())
+ const Formula *Constraints = FlowConditionConstraints.lookup(Token);
+ if (Constraints == nullptr)
continue;
- LC.TokenDefs[Token] = ConstraintsIt->second;
+ LC.TokenDefs[Token] = Constraints;
if (auto DepsIt = FlowConditionDeps.find(Token);
DepsIt != FlowConditionDeps.end())
@@ -316,7 +316,7 @@ void DataflowAnalysisContext::initLogicalContext(SimpleLogicalContext LC) {
Invariant = LC.Invariant;
FlowConditionConstraints = std::move(LC.TokenDefs);
// TODO: The dependencies in `LC.TokenDeps` can be reconstructed from
- // `LC.TokenDefs`. Give the caller the option to reconstruct, , rather than
+ // `LC.TokenDefs`. Give the caller the option to reconstruct, rather than
// providing them directly, to save caller space (memory/disk).
FlowConditionDeps = std::move(LC.TokenDeps);
}
diff --git a/clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp b/clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
index 56cbf5aba8e55..cff1107f34291 100644
--- a/clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
+++ b/clang/lib/Analysis/FlowSensitive/FormulaSerialization.cpp
@@ -70,6 +70,25 @@ void serializeFormula(const Formula &F, llvm::raw_ostream &OS) {
}
}
+static llvm::Expected<const Formula *>
+parsePrefix(llvm::StringRef &Str, Arena &A,
+ llvm::DenseMap<unsigned, Atom> &AtomMap);
+
+static llvm::Expected<const Formula *>
+parseBinaryOp(llvm::StringRef &Str, Arena &A,
+ llvm::DenseMap<unsigned, Atom> &AtomMap,
+ llvm::function_ref<const Formula *(Arena &A, const Formula &L,
+ const Formula &R)>
+ Op) {
+ auto LeftOrErr = parsePrefix(Str, A, AtomMap);
+ if (!LeftOrErr)
+ return LeftOrErr.takeError();
+ auto RightOrErr = parsePrefix(Str, A, AtomMap);
+ if (!RightOrErr)
+ return RightOrErr.takeError();
+ return Op(A, **LeftOrErr, **RightOrErr);
+}
+
static llvm::Expected<const Formula *>
parsePrefix(llvm::StringRef &Str, Arena &A,
llvm::DenseMap<unsigned, Atom> &AtomMap) {
@@ -101,45 +120,29 @@ parsePrefix(llvm::StringRef &Str, Arena &A,
return OperandOrErr.takeError();
return &A.makeNot(**OperandOrErr);
}
- case '&': {
- auto LeftOrErr = parsePrefix(Str, A, AtomMap);
- if (!LeftOrErr)
- return LeftOrErr.takeError();
- auto RightOrErr = parsePrefix(Str, A, AtomMap);
- if (!RightOrErr)
- return RightOrErr.takeError();
- return &A.makeAnd(**LeftOrErr, **RightOrErr);
- }
- case '|': {
- auto LeftOrErr = parsePrefix(Str, A, AtomMap);
- if (!LeftOrErr)
- return LeftOrErr.takeError();
- auto RightOrErr = parsePrefix(Str, A, AtomMap);
- if (!RightOrErr)
- return RightOrErr.takeError();
- return &A.makeOr(**LeftOrErr, **RightOrErr);
- }
- case '>': {
- auto LeftOrErr = parsePrefix(Str, A, AtomMap);
- if (!LeftOrErr)
- return LeftOrErr.takeError();
- auto RightOrErr = parsePrefix(Str, A, AtomMap);
- if (!RightOrErr)
- return RightOrErr.takeError();
- return &A.makeImplies(**LeftOrErr, **RightOrErr);
- }
- case '=': {
- auto LeftOrErr = parsePrefix(Str, A, AtomMap);
- if (!LeftOrErr)
- return LeftOrErr.takeError();
- auto RightOrErr = parsePrefix(Str, A, AtomMap);
- if (!RightOrErr)
- return RightOrErr.takeError();
- return &A.makeEquals(**LeftOrErr, **RightOrErr);
- }
+ case '&':
+ return parseBinaryOp(Str, A, AtomMap,
+ [](Arena &A, const Formula &L, const Formula &R) {
+ return &A.makeAnd(L, R);
+ });
+ case '|':
+ return parseBinaryOp(Str, A, AtomMap,
+ [](Arena &A, const Formula &L, const Formula &R) {
+ return &A.makeOr(L, R);
+ });
+ case '>':
+ return parseBinaryOp(Str, A, AtomMap,
+ [](Arena &A, const Formula &L, const Formula &R) {
+ return &A.makeImplies(L, R);
+ });
+ case '=':
+ return parseBinaryOp(Str, A, AtomMap,
+ [](Arena &A, const Formula &L, const Formula &R) {
+ return &A.makeEquals(L, R);
+ });
default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
- "unexpected prefix character");
+ "unexpected prefix character: %c", Prefix);
}
}
diff --git a/clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp b/clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp
index 49c02a8f192f2..99f5c11a9c0e8 100644
--- a/clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/FormulaTest.cpp
@@ -1,4 +1,4 @@
-//===- unittests/Analysis/FlowSensitive/FormulaTest.cpp -------===//
+//===- unittests/Analysis/FlowSensitive/FormulaTest.cpp -------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -7,7 +7,6 @@
//===----------------------------------------------------------------------===//
#include "clang/Analysis/FlowSensitive/Formula.h"
-#include "TestingSupport.h"
#include "clang/Analysis/FlowSensitive/Arena.h"
#include "clang/Analysis/FlowSensitive/FormulaSerialization.h"
#include "llvm/Support/raw_ostream.h"
@@ -20,7 +19,9 @@ namespace {
using namespace clang;
using namespace dataflow;
+using ::llvm::Failed;
using ::llvm::HasValue;
+using ::llvm::Succeeded;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
@@ -95,6 +96,11 @@ class ParseFormulaTest : public ::testing::Test {
AtomMap[1] = Atom2;
}
+ // Convenience wrapper for `testParseFormula`.
+ llvm::Expected<const Formula *> testParseFormula(llvm::StringRef Str) {
+ return parseFormula(Str, A, AtomMap);
+ }
+
Arena A;
std::string Out;
llvm::raw_string_ostream OS{Out};
@@ -107,53 +113,58 @@ class ParseFormulaTest : public ::testing::Test {
};
TEST_F(ParseFormulaTest, Atom) {
- EXPECT_THAT_EXPECTED(parseFormula("V0", A, AtomMap), HasValue(&A1));
- EXPECT_THAT_EXPECTED(parseFormula("V1", A, AtomMap), HasValue(&A2));
+ EXPECT_THAT_EXPECTED(testParseFormula("V0"), HasValue(&A1));
+ EXPECT_THAT_EXPECTED(testParseFormula("V1"), HasValue(&A2));
}
TEST_F(ParseFormulaTest, LiteralTrue) {
- EXPECT_THAT_EXPECTED(parseFormula("T", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula("T"),
HasValue(&A.makeLiteral(true)));
}
TEST_F(ParseFormulaTest, LiteralFalse) {
- EXPECT_THAT_EXPECTED(parseFormula("F", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula("F"),
HasValue(&A.makeLiteral(false)));
}
TEST_F(ParseFormulaTest, Not) {
- EXPECT_THAT_EXPECTED(parseFormula("!V0", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula("!V0"),
HasValue(&A.makeNot(A1)));
}
TEST_F(ParseFormulaTest, Or) {
- EXPECT_THAT_EXPECTED(parseFormula("|V0V1", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula("|V0V1"),
HasValue(&A.makeOr(A1, A2)));
}
TEST_F(ParseFormulaTest, And) {
- EXPECT_THAT_EXPECTED(parseFormula("&V0V1", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula("&V0V1"),
HasValue(&A.makeAnd(A1, A2)));
}
+TEST_F(ParseFormulaTest, OutOfNumericOrder) {
+ EXPECT_THAT_EXPECTED(parseFormula("&V1V0", A, AtomMap),
+ HasValue(&A.makeAnd(A2, A1)));
+}
+
TEST_F(ParseFormulaTest, Implies) {
- EXPECT_THAT_EXPECTED(parseFormula(">V0V1", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula(">V0V1"),
HasValue(&A.makeImplies(A1, A2)));
}
TEST_F(ParseFormulaTest, Equal) {
- EXPECT_THAT_EXPECTED(parseFormula("=V0V1", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula("=V0V1"),
HasValue(&A.makeEquals(A1, A2)));
}
TEST_F(ParseFormulaTest, NestedBinaryUnary) {
- EXPECT_THAT_EXPECTED(parseFormula("=|V0V1V1", A, AtomMap),
+ EXPECT_THAT_EXPECTED(testParseFormula("=|V0V1V1"),
HasValue(&A.makeEquals(A.makeOr(A1, A2), A2)));
}
TEST_F(ParseFormulaTest, NestedBinaryBinary) {
EXPECT_THAT_EXPECTED(
- parseFormula("=|V0V1&V0V1", A, AtomMap),
+ testParseFormula("=|V0V1&V0V1"),
HasValue(&A.makeEquals(A.makeOr(A1, A2), A.makeAnd(A1, A2))));
}
@@ -161,19 +172,36 @@ TEST_F(ParseFormulaTest, NestedBinaryBinary) {
// map.
TEST_F(ParseFormulaTest, GeneratesAtoms) {
llvm::DenseMap<unsigned, Atom> FreshAtomMap;
- ASSERT_THAT_EXPECTED(parseFormula("=V0V1", A, FreshAtomMap),
- llvm::Succeeded());
+ ASSERT_THAT_EXPECTED(parseFormula("=V0V1", A, FreshAtomMap), Succeeded());
// The map contains two, unique elements.
ASSERT_EQ(FreshAtomMap.size(), 2U);
EXPECT_NE(FreshAtomMap[0], FreshAtomMap[1]);
}
-TEST_F(ParseFormulaTest, BadFormulaFails) {
- EXPECT_THAT_EXPECTED(parseFormula("Hello", A, AtomMap), llvm::Failed());
-}
-
-TEST_F(ParseFormulaTest, FormulaWithSuffixFails) {
- EXPECT_THAT_EXPECTED(parseFormula("=V0V1Hello", A, AtomMap), llvm::Failed());
+TEST_F(ParseFormulaTest, MalformedFormulaFails) {
+ // Arbitrary string.
+ EXPECT_THAT_EXPECTED(testParseFormula("Hello"), Failed());
+ // Empty string.
+ EXPECT_THAT_EXPECTED(testParseFormula(""), Failed());
+ // Malformed atom.
+ EXPECT_THAT_EXPECTED(testParseFormula("Vabc"), Failed());
+ // Irrelevant suffix.
+ EXPECT_THAT_EXPECTED(testParseFormula("V0Hello"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula("=V0V1Hello"), Failed());
+ // Sequence without operator.
+ EXPECT_THAT_EXPECTED(testParseFormula("TF"), Failed());
+ // Bad subformula.
+ EXPECT_THAT_EXPECTED(testParseFormula("!G"), Failed());
+ // Incomplete formulas.
+ EXPECT_THAT_EXPECTED(testParseFormula("V"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula("&"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula("|"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula(">"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula("="), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula("&V0"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula("|V0"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula(">V0"), Failed());
+ EXPECT_THAT_EXPECTED(testParseFormula("=V0"), Failed());
}
} // namespace
More information about the cfe-commits
mailing list