1 //===- unittests/Analysis/FlowSensitive/SolverTest.cpp --------------------===//
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 //===----------------------------------------------------------------------===//
11 #include "TestingSupport.h"
12 #include "clang/Analysis/FlowSensitive/Arena.h"
13 #include "clang/Analysis/FlowSensitive/Formula.h"
14 #include "clang/Analysis/FlowSensitive/Solver.h"
15 #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
16 #include "clang/Basic/LLVM.h"
17 #include "llvm/ADT/ArrayRef.h"
18 #include "gmock/gmock.h"
19 #include "gtest/gtest.h"
24 using namespace clang
;
25 using namespace dataflow
;
27 using test::ConstraintContext
;
28 using test::parseFormulas
;
32 using testing::UnorderedElementsAre
;
34 constexpr auto AssignedTrue
= Solver::Result::Assignment::AssignedTrue
;
35 constexpr auto AssignedFalse
= Solver::Result::Assignment::AssignedFalse
;
37 // Checks if the conjunction of `Vals` is satisfiable and returns the
38 // corresponding result.
39 Solver::Result
solve(llvm::ArrayRef
<const Formula
*> Vals
) {
40 return WatchedLiteralsSolver().solve(Vals
);
44 return arg
.getStatus() == Solver::Result::Status::Unsatisfiable
;
46 MATCHER_P(sat
, SolutionMatcher
,
47 "is satisfiable, where solution " +
48 (testing::DescribeMatcher
<
49 llvm::DenseMap
<Atom
, Solver::Result::Assignment
>>(
51 if (arg
.getStatus() != Solver::Result::Status::Satisfiable
)
53 auto Solution
= *arg
.getSolution();
54 return testing::ExplainMatchResult(SolutionMatcher
, Solution
,
58 TEST(SolverTest
, Var
) {
59 ConstraintContext Ctx
;
63 EXPECT_THAT(solve({X
}),
64 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
))));
67 TEST(SolverTest
, NegatedVar
) {
68 ConstraintContext Ctx
;
70 auto NotX
= Ctx
.neg(X
);
73 EXPECT_THAT(solve({NotX
}),
74 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
))));
77 TEST(SolverTest
, UnitConflict
) {
78 ConstraintContext Ctx
;
80 auto NotX
= Ctx
.neg(X
);
83 EXPECT_THAT(solve({X
, NotX
}), unsat());
86 TEST(SolverTest
, DistinctVars
) {
87 ConstraintContext Ctx
;
90 auto NotY
= Ctx
.neg(Y
);
93 EXPECT_THAT(solve({X
, NotY
}),
94 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
),
95 Pair(Y
->getAtom(), AssignedFalse
))));
98 TEST(SolverTest
, DoubleNegation
) {
99 ConstraintContext Ctx
;
101 auto NotX
= Ctx
.neg(X
);
102 auto NotNotX
= Ctx
.neg(NotX
);
105 EXPECT_THAT(solve({NotNotX
, NotX
}), unsat());
108 TEST(SolverTest
, NegatedDisjunction
) {
109 ConstraintContext Ctx
;
112 auto XOrY
= Ctx
.disj(X
, Y
);
113 auto NotXOrY
= Ctx
.neg(XOrY
);
115 // !(X v Y) ^ (X v Y)
116 EXPECT_THAT(solve({NotXOrY
, XOrY
}), unsat());
119 TEST(SolverTest
, NegatedConjunction
) {
120 ConstraintContext Ctx
;
123 auto XAndY
= Ctx
.conj(X
, Y
);
124 auto NotXAndY
= Ctx
.neg(XAndY
);
126 // !(X ^ Y) ^ (X ^ Y)
127 EXPECT_THAT(solve({NotXAndY
, XAndY
}), unsat());
130 TEST(SolverTest
, DisjunctionSameVarWithNegation
) {
131 ConstraintContext Ctx
;
133 auto NotX
= Ctx
.neg(X
);
134 auto XOrNotX
= Ctx
.disj(X
, NotX
);
137 EXPECT_THAT(solve({XOrNotX
}), sat(_
));
140 TEST(SolverTest
, DisjunctionSameVar
) {
141 ConstraintContext Ctx
;
143 auto XOrX
= Ctx
.disj(X
, X
);
146 EXPECT_THAT(solve({XOrX
}), sat(_
));
149 TEST(SolverTest
, ConjunctionSameVarsConflict
) {
150 ConstraintContext Ctx
;
152 auto NotX
= Ctx
.neg(X
);
153 auto XAndNotX
= Ctx
.conj(X
, NotX
);
156 EXPECT_THAT(solve({XAndNotX
}), unsat());
159 TEST(SolverTest
, ConjunctionSameVar
) {
160 ConstraintContext Ctx
;
162 auto XAndX
= Ctx
.conj(X
, X
);
165 EXPECT_THAT(solve({XAndX
}), sat(_
));
168 TEST(SolverTest
, PureVar
) {
169 ConstraintContext Ctx
;
172 auto NotX
= Ctx
.neg(X
);
173 auto NotXOrY
= Ctx
.disj(NotX
, Y
);
174 auto NotY
= Ctx
.neg(Y
);
175 auto NotXOrNotY
= Ctx
.disj(NotX
, NotY
);
177 // (!X v Y) ^ (!X v !Y)
178 EXPECT_THAT(solve({NotXOrY
, NotXOrNotY
}),
179 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
),
180 Pair(Y
->getAtom(), _
))));
183 TEST(SolverTest
, MustAssumeVarIsFalse
) {
184 ConstraintContext Ctx
;
187 auto XOrY
= Ctx
.disj(X
, Y
);
188 auto NotX
= Ctx
.neg(X
);
189 auto NotXOrY
= Ctx
.disj(NotX
, Y
);
190 auto NotY
= Ctx
.neg(Y
);
191 auto NotXOrNotY
= Ctx
.disj(NotX
, NotY
);
193 // (X v Y) ^ (!X v Y) ^ (!X v !Y)
194 EXPECT_THAT(solve({XOrY
, NotXOrY
, NotXOrNotY
}),
195 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
),
196 Pair(Y
->getAtom(), AssignedTrue
))));
199 TEST(SolverTest
, DeepConflict
) {
200 ConstraintContext Ctx
;
203 auto XOrY
= Ctx
.disj(X
, Y
);
204 auto NotX
= Ctx
.neg(X
);
205 auto NotXOrY
= Ctx
.disj(NotX
, Y
);
206 auto NotY
= Ctx
.neg(Y
);
207 auto NotXOrNotY
= Ctx
.disj(NotX
, NotY
);
208 auto XOrNotY
= Ctx
.disj(X
, NotY
);
210 // (X v Y) ^ (!X v Y) ^ (!X v !Y) ^ (X v !Y)
211 EXPECT_THAT(solve({XOrY
, NotXOrY
, NotXOrNotY
, XOrNotY
}), unsat());
214 TEST(SolverTest
, IffIsEquivalentToDNF
) {
215 ConstraintContext Ctx
;
218 auto NotX
= Ctx
.neg(X
);
219 auto NotY
= Ctx
.neg(Y
);
220 auto XIffY
= Ctx
.iff(X
, Y
);
221 auto XIffYDNF
= Ctx
.disj(Ctx
.conj(X
, Y
), Ctx
.conj(NotX
, NotY
));
222 auto NotEquivalent
= Ctx
.neg(Ctx
.iff(XIffY
, XIffYDNF
));
224 // !((X <=> Y) <=> ((X ^ Y) v (!X ^ !Y)))
225 EXPECT_THAT(solve({NotEquivalent
}), unsat());
228 TEST(SolverTest
, IffSameVars
) {
229 ConstraintContext Ctx
;
231 auto XEqX
= Ctx
.iff(X
, X
);
234 EXPECT_THAT(solve({XEqX
}), sat(_
));
237 TEST(SolverTest
, IffDistinctVars
) {
238 ConstraintContext Ctx
;
241 auto XEqY
= Ctx
.iff(X
, Y
);
246 sat(AnyOf(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
),
247 Pair(Y
->getAtom(), AssignedTrue
)),
248 UnorderedElementsAre(Pair(X
->getAtom(), AssignedFalse
),
249 Pair(Y
->getAtom(), AssignedFalse
)))));
252 TEST(SolverTest
, IffWithUnits
) {
253 ConstraintContext Ctx
;
256 auto XEqY
= Ctx
.iff(X
, Y
);
259 EXPECT_THAT(solve({XEqY
, X
, Y
}),
260 sat(UnorderedElementsAre(Pair(X
->getAtom(), AssignedTrue
),
261 Pair(Y
->getAtom(), AssignedTrue
))));
264 TEST(SolverTest
, IffWithUnitsConflict
) {
266 auto Constraints
= parseFormulas(A
, R
"(
271 EXPECT_THAT(solve(Constraints
), unsat());
274 TEST(SolverTest
, IffTransitiveConflict
) {
276 auto Constraints
= parseFormulas(A
, R
"(
282 EXPECT_THAT(solve(Constraints
), unsat());
285 TEST(SolverTest
, DeMorgan
) {
287 auto Constraints
= parseFormulas(A
, R
"(
288 (!(V0 | V1) = (!V0 & !V1))
289 (!(V2 & V3) = (!V2 | !V3))
291 EXPECT_THAT(solve(Constraints
), sat(_
));
294 TEST(SolverTest
, RespectsAdditionalConstraints
) {
296 auto Constraints
= parseFormulas(A
, R
"(
301 EXPECT_THAT(solve(Constraints
), unsat());
304 TEST(SolverTest
, ImplicationIsEquivalentToDNF
) {
306 auto Constraints
= parseFormulas(A
, R
"(
307 !((V0 => V1) = (!V0 | V1))
309 EXPECT_THAT(solve(Constraints
), unsat());
312 TEST(SolverTest
, ImplicationConflict
) {
314 auto Constraints
= parseFormulas(A
, R
"(
318 EXPECT_THAT(solve(Constraints
), unsat());
321 TEST(SolverTest
, ReachedLimitsReflectsTimeouts
) {
323 auto Constraints
= parseFormulas(A
, R
"(
324 (!(V0 | V1) = (!V0 & !V1))
325 (!(V2 & V3) = (!V2 & !V3))
327 WatchedLiteralsSolver
solver(10);
328 ASSERT_EQ(solver
.solve(Constraints
).getStatus(),
329 Solver::Result::Status::TimedOut
);
330 EXPECT_TRUE(solver
.reachedLimit());
333 TEST(SolverTest
, SimpleButLargeContradiction
) {
334 // This test ensures that the solver takes a short-cut on known
335 // contradictory inputs, without using max_iterations. At the time
336 // this test is added, formulas that are easily recognized to be
337 // contradictory at CNF construction time would lead to timeout.
338 WatchedLiteralsSolver
solver(10);
339 ConstraintContext Ctx
;
340 auto first
= Ctx
.atom();
342 for (int i
= 1; i
< 10000; ++i
) {
343 last
= Ctx
.conj(last
, Ctx
.atom());
345 last
= Ctx
.conj(Ctx
.neg(first
), last
);
346 ASSERT_EQ(solver
.solve({last
}).getStatus(),
347 Solver::Result::Status::Unsatisfiable
);
348 EXPECT_FALSE(solver
.reachedLimit());
351 last
= Ctx
.neg(first
);
352 for (int i
= 1; i
< 10000; ++i
) {
353 last
= Ctx
.conj(last
, Ctx
.neg(Ctx
.atom()));
355 last
= Ctx
.conj(first
, last
);
356 ASSERT_EQ(solver
.solve({last
}).getStatus(),
357 Solver::Result::Status::Unsatisfiable
);
358 EXPECT_FALSE(solver
.reachedLimit());