1 //===--- SolverTest.h - Type-parameterized test for solvers ---------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #ifndef LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_SOLVER_TEST_H_
10 #define LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_SOLVER_TEST_H_
12 #include "TestingSupport.h"
13 #include "clang/Analysis/FlowSensitive/Solver.h"
14 #include "gmock/gmock.h"
15 #include "gtest/gtest.h"
17 namespace clang::dataflow::test
{
21 constexpr auto AssignedTrue
= Solver::Result::Assignment::AssignedTrue
;
22 constexpr auto AssignedFalse
= Solver::Result::Assignment::AssignedFalse
;
27 using testing::UnorderedElementsAre
;
31 /// Type-parameterized test for implementations of the `Solver` interface.
33 /// 1. Implement a specialization of `createSolverWithLowTimeout()` for the
34 /// solver you want to test.
35 /// 2. Instantiate the test suite for the solver you want to test using
36 /// `INSTANTIATE_TYPED_TEST_SUITE_P()`.
37 /// See WatchedLiteralsSolverTest.cpp for an example.
38 template <typename SolverT
> class SolverTest
: public ::testing::Test
{
40 // Checks if the conjunction of `Vals` is satisfiable and returns the
41 // corresponding result.
42 Solver::Result
solve(llvm::ArrayRef
<const Formula
*> Vals
) {
43 return SolverT().solve(Vals
);
46 // Create a specialization for the solver type to test.
47 SolverT
createSolverWithLowTimeout();
50 TYPED_TEST_SUITE_P(SolverTest
);
53 return arg
.getStatus() == Solver::Result::Status::Unsatisfiable
;
56 MATCHER_P(sat
, SolutionMatcher
,
57 "is satisfiable, where solution " +
58 (testing::DescribeMatcher
<
59 llvm::DenseMap
<Atom
, Solver::Result::Assignment
>>(
61 if (arg
.getStatus() != Solver::Result::Status::Satisfiable
)
63 auto Solution
= *arg
.getSolution();
64 return testing::ExplainMatchResult(SolutionMatcher
, Solution
,
68 TYPED_TEST_P(SolverTest
, Var
) {
69 ConstraintContext Ctx
;
73 EXPECT_THAT(this->solve({X
}),
74 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
))));
77 TYPED_TEST_P(SolverTest
, NegatedVar
) {
78 ConstraintContext Ctx
;
80 auto NotX
= Ctx
.neg(X
);
83 EXPECT_THAT(this->solve({NotX
}),
84 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
))));
87 TYPED_TEST_P(SolverTest
, UnitConflict
) {
88 ConstraintContext Ctx
;
90 auto NotX
= Ctx
.neg(X
);
93 EXPECT_THAT(this->solve({X
, NotX
}), unsat());
96 TYPED_TEST_P(SolverTest
, DistinctVars
) {
97 ConstraintContext Ctx
;
100 auto NotY
= Ctx
.neg(Y
);
103 EXPECT_THAT(this->solve({X
, NotY
}),
104 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
),
105 Pair(Y
->getAtom(), AssignedFalse
))));
108 TYPED_TEST_P(SolverTest
, DoubleNegation
) {
109 ConstraintContext Ctx
;
111 auto NotX
= Ctx
.neg(X
);
112 auto NotNotX
= Ctx
.neg(NotX
);
115 EXPECT_THAT(this->solve({NotNotX
, NotX
}), unsat());
118 TYPED_TEST_P(SolverTest
, NegatedDisjunction
) {
119 ConstraintContext Ctx
;
122 auto XOrY
= Ctx
.disj(X
, Y
);
123 auto NotXOrY
= Ctx
.neg(XOrY
);
125 // !(X v Y) ^ (X v Y)
126 EXPECT_THAT(this->solve({NotXOrY
, XOrY
}), unsat());
129 TYPED_TEST_P(SolverTest
, NegatedConjunction
) {
130 ConstraintContext Ctx
;
133 auto XAndY
= Ctx
.conj(X
, Y
);
134 auto NotXAndY
= Ctx
.neg(XAndY
);
136 // !(X ^ Y) ^ (X ^ Y)
137 EXPECT_THAT(this->solve({NotXAndY
, XAndY
}), unsat());
140 TYPED_TEST_P(SolverTest
, DisjunctionSameVarWithNegation
) {
141 ConstraintContext Ctx
;
143 auto NotX
= Ctx
.neg(X
);
144 auto XOrNotX
= Ctx
.disj(X
, NotX
);
147 EXPECT_THAT(this->solve({XOrNotX
}), sat(_
));
150 TYPED_TEST_P(SolverTest
, DisjunctionSameVar
) {
151 ConstraintContext Ctx
;
153 auto XOrX
= Ctx
.disj(X
, X
);
156 EXPECT_THAT(this->solve({XOrX
}), sat(_
));
159 TYPED_TEST_P(SolverTest
, ConjunctionSameVarsConflict
) {
160 ConstraintContext Ctx
;
162 auto NotX
= Ctx
.neg(X
);
163 auto XAndNotX
= Ctx
.conj(X
, NotX
);
166 EXPECT_THAT(this->solve({XAndNotX
}), unsat());
169 TYPED_TEST_P(SolverTest
, ConjunctionSameVar
) {
170 ConstraintContext Ctx
;
172 auto XAndX
= Ctx
.conj(X
, X
);
175 EXPECT_THAT(this->solve({XAndX
}), sat(_
));
178 TYPED_TEST_P(SolverTest
, PureVar
) {
179 ConstraintContext Ctx
;
182 auto NotX
= Ctx
.neg(X
);
183 auto NotXOrY
= Ctx
.disj(NotX
, Y
);
184 auto NotY
= Ctx
.neg(Y
);
185 auto NotXOrNotY
= Ctx
.disj(NotX
, NotY
);
187 // (!X v Y) ^ (!X v !Y)
188 EXPECT_THAT(this->solve({NotXOrY
, NotXOrNotY
}),
189 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
),
190 Pair(Y
->getAtom(), _
))));
193 TYPED_TEST_P(SolverTest
, MustAssumeVarIsFalse
) {
194 ConstraintContext Ctx
;
197 auto XOrY
= Ctx
.disj(X
, Y
);
198 auto NotX
= Ctx
.neg(X
);
199 auto NotXOrY
= Ctx
.disj(NotX
, Y
);
200 auto NotY
= Ctx
.neg(Y
);
201 auto NotXOrNotY
= Ctx
.disj(NotX
, NotY
);
203 // (X v Y) ^ (!X v Y) ^ (!X v !Y)
204 EXPECT_THAT(this->solve({XOrY
, NotXOrY
, NotXOrNotY
}),
205 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
),
206 Pair(Y
->getAtom(), AssignedTrue
))));
209 TYPED_TEST_P(SolverTest
, DeepConflict
) {
210 ConstraintContext Ctx
;
213 auto XOrY
= Ctx
.disj(X
, Y
);
214 auto NotX
= Ctx
.neg(X
);
215 auto NotXOrY
= Ctx
.disj(NotX
, Y
);
216 auto NotY
= Ctx
.neg(Y
);
217 auto NotXOrNotY
= Ctx
.disj(NotX
, NotY
);
218 auto XOrNotY
= Ctx
.disj(X
, NotY
);
220 // (X v Y) ^ (!X v Y) ^ (!X v !Y) ^ (X v !Y)
221 EXPECT_THAT(this->solve({XOrY
, NotXOrY
, NotXOrNotY
, XOrNotY
}), unsat());
224 TYPED_TEST_P(SolverTest
, IffIsEquivalentToDNF
) {
225 ConstraintContext Ctx
;
228 auto NotX
= Ctx
.neg(X
);
229 auto NotY
= Ctx
.neg(Y
);
230 auto XIffY
= Ctx
.iff(X
, Y
);
231 auto XIffYDNF
= Ctx
.disj(Ctx
.conj(X
, Y
), Ctx
.conj(NotX
, NotY
));
232 auto NotEquivalent
= Ctx
.neg(Ctx
.iff(XIffY
, XIffYDNF
));
234 // !((X <=> Y) <=> ((X ^ Y) v (!X ^ !Y)))
235 EXPECT_THAT(this->solve({NotEquivalent
}), unsat());
238 TYPED_TEST_P(SolverTest
, IffSameVars
) {
239 ConstraintContext Ctx
;
241 auto XEqX
= Ctx
.iff(X
, X
);
244 EXPECT_THAT(this->solve({XEqX
}), sat(_
));
247 TYPED_TEST_P(SolverTest
, IffDistinctVars
) {
248 ConstraintContext Ctx
;
251 auto XEqY
= Ctx
.iff(X
, Y
);
256 sat(AnyOf(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
),
257 Pair(Y
->getAtom(), AssignedTrue
)),
258 UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
),
259 Pair(Y
->getAtom(), AssignedFalse
)))));
262 TYPED_TEST_P(SolverTest
, IffWithUnits
) {
263 ConstraintContext Ctx
;
266 auto XEqY
= Ctx
.iff(X
, Y
);
269 EXPECT_THAT(this->solve({XEqY
, X
, Y
}),
270 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
),
271 Pair(Y
->getAtom(), AssignedTrue
))));
274 TYPED_TEST_P(SolverTest
, IffWithUnitsConflict
) {
276 auto Constraints
= parseFormulas(A
, R
"(
281 EXPECT_THAT(this->solve(Constraints
), unsat());
284 TYPED_TEST_P(SolverTest
, IffTransitiveConflict
) {
286 auto Constraints
= parseFormulas(A
, R
"(
292 EXPECT_THAT(this->solve(Constraints
), unsat());
295 TYPED_TEST_P(SolverTest
, DeMorgan
) {
297 auto Constraints
= parseFormulas(A
, R
"(
298 (!(V0 | V1) = (!V0 & !V1))
299 (!(V2 & V3) = (!V2 | !V3))
301 EXPECT_THAT(this->solve(Constraints
), sat(_
));
304 TYPED_TEST_P(SolverTest
, RespectsAdditionalConstraints
) {
306 auto Constraints
= parseFormulas(A
, R
"(
311 EXPECT_THAT(this->solve(Constraints
), unsat());
314 TYPED_TEST_P(SolverTest
, ImplicationIsEquivalentToDNF
) {
316 auto Constraints
= parseFormulas(A
, R
"(
317 !((V0 => V1) = (!V0 | V1))
319 EXPECT_THAT(this->solve(Constraints
), unsat());
322 TYPED_TEST_P(SolverTest
, ImplicationConflict
) {
324 auto Constraints
= parseFormulas(A
, R
"(
328 EXPECT_THAT(this->solve(Constraints
), unsat());
331 TYPED_TEST_P(SolverTest
, ReachedLimitsReflectsTimeouts
) {
333 auto Constraints
= parseFormulas(A
, R
"(
334 (!(V0 | V1) = (!V0 & !V1))
335 (!(V2 & V3) = (!V2 & !V3))
337 TypeParam solver
= this->createSolverWithLowTimeout();
338 ASSERT_EQ(solver
.solve(Constraints
).getStatus(),
339 Solver::Result::Status::TimedOut
);
340 EXPECT_TRUE(solver
.reachedLimit());
343 TYPED_TEST_P(SolverTest
, SimpleButLargeContradiction
) {
344 // This test ensures that the solver takes a short-cut on known
345 // contradictory inputs, without using max_iterations. At the time
346 // this test is added, formulas that are easily recognized to be
347 // contradictory at CNF construction time would lead to timeout.
348 TypeParam solver
= this->createSolverWithLowTimeout();
349 ConstraintContext Ctx
;
350 auto first
= Ctx
.atom();
352 for (int i
= 1; i
< 10000; ++i
) {
353 last
= Ctx
.conj(last
, Ctx
.atom());
355 last
= Ctx
.conj(Ctx
.neg(first
), last
);
356 ASSERT_EQ(solver
.solve({last
}).getStatus(),
357 Solver::Result::Status::Unsatisfiable
);
358 EXPECT_FALSE(solver
.reachedLimit());
361 last
= Ctx
.neg(first
);
362 for (int i
= 1; i
< 10000; ++i
) {
363 last
= Ctx
.conj(last
, Ctx
.neg(Ctx
.atom()));
365 last
= Ctx
.conj(first
, last
);
366 ASSERT_EQ(solver
.solve({last
}).getStatus(),
367 Solver::Result::Status::Unsatisfiable
);
368 EXPECT_FALSE(solver
.reachedLimit());
371 REGISTER_TYPED_TEST_SUITE_P(
372 SolverTest
, Var
, NegatedVar
, UnitConflict
, DistinctVars
, DoubleNegation
,
373 NegatedDisjunction
, NegatedConjunction
, DisjunctionSameVarWithNegation
,
374 DisjunctionSameVar
, ConjunctionSameVarsConflict
, ConjunctionSameVar
,
375 PureVar
, MustAssumeVarIsFalse
, DeepConflict
, IffIsEquivalentToDNF
,
376 IffSameVars
, IffDistinctVars
, IffWithUnits
, IffWithUnitsConflict
,
377 IffTransitiveConflict
, DeMorgan
, RespectsAdditionalConstraints
,
378 ImplicationIsEquivalentToDNF
, ImplicationConflict
,
379 ReachedLimitsReflectsTimeouts
, SimpleButLargeContradiction
);
381 } // namespace clang::dataflow::test
383 #endif // LLVM_CLANG_ANALYSIS_FLOW_SENSITIVE_TESTING_SUPPORT_H_