| //===- unittests/Analysis/FlowSensitive/SolverTest.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/Solver.h" |
| #include "clang/Analysis/FlowSensitive/Value.h" |
| #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| namespace { |
| |
| using namespace clang; |
| using namespace dataflow; |
| |
| class SolverTest : public ::testing::Test { |
| protected: |
| // Checks if the conjunction of `Vals` is satisfiable and returns the |
| // corresponding result. |
| Solver::Result solve(llvm::DenseSet<BoolValue *> Vals) { |
| return WatchedLiteralsSolver().solve(std::move(Vals)); |
| } |
| |
| // Creates an atomic boolean value. |
| BoolValue *atom() { |
| Vals.push_back(std::make_unique<AtomicBoolValue>()); |
| return Vals.back().get(); |
| } |
| |
| // Creates a boolean conjunction value. |
| BoolValue *conj(BoolValue *LeftSubVal, BoolValue *RightSubVal) { |
| Vals.push_back( |
| std::make_unique<ConjunctionValue>(*LeftSubVal, *RightSubVal)); |
| return Vals.back().get(); |
| } |
| |
| // Creates a boolean disjunction value. |
| BoolValue *disj(BoolValue *LeftSubVal, BoolValue *RightSubVal) { |
| Vals.push_back( |
| std::make_unique<DisjunctionValue>(*LeftSubVal, *RightSubVal)); |
| return Vals.back().get(); |
| } |
| |
| // Creates a boolean negation value. |
| BoolValue *neg(BoolValue *SubVal) { |
| Vals.push_back(std::make_unique<NegationValue>(*SubVal)); |
| return Vals.back().get(); |
| } |
| |
| // Creates a boolean implication value. |
| BoolValue *impl(BoolValue *LeftSubVal, BoolValue *RightSubVal) { |
| return disj(neg(LeftSubVal), RightSubVal); |
| } |
| |
| // Creates a boolean biconditional value. |
| BoolValue *iff(BoolValue *LeftSubVal, BoolValue *RightSubVal) { |
| return conj(impl(LeftSubVal, RightSubVal), impl(RightSubVal, LeftSubVal)); |
| } |
| |
| private: |
| std::vector<std::unique_ptr<BoolValue>> Vals; |
| }; |
| |
| TEST_F(SolverTest, Var) { |
| auto X = atom(); |
| |
| // X |
| EXPECT_EQ(solve({X}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, NegatedVar) { |
| auto X = atom(); |
| auto NotX = neg(X); |
| |
| // !X |
| EXPECT_EQ(solve({NotX}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, UnitConflict) { |
| auto X = atom(); |
| auto NotX = neg(X); |
| |
| // X ^ !X |
| EXPECT_EQ(solve({X, NotX}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, DistinctVars) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto NotY = neg(Y); |
| |
| // X ^ !Y |
| EXPECT_EQ(solve({X, NotY}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, DoubleNegation) { |
| auto X = atom(); |
| auto NotX = neg(X); |
| auto NotNotX = neg(NotX); |
| |
| // !!X ^ !X |
| EXPECT_EQ(solve({NotNotX, NotX}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, NegatedDisjunction) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XOrY = disj(X, Y); |
| auto NotXOrY = neg(XOrY); |
| |
| // !(X v Y) ^ (X v Y) |
| EXPECT_EQ(solve({NotXOrY, XOrY}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, NegatedConjunction) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XAndY = conj(X, Y); |
| auto NotXAndY = neg(XAndY); |
| |
| // !(X ^ Y) ^ (X ^ Y) |
| EXPECT_EQ(solve({NotXAndY, XAndY}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, DisjunctionSameVars) { |
| auto X = atom(); |
| auto NotX = neg(X); |
| auto XOrNotX = disj(X, NotX); |
| |
| // X v !X |
| EXPECT_EQ(solve({XOrNotX}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, ConjunctionSameVarsConflict) { |
| auto X = atom(); |
| auto NotX = neg(X); |
| auto XAndNotX = conj(X, NotX); |
| |
| // X ^ !X |
| EXPECT_EQ(solve({XAndNotX}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, PureVar) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto NotX = neg(X); |
| auto NotXOrY = disj(NotX, Y); |
| auto NotY = neg(Y); |
| auto NotXOrNotY = disj(NotX, NotY); |
| |
| // (!X v Y) ^ (!X v !Y) |
| EXPECT_EQ(solve({NotXOrY, NotXOrNotY}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, MustAssumeVarIsFalse) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XOrY = disj(X, Y); |
| auto NotX = neg(X); |
| auto NotXOrY = disj(NotX, Y); |
| auto NotY = neg(Y); |
| auto NotXOrNotY = disj(NotX, NotY); |
| |
| // (X v Y) ^ (!X v Y) ^ (!X v !Y) |
| EXPECT_EQ(solve({XOrY, NotXOrY, NotXOrNotY}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, DeepConflict) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XOrY = disj(X, Y); |
| auto NotX = neg(X); |
| auto NotXOrY = disj(NotX, Y); |
| auto NotY = neg(Y); |
| auto NotXOrNotY = disj(NotX, NotY); |
| auto XOrNotY = disj(X, NotY); |
| |
| // (X v Y) ^ (!X v Y) ^ (!X v !Y) ^ (X v !Y) |
| EXPECT_EQ(solve({XOrY, NotXOrY, NotXOrNotY, XOrNotY}), |
| Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, IffSameVars) { |
| auto X = atom(); |
| auto XEqX = iff(X, X); |
| |
| // X <=> X |
| EXPECT_EQ(solve({XEqX}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, IffDistinctVars) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XEqY = iff(X, Y); |
| |
| // X <=> Y |
| EXPECT_EQ(solve({XEqY}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, IffWithUnits) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XEqY = iff(X, Y); |
| |
| // (X <=> Y) ^ X ^ Y |
| EXPECT_EQ(solve({XEqY, X, Y}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, IffWithUnitsConflict) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XEqY = iff(X, Y); |
| auto NotY = neg(Y); |
| |
| // (X <=> Y) ^ X !Y |
| EXPECT_EQ(solve({XEqY, X, NotY}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, IffTransitiveConflict) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto Z = atom(); |
| auto XEqY = iff(X, Y); |
| auto YEqZ = iff(Y, Z); |
| auto NotX = neg(X); |
| |
| // (X <=> Y) ^ (Y <=> Z) ^ Z ^ !X |
| EXPECT_EQ(solve({XEqY, YEqZ, Z, NotX}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, DeMorgan) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto Z = atom(); |
| auto W = atom(); |
| |
| // !(X v Y) <=> !X ^ !Y |
| auto A = iff(neg(disj(X, Y)), conj(neg(X), neg(Y))); |
| |
| // !(Z ^ W) <=> !Z v !W |
| auto B = iff(neg(conj(Z, W)), disj(neg(Z), neg(W))); |
| |
| // A ^ B |
| EXPECT_EQ(solve({A, B}), Solver::Result::Satisfiable); |
| } |
| |
| TEST_F(SolverTest, RespectsAdditionalConstraints) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto XEqY = iff(X, Y); |
| auto NotY = neg(Y); |
| |
| // (X <=> Y) ^ X ^ !Y |
| EXPECT_EQ(solve({XEqY, X, NotY}), Solver::Result::Unsatisfiable); |
| } |
| |
| TEST_F(SolverTest, ImplicationConflict) { |
| auto X = atom(); |
| auto Y = atom(); |
| auto *XImplY = impl(X, Y); |
| auto *XAndNotY = conj(X, neg(Y)); |
| |
| // X => Y ^ X ^ !Y |
| EXPECT_EQ(solve({XImplY, XAndNotY}), Solver::Result::Unsatisfiable); |
| } |
| |
| } // namespace |