|
| 1 | +// Unit tests for exporting abstract states as linear constraint systems, |
| 2 | +// organized as Boost.Test suites: |
| 3 | +// |
| 4 | +// flat_bool_export to_linear_constraint_system on the 3-valued boolean |
| 5 | +// domain: top is the empty system, bottom carries a |
| 6 | +// contradiction, known values become 0/1 equalities |
| 7 | +// product_export to_disjunctive_linear_constraint_system on |
| 8 | +// flat_boolean_numerical_domain: a product denotes the |
| 9 | +// *conjunction* of its components |
| 10 | +// array_export to_disjunctive_linear_constraint_system on the array |
| 11 | +// wrappers, which must propagate a bottom base domain |
| 12 | +// rather than iterating it |
| 13 | +// |
| 14 | +// The product and array suites are regression tests: exporting a state whose |
| 15 | +// boolean component was top used to terminate the process ("cannot add true"), |
| 16 | +// and a state with both boolean and numerical information used to be exported |
| 17 | +// as their disjunction instead of their conjunction. |
| 18 | +// |
| 19 | +// Nothing is printed to standard output: the whole Boost.Test log is routed |
| 20 | +// to stderr (tests/run_tests.sh diffs stdout against a golden file) and |
| 21 | +// ctest checks the exit code. |
| 22 | +#define BOOST_TEST_MODULE unittests_lincst_export |
| 23 | +#define BOOST_TEST_ALTERNATIVE_INIT_API |
| 24 | +#define BOOST_TEST_NO_MAIN |
| 25 | +#include <boost/test/included/unit_test.hpp> |
| 26 | + |
| 27 | +#include "../common.hpp" |
| 28 | + |
| 29 | +#include <crab/support/debug.hpp> |
| 30 | + |
| 31 | +#include <set> |
| 32 | +#include <string> |
| 33 | +#include <vector> |
| 34 | + |
| 35 | +using namespace crab::cfg_impl; |
| 36 | +using namespace crab::domain_impl; |
| 37 | + |
| 38 | +using flat_bool_domain_t = |
| 39 | + crab::domains::flat_boolean_domain<ikos::z_number, varname_t>; |
| 40 | +using product_domain_t = z_bool_interval_domain_t; |
| 41 | +using z_lin_cst_sys_t = ikos::linear_constraint_system<ikos::z_number, varname_t>; |
| 42 | +using z_disj_lin_cst_sys_t = |
| 43 | + ikos::disjunctive_linear_constraint_system<ikos::z_number, varname_t>; |
| 44 | + |
| 45 | +namespace { |
| 46 | + |
| 47 | +/** The constraints of a system, as strings, so they can be compared by set. */ |
| 48 | +std::set<std::string> constraints_of(const z_lin_cst_sys_t &csts) { |
| 49 | + std::set<std::string> res; |
| 50 | + for (auto const &c : csts) { |
| 51 | + crab::crab_string_os os; |
| 52 | + os << c; |
| 53 | + res.insert(os.str()); |
| 54 | + } |
| 55 | + return res; |
| 56 | +} |
| 57 | + |
| 58 | +/** The disjuncts of a disjunctive system. Must not be called on bottom. */ |
| 59 | +std::vector<std::set<std::string>> |
| 60 | +disjuncts_of(const z_disj_lin_cst_sys_t &dcsts) { |
| 61 | + std::vector<std::set<std::string>> res; |
| 62 | + for (auto const &csts : dcsts) { |
| 63 | + res.push_back(constraints_of(csts)); |
| 64 | + } |
| 65 | + return res; |
| 66 | +} |
| 67 | + |
| 68 | +} // namespace |
| 69 | + |
| 70 | +// --------------------------------------------------------------------------- |
| 71 | +BOOST_AUTO_TEST_SUITE(flat_bool_export) |
| 72 | + |
| 73 | +// Top is the empty system, matching the convention of the numerical domains |
| 74 | +// (e.g. split_dbm): is_true() is "no constraints". Returning a system that |
| 75 | +// *contains* a tautology instead would not be recognized as top, and would add |
| 76 | +// a redundant `true` to any conjunction it took part in. |
| 77 | +BOOST_AUTO_TEST_CASE(top_exports_the_empty_system) { |
| 78 | + flat_bool_domain_t dom; |
| 79 | + auto csts = dom.to_linear_constraint_system(); |
| 80 | + BOOST_CHECK_MESSAGE(csts.is_true(), "top must export as the empty system"); |
| 81 | + BOOST_CHECK_EQUAL(csts.size(), 0u); |
| 82 | +} |
| 83 | + |
| 84 | +// Bottom, by contrast, *is* represented by a constraint, so that is_false() |
| 85 | +// can detect it. |
| 86 | +BOOST_AUTO_TEST_CASE(bottom_exports_a_contradiction) { |
| 87 | + flat_bool_domain_t dom; |
| 88 | + dom.set_to_bottom(); |
| 89 | + auto csts = dom.to_linear_constraint_system(); |
| 90 | + BOOST_CHECK(csts.is_false()); |
| 91 | +} |
| 92 | + |
| 93 | +BOOST_AUTO_TEST_CASE(known_booleans_export_as_zero_one_equalities) { |
| 94 | + variable_factory_t vfac; |
| 95 | + z_var b(vfac["b"], crab::BOOL_TYPE, 1); |
| 96 | + z_var c(vfac["c"], crab::BOOL_TYPE, 1); |
| 97 | + |
| 98 | + flat_bool_domain_t dom; |
| 99 | + dom.assume_bool(b, false /*not negated*/); // b is true |
| 100 | + dom.assume_bool(c, true /*negated*/); // c is false |
| 101 | + |
| 102 | + auto csts = constraints_of(dom.to_linear_constraint_system()); |
| 103 | + BOOST_CHECK_EQUAL(csts.count("b = 1"), 1u); |
| 104 | + BOOST_CHECK_EQUAL(csts.count("c = 0"), 1u); |
| 105 | +} |
| 106 | + |
| 107 | +BOOST_AUTO_TEST_SUITE_END() |
| 108 | + |
| 109 | +// --------------------------------------------------------------------------- |
| 110 | +BOOST_AUTO_TEST_SUITE(product_export) |
| 111 | + |
| 112 | +BOOST_AUTO_TEST_CASE(top_exports_as_top) { |
| 113 | + product_domain_t dom; |
| 114 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 115 | + BOOST_CHECK(!dcsts.is_false()); |
| 116 | + BOOST_CHECK_MESSAGE(dcsts.is_true(), "a top product must export as top"); |
| 117 | +} |
| 118 | + |
| 119 | +BOOST_AUTO_TEST_CASE(bottom_exports_as_bottom) { |
| 120 | + product_domain_t dom; |
| 121 | + dom.set_to_bottom(); |
| 122 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 123 | + BOOST_CHECK_MESSAGE(dcsts.is_false(), |
| 124 | + "a bottom product must export as bottom"); |
| 125 | +} |
| 126 | + |
| 127 | +// Regression: with a top boolean component, exporting used to reach |
| 128 | +// `operator+=(true)` on a disjunctive system, which terminates the process. |
| 129 | +// Most program points have no boolean information, so this was the common case. |
| 130 | +BOOST_AUTO_TEST_CASE(numerical_only_survives_a_top_boolean_component) { |
| 131 | + variable_factory_t vfac; |
| 132 | + z_var y(vfac["y"], crab::INT_TYPE, 32); |
| 133 | + |
| 134 | + product_domain_t dom; |
| 135 | + dom += z_lin_cst_t(z_lin_exp_t(y) >= ikos::z_number(1)); |
| 136 | + dom += z_lin_cst_t(z_lin_exp_t(y) <= ikos::z_number(10)); |
| 137 | + |
| 138 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 139 | + BOOST_REQUIRE(!dcsts.is_false()); |
| 140 | + BOOST_CHECK_MESSAGE(!dcsts.is_true(), |
| 141 | + "numerical information must not be exported as top"); |
| 142 | + |
| 143 | + auto disjuncts = disjuncts_of(dcsts); |
| 144 | + BOOST_REQUIRE_EQUAL(disjuncts.size(), 1u); |
| 145 | + BOOST_CHECK_EQUAL(disjuncts[0].count("-y <= -1"), 1u); |
| 146 | + BOOST_CHECK_EQUAL(disjuncts[0].count("y <= 10"), 1u); |
| 147 | + // No stray tautology from the top boolean component. |
| 148 | + BOOST_CHECK_EQUAL(disjuncts[0].count("true"), 0u); |
| 149 | +} |
| 150 | + |
| 151 | +BOOST_AUTO_TEST_CASE(boolean_only_is_exported) { |
| 152 | + variable_factory_t vfac; |
| 153 | + z_var b(vfac["b"], crab::BOOL_TYPE, 1); |
| 154 | + |
| 155 | + product_domain_t dom; |
| 156 | + dom.assume_bool(b, false /*not negated*/); |
| 157 | + |
| 158 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 159 | + BOOST_REQUIRE(!dcsts.is_false()); |
| 160 | + BOOST_REQUIRE(!dcsts.is_true()); |
| 161 | + auto disjuncts = disjuncts_of(dcsts); |
| 162 | + BOOST_REQUIRE_EQUAL(disjuncts.size(), 1u); |
| 163 | + BOOST_CHECK_EQUAL(disjuncts[0].count("b = 1"), 1u); |
| 164 | +} |
| 165 | + |
| 166 | +// Regression, and the reason this file exists: a product denotes the |
| 167 | +// conjunction of its components. Adding each component to a disjunctive system |
| 168 | +// in turn computes their *disjunction* instead, which is far weaker -- here it |
| 169 | +// would yield `(b = 1) OR (1 <= y <= 10)` as two disjuncts rather than one |
| 170 | +// disjunct holding both facts. |
| 171 | +BOOST_AUTO_TEST_CASE(components_are_conjoined_not_disjoined) { |
| 172 | + variable_factory_t vfac; |
| 173 | + z_var b(vfac["b"], crab::BOOL_TYPE, 1); |
| 174 | + z_var y(vfac["y"], crab::INT_TYPE, 32); |
| 175 | + |
| 176 | + product_domain_t dom; |
| 177 | + dom.assume_bool(b, false /*not negated*/); |
| 178 | + dom += z_lin_cst_t(z_lin_exp_t(y) >= ikos::z_number(1)); |
| 179 | + dom += z_lin_cst_t(z_lin_exp_t(y) <= ikos::z_number(10)); |
| 180 | + |
| 181 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 182 | + BOOST_REQUIRE(!dcsts.is_false()); |
| 183 | + BOOST_REQUIRE(!dcsts.is_true()); |
| 184 | + |
| 185 | + auto disjuncts = disjuncts_of(dcsts); |
| 186 | + BOOST_REQUIRE_MESSAGE(disjuncts.size() == 1u, |
| 187 | + "the two components must be conjoined into a single " |
| 188 | + "disjunct, not unioned into two"); |
| 189 | + BOOST_CHECK_EQUAL(disjuncts[0].count("b = 1"), 1u); |
| 190 | + BOOST_CHECK_EQUAL(disjuncts[0].count("-y <= -1"), 1u); |
| 191 | + BOOST_CHECK_EQUAL(disjuncts[0].count("y <= 10"), 1u); |
| 192 | +} |
| 193 | + |
| 194 | +BOOST_AUTO_TEST_SUITE_END() |
| 195 | + |
| 196 | +// --------------------------------------------------------------------------- |
| 197 | +BOOST_AUTO_TEST_SUITE(array_export) |
| 198 | + |
| 199 | +// Regression: the array wrappers range-iterate the base domain's disjunctive |
| 200 | +// system, and begin() raises an error on a bottom system. This was unreachable |
| 201 | +// only while the product never reported bottom. |
| 202 | +BOOST_AUTO_TEST_CASE(array_adaptive_propagates_bottom) { |
| 203 | + z_aa_bool_int_t dom; |
| 204 | + dom.set_to_bottom(); |
| 205 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 206 | + BOOST_CHECK(dcsts.is_false()); |
| 207 | +} |
| 208 | + |
| 209 | +BOOST_AUTO_TEST_CASE(array_smashing_propagates_bottom) { |
| 210 | + z_as_bool_num_t dom; |
| 211 | + dom.set_to_bottom(); |
| 212 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 213 | + BOOST_CHECK(dcsts.is_false()); |
| 214 | +} |
| 215 | + |
| 216 | +BOOST_AUTO_TEST_CASE(array_adaptive_exports_scalar_facts) { |
| 217 | + variable_factory_t vfac; |
| 218 | + z_var y(vfac["y"], crab::INT_TYPE, 32); |
| 219 | + |
| 220 | + z_aa_bool_int_t dom; |
| 221 | + dom += z_lin_cst_t(z_lin_exp_t(y) >= ikos::z_number(1)); |
| 222 | + dom += z_lin_cst_t(z_lin_exp_t(y) <= ikos::z_number(10)); |
| 223 | + |
| 224 | + auto dcsts = dom.to_disjunctive_linear_constraint_system(); |
| 225 | + BOOST_REQUIRE(!dcsts.is_false()); |
| 226 | + BOOST_REQUIRE(!dcsts.is_true()); |
| 227 | + auto disjuncts = disjuncts_of(dcsts); |
| 228 | + BOOST_REQUIRE_EQUAL(disjuncts.size(), 1u); |
| 229 | + BOOST_CHECK_EQUAL(disjuncts[0].count("-y <= -1"), 1u); |
| 230 | + BOOST_CHECK_EQUAL(disjuncts[0].count("y <= 10"), 1u); |
| 231 | +} |
| 232 | + |
| 233 | +BOOST_AUTO_TEST_SUITE_END() |
| 234 | + |
| 235 | +// Keep stdout empty: the golden-output harness (tests/run_tests.sh) diffs |
| 236 | +// the standard output of every test binary, so the whole Boost.Test log is |
| 237 | +// routed to stderr. ctest passes --disable-warnings, which Boost.Test would |
| 238 | +// reject, so it is translated into the crab flag it stands for. |
| 239 | +int main(int argc, char **argv) { |
| 240 | + std::vector<char *> args; |
| 241 | + for (int idx = 0; idx < argc; ++idx) { |
| 242 | + if (std::string(argv[idx]) == "--disable-warnings") { |
| 243 | + crab::CrabEnableWarningMsg(false); |
| 244 | + continue; |
| 245 | + } |
| 246 | + args.push_back(argv[idx]); |
| 247 | + } |
| 248 | + char log_sink[] = "--log_sink=stderr"; |
| 249 | + char report_sink[] = "--report_sink=stderr"; |
| 250 | + args.push_back(log_sink); |
| 251 | + args.push_back(report_sink); |
| 252 | + return boost::unit_test::unit_test_main( |
| 253 | + &init_unit_test, static_cast<int>(args.size()), args.data()); |
| 254 | +} |
0 commit comments