diff --git a/clang/include/clang/Analysis/FlowSensitive/Arena.h b/clang/include/clang/Analysis/FlowSensitive/Arena.h --- a/clang/include/clang/Analysis/FlowSensitive/Arena.h +++ b/clang/include/clang/Analysis/FlowSensitive/Arena.h @@ -8,6 +8,7 @@ #ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE__ARENA_H #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE__ARENA_H +#include "clang/Analysis/FlowSensitive/Formula.h" #include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include @@ -104,7 +105,17 @@ return create(); } + /// Gets the boolean formula equivalent of a BoolValue. + /// Each unique Top values is translated to an Atom. + /// TODO: migrate to storing Formula directly in Values instead. + const Formula &getFormula(const BoolValue &); + + /// Returns a new atomic boolean variable, distinct from any other. + Atom makeAtom() { return static_cast(NextAtom++); }; + private: + llvm::BumpPtrAllocator Alloc; + // Storage for the state of a program. std::vector> Locs; std::vector> Vals; @@ -122,6 +133,9 @@ llvm::DenseMap, BiconditionalValue *> BiconditionalVals; + llvm::DenseMap ValToFormula; + unsigned NextAtom = 0; + AtomicBoolValue &TrueVal; AtomicBoolValue &FalseVal; }; diff --git a/clang/include/clang/Analysis/FlowSensitive/DebugSupport.h b/clang/include/clang/Analysis/FlowSensitive/DebugSupport.h --- a/clang/include/clang/Analysis/FlowSensitive/DebugSupport.h +++ b/clang/include/clang/Analysis/FlowSensitive/DebugSupport.h @@ -19,7 +19,6 @@ #include "clang/Analysis/FlowSensitive/Solver.h" #include "clang/Analysis/FlowSensitive/Value.h" -#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" namespace clang { @@ -28,60 +27,9 @@ /// Returns a string representation of a value kind. llvm::StringRef debugString(Value::Kind Kind); -/// Returns a string representation of a boolean assignment to true or false. -llvm::StringRef debugString(Solver::Result::Assignment Assignment); - /// Returns a string representation of the result status of a SAT check. llvm::StringRef debugString(Solver::Result::Status Status); -/// Returns a string representation for the boolean value `B`. -/// -/// Atomic booleans appearing in the boolean value `B` are assigned to labels -/// either specified in `AtomNames` or created by default rules as B0, B1, ... -/// -/// Requirements: -/// -/// Names assigned to atoms should not be repeated in `AtomNames`. -std::string debugString( - const BoolValue &B, - llvm::DenseMap AtomNames = {{}}); - -/// Returns a string representation for `Constraints` - a collection of boolean -/// formulas. -/// -/// Atomic booleans appearing in the boolean value `Constraints` are assigned to -/// labels either specified in `AtomNames` or created by default rules as B0, -/// B1, ... -/// -/// Requirements: -/// -/// Names assigned to atoms should not be repeated in `AtomNames`. -std::string debugString( - const llvm::DenseSet &Constraints, - llvm::DenseMap AtomNames = {{}}); - -/// Returns a string representation for `Constraints` - a collection of boolean -/// formulas and the `Result` of satisfiability checking. -/// -/// Atomic booleans appearing in `Constraints` and `Result` are assigned to -/// labels either specified in `AtomNames` or created by default rules as B0, -/// B1, ... -/// -/// Requirements: -/// -/// Names assigned to atoms should not be repeated in `AtomNames`. -std::string debugString( - ArrayRef Constraints, const Solver::Result &Result, - llvm::DenseMap AtomNames = {{}}); -inline std::string debugString( - const llvm::DenseSet &Constraints, - const Solver::Result &Result, - llvm::DenseMap AtomNames = {{}}) { - std::vector ConstraintsVec(Constraints.begin(), - Constraints.end()); - return debugString(ConstraintsVec, Result, std::move(AtomNames)); -} - } // namespace dataflow } // namespace clang diff --git a/clang/include/clang/Analysis/FlowSensitive/Formula.h b/clang/include/clang/Analysis/FlowSensitive/Formula.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Analysis/FlowSensitive/Formula.h @@ -0,0 +1,133 @@ +//===- Formula.h - Boolean formulas -----------------------------*- 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_H +#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_FORMULA_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/STLFunctionalExtras.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +namespace clang::dataflow { + +/// Identifies an atomic boolean variable such as "V1". +/// +/// This often represents an assertion that is interesting to the analysis but +/// cannot immediately be proven true or false. For example: +/// - V1 may mean "the program reaches this point", +/// - V2 may mean "the parameter was null" +/// +/// We can use these variables in formulas to describe relationships we know +/// to be true: "if the parameter was null, the program reaches this point". +/// We also express hypotheses as formulas, and use a SAT solver to check +/// whether they are consistent with the known facts. +enum class Atom : unsigned {}; + +/// A boolean expression such as "true" or "V1 & !V2". +/// Expressions may refer to boolean atomic variables. These should take a +/// consistent true/false value across the set of formulas being considered. +/// +/// (Formulas are always expressions in terms of boolean variables rather than +/// e.g. integers because our underlying model is SAT rather than e.g. SMT). +/// +/// Simple formulas such as "true" and "V1" are self-contained. +/// Compound formulas connect other formulas, e.g. "(V1 & V2) || V3" is an 'or' +/// formula, with pointers to its operands "(V1 & V2)" and "V3" stored as +/// trailing objects. +/// For this reason, Formulas are Arena-allocated and over-aligned. +class Formula; +class alignas(const Formula *) Formula { +public: + enum Kind : unsigned { + /// A reference to an atomic boolean variable. + /// We name these e.g. "V3", where 3 == atom identity == Value. + AtomRef, + // FIXME: add const true/false rather than modeling them as variables + + Not, /// True if its only operand is false + + // These kinds connect two operands LHS and RHS + And, /// True if LHS and RHS are both true + Or, /// True if either LHS or RHS is true + Implies, /// True if LHS is false or RHS is true + Equal, /// True if LHS and RHS have the same truth value + }; + Kind kind() const { return FormulaKind; } + + Atom atom() const { + assert(kind() == AtomRef); + return static_cast(Value); + } + + ArrayRef operands() const { + return ArrayRef(reinterpret_cast(this + 1), + numOperands(kind())); + } + + // A function that can optionally override the printing of formulas. + using DelegatePrinter = bool(llvm::raw_ostream &OS, const Formula &); + void print(llvm::raw_ostream &OS, + llvm::function_ref = nullptr) const; + + // Allocate Formulas using Arena rather than calling this function directly. + static Formula &create(llvm::BumpPtrAllocator &Alloc, Kind K, + ArrayRef Operands, + unsigned Value = 0); + +private: + Formula() = default; + Formula(const Formula &) = delete; + Formula &operator=(const Formula &) = delete; + + static unsigned numOperands(Kind K) { + switch (K) { + case AtomRef: + return 0; + case Not: + return 1; + case And: + case Or: + case Implies: + case Equal: + return 2; + } + } + + Kind FormulaKind; + // Some kinds of formula have scalar values, e.g. AtomRef's atom number. + unsigned Value; +}; + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, Atom A) { + return OS << 'V' << static_cast(A); +} +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Formula &F) { + F.print(OS); + return OS; +} + +} // namespace clang::dataflow +namespace llvm { +template <> struct DenseMapInfo { + using Atom = clang::dataflow::Atom; + using Underlying = std::underlying_type_t; + + static inline Atom getEmptyKey() { return Atom(Underlying(-1)); } + static inline Atom getTombstoneKey() { return Atom(Underlying(-2)); } + static unsigned getHashValue(const Atom &Val) { + return DenseMapInfo::getHashValue(Underlying(Val)); + } + static bool isEqual(const Atom &LHS, const Atom &RHS) { return LHS == RHS; } +}; +} // namespace llvm +#endif diff --git a/clang/include/clang/Analysis/FlowSensitive/Solver.h b/clang/include/clang/Analysis/FlowSensitive/Solver.h --- a/clang/include/clang/Analysis/FlowSensitive/Solver.h +++ b/clang/include/clang/Analysis/FlowSensitive/Solver.h @@ -14,7 +14,7 @@ #ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SOLVER_H #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SOLVER_H -#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Analysis/FlowSensitive/Formula.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include @@ -45,8 +45,7 @@ /// Constructs a result indicating that the queried boolean formula is /// satisfiable. The result will hold a solution found by the solver. - static Result - Satisfiable(llvm::DenseMap Solution) { + static Result Satisfiable(llvm::DenseMap Solution) { return Result(Status::Satisfiable, std::move(Solution)); } @@ -64,19 +63,17 @@ /// Returns a truth assignment to boolean values that satisfies the queried /// boolean formula if available. Otherwise, an empty optional is returned. - std::optional> - getSolution() const { + std::optional> getSolution() const { return Solution; } private: - Result( - enum Status SATCheckStatus, - std::optional> Solution) + Result(enum Status SATCheckStatus, + std::optional> Solution) : SATCheckStatus(SATCheckStatus), Solution(std::move(Solution)) {} Status SATCheckStatus; - std::optional> Solution; + std::optional> Solution; }; virtual ~Solver() = default; @@ -87,9 +84,12 @@ /// Requirements: /// /// All elements in `Vals` must not be null. - virtual Result solve(llvm::DenseSet Vals) = 0; + virtual Result solve(const llvm::DenseSet &Vals) = 0; }; +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Solver::Result &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, Solver::Result::Assignment); + } // namespace dataflow } // namespace clang diff --git a/clang/include/clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h b/clang/include/clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h --- a/clang/include/clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h +++ b/clang/include/clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h @@ -14,8 +14,8 @@ #ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_WATCHEDLITERALSSOLVER_H #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_WATCHEDLITERALSSOLVER_H +#include "clang/Analysis/FlowSensitive/Formula.h" #include "clang/Analysis/FlowSensitive/Solver.h" -#include "clang/Analysis/FlowSensitive/Value.h" #include "llvm/ADT/DenseSet.h" #include @@ -46,7 +46,7 @@ explicit WatchedLiteralsSolver(std::int64_t WorkLimit) : MaxIterations(WorkLimit) {} - Result solve(llvm::DenseSet Vals) override; + Result solve(const llvm::DenseSet &Vals) override; }; } // namespace dataflow diff --git a/clang/lib/Analysis/FlowSensitive/Arena.cpp b/clang/lib/Analysis/FlowSensitive/Arena.cpp --- a/clang/lib/Analysis/FlowSensitive/Arena.cpp +++ b/clang/lib/Analysis/FlowSensitive/Arena.cpp @@ -76,4 +76,50 @@ return *It->second; } +const Formula &Arena::getFormula(const BoolValue &B) { + auto It = ValToFormula.find(&B); + if (It != ValToFormula.end()) + return *It->second; + Formula &F = [&]() -> Formula & { + switch (B.getKind()) { + case Value::Kind::Integer: + case Value::Kind::Reference: + case Value::Kind::Pointer: + case Value::Kind::Struct: + llvm_unreachable("not a boolean"); + case Value::Kind::TopBool: + case Value::Kind::AtomicBool: + // TODO: assign atom numbers on creation rather than in getFormula(), so + // they will be deterministic and maybe even meaningful. + return Formula::create(Alloc, Formula::AtomRef, {}, + static_cast(makeAtom())); + case Value::Kind::Conjunction: + return Formula::create( + Alloc, Formula::And, + {&getFormula(cast(B).getLeftSubValue()), + &getFormula(cast(B).getRightSubValue())}); + case Value::Kind::Disjunction: + return Formula::create( + Alloc, Formula::Or, + {&getFormula(cast(B).getLeftSubValue()), + &getFormula(cast(B).getRightSubValue())}); + case Value::Kind::Negation: + return Formula::create(Alloc, Formula::Not, + {&getFormula(cast(B).getSubVal())}); + case Value::Kind::Implication: + return Formula::create( + Alloc, Formula::Implies, + {&getFormula(cast(B).getLeftSubValue()), + &getFormula(cast(B).getRightSubValue())}); + case Value::Kind::Biconditional: + return Formula::create( + Alloc, Formula::Equal, + {&getFormula(cast(B).getLeftSubValue()), + &getFormula(cast(B).getRightSubValue())}); + } + }(); + ValToFormula.try_emplace(&B, &F); + return F; +} + } // namespace clang::dataflow diff --git a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt --- a/clang/lib/Analysis/FlowSensitive/CMakeLists.txt +++ b/clang/lib/Analysis/FlowSensitive/CMakeLists.txt @@ -3,6 +3,7 @@ ControlFlowContext.cpp DataflowAnalysisContext.cpp DataflowEnvironment.cpp + Formula.cpp HTMLLogger.cpp Logger.cpp Transfer.cpp diff --git a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp --- a/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp +++ b/clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp @@ -22,6 +22,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" #include #include #include @@ -129,7 +130,10 @@ Constraints.insert(&arena().makeLiteral(true)); Constraints.insert( &arena().makeNot(arena().makeLiteral(false))); - return S->solve(std::move(Constraints)); + llvm::DenseSet Formulas; + for (const BoolValue * Constraint : Constraints) + Formulas.insert(&arena().getFormula(*Constraint)); + return S->solve(Formulas); } bool DataflowAnalysisContext::flowConditionImplies(AtomicBoolValue &Token, @@ -190,14 +194,35 @@ void DataflowAnalysisContext::dumpFlowCondition(AtomicBoolValue &Token, llvm::raw_ostream &OS) { + // TODO: accumulate formulas directly instead llvm::DenseSet Constraints = {&Token}; llvm::DenseSet VisitedTokens; addTransitiveFlowConditionConstraints(Token, Constraints, VisitedTokens); - llvm::DenseMap AtomNames = { - {&arena().makeLiteral(false), "False"}, - {&arena().makeLiteral(true), "True"}}; - OS << debugString(Constraints, AtomNames); + // TODO: have formulas know about true/false directly instead + auto &True = arena().getFormula(arena().makeLiteral(true)); + auto &False = arena().getFormula(arena().makeLiteral(false)); + auto Delegate = [&](llvm::raw_ostream &OS, const Formula &F) { + if (&F == &True) { + OS << "True"; + return true; + } + if (&F == &False) { + OS << "False"; + return true; + } + return false; + }; + + std::vector Lines; + for (const auto *Constraint : Constraints) { + Lines.emplace_back(); + llvm::raw_string_ostream LineOS(Lines.back()); + arena().getFormula(*Constraint).print(LineOS, Delegate); + } + llvm::sort(Lines); + for (const auto &Line : Lines) + OS << Line << "\n"; } const ControlFlowContext * diff --git a/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp b/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp --- a/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp +++ b/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp @@ -16,22 +16,12 @@ #include "clang/Analysis/FlowSensitive/DebugSupport.h" #include "clang/Analysis/FlowSensitive/Solver.h" #include "clang/Analysis/FlowSensitive/Value.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" -#include "llvm/ADT/StringSet.h" #include "llvm/Support/ErrorHandling.h" -#include "llvm/Support/FormatAdapters.h" -#include "llvm/Support/FormatCommon.h" -#include "llvm/Support/FormatVariadic.h" namespace clang { namespace dataflow { -using llvm::AlignStyle; -using llvm::fmt_pad; -using llvm::formatv; - llvm::StringRef debugString(Value::Kind Kind) { switch (Kind) { case Value::Kind::Integer: @@ -60,12 +50,13 @@ llvm_unreachable("Unhandled value kind"); } -llvm::StringRef debugString(Solver::Result::Assignment Assignment) { +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + Solver::Result::Assignment Assignment) { switch (Assignment) { case Solver::Result::Assignment::AssignedFalse: - return "False"; + return OS << "False"; case Solver::Result::Assignment::AssignedTrue: - return "True"; + return OS << "True"; } llvm_unreachable("Booleans can only be assigned true/false"); } @@ -82,181 +73,16 @@ llvm_unreachable("Unhandled SAT check result status"); } -namespace { - -class DebugStringGenerator { -public: - explicit DebugStringGenerator( - llvm::DenseMap AtomNamesArg) - : Counter(0), AtomNames(std::move(AtomNamesArg)) { -#ifndef NDEBUG - llvm::StringSet<> Names; - for (auto &N : AtomNames) { - assert(Names.insert(N.second).second && - "The same name must not assigned to different atoms"); - } -#endif - } - - /// Returns a string representation of a boolean value `B`. - std::string debugString(const BoolValue &B, size_t Depth = 0) { - std::string S; - switch (B.getKind()) { - case Value::Kind::AtomicBool: { - S = getAtomName(&cast(B)); - break; - } - case Value::Kind::TopBool: { - S = "top"; - break; - } - case Value::Kind::Conjunction: { - auto &C = cast(B); - auto L = debugString(C.getLeftSubValue(), Depth + 1); - auto R = debugString(C.getRightSubValue(), Depth + 1); - S = formatv("(and\n{0}\n{1})", L, R); - break; - } - case Value::Kind::Disjunction: { - auto &D = cast(B); - auto L = debugString(D.getLeftSubValue(), Depth + 1); - auto R = debugString(D.getRightSubValue(), Depth + 1); - S = formatv("(or\n{0}\n{1})", L, R); - break; - } - case Value::Kind::Negation: { - auto &N = cast(B); - S = formatv("(not\n{0})", debugString(N.getSubVal(), Depth + 1)); - break; - } - case Value::Kind::Implication: { - auto &IV = cast(B); - auto L = debugString(IV.getLeftSubValue(), Depth + 1); - auto R = debugString(IV.getRightSubValue(), Depth + 1); - S = formatv("(=>\n{0}\n{1})", L, R); - break; - } - case Value::Kind::Biconditional: { - auto &BV = cast(B); - auto L = debugString(BV.getLeftSubValue(), Depth + 1); - auto R = debugString(BV.getRightSubValue(), Depth + 1); - S = formatv("(=\n{0}\n{1})", L, R); - break; - } - default: - llvm_unreachable("Unhandled value kind"); - } - auto Indent = Depth * 4; - return formatv("{0}", fmt_pad(S, Indent, 0)); - } - - std::string debugString(const llvm::DenseSet &Constraints) { - std::vector ConstraintsStrings; - ConstraintsStrings.reserve(Constraints.size()); - for (BoolValue *Constraint : Constraints) { - ConstraintsStrings.push_back(debugString(*Constraint)); - } - llvm::sort(ConstraintsStrings); - - std::string Result; - for (const std::string &S : ConstraintsStrings) { - Result += S; - Result += '\n'; - } - return Result; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Solver::Result &R) { + OS << debugString(R.getStatus()) << "\n"; + if (auto Solution = R.getSolution()) { + std::vector> Sorted = { + Solution->begin(), Solution->end()}; + llvm::sort(Sorted); + for (const auto &Entry : Sorted) + OS << Entry.first << " = " << Entry.second << "\n"; } - - /// Returns a string representation of a set of boolean `Constraints` and the - /// `Result` of satisfiability checking on the `Constraints`. - std::string debugString(ArrayRef &Constraints, - const Solver::Result &Result) { - auto Template = R"( -Constraints ------------- -{0:$[ - -]} ------------- -{1}. -{2} -)"; - - std::vector ConstraintsStrings; - ConstraintsStrings.reserve(Constraints.size()); - for (auto &Constraint : Constraints) { - ConstraintsStrings.push_back(debugString(*Constraint)); - } - - auto StatusString = clang::dataflow::debugString(Result.getStatus()); - auto Solution = Result.getSolution(); - auto SolutionString = Solution ? "\n" + debugString(*Solution) : ""; - - return formatv( - Template, - llvm::make_range(ConstraintsStrings.begin(), ConstraintsStrings.end()), - StatusString, SolutionString); - } - -private: - /// Returns a string representation of a truth assignment to atom booleans. - std::string debugString( - const llvm::DenseMap - &AtomAssignments) { - size_t MaxNameLength = 0; - for (auto &AtomName : AtomNames) { - MaxNameLength = std::max(MaxNameLength, AtomName.second.size()); - } - - std::vector Lines; - for (auto &AtomAssignment : AtomAssignments) { - auto Line = formatv("{0} = {1}", - fmt_align(getAtomName(AtomAssignment.first), - AlignStyle::Left, MaxNameLength), - clang::dataflow::debugString(AtomAssignment.second)); - Lines.push_back(Line); - } - llvm::sort(Lines); - - return formatv("{0:$[\n]}", llvm::make_range(Lines.begin(), Lines.end())); - } - - /// Returns the name assigned to `Atom`, either user-specified or created by - /// default rules (B0, B1, ...). - std::string getAtomName(const AtomicBoolValue *Atom) { - auto Entry = AtomNames.try_emplace(Atom, formatv("B{0}", Counter)); - if (Entry.second) { - Counter++; - } - return Entry.first->second; - } - - // Keep track of number of atoms without a user-specified name, used to assign - // non-repeating default names to such atoms. - size_t Counter; - - // Keep track of names assigned to atoms. - llvm::DenseMap AtomNames; -}; - -} // namespace - -std::string -debugString(const BoolValue &B, - llvm::DenseMap AtomNames) { - return DebugStringGenerator(std::move(AtomNames)).debugString(B); -} - -std::string -debugString(const llvm::DenseSet &Constraints, - llvm::DenseMap AtomNames) { - return DebugStringGenerator(std::move(AtomNames)).debugString(Constraints); -} - -std::string -debugString(ArrayRef Constraints, const Solver::Result &Result, - llvm::DenseMap AtomNames) { - return DebugStringGenerator(std::move(AtomNames)) - .debugString(Constraints, Result); + return OS; } } // namespace dataflow diff --git a/clang/lib/Analysis/FlowSensitive/Formula.cpp b/clang/lib/Analysis/FlowSensitive/Formula.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/Formula.cpp @@ -0,0 +1,80 @@ +//===- Formula.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/Formula.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/ErrorHandling.h" +#include + +namespace clang::dataflow { + +Formula &Formula::create(llvm::BumpPtrAllocator &Alloc, Kind K, + ArrayRef Operands, unsigned Value) { + assert(Operands.size() == numOperands(K)); + if (Value != 0) // Currently, formulas have values or operands, not both. + assert(numOperands(K) == 0); + void *Mem = Alloc.Allocate(sizeof(Formula) + + Operands.size() * sizeof(Operands.front()), + alignof(Formula)); + Formula *Result = new (Mem) Formula(); + Result->FormulaKind = K; + Result->Value = Value; + // Operands are stored as `const Formula *`s after the formula itself. + // We don't need to construct an object as pointers are trivial types. + // Formula is alignas(const Formula *), so alignment is satisfied. + llvm::copy(Operands, reinterpret_cast(Result + 1)); + return *Result; +} + +static llvm::StringLiteral sigil(Formula::Kind K) { + switch (K) { + case Formula::AtomRef: + return ""; + 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 Formula::print(llvm::raw_ostream &OS, + llvm::function_ref Delegate) const { + if (Delegate && Delegate(OS, *this)) + return; + + switch (numOperands(kind())) { + case 0: + OS << atom(); + break; + case 1: + OS << sigil(kind()); + operands()[0]->print(OS, Delegate); + break; + case 2: + OS << '('; + operands()[0]->print(OS, Delegate); + OS << sigil(kind()); + operands()[1]->print(OS, Delegate); + OS << ')'; + break; + default: + llvm_unreachable("unhandled formula arity"); + } +} + +} // namespace clang::dataflow \ No newline at end of file diff --git a/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp b/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp --- a/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp +++ b/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp @@ -17,8 +17,8 @@ #include #include +#include "clang/Analysis/FlowSensitive/Formula.h" #include "clang/Analysis/FlowSensitive/Solver.h" -#include "clang/Analysis/FlowSensitive/Value.h" #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" @@ -78,7 +78,7 @@ static constexpr ClauseID NullClause = 0; /// A boolean formula in conjunctive normal form. -struct BooleanFormula { +struct CNFFormula { /// `LargestVar` is equal to the largest positive integer that represents a /// variable in the formula. const Variable LargestVar; @@ -120,12 +120,12 @@ /// clauses in the formula start from the element at index 1. std::vector NextWatched; - /// Stores the variable identifier and value location for atomic booleans in - /// the formula. - llvm::DenseMap Atomics; + /// Stores the variable identifier and Atom for atomic booleans in the + /// formula. + llvm::DenseMap Atomics; - explicit BooleanFormula(Variable LargestVar, - llvm::DenseMap Atomics) + explicit CNFFormula(Variable LargestVar, + llvm::DenseMap Atomics) : LargestVar(LargestVar), Atomics(std::move(Atomics)) { Clauses.push_back(0); ClauseStarts.push_back(0); @@ -143,8 +143,8 @@ /// /// All literals in the input that are not `NullLit` must be distinct. void addClause(Literal L1, Literal L2 = NullLit, Literal L3 = NullLit) { - // The literals are guaranteed to be distinct from properties of BoolValue - // and the construction in `buildBooleanFormula`. + // The literals are guaranteed to be distinct from properties of Formula + // and the construction in `buildCNF`. assert(L1 != NullLit && L1 != L2 && L1 != L3 && (L2 != L3 || L2 == NullLit)); @@ -177,98 +177,59 @@ /// Converts the conjunction of `Vals` into a formula in conjunctive normal /// form where each clause has at least one and at most three literals. -BooleanFormula buildBooleanFormula(const llvm::DenseSet &Vals) { +CNFFormula buildCNF(const llvm::DenseSet &Vals) { // The general strategy of the algorithm implemented below is to map each // of the sub-values in `Vals` to a unique variable and use these variables in // the resulting CNF expression to avoid exponential blow up. The number of // literals in the resulting formula is guaranteed to be linear in the number - // of sub-values in `Vals`. + // of sub-formulas in `Vals`. - // Map each sub-value in `Vals` to a unique variable. - llvm::DenseMap SubValsToVar; - // Store variable identifiers and value location of atomic booleans. - llvm::DenseMap Atomics; + // Map each sub-formula in `Vals` to a unique variable. + llvm::DenseMap SubValsToVar; + // Store variable identifiers and Atom of atomic booleans. + llvm::DenseMap Atomics; Variable NextVar = 1; { - std::queue UnprocessedSubVals; - for (BoolValue *Val : Vals) + std::queue UnprocessedSubVals; + for (const Formula *Val : Vals) UnprocessedSubVals.push(Val); while (!UnprocessedSubVals.empty()) { Variable Var = NextVar; - BoolValue *Val = UnprocessedSubVals.front(); + const Formula *Val = UnprocessedSubVals.front(); UnprocessedSubVals.pop(); if (!SubValsToVar.try_emplace(Val, Var).second) continue; ++NextVar; - // Visit the sub-values of `Val`. - switch (Val->getKind()) { - case Value::Kind::Conjunction: { - auto *C = cast(Val); - UnprocessedSubVals.push(&C->getLeftSubValue()); - UnprocessedSubVals.push(&C->getRightSubValue()); - break; - } - case Value::Kind::Disjunction: { - auto *D = cast(Val); - UnprocessedSubVals.push(&D->getLeftSubValue()); - UnprocessedSubVals.push(&D->getRightSubValue()); - break; - } - case Value::Kind::Negation: { - auto *N = cast(Val); - UnprocessedSubVals.push(&N->getSubVal()); - break; - } - case Value::Kind::Implication: { - auto *I = cast(Val); - UnprocessedSubVals.push(&I->getLeftSubValue()); - UnprocessedSubVals.push(&I->getRightSubValue()); - break; - } - case Value::Kind::Biconditional: { - auto *B = cast(Val); - UnprocessedSubVals.push(&B->getLeftSubValue()); - UnprocessedSubVals.push(&B->getRightSubValue()); - break; - } - case Value::Kind::TopBool: - // Nothing more to do. This `TopBool` instance has already been mapped - // to a fresh solver variable (`NextVar`, above) and is thereafter - // anonymous. The solver never sees `Top`. - break; - case Value::Kind::AtomicBool: { - Atomics[Var] = cast(Val); - break; - } - default: - llvm_unreachable("buildBooleanFormula: unhandled value kind"); - } + for (const Formula *F : Val->operands()) + UnprocessedSubVals.push(F); + if (Val->kind() == Formula::AtomRef) + Atomics[Var] = Val->atom(); } } - auto GetVar = [&SubValsToVar](const BoolValue *Val) { + auto GetVar = [&SubValsToVar](const Formula *Val) { auto ValIt = SubValsToVar.find(Val); assert(ValIt != SubValsToVar.end()); return ValIt->second; }; - BooleanFormula Formula(NextVar - 1, std::move(Atomics)); + CNFFormula CNF(NextVar - 1, std::move(Atomics)); std::vector ProcessedSubVals(NextVar, false); - // Add a conjunct for each variable that represents a top-level conjunction - // value in `Vals`. - for (BoolValue *Val : Vals) - Formula.addClause(posLit(GetVar(Val))); + // Add a conjunct for each variable that represents a top-level formula in + // `Vals`. + for (const Formula *Val : Vals) + CNF.addClause(posLit(GetVar(Val))); // Add conjuncts that represent the mapping between newly-created variables - // and their corresponding sub-values. - std::queue UnprocessedSubVals; - for (BoolValue *Val : Vals) + // and their corresponding sub-formulas. + std::queue UnprocessedSubVals; + for (const Formula *Val : Vals) UnprocessedSubVals.push(Val); while (!UnprocessedSubVals.empty()) { - const BoolValue *Val = UnprocessedSubVals.front(); + const Formula *Val = UnprocessedSubVals.front(); UnprocessedSubVals.pop(); const Variable Var = GetVar(Val); @@ -276,117 +237,107 @@ continue; ProcessedSubVals[Var] = true; - if (auto *C = dyn_cast(Val)) { - const Variable LeftSubVar = GetVar(&C->getLeftSubValue()); - const Variable RightSubVar = GetVar(&C->getRightSubValue()); + switch (Val->kind()) { + case Formula::AtomRef: + break; + case Formula::And: { + const Variable LHS = GetVar(Val->operands()[0]); + const Variable RHS = GetVar(Val->operands()[1]); - if (LeftSubVar == RightSubVar) { + if (LHS == RHS) { // `X <=> (A ^ A)` is equivalent to `(!X v A) ^ (X v !A)` which is // already in conjunctive normal form. Below we add each of the // conjuncts of the latter expression to the result. - Formula.addClause(negLit(Var), posLit(LeftSubVar)); - Formula.addClause(posLit(Var), negLit(LeftSubVar)); - - // Visit a sub-value of `Val` (pick any, they are identical). - UnprocessedSubVals.push(&C->getLeftSubValue()); + CNF.addClause(negLit(Var), posLit(LHS)); + CNF.addClause(posLit(Var), negLit(LHS)); } else { // `X <=> (A ^ B)` is equivalent to `(!X v A) ^ (!X v B) ^ (X v !A v !B)` // which is already in conjunctive normal form. Below we add each of the // conjuncts of the latter expression to the result. - Formula.addClause(negLit(Var), posLit(LeftSubVar)); - Formula.addClause(negLit(Var), posLit(RightSubVar)); - Formula.addClause(posLit(Var), negLit(LeftSubVar), negLit(RightSubVar)); - - // Visit the sub-values of `Val`. - UnprocessedSubVals.push(&C->getLeftSubValue()); - UnprocessedSubVals.push(&C->getRightSubValue()); + CNF.addClause(negLit(Var), posLit(LHS)); + CNF.addClause(negLit(Var), posLit(RHS)); + CNF.addClause(posLit(Var), negLit(LHS), negLit(RHS)); } - } else if (auto *D = dyn_cast(Val)) { - const Variable LeftSubVar = GetVar(&D->getLeftSubValue()); - const Variable RightSubVar = GetVar(&D->getRightSubValue()); + break; + } + case Formula::Or: { + const Variable LHS = GetVar(Val->operands()[0]); + const Variable RHS = GetVar(Val->operands()[1]); - if (LeftSubVar == RightSubVar) { + if (LHS == RHS) { // `X <=> (A v A)` is equivalent to `(!X v A) ^ (X v !A)` which is // already in conjunctive normal form. Below we add each of the // conjuncts of the latter expression to the result. - Formula.addClause(negLit(Var), posLit(LeftSubVar)); - Formula.addClause(posLit(Var), negLit(LeftSubVar)); - - // Visit a sub-value of `Val` (pick any, they are identical). - UnprocessedSubVals.push(&D->getLeftSubValue()); + CNF.addClause(negLit(Var), posLit(LHS)); + CNF.addClause(posLit(Var), negLit(LHS)); } else { - // `X <=> (A v B)` is equivalent to `(!X v A v B) ^ (X v !A) ^ (X v !B)` - // which is already in conjunctive normal form. Below we add each of the - // conjuncts of the latter expression to the result. - Formula.addClause(negLit(Var), posLit(LeftSubVar), posLit(RightSubVar)); - Formula.addClause(posLit(Var), negLit(LeftSubVar)); - Formula.addClause(posLit(Var), negLit(RightSubVar)); - - // Visit the sub-values of `Val`. - UnprocessedSubVals.push(&D->getLeftSubValue()); - UnprocessedSubVals.push(&D->getRightSubValue()); + // `X <=> (A v B)` is equivalent to `(!X v A v B) ^ (X v !A) ^ (X v + // !B)` which is already in conjunctive normal form. Below we add each + // of the conjuncts of the latter expression to the result. + CNF.addClause(negLit(Var), posLit(LHS), posLit(RHS)); + CNF.addClause(posLit(Var), negLit(LHS)); + CNF.addClause(posLit(Var), negLit(RHS)); } - } else if (auto *N = dyn_cast(Val)) { - const Variable SubVar = GetVar(&N->getSubVal()); - - // `X <=> !Y` is equivalent to `(!X v !Y) ^ (X v Y)` which is already in - // conjunctive normal form. Below we add each of the conjuncts of the - // latter expression to the result. - Formula.addClause(negLit(Var), negLit(SubVar)); - Formula.addClause(posLit(Var), posLit(SubVar)); - - // Visit the sub-values of `Val`. - UnprocessedSubVals.push(&N->getSubVal()); - } else if (auto *I = dyn_cast(Val)) { - const Variable LeftSubVar = GetVar(&I->getLeftSubValue()); - const Variable RightSubVar = GetVar(&I->getRightSubValue()); + break; + } + case Formula::Not: { + const Variable Operand = GetVar(Val->operands()[0]); + + // `X <=> !Y` is equivalent to `(!X v !Y) ^ (X v Y)` which is + // already in conjunctive normal form. Below we add each of the + // conjuncts of the latter expression to the result. + CNF.addClause(negLit(Var), negLit(Operand)); + CNF.addClause(posLit(Var), posLit(Operand)); + break; + } + case Formula::Implies: { + const Variable LHS = GetVar(Val->operands()[0]); + const Variable RHS = GetVar(Val->operands()[1]); // `X <=> (A => B)` is equivalent to // `(X v A) ^ (X v !B) ^ (!X v !A v B)` which is already in - // conjunctive normal form. Below we add each of the conjuncts of the - // latter expression to the result. - Formula.addClause(posLit(Var), posLit(LeftSubVar)); - Formula.addClause(posLit(Var), negLit(RightSubVar)); - Formula.addClause(negLit(Var), negLit(LeftSubVar), posLit(RightSubVar)); - - // Visit the sub-values of `Val`. - UnprocessedSubVals.push(&I->getLeftSubValue()); - UnprocessedSubVals.push(&I->getRightSubValue()); - } else if (auto *B = dyn_cast(Val)) { - const Variable LeftSubVar = GetVar(&B->getLeftSubValue()); - const Variable RightSubVar = GetVar(&B->getRightSubValue()); - - if (LeftSubVar == RightSubVar) { + // conjunctive normal form. Below we add each of the conjuncts of + // the latter expression to the result. + CNF.addClause(posLit(Var), posLit(LHS)); + CNF.addClause(posLit(Var), negLit(RHS)); + CNF.addClause(negLit(Var), negLit(LHS), posLit(RHS)); + break; + } + case Formula::Equal: { + const Variable LHS = GetVar(Val->operands()[0]); + const Variable RHS = GetVar(Val->operands()[1]); + + if (LHS == RHS) { // `X <=> (A <=> A)` is equvalent to `X` which is already in // conjunctive normal form. Below we add each of the conjuncts of the // latter expression to the result. - Formula.addClause(posLit(Var)); + CNF.addClause(posLit(Var)); // No need to visit the sub-values of `Val`. - } else { - // `X <=> (A <=> B)` is equivalent to - // `(X v A v B) ^ (X v !A v !B) ^ (!X v A v !B) ^ (!X v !A v B)` which is - // already in conjunctive normal form. Below we add each of the conjuncts - // of the latter expression to the result. - Formula.addClause(posLit(Var), posLit(LeftSubVar), posLit(RightSubVar)); - Formula.addClause(posLit(Var), negLit(LeftSubVar), negLit(RightSubVar)); - Formula.addClause(negLit(Var), posLit(LeftSubVar), negLit(RightSubVar)); - Formula.addClause(negLit(Var), negLit(LeftSubVar), posLit(RightSubVar)); - - // Visit the sub-values of `Val`. - UnprocessedSubVals.push(&B->getLeftSubValue()); - UnprocessedSubVals.push(&B->getRightSubValue()); + continue; } + // `X <=> (A <=> B)` is equivalent to + // `(X v A v B) ^ (X v !A v !B) ^ (!X v A v !B) ^ (!X v !A v B)` which + // is already in conjunctive normal form. Below we add each of the + // conjuncts of the latter expression to the result. + CNF.addClause(posLit(Var), posLit(LHS), posLit(RHS)); + CNF.addClause(posLit(Var), negLit(LHS), negLit(RHS)); + CNF.addClause(negLit(Var), posLit(LHS), negLit(RHS)); + CNF.addClause(negLit(Var), negLit(LHS), posLit(RHS)); + break; + } } + for (const Formula *Child : Val->operands()) + UnprocessedSubVals.push(Child); } - return Formula; + return CNF; } class WatchedLiteralsSolverImpl { /// A boolean formula in conjunctive normal form that the solver will attempt /// to prove satisfiable. The formula will be modified in the process. - BooleanFormula Formula; + CNFFormula CNF; /// The search for a satisfying assignment of the variables in `Formula` will /// proceed in levels, starting from 1 and going up to `Formula.LargestVar` @@ -438,9 +389,10 @@ std::vector ActiveVars; public: - explicit WatchedLiteralsSolverImpl(const llvm::DenseSet &Vals) - : Formula(buildBooleanFormula(Vals)), LevelVars(Formula.LargestVar + 1), - LevelStates(Formula.LargestVar + 1) { + explicit WatchedLiteralsSolverImpl( + const llvm::DenseSet &Vals) + : CNF(buildCNF(Vals)), LevelVars(CNF.LargestVar + 1), + LevelStates(CNF.LargestVar + 1) { assert(!Vals.empty()); // Initialize the state at the root level to a decision so that in @@ -449,10 +401,10 @@ LevelStates[0] = State::Decision; // Initialize all variables as unassigned. - VarAssignments.resize(Formula.LargestVar + 1, Assignment::Unassigned); + VarAssignments.resize(CNF.LargestVar + 1, Assignment::Unassigned); // Initialize the active variables. - for (Variable Var = Formula.LargestVar; Var != NullVar; --Var) { + for (Variable Var = CNF.LargestVar; Var != NullVar; --Var) { if (isWatched(posLit(Var)) || isWatched(negLit(Var))) ActiveVars.push_back(Var); } @@ -473,7 +425,7 @@ // 3. Unassigned variables that form watched literals are active. // FIXME: Consider replacing these with test cases that fail if the any // of the invariants is broken. That might not be easy due to the - // transformations performed by `buildBooleanFormula`. + // transformations performed by `buildCNF`. assert(activeVarsAreUnassigned()); assert(activeVarsFormWatchedLiterals()); assert(unassignedVarsFormingWatchedLiteralsAreActive()); @@ -554,12 +506,10 @@ } private: - /// Returns a satisfying truth assignment to the atomic values in the boolean - /// formula. - llvm::DenseMap - buildSolution() { - llvm::DenseMap Solution; - for (auto &Atomic : Formula.Atomics) { + /// Returns a satisfying truth assignment to the atoms in the boolean formula. + llvm::DenseMap buildSolution() { + llvm::DenseMap Solution; + for (auto &Atomic : CNF.Atomics) { // A variable may have a definite true/false assignment, or it may be // unassigned indicating its truth value does not affect the result of // the formula. Unassigned variables are assigned to true as a default. @@ -595,24 +545,24 @@ const Literal FalseLit = VarAssignments[Var] == Assignment::AssignedTrue ? negLit(Var) : posLit(Var); - ClauseID FalseLitWatcher = Formula.WatchedHead[FalseLit]; - Formula.WatchedHead[FalseLit] = NullClause; + ClauseID FalseLitWatcher = CNF.WatchedHead[FalseLit]; + CNF.WatchedHead[FalseLit] = NullClause; while (FalseLitWatcher != NullClause) { - const ClauseID NextFalseLitWatcher = Formula.NextWatched[FalseLitWatcher]; + const ClauseID NextFalseLitWatcher = CNF.NextWatched[FalseLitWatcher]; // Pick the first non-false literal as the new watched literal. - const size_t FalseLitWatcherStart = Formula.ClauseStarts[FalseLitWatcher]; + const size_t FalseLitWatcherStart = CNF.ClauseStarts[FalseLitWatcher]; size_t NewWatchedLitIdx = FalseLitWatcherStart + 1; - while (isCurrentlyFalse(Formula.Clauses[NewWatchedLitIdx])) + while (isCurrentlyFalse(CNF.Clauses[NewWatchedLitIdx])) ++NewWatchedLitIdx; - const Literal NewWatchedLit = Formula.Clauses[NewWatchedLitIdx]; + const Literal NewWatchedLit = CNF.Clauses[NewWatchedLitIdx]; const Variable NewWatchedLitVar = var(NewWatchedLit); // Swap the old watched literal for the new one in `FalseLitWatcher` to // maintain the invariant that the watched literal is at the beginning of // the clause. - Formula.Clauses[NewWatchedLitIdx] = FalseLit; - Formula.Clauses[FalseLitWatcherStart] = NewWatchedLit; + CNF.Clauses[NewWatchedLitIdx] = FalseLit; + CNF.Clauses[FalseLitWatcherStart] = NewWatchedLit; // If the new watched literal isn't watched by any other clause and its // variable isn't assigned we need to add it to the active variables. @@ -620,8 +570,8 @@ VarAssignments[NewWatchedLitVar] == Assignment::Unassigned) ActiveVars.push_back(NewWatchedLitVar); - Formula.NextWatched[FalseLitWatcher] = Formula.WatchedHead[NewWatchedLit]; - Formula.WatchedHead[NewWatchedLit] = FalseLitWatcher; + CNF.NextWatched[FalseLitWatcher] = CNF.WatchedHead[NewWatchedLit]; + CNF.WatchedHead[NewWatchedLit] = FalseLitWatcher; // Go to the next clause that watches `FalseLit`. FalseLitWatcher = NextFalseLitWatcher; @@ -631,16 +581,16 @@ /// Returns true if and only if one of the clauses that watch `Lit` is a unit /// clause. bool watchedByUnitClause(Literal Lit) const { - for (ClauseID LitWatcher = Formula.WatchedHead[Lit]; + for (ClauseID LitWatcher = CNF.WatchedHead[Lit]; LitWatcher != NullClause; - LitWatcher = Formula.NextWatched[LitWatcher]) { - llvm::ArrayRef Clause = Formula.clauseLiterals(LitWatcher); + LitWatcher = CNF.NextWatched[LitWatcher]) { + llvm::ArrayRef Clause = CNF.clauseLiterals(LitWatcher); // Assert the invariant that the watched literal is always the first one // in the clause. // FIXME: Consider replacing this with a test case that fails if the // invariant is broken by `updateWatchedLiterals`. That might not be easy - // due to the transformations performed by `buildBooleanFormula`. + // due to the transformations performed by `buildCNF`. assert(Clause.front() == Lit); if (isUnit(Clause)) @@ -664,7 +614,7 @@ /// Returns true if and only if `Lit` is watched by a clause in `Formula`. bool isWatched(Literal Lit) const { - return Formula.WatchedHead[Lit] != NullClause; + return CNF.WatchedHead[Lit] != NullClause; } /// Returns an assignment for an unassigned variable. @@ -677,8 +627,8 @@ /// Returns a set of all watched literals. llvm::DenseSet watchedLiterals() const { llvm::DenseSet WatchedLiterals; - for (Literal Lit = 2; Lit < Formula.WatchedHead.size(); Lit++) { - if (Formula.WatchedHead[Lit] == NullClause) + for (Literal Lit = 2; Lit < CNF.WatchedHead.size(); Lit++) { + if (CNF.WatchedHead[Lit] == NullClause) continue; WatchedLiterals.insert(Lit); } @@ -718,7 +668,8 @@ } }; -Solver::Result WatchedLiteralsSolver::solve(llvm::DenseSet Vals) { +Solver::Result +WatchedLiteralsSolver::solve(const llvm::DenseSet &Vals) { if (Vals.empty()) return Solver::Result::Satisfiable({{}}); auto [Res, Iterations] = diff --git a/clang/unittests/Analysis/FlowSensitive/ArenaTest.cpp b/clang/unittests/Analysis/FlowSensitive/ArenaTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/ArenaTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/ArenaTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/FlowSensitive/Arena.h" +#include "llvm/Support/ScopedPrinter.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -125,5 +126,26 @@ EXPECT_NE(&XIffY1, &XIffZ); } +TEST_F(ArenaTest, ValueToFormula) { + auto &X = A.create(); + auto &Y = A.create(); + auto &XIffY = A.makeEquals(X, Y); + auto &XOrNotY = A.makeOr(X, A.makeNot(Y)); + auto &Implies = A.makeImplies(XIffY, XOrNotY); + + EXPECT_EQ(llvm::to_string(A.getFormula(Implies)), + "((V0 = V1) => (V0 | !V1))"); +} + +TEST_F(ArenaTest, ValueToFormulaCached) { + auto &X = A.create(); + auto &Y = A.create(); + auto &XIffY = A.makeEquals(X, Y); + + auto &Formula1 = A.getFormula(XIffY); + auto &Formula2 = A.getFormula(XIffY); + EXPECT_EQ(&Formula1, &Formula2); +} + } // namespace } // namespace clang::dataflow diff --git a/clang/unittests/Analysis/FlowSensitive/DebugSupportTest.cpp b/clang/unittests/Analysis/FlowSensitive/DebugSupportTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/DebugSupportTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/DebugSupportTest.cpp @@ -8,8 +8,11 @@ #include "clang/Analysis/FlowSensitive/DebugSupport.h" #include "TestingSupport.h" +#include "clang/Analysis/FlowSensitive/Formula.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/raw_ostream.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -22,431 +25,96 @@ using testing::StrEq; TEST(BoolValueDebugStringTest, AtomicBoolean) { - // B0 ConstraintContext Ctx; auto B = Ctx.atom(); - auto Expected = R"(B0)"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); -} - -TEST(BoolValueDebugStringTest, Top) { - ConstraintContext Ctx; - auto B = Ctx.top(); - - auto Expected = R"(top)"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); + auto Expected = "V0"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, Negation) { - // !B0 ConstraintContext Ctx; auto B = Ctx.neg(Ctx.atom()); - auto Expected = R"((not - B0))"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); + auto Expected = "!V0"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, Conjunction) { - // B0 ^ B1 ConstraintContext Ctx; auto B = Ctx.conj(Ctx.atom(), Ctx.atom()); - auto Expected = R"((and - B0 - B1))"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); + auto Expected = "(V0 & V1)"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, Disjunction) { - // B0 v B1 ConstraintContext Ctx; auto B = Ctx.disj(Ctx.atom(), Ctx.atom()); - auto Expected = R"((or - B0 - B1))"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); + auto Expected = "(V0 | V1)"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, Implication) { - // B0 => B1 ConstraintContext Ctx; auto B = Ctx.impl(Ctx.atom(), Ctx.atom()); - auto Expected = R"((=> - B0 - B1))"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); + auto Expected = "(V0 => V1)"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, Iff) { - // B0 <=> B1 ConstraintContext Ctx; auto B = Ctx.iff(Ctx.atom(), Ctx.atom()); - auto Expected = R"((= - B0 - B1))"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); + auto Expected = "(V0 = V1)"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, Xor) { - // (B0 ^ !B1) V (!B0 ^ B1) ConstraintContext Ctx; auto B0 = Ctx.atom(); auto B1 = Ctx.atom(); auto B = Ctx.disj(Ctx.conj(B0, Ctx.neg(B1)), Ctx.conj(Ctx.neg(B0), B1)); - auto Expected = R"((or - (and - B0 - (not - B1)) - (and - (not - B0) - B1)))"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); + auto Expected = "((V0 & !V1) | (!V0 & V1))"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, NestedBoolean) { - // B0 ^ (B1 v (B2 ^ (B3 v B4))) ConstraintContext Ctx; auto B = Ctx.conj( Ctx.atom(), Ctx.disj(Ctx.atom(), Ctx.conj(Ctx.atom(), Ctx.disj(Ctx.atom(), Ctx.atom())))); - auto Expected = R"((and - B0 - (or - B1 - (and - B2 - (or - B3 - B4)))))"; - EXPECT_THAT(debugString(*B), StrEq(Expected)); -} - -TEST(BoolValueDebugStringTest, AtomicBooleanWithName) { - // True - ConstraintContext Ctx; - auto True = cast(Ctx.atom()); - auto B = True; - - auto Expected = R"(True)"; - EXPECT_THAT(debugString(*B, {{True, "True"}}), StrEq(Expected)); -} - -TEST(BoolValueDebugStringTest, ComplexBooleanWithNames) { - // (Cond ^ Then ^ !Else) v (!Cond ^ !Then ^ Else) - ConstraintContext Ctx; - auto Cond = cast(Ctx.atom()); - auto Then = cast(Ctx.atom()); - auto Else = cast(Ctx.atom()); - auto B = Ctx.disj(Ctx.conj(Cond, Ctx.conj(Then, Ctx.neg(Else))), - Ctx.conj(Ctx.neg(Cond), Ctx.conj(Ctx.neg(Then), Else))); - - auto Expected = R"((or - (and - Cond - (and - Then - (not - Else))) - (and - (not - Cond) - (and - (not - Then) - Else))))"; - EXPECT_THAT(debugString(*B, {{Cond, "Cond"}, {Then, "Then"}, {Else, "Else"}}), - StrEq(Expected)); + auto Expected = "(V0 & (V1 | (V2 & (V3 | V4))))"; + EXPECT_THAT(llvm::to_string(*B), StrEq(Expected)); } TEST(BoolValueDebugStringTest, ComplexBooleanWithSomeNames) { - // (False && B0) v (True v B1) ConstraintContext Ctx; - auto True = cast(Ctx.atom()); - auto False = cast(Ctx.atom()); + auto True = Ctx.atom(); + auto False = Ctx.atom(); + auto Delegate = [&](llvm::raw_ostream &OS, const Formula &F) { + if (&F == True) { + OS << "True"; + return true; + } + if (&F == False) { + OS << "False"; + return true; + } + return false; + }; auto B = Ctx.disj(Ctx.conj(False, Ctx.atom()), Ctx.disj(True, Ctx.atom())); - auto Expected = R"((or - (and - False - B0) - (or - True - B1)))"; - EXPECT_THAT(debugString(*B, {{True, "True"}, {False, "False"}}), - StrEq(Expected)); -} - -TEST(ConstraintSetDebugStringTest, Empty) { - llvm::DenseSet Constraints; - EXPECT_THAT(debugString(Constraints), StrEq("")); -} - -TEST(ConstraintSetDebugStringTest, Simple) { - ConstraintContext Ctx; - llvm::DenseSet Constraints; - auto X = cast(Ctx.atom()); - auto Y = cast(Ctx.atom()); - Constraints.insert(Ctx.disj(X, Y)); - Constraints.insert(Ctx.disj(X, Ctx.neg(Y))); - - auto Expected = R"((or - X - (not - Y)) -(or - X - Y) -)"; - EXPECT_THAT(debugString(Constraints, {{X, "X"}, {Y, "Y"}}), - StrEq(Expected)); -} - -Solver::Result CheckSAT(std::vector Constraints) { - llvm::DenseSet ConstraintsSet(Constraints.begin(), - Constraints.end()); - return WatchedLiteralsSolver().solve(std::move(ConstraintsSet)); -} - -TEST(SATCheckDebugStringTest, AtomicBoolean) { - // B0 - ConstraintContext Ctx; - std::vector Constraints({Ctx.atom()}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -B0 ------------- -Satisfiable. - -B0 = True -)"; - EXPECT_THAT(debugString(Constraints, Result), StrEq(Expected)); -} - -TEST(SATCheckDebugStringTest, AtomicBooleanAndNegation) { - // B0, !B0 - ConstraintContext Ctx; - auto B0 = Ctx.atom(); - std::vector Constraints({B0, Ctx.neg(B0)}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -B0 - -(not - B0) ------------- -Unsatisfiable. - -)"; - EXPECT_THAT(debugString(Constraints, Result), StrEq(Expected)); -} - -TEST(SATCheckDebugStringTest, MultipleAtomicBooleans) { - // B0, B1 - ConstraintContext Ctx; - std::vector Constraints({Ctx.atom(), Ctx.atom()}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -B0 - -B1 ------------- -Satisfiable. - -B0 = True -B1 = True -)"; - EXPECT_THAT(debugString(Constraints, Result), StrEq(Expected)); -} - -TEST(SATCheckDebugStringTest, Implication) { - // B0, B0 => B1 - ConstraintContext Ctx; - auto B0 = Ctx.atom(); - auto B1 = Ctx.atom(); - auto Impl = Ctx.disj(Ctx.neg(B0), B1); - std::vector Constraints({B0, Impl}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -B0 - -(or - (not - B0) - B1) ------------- -Satisfiable. - -B0 = True -B1 = True -)"; - EXPECT_THAT(debugString(Constraints, Result), StrEq(Expected)); + auto Expected = R"(((False & V2) | (True | V3)))"; + std::string Actual; + llvm::raw_string_ostream OS(Actual); + B->print(OS, Delegate); + EXPECT_THAT(Actual, StrEq(Expected)); } -TEST(SATCheckDebugStringTest, Iff) { - // B0, B0 <=> B1 - ConstraintContext Ctx; - auto B0 = Ctx.atom(); - auto B1 = Ctx.atom(); - auto Iff = Ctx.conj(Ctx.disj(Ctx.neg(B0), B1), Ctx.disj(B0, Ctx.neg(B1))); - std::vector Constraints({B0, Iff}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -B0 - -(and - (or - (not - B0) - B1) - (or - B0 - (not - B1))) ------------- -Satisfiable. - -B0 = True -B1 = True -)"; - EXPECT_THAT(debugString(Constraints, Result), StrEq(Expected)); -} - -TEST(SATCheckDebugStringTest, Xor) { - // B0, XOR(B0, B1) - ConstraintContext Ctx; - auto B0 = Ctx.atom(); - auto B1 = Ctx.atom(); - auto XOR = Ctx.disj(Ctx.conj(B0, Ctx.neg(B1)), Ctx.conj(Ctx.neg(B0), B1)); - std::vector Constraints({B0, XOR}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -B0 - -(or - (and - B0 - (not - B1)) - (and - (not - B0) - B1)) ------------- -Satisfiable. - -B0 = True -B1 = False -)"; - EXPECT_THAT(debugString(Constraints, Result), StrEq(Expected)); -} - -TEST(SATCheckDebugStringTest, ComplexBooleanWithNames) { - // Cond, (Cond ^ Then ^ !Else) v (!Cond ^ !Then ^ Else) - ConstraintContext Ctx; - auto Cond = cast(Ctx.atom()); - auto Then = cast(Ctx.atom()); - auto Else = cast(Ctx.atom()); - auto B = Ctx.disj(Ctx.conj(Cond, Ctx.conj(Then, Ctx.neg(Else))), - Ctx.conj(Ctx.neg(Cond), Ctx.conj(Ctx.neg(Then), Else))); - std::vector Constraints({Cond, B}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -Cond - -(or - (and - Cond - (and - Then - (not - Else))) - (and - (not - Cond) - (and - (not - Then) - Else))) ------------- -Satisfiable. - -Cond = True -Else = False -Then = True -)"; - EXPECT_THAT(debugString(Constraints, Result, - {{Cond, "Cond"}, {Then, "Then"}, {Else, "Else"}}), - StrEq(Expected)); -} - -TEST(SATCheckDebugStringTest, ComplexBooleanWithLongNames) { - // ThisIntEqZero, !IntsAreEq, ThisIntEqZero ^ OtherIntEqZero => IntsAreEq - ConstraintContext Ctx; - auto IntsAreEq = cast(Ctx.atom()); - auto ThisIntEqZero = cast(Ctx.atom()); - auto OtherIntEqZero = cast(Ctx.atom()); - auto BothZeroImpliesEQ = - Ctx.disj(Ctx.neg(Ctx.conj(ThisIntEqZero, OtherIntEqZero)), IntsAreEq); - std::vector Constraints( - {ThisIntEqZero, Ctx.neg(IntsAreEq), BothZeroImpliesEQ}); - auto Result = CheckSAT(Constraints); - - auto Expected = R"( -Constraints ------------- -ThisIntEqZero - -(not - IntsAreEq) - -(or - (not - (and - ThisIntEqZero - OtherIntEqZero)) - IntsAreEq) ------------- -Satisfiable. - -IntsAreEq = False -OtherIntEqZero = False -ThisIntEqZero = True -)"; - EXPECT_THAT(debugString(Constraints, Result, - {{IntsAreEq, "IntsAreEq"}, - {ThisIntEqZero, "ThisIntEqZero"}, - {OtherIntEqZero, "OtherIntEqZero"}}), - StrEq(Expected)); -} } // namespace diff --git a/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp b/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp --- a/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp @@ -30,19 +30,23 @@ // Checks if the conjunction of `Vals` is satisfiable and returns the // corresponding result. -Solver::Result solve(llvm::DenseSet Vals) { +Solver::Result solve(llvm::DenseSet Vals) { return WatchedLiteralsSolver().solve(std::move(Vals)); } -void expectUnsatisfiable(Solver::Result Result) { - EXPECT_EQ(Result.getStatus(), Solver::Result::Status::Unsatisfiable); - EXPECT_FALSE(Result.getSolution().has_value()); +MATCHER(unsat, "") { + return arg.getStatus() == Solver::Result::Status::Unsatisfiable; } - -template -void expectSatisfiable(Solver::Result Result, Matcher Solution) { - EXPECT_EQ(Result.getStatus(), Solver::Result::Status::Satisfiable); - EXPECT_THAT(Result.getSolution(), Optional(Solution)); +MATCHER_P(sat, SolutionMatcher, + "is satisfiable, where solution " + + (testing::DescribeMatcher< + llvm::DenseMap>( + SolutionMatcher))) { + if (arg.getStatus() != Solver::Result::Status::Satisfiable) + return false; + auto Solution = *arg.getSolution(); + return testing::ExplainMatchResult(SolutionMatcher, Solution, + result_listener); } TEST(SolverTest, Var) { @@ -50,9 +54,9 @@ auto X = Ctx.atom(); // X - expectSatisfiable( - solve({X}), - UnorderedElementsAre(Pair(X, Solver::Result::Assignment::AssignedTrue))); + EXPECT_THAT(solve({X}), + sat(UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedTrue)))); } TEST(SolverTest, NegatedVar) { @@ -61,9 +65,9 @@ auto NotX = Ctx.neg(X); // !X - expectSatisfiable( - solve({NotX}), - UnorderedElementsAre(Pair(X, Solver::Result::Assignment::AssignedFalse))); + EXPECT_THAT(solve({NotX}), + sat(UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedFalse)))); } TEST(SolverTest, UnitConflict) { @@ -72,7 +76,7 @@ auto NotX = Ctx.neg(X); // X ^ !X - expectUnsatisfiable(solve({X, NotX})); + EXPECT_THAT(solve({X, NotX}), unsat()); } TEST(SolverTest, DistinctVars) { @@ -82,36 +86,10 @@ auto NotY = Ctx.neg(Y); // X ^ !Y - expectSatisfiable( - solve({X, NotY}), - UnorderedElementsAre(Pair(X, Solver::Result::Assignment::AssignedTrue), - Pair(Y, Solver::Result::Assignment::AssignedFalse))); -} - -TEST(SolverTest, Top) { - ConstraintContext Ctx; - auto X = Ctx.top(); - - // X. Since Top is anonymous, we do not get any results in the solution. - expectSatisfiable(solve({X}), IsEmpty()); -} - -TEST(SolverTest, NegatedTop) { - ConstraintContext Ctx; - auto X = Ctx.top(); - - // !X - expectSatisfiable(solve({Ctx.neg(X)}), IsEmpty()); -} - -TEST(SolverTest, DistinctTops) { - ConstraintContext Ctx; - auto X = Ctx.top(); - auto Y = Ctx.top(); - auto NotY = Ctx.neg(Y); - - // X ^ !Y - expectSatisfiable(solve({X, NotY}), IsEmpty()); + EXPECT_THAT(solve({X, NotY}), + sat(UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedTrue), + Pair(Y->atom(), Solver::Result::Assignment::AssignedFalse)))); } TEST(SolverTest, DoubleNegation) { @@ -121,7 +99,7 @@ auto NotNotX = Ctx.neg(NotX); // !!X ^ !X - expectUnsatisfiable(solve({NotNotX, NotX})); + EXPECT_THAT(solve({NotNotX, NotX}), unsat()); } TEST(SolverTest, NegatedDisjunction) { @@ -132,7 +110,7 @@ auto NotXOrY = Ctx.neg(XOrY); // !(X v Y) ^ (X v Y) - expectUnsatisfiable(solve({NotXOrY, XOrY})); + EXPECT_THAT(solve({NotXOrY, XOrY}), unsat()); } TEST(SolverTest, NegatedConjunction) { @@ -143,7 +121,7 @@ auto NotXAndY = Ctx.neg(XAndY); // !(X ^ Y) ^ (X ^ Y) - expectUnsatisfiable(solve({NotXAndY, XAndY})); + EXPECT_THAT(solve({NotXAndY, XAndY}), unsat()); } TEST(SolverTest, DisjunctionSameVarWithNegation) { @@ -153,7 +131,7 @@ auto XOrNotX = Ctx.disj(X, NotX); // X v !X - expectSatisfiable(solve({XOrNotX}), _); + EXPECT_THAT(solve({XOrNotX}), sat(_)); } TEST(SolverTest, DisjunctionSameVar) { @@ -162,7 +140,7 @@ auto XOrX = Ctx.disj(X, X); // X v X - expectSatisfiable(solve({XOrX}), _); + EXPECT_THAT(solve({XOrX}), sat(_)); } TEST(SolverTest, ConjunctionSameVarsConflict) { @@ -172,7 +150,7 @@ auto XAndNotX = Ctx.conj(X, NotX); // X ^ !X - expectUnsatisfiable(solve({XAndNotX})); + EXPECT_THAT(solve({XAndNotX}), unsat()); } TEST(SolverTest, ConjunctionSameVar) { @@ -181,7 +159,7 @@ auto XAndX = Ctx.conj(X, X); // X ^ X - expectSatisfiable(solve({XAndX}), _); + EXPECT_THAT(solve({XAndX}), sat(_)); } TEST(SolverTest, PureVar) { @@ -194,10 +172,10 @@ auto NotXOrNotY = Ctx.disj(NotX, NotY); // (!X v Y) ^ (!X v !Y) - expectSatisfiable( - solve({NotXOrY, NotXOrNotY}), - UnorderedElementsAre(Pair(X, Solver::Result::Assignment::AssignedFalse), - Pair(Y, _))); + EXPECT_THAT(solve({NotXOrY, NotXOrNotY}), + sat(UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedFalse), + Pair(Y->atom(), _)))); } TEST(SolverTest, MustAssumeVarIsFalse) { @@ -211,10 +189,10 @@ auto NotXOrNotY = Ctx.disj(NotX, NotY); // (X v Y) ^ (!X v Y) ^ (!X v !Y) - expectSatisfiable( - solve({XOrY, NotXOrY, NotXOrNotY}), - UnorderedElementsAre(Pair(X, Solver::Result::Assignment::AssignedFalse), - Pair(Y, Solver::Result::Assignment::AssignedTrue))); + EXPECT_THAT(solve({XOrY, NotXOrY, NotXOrNotY}), + sat(UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedFalse), + Pair(Y->atom(), Solver::Result::Assignment::AssignedTrue)))); } TEST(SolverTest, DeepConflict) { @@ -229,7 +207,7 @@ auto XOrNotY = Ctx.disj(X, NotY); // (X v Y) ^ (!X v Y) ^ (!X v !Y) ^ (X v !Y) - expectUnsatisfiable(solve({XOrY, NotXOrY, NotXOrNotY, XOrNotY})); + EXPECT_THAT(solve({XOrY, NotXOrY, NotXOrNotY, XOrNotY}), unsat()); } TEST(SolverTest, IffIsEquivalentToDNF) { @@ -243,7 +221,7 @@ auto NotEquivalent = Ctx.neg(Ctx.iff(XIffY, XIffYDNF)); // !((X <=> Y) <=> ((X ^ Y) v (!X ^ !Y))) - expectUnsatisfiable(solve({NotEquivalent})); + EXPECT_THAT(solve({NotEquivalent}), unsat()); } TEST(SolverTest, IffSameVars) { @@ -252,7 +230,7 @@ auto XEqX = Ctx.iff(X, X); // X <=> X - expectSatisfiable(solve({XEqX}), _); + EXPECT_THAT(solve({XEqX}), sat(_)); } TEST(SolverTest, IffDistinctVars) { @@ -262,14 +240,15 @@ auto XEqY = Ctx.iff(X, Y); // X <=> Y - expectSatisfiable( + EXPECT_THAT( solve({XEqY}), - AnyOf(UnorderedElementsAre( - Pair(X, Solver::Result::Assignment::AssignedTrue), - Pair(Y, Solver::Result::Assignment::AssignedTrue)), - UnorderedElementsAre( - Pair(X, Solver::Result::Assignment::AssignedFalse), - Pair(Y, Solver::Result::Assignment::AssignedFalse)))); + sat(AnyOf( + UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedTrue), + Pair(Y->atom(), Solver::Result::Assignment::AssignedTrue)), + UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedFalse), + Pair(Y->atom(), Solver::Result::Assignment::AssignedFalse))))); } TEST(SolverTest, IffWithUnits) { @@ -279,10 +258,10 @@ auto XEqY = Ctx.iff(X, Y); // (X <=> Y) ^ X ^ Y - expectSatisfiable( - solve({XEqY, X, Y}), - UnorderedElementsAre(Pair(X, Solver::Result::Assignment::AssignedTrue), - Pair(Y, Solver::Result::Assignment::AssignedTrue))); + EXPECT_THAT(solve({XEqY, X, Y}), + sat(UnorderedElementsAre( + Pair(X->atom(), Solver::Result::Assignment::AssignedTrue), + Pair(Y->atom(), Solver::Result::Assignment::AssignedTrue)))); } TEST(SolverTest, IffWithUnitsConflict) { @@ -293,7 +272,7 @@ auto NotY = Ctx.neg(Y); // (X <=> Y) ^ X !Y - expectUnsatisfiable(solve({XEqY, X, NotY})); + EXPECT_THAT(solve({XEqY, X, NotY}), unsat()); } TEST(SolverTest, IffTransitiveConflict) { @@ -306,7 +285,7 @@ auto NotX = Ctx.neg(X); // (X <=> Y) ^ (Y <=> Z) ^ Z ^ !X - expectUnsatisfiable(solve({XEqY, YEqZ, Z, NotX})); + EXPECT_THAT(solve({XEqY, YEqZ, Z, NotX}), unsat()); } TEST(SolverTest, DeMorgan) { @@ -323,7 +302,7 @@ auto B = Ctx.iff(Ctx.neg(Ctx.conj(Z, W)), Ctx.disj(Ctx.neg(Z), Ctx.neg(W))); // A ^ B - expectSatisfiable(solve({A, B}), _); + EXPECT_THAT(solve({A, B}), sat(_)); } TEST(SolverTest, RespectsAdditionalConstraints) { @@ -334,7 +313,7 @@ auto NotY = Ctx.neg(Y); // (X <=> Y) ^ X ^ !Y - expectUnsatisfiable(solve({XEqY, X, NotY})); + EXPECT_THAT(solve({XEqY, X, NotY}), unsat()); } TEST(SolverTest, ImplicationIsEquivalentToDNF) { @@ -346,7 +325,7 @@ auto NotEquivalent = Ctx.neg(Ctx.iff(XImpliesY, XImpliesYDNF)); // !((X => Y) <=> (!X v Y)) - expectUnsatisfiable(solve({NotEquivalent})); + EXPECT_THAT(solve({NotEquivalent}), unsat()); } TEST(SolverTest, ImplicationConflict) { @@ -357,7 +336,7 @@ auto *XAndNotY = Ctx.conj(X, Ctx.neg(Y)); // X => Y ^ X ^ !Y - expectUnsatisfiable(solve({XImplY, XAndNotY})); + EXPECT_THAT(solve({XImplY, XAndNotY}), unsat()); } TEST(SolverTest, LowTimeoutResultsInTimedOut) { diff --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h --- a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h +++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h @@ -42,6 +42,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Allocator.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Testing/Annotations/Annotations.h" @@ -404,55 +405,43 @@ /// Creates and owns constraints which are boolean values. class ConstraintContext { -public: - // Creates an atomic boolean value. - BoolValue *atom() { - Vals.push_back(std::make_unique()); - return Vals.back().get(); + unsigned NextAtom = 0; + llvm::BumpPtrAllocator A; + + const Formula *make(Formula::Kind K, llvm::ArrayRef Operands) { + return &Formula::create(A, K, Operands); } - // Creates an instance of the Top boolean value. - BoolValue *top() { - Vals.push_back(std::make_unique()); - return Vals.back().get(); +public: + // Creates a reference to a fresh atomic variable. + const Formula *atom() { + return &Formula::create(A, Formula::AtomRef, {}, NextAtom++); } // Creates a boolean conjunction value. - BoolValue *conj(BoolValue *LeftSubVal, BoolValue *RightSubVal) { - Vals.push_back( - std::make_unique(*LeftSubVal, *RightSubVal)); - return Vals.back().get(); + const Formula *conj(const Formula *LeftSubVal, const Formula *RightSubVal) { + return make(Formula::And, {LeftSubVal, RightSubVal}); } // Creates a boolean disjunction value. - BoolValue *disj(BoolValue *LeftSubVal, BoolValue *RightSubVal) { - Vals.push_back( - std::make_unique(*LeftSubVal, *RightSubVal)); - return Vals.back().get(); + const Formula*disj(const Formula*LeftSubVal, const Formula*RightSubVal) { + return make(Formula::Or, {LeftSubVal, RightSubVal}); } // Creates a boolean negation value. - BoolValue *neg(BoolValue *SubVal) { - Vals.push_back(std::make_unique(*SubVal)); - return Vals.back().get(); + const Formula *neg(const Formula *SubVal) { + return make(Formula::Not, {SubVal}); } // Creates a boolean implication value. - BoolValue *impl(BoolValue *LeftSubVal, BoolValue *RightSubVal) { - Vals.push_back( - std::make_unique(*LeftSubVal, *RightSubVal)); - return Vals.back().get(); + const Formula *impl(const Formula *LeftSubVal, const Formula *RightSubVal) { + return make(Formula::Implies, {LeftSubVal, RightSubVal}); } // Creates a boolean biconditional value. - BoolValue *iff(BoolValue *LeftSubVal, BoolValue *RightSubVal) { - Vals.push_back( - std::make_unique(*LeftSubVal, *RightSubVal)); - return Vals.back().get(); + const Formula *iff(const Formula *LeftSubVal, const Formula *RightSubVal) { + return make(Formula::Equal, {LeftSubVal, RightSubVal}); } - -private: - std::vector> Vals; }; } // namespace test