blob: 28f4adb2d39354c5bf0bcf64629c66c83b3eb2e5 [file] [log] [blame] [edit]
//===- 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 <utility>
#include "TestingSupport.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/Solver.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "llvm/ADT/ArrayRef.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace {
using namespace clang;
using namespace dataflow;
using test::ConstraintContext;
using testing::_;
using testing::AnyOf;
using testing::Pair;
using testing::UnorderedElementsAre;
constexpr auto AssignedTrue = Solver::Result::Assignment::AssignedTrue;
constexpr auto AssignedFalse = Solver::Result::Assignment::AssignedFalse;
// Checks if the conjunction of `Vals` is satisfiable and returns the
// corresponding result.
Solver::Result solve(llvm::ArrayRef<const Formula *> Vals) {
return WatchedLiteralsSolver().solve(Vals);
}
MATCHER(unsat, "") {
return arg.getStatus() == Solver::Result::Status::Unsatisfiable;
}
MATCHER_P(sat, SolutionMatcher,
"is satisfiable, where solution " +
(testing::DescribeMatcher<
llvm::DenseMap<Atom, Solver::Result::Assignment>>(
SolutionMatcher))) {
if (arg.getStatus() != Solver::Result::Status::Satisfiable)
return false;
auto Solution = *arg.getSolution();
return testing::ExplainMatchResult(SolutionMatcher, Solution,
result_listener);
}
TEST(SolverTest, Var) {
ConstraintContext Ctx;
auto X = Ctx.atom();
// X
EXPECT_THAT(solve({X}),
sat(UnorderedElementsAre(Pair(X->getAtom(), AssignedTrue))));
}
TEST(SolverTest, NegatedVar) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto NotX = Ctx.neg(X);
// !X
EXPECT_THAT(solve({NotX}),
sat(UnorderedElementsAre(Pair(X->getAtom(), AssignedFalse))));
}
TEST(SolverTest, UnitConflict) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto NotX = Ctx.neg(X);
// X ^ !X
EXPECT_THAT(solve({X, NotX}), unsat());
}
TEST(SolverTest, DistinctVars) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto NotY = Ctx.neg(Y);
// X ^ !Y
EXPECT_THAT(solve({X, NotY}),
sat(UnorderedElementsAre(Pair(X->getAtom(), AssignedTrue),
Pair(Y->getAtom(), AssignedFalse))));
}
TEST(SolverTest, DoubleNegation) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto NotX = Ctx.neg(X);
auto NotNotX = Ctx.neg(NotX);
// !!X ^ !X
EXPECT_THAT(solve({NotNotX, NotX}), unsat());
}
TEST(SolverTest, NegatedDisjunction) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XOrY = Ctx.disj(X, Y);
auto NotXOrY = Ctx.neg(XOrY);
// !(X v Y) ^ (X v Y)
EXPECT_THAT(solve({NotXOrY, XOrY}), unsat());
}
TEST(SolverTest, NegatedConjunction) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XAndY = Ctx.conj(X, Y);
auto NotXAndY = Ctx.neg(XAndY);
// !(X ^ Y) ^ (X ^ Y)
EXPECT_THAT(solve({NotXAndY, XAndY}), unsat());
}
TEST(SolverTest, DisjunctionSameVarWithNegation) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto NotX = Ctx.neg(X);
auto XOrNotX = Ctx.disj(X, NotX);
// X v !X
EXPECT_THAT(solve({XOrNotX}), sat(_));
}
TEST(SolverTest, DisjunctionSameVar) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto XOrX = Ctx.disj(X, X);
// X v X
EXPECT_THAT(solve({XOrX}), sat(_));
}
TEST(SolverTest, ConjunctionSameVarsConflict) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto NotX = Ctx.neg(X);
auto XAndNotX = Ctx.conj(X, NotX);
// X ^ !X
EXPECT_THAT(solve({XAndNotX}), unsat());
}
TEST(SolverTest, ConjunctionSameVar) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto XAndX = Ctx.conj(X, X);
// X ^ X
EXPECT_THAT(solve({XAndX}), sat(_));
}
TEST(SolverTest, PureVar) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto NotX = Ctx.neg(X);
auto NotXOrY = Ctx.disj(NotX, Y);
auto NotY = Ctx.neg(Y);
auto NotXOrNotY = Ctx.disj(NotX, NotY);
// (!X v Y) ^ (!X v !Y)
EXPECT_THAT(solve({NotXOrY, NotXOrNotY}),
sat(UnorderedElementsAre(Pair(X->getAtom(), AssignedFalse),
Pair(Y->getAtom(), _))));
}
TEST(SolverTest, MustAssumeVarIsFalse) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XOrY = Ctx.disj(X, Y);
auto NotX = Ctx.neg(X);
auto NotXOrY = Ctx.disj(NotX, Y);
auto NotY = Ctx.neg(Y);
auto NotXOrNotY = Ctx.disj(NotX, NotY);
// (X v Y) ^ (!X v Y) ^ (!X v !Y)
EXPECT_THAT(solve({XOrY, NotXOrY, NotXOrNotY}),
sat(UnorderedElementsAre(Pair(X->getAtom(), AssignedFalse),
Pair(Y->getAtom(), AssignedTrue))));
}
TEST(SolverTest, DeepConflict) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XOrY = Ctx.disj(X, Y);
auto NotX = Ctx.neg(X);
auto NotXOrY = Ctx.disj(NotX, Y);
auto NotY = Ctx.neg(Y);
auto NotXOrNotY = Ctx.disj(NotX, NotY);
auto XOrNotY = Ctx.disj(X, NotY);
// (X v Y) ^ (!X v Y) ^ (!X v !Y) ^ (X v !Y)
EXPECT_THAT(solve({XOrY, NotXOrY, NotXOrNotY, XOrNotY}), unsat());
}
TEST(SolverTest, IffIsEquivalentToDNF) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto NotX = Ctx.neg(X);
auto NotY = Ctx.neg(Y);
auto XIffY = Ctx.iff(X, Y);
auto XIffYDNF = Ctx.disj(Ctx.conj(X, Y), Ctx.conj(NotX, NotY));
auto NotEquivalent = Ctx.neg(Ctx.iff(XIffY, XIffYDNF));
// !((X <=> Y) <=> ((X ^ Y) v (!X ^ !Y)))
EXPECT_THAT(solve({NotEquivalent}), unsat());
}
TEST(SolverTest, IffSameVars) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto XEqX = Ctx.iff(X, X);
// X <=> X
EXPECT_THAT(solve({XEqX}), sat(_));
}
TEST(SolverTest, IffDistinctVars) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XEqY = Ctx.iff(X, Y);
// X <=> Y
EXPECT_THAT(
solve({XEqY}),
sat(AnyOf(UnorderedElementsAre(Pair(X->getAtom(), AssignedTrue),
Pair(Y->getAtom(), AssignedTrue)),
UnorderedElementsAre(Pair(X->getAtom(), AssignedFalse),
Pair(Y->getAtom(), AssignedFalse)))));
}
TEST(SolverTest, IffWithUnits) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XEqY = Ctx.iff(X, Y);
// (X <=> Y) ^ X ^ Y
EXPECT_THAT(solve({XEqY, X, Y}),
sat(UnorderedElementsAre(Pair(X->getAtom(), AssignedTrue),
Pair(Y->getAtom(), AssignedTrue))));
}
TEST(SolverTest, IffWithUnitsConflict) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XEqY = Ctx.iff(X, Y);
auto NotY = Ctx.neg(Y);
// (X <=> Y) ^ X !Y
EXPECT_THAT(solve({XEqY, X, NotY}), unsat());
}
TEST(SolverTest, IffTransitiveConflict) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto Z = Ctx.atom();
auto XEqY = Ctx.iff(X, Y);
auto YEqZ = Ctx.iff(Y, Z);
auto NotX = Ctx.neg(X);
// (X <=> Y) ^ (Y <=> Z) ^ Z ^ !X
EXPECT_THAT(solve({XEqY, YEqZ, Z, NotX}), unsat());
}
TEST(SolverTest, DeMorgan) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto Z = Ctx.atom();
auto W = Ctx.atom();
// !(X v Y) <=> !X ^ !Y
auto A = Ctx.iff(Ctx.neg(Ctx.disj(X, Y)), Ctx.conj(Ctx.neg(X), Ctx.neg(Y)));
// !(Z ^ W) <=> !Z v !W
auto B = Ctx.iff(Ctx.neg(Ctx.conj(Z, W)), Ctx.disj(Ctx.neg(Z), Ctx.neg(W)));
// A ^ B
EXPECT_THAT(solve({A, B}), sat(_));
}
TEST(SolverTest, RespectsAdditionalConstraints) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XEqY = Ctx.iff(X, Y);
auto NotY = Ctx.neg(Y);
// (X <=> Y) ^ X ^ !Y
EXPECT_THAT(solve({XEqY, X, NotY}), unsat());
}
TEST(SolverTest, ImplicationIsEquivalentToDNF) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto XImpliesY = Ctx.impl(X, Y);
auto XImpliesYDNF = Ctx.disj(Ctx.neg(X), Y);
auto NotEquivalent = Ctx.neg(Ctx.iff(XImpliesY, XImpliesYDNF));
// !((X => Y) <=> (!X v Y))
EXPECT_THAT(solve({NotEquivalent}), unsat());
}
TEST(SolverTest, ImplicationConflict) {
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto *XImplY = Ctx.impl(X, Y);
auto *XAndNotY = Ctx.conj(X, Ctx.neg(Y));
// X => Y ^ X ^ !Y
EXPECT_THAT(solve({XImplY, XAndNotY}), unsat());
}
TEST(SolverTest, LowTimeoutResultsInTimedOut) {
WatchedLiteralsSolver solver(10);
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto Z = Ctx.atom();
auto W = Ctx.atom();
// !(X v Y) <=> !X ^ !Y
auto A = Ctx.iff(Ctx.neg(Ctx.disj(X, Y)), Ctx.conj(Ctx.neg(X), Ctx.neg(Y)));
// !(Z ^ W) <=> !Z v !W
auto B = Ctx.iff(Ctx.neg(Ctx.conj(Z, W)), Ctx.disj(Ctx.neg(Z), Ctx.neg(W)));
// A ^ B
EXPECT_EQ(solver.solve({A, B}).getStatus(), Solver::Result::Status::TimedOut);
}
TEST(SolverTest, ReachedLimitsReflectsTimeouts) {
WatchedLiteralsSolver solver(10);
ConstraintContext Ctx;
auto X = Ctx.atom();
auto Y = Ctx.atom();
auto Z = Ctx.atom();
auto W = Ctx.atom();
// !(X v Y) <=> !X ^ !Y
auto A = Ctx.iff(Ctx.neg(Ctx.disj(X, Y)), Ctx.conj(Ctx.neg(X), Ctx.neg(Y)));
// !(Z ^ W) <=> !Z v !W
auto B = Ctx.iff(Ctx.neg(Ctx.conj(Z, W)), Ctx.disj(Ctx.neg(Z), Ctx.neg(W)));
// A ^ B
ASSERT_EQ(solver.solve({A, B}).getStatus(), Solver::Result::Status::TimedOut);
EXPECT_TRUE(solver.reachedLimit());
}
} // namespace