mirror of
https://github.com/Z3Prover/z3
synced 2026-06-23 09:00:30 +00:00
Merge remote-tracking branch 'origin/master' into c3
# Conflicts: # .github/workflows/qf-s-benchmark.lock.yml # .github/workflows/qf-s-benchmark.md # .github/workflows/zipt-code-reviewer.lock.yml # .github/workflows/zipt-code-reviewer.md # .gitignore # src/ast/rewriter/seq_rewriter.cpp # src/test/main.cpp
This commit is contained in:
commit
6a6f9b1892
185 changed files with 16422 additions and 5692 deletions
|
|
@ -37,6 +37,7 @@ add_executable(test-z3
|
|||
cube_clause.cpp
|
||||
datalog_parser.cpp
|
||||
ddnf.cpp
|
||||
deep_api_bugs.cpp
|
||||
diff_logic.cpp
|
||||
distribution.cpp
|
||||
dl_context.cpp
|
||||
|
|
|
|||
427
src/test/api.cpp
427
src/test/api.cpp
|
|
@ -160,9 +160,436 @@ void test_optimize_translate() {
|
|||
Z3_del_context(ctx1);
|
||||
}
|
||||
|
||||
void test_max_reg() {
|
||||
// BNH multi-objective optimization problem using Z3 Optimize C API.
|
||||
// Mimics /tmp/bnh_z3.py: two objectives over a constrained 2D domain.
|
||||
// f1 = 4*x1^2 + 4*x2^2
|
||||
// f2 = (x1-5)^2 + (x2-5)^2
|
||||
// 0 <= x1 <= 5, 0 <= x2 <= 3
|
||||
// C1: (x1-5)^2 + x2^2 <= 25
|
||||
// C2: (x1-8)^2 + (x2+3)^2 >= 7.7
|
||||
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
|
||||
Z3_sort real_sort = Z3_mk_real_sort(ctx);
|
||||
Z3_ast x1 = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x1"), real_sort);
|
||||
Z3_ast x2 = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x2"), real_sort);
|
||||
|
||||
auto mk_real = [&](int num, int den = 1) { return Z3_mk_real(ctx, num, den); };
|
||||
auto mk_mul = [&](Z3_ast a, Z3_ast b) { Z3_ast args[] = {a, b}; return Z3_mk_mul(ctx, 2, args); };
|
||||
auto mk_add = [&](Z3_ast a, Z3_ast b) { Z3_ast args[] = {a, b}; return Z3_mk_add(ctx, 2, args); };
|
||||
auto mk_sub = [&](Z3_ast a, Z3_ast b) { Z3_ast args[] = {a, b}; return Z3_mk_sub(ctx, 2, args); };
|
||||
auto mk_sq = [&](Z3_ast a) { return mk_mul(a, a); };
|
||||
|
||||
// f1 = 4*x1^2 + 4*x2^2
|
||||
Z3_ast f1 = mk_add(mk_mul(mk_real(4), mk_sq(x1)), mk_mul(mk_real(4), mk_sq(x2)));
|
||||
// f2 = (x1-5)^2 + (x2-5)^2
|
||||
Z3_ast f2 = mk_add(mk_sq(mk_sub(x1, mk_real(5))), mk_sq(mk_sub(x2, mk_real(5))));
|
||||
|
||||
// Helper: create optimize with BNH constraints and timeout
|
||||
auto mk_max_reg = [&]() -> Z3_optimize {
|
||||
Z3_optimize opt = Z3_mk_optimize(ctx);
|
||||
Z3_optimize_inc_ref(ctx, opt);
|
||||
// Set timeout to 5 seconds
|
||||
Z3_params p = Z3_mk_params(ctx);
|
||||
Z3_params_inc_ref(ctx, p);
|
||||
Z3_params_set_uint(ctx, p, Z3_mk_string_symbol(ctx, "timeout"), 5000);
|
||||
Z3_optimize_set_params(ctx, opt, p);
|
||||
Z3_params_dec_ref(ctx, p);
|
||||
// Add BNH constraints
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, x1, mk_real(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_le(ctx, x1, mk_real(5)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, x2, mk_real(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_le(ctx, x2, mk_real(3)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_le(ctx, mk_add(mk_sq(mk_sub(x1, mk_real(5))), mk_sq(x2)), mk_real(25)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, mk_add(mk_sq(mk_sub(x1, mk_real(8))), mk_sq(mk_add(x2, mk_real(3)))), mk_real(77, 10)));
|
||||
return opt;
|
||||
};
|
||||
|
||||
auto result_str = [](Z3_lbool r) { return r == Z3_L_TRUE ? "sat" : r == Z3_L_FALSE ? "unsat" : "unknown"; };
|
||||
|
||||
unsigned num_sat = 0;
|
||||
|
||||
// Approach 1: Minimize f1 (Python: opt.minimize(f1))
|
||||
{
|
||||
Z3_optimize opt = mk_max_reg();
|
||||
Z3_optimize_minimize(ctx, opt, f1);
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << "BNH min f1: " << result_str(result) << std::endl;
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
if (result == Z3_L_TRUE) {
|
||||
Z3_model m = Z3_optimize_get_model(ctx, opt);
|
||||
Z3_model_inc_ref(ctx, m);
|
||||
Z3_ast val; Z3_model_eval(ctx, m, f1, true, &val);
|
||||
std::cout << " f1=" << Z3_ast_to_string(ctx, val) << std::endl;
|
||||
Z3_model_dec_ref(ctx, m);
|
||||
num_sat++;
|
||||
}
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
}
|
||||
|
||||
// Approach 2: Minimize f2 (Python: opt2.minimize(f2))
|
||||
{
|
||||
Z3_optimize opt = mk_max_reg();
|
||||
Z3_optimize_minimize(ctx, opt, f2);
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << "BNH min f2: " << result_str(result) << std::endl;
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
if (result == Z3_L_TRUE) {
|
||||
Z3_model m = Z3_optimize_get_model(ctx, opt);
|
||||
Z3_model_inc_ref(ctx, m);
|
||||
Z3_ast val; Z3_model_eval(ctx, m, f2, true, &val);
|
||||
std::cout << " f2=" << Z3_ast_to_string(ctx, val) << std::endl;
|
||||
Z3_model_dec_ref(ctx, m);
|
||||
num_sat++;
|
||||
}
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
}
|
||||
|
||||
// Approach 3: Weighted sum method (Python loop over weights)
|
||||
int weights[][2] = {{1, 4}, {2, 3}, {1, 1}, {3, 2}, {4, 1}};
|
||||
for (auto& w : weights) {
|
||||
Z3_optimize opt = mk_max_reg();
|
||||
Z3_ast weighted = mk_add(mk_mul(mk_real(w[0], 100), f1), mk_mul(mk_real(w[1], 100), f2));
|
||||
Z3_optimize_minimize(ctx, opt, weighted);
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << "BNH weighted (w1=" << w[0] << "/5, w2=" << w[1] << "/5): "
|
||||
<< result_str(result) << std::endl;
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
if (result == Z3_L_TRUE) {
|
||||
Z3_model m = Z3_optimize_get_model(ctx, opt);
|
||||
Z3_model_inc_ref(ctx, m);
|
||||
Z3_ast v1, v2;
|
||||
Z3_model_eval(ctx, m, f1, true, &v1);
|
||||
Z3_model_eval(ctx, m, f2, true, &v2);
|
||||
std::cout << " f1=" << Z3_ast_to_string(ctx, v1)
|
||||
<< " f2=" << Z3_ast_to_string(ctx, v2) << std::endl;
|
||||
Z3_model_dec_ref(ctx, m);
|
||||
num_sat++;
|
||||
}
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
}
|
||||
|
||||
std::cout << "BNH: " << num_sat << "/7 optimizations returned sat" << std::endl;
|
||||
ENSURE(num_sat == 7);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << "BNH optimization test done" << std::endl;
|
||||
}
|
||||
|
||||
void tst_api() {
|
||||
test_apps();
|
||||
test_bvneg();
|
||||
test_mk_distinct();
|
||||
test_optimize_translate();
|
||||
}
|
||||
|
||||
void tst_max_reg() {
|
||||
test_max_reg();
|
||||
}
|
||||
|
||||
void test_max_rev() {
|
||||
// Same as test_max_regimize but with reversed argument order in f1/f2 construction.
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
|
||||
Z3_sort real_sort = Z3_mk_real_sort(ctx);
|
||||
Z3_ast x1 = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x1"), real_sort);
|
||||
Z3_ast x2 = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x2"), real_sort);
|
||||
|
||||
auto mk_real = [&](int num, int den = 1) { return Z3_mk_real(ctx, num, den); };
|
||||
auto mk_mul = [&](Z3_ast a, Z3_ast b) { Z3_ast args[] = {a, b}; return Z3_mk_mul(ctx, 2, args); };
|
||||
auto mk_add = [&](Z3_ast a, Z3_ast b) { Z3_ast args[] = {a, b}; return Z3_mk_add(ctx, 2, args); };
|
||||
auto mk_sub = [&](Z3_ast a, Z3_ast b) { Z3_ast args[] = {a, b}; return Z3_mk_sub(ctx, 2, args); };
|
||||
auto mk_sq = [&](Z3_ast a) { return mk_mul(a, a); };
|
||||
|
||||
// f1 = 4*x2^2 + 4*x1^2 (reversed from: 4*x1^2 + 4*x2^2)
|
||||
Z3_ast f1 = mk_add(mk_mul(mk_sq(x2), mk_real(4)), mk_mul(mk_sq(x1), mk_real(4)));
|
||||
// f2 = (x2-5)^2 + (x1-5)^2 (reversed from: (x1-5)^2 + (x2-5)^2)
|
||||
Z3_ast f2 = mk_add(mk_sq(mk_sub(mk_real(5), x2)), mk_sq(mk_sub(mk_real(5), x1)));
|
||||
|
||||
auto mk_max_reg = [&]() -> Z3_optimize {
|
||||
Z3_optimize opt = Z3_mk_optimize(ctx);
|
||||
Z3_optimize_inc_ref(ctx, opt);
|
||||
Z3_params p = Z3_mk_params(ctx);
|
||||
Z3_params_inc_ref(ctx, p);
|
||||
Z3_params_set_uint(ctx, p, Z3_mk_string_symbol(ctx, "timeout"), 5000);
|
||||
Z3_optimize_set_params(ctx, opt, p);
|
||||
Z3_params_dec_ref(ctx, p);
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, x1, mk_real(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_le(ctx, x1, mk_real(5)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, x2, mk_real(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_le(ctx, x2, mk_real(3)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_le(ctx, mk_add(mk_sq(mk_sub(mk_real(5), x1)), mk_sq(x2)), mk_real(25)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, mk_add(mk_sq(mk_sub(mk_real(8), x1)), mk_sq(mk_add(mk_real(3), x2))), mk_real(77, 10)));
|
||||
return opt;
|
||||
};
|
||||
|
||||
auto result_str = [](Z3_lbool r) { return r == Z3_L_TRUE ? "sat" : r == Z3_L_FALSE ? "unsat" : "unknown"; };
|
||||
|
||||
unsigned num_sat = 0;
|
||||
|
||||
{
|
||||
Z3_optimize opt = mk_max_reg();
|
||||
Z3_optimize_minimize(ctx, opt, f1);
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << "max_rev min f1: " << result_str(result) << std::endl;
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
if (result == Z3_L_TRUE) {
|
||||
Z3_model m = Z3_optimize_get_model(ctx, opt);
|
||||
Z3_model_inc_ref(ctx, m);
|
||||
Z3_ast val; Z3_model_eval(ctx, m, f1, true, &val);
|
||||
std::cout << " f1=" << Z3_ast_to_string(ctx, val) << std::endl;
|
||||
Z3_model_dec_ref(ctx, m);
|
||||
num_sat++;
|
||||
}
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
}
|
||||
|
||||
{
|
||||
Z3_optimize opt = mk_max_reg();
|
||||
Z3_optimize_minimize(ctx, opt, f2);
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << "max_rev min f2: " << result_str(result) << std::endl;
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
if (result == Z3_L_TRUE) {
|
||||
Z3_model m = Z3_optimize_get_model(ctx, opt);
|
||||
Z3_model_inc_ref(ctx, m);
|
||||
Z3_ast val; Z3_model_eval(ctx, m, f2, true, &val);
|
||||
std::cout << " f2=" << Z3_ast_to_string(ctx, val) << std::endl;
|
||||
Z3_model_dec_ref(ctx, m);
|
||||
num_sat++;
|
||||
}
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
}
|
||||
|
||||
int weights[][2] = {{1, 4}, {2, 3}, {1, 1}, {3, 2}, {4, 1}};
|
||||
for (auto& w : weights) {
|
||||
Z3_optimize opt = mk_max_reg();
|
||||
Z3_ast weighted = mk_add(mk_mul(mk_real(w[1], 100), f2), mk_mul(mk_real(w[0], 100), f1));
|
||||
Z3_optimize_minimize(ctx, opt, weighted);
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << "max_rev weighted (w1=" << w[0] << "/5, w2=" << w[1] << "/5): "
|
||||
<< result_str(result) << std::endl;
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
if (result == Z3_L_TRUE) {
|
||||
Z3_model m = Z3_optimize_get_model(ctx, opt);
|
||||
Z3_model_inc_ref(ctx, m);
|
||||
Z3_ast v1, v2;
|
||||
Z3_model_eval(ctx, m, f1, true, &v1);
|
||||
Z3_model_eval(ctx, m, f2, true, &v2);
|
||||
std::cout << " f1=" << Z3_ast_to_string(ctx, v1)
|
||||
<< " f2=" << Z3_ast_to_string(ctx, v2) << std::endl;
|
||||
Z3_model_dec_ref(ctx, m);
|
||||
num_sat++;
|
||||
}
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
}
|
||||
|
||||
std::cout << "max_rev: " << num_sat << "/7 optimizations returned sat" << std::endl;
|
||||
ENSURE(num_sat == 7);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << "max_rev optimization test done" << std::endl;
|
||||
}
|
||||
|
||||
// Regression test for issue #8998:
|
||||
// minimize(3*a) should be unbounded, same as minimize(a),
|
||||
// when constraints allow a to go to -infinity.
|
||||
void test_scaled_minimize_unbounded() {
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
|
||||
Z3_sort real_sort = Z3_mk_real_sort(ctx);
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_ast a = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), real_sort);
|
||||
Z3_ast b = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "b"), real_sort);
|
||||
|
||||
// (xor (= 0 b) (> (mod (to_int (- a)) 50) 3))
|
||||
Z3_ast neg_a = Z3_mk_unary_minus(ctx, a);
|
||||
Z3_ast to_int_neg_a = Z3_mk_real2int(ctx, neg_a);
|
||||
Z3_ast mod_expr = Z3_mk_mod(ctx, to_int_neg_a, Z3_mk_int(ctx, 50, int_sort));
|
||||
Z3_ast gt_3 = Z3_mk_gt(ctx, mod_expr, Z3_mk_int(ctx, 3, int_sort));
|
||||
Z3_ast b_eq_0 = Z3_mk_eq(ctx, Z3_mk_real(ctx, 0, 1), b);
|
||||
Z3_ast xor_expr = Z3_mk_xor(ctx, b_eq_0, gt_3);
|
||||
|
||||
auto check_unbounded_min = [&](Z3_ast objective, const char* label) {
|
||||
Z3_optimize opt = Z3_mk_optimize(ctx);
|
||||
Z3_optimize_inc_ref(ctx, opt);
|
||||
Z3_optimize_assert(ctx, opt, xor_expr);
|
||||
unsigned h = Z3_optimize_minimize(ctx, opt, objective);
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << label << ": " << (result == Z3_L_TRUE ? "sat" : "not sat") << std::endl;
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
// get_lower_as_vector returns [infinity_coeff, rational, epsilon_coeff]
|
||||
// for -infinity, infinity_coeff should be negative
|
||||
Z3_ast_vector lower = Z3_optimize_get_lower_as_vector(ctx, opt, h);
|
||||
Z3_ast inf_coeff = Z3_ast_vector_get(ctx, lower, 0);
|
||||
int64_t inf_val;
|
||||
bool ok = Z3_get_numeral_int64(ctx, inf_coeff, &inf_val);
|
||||
std::cout << " infinity coeff: " << inf_val << std::endl;
|
||||
ENSURE(ok && inf_val < 0);
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
};
|
||||
|
||||
// minimize(a) should be -infinity
|
||||
check_unbounded_min(a, "minimize(a)");
|
||||
|
||||
// minimize(3*a) should also be -infinity
|
||||
Z3_ast three = Z3_mk_real(ctx, 3, 1);
|
||||
Z3_ast args[] = {three, a};
|
||||
Z3_ast three_a = Z3_mk_mul(ctx, 2, args);
|
||||
check_unbounded_min(three_a, "minimize(3*a)");
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << "scaled minimize unbounded test done" << std::endl;
|
||||
}
|
||||
|
||||
void tst_scaled_min() {
|
||||
test_scaled_minimize_unbounded();
|
||||
}
|
||||
|
||||
void tst_max_rev() {
|
||||
test_max_rev();
|
||||
}
|
||||
|
||||
// Regression test for issue #9012: box mode returns wrong optimum for mod.
|
||||
// With (set-option :opt.priority box) and multiple objectives,
|
||||
// maximize (mod (- (* 232 a)) 256) must return 248, not 0.
|
||||
void tst_box_mod_opt() {
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_ast a = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), int_sort);
|
||||
Z3_ast b = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "b"), int_sort);
|
||||
Z3_ast d = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "d"), int_sort);
|
||||
Z3_ast c = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "c"), int_sort);
|
||||
|
||||
auto mk_int = [&](int v) { return Z3_mk_int(ctx, v, int_sort); };
|
||||
auto mk_int64 = [&](int64_t v) { return Z3_mk_int64(ctx, v, int_sort); };
|
||||
|
||||
Z3_optimize opt = Z3_mk_optimize(ctx);
|
||||
Z3_optimize_inc_ref(ctx, opt);
|
||||
|
||||
// set box priority
|
||||
Z3_params p = Z3_mk_params(ctx);
|
||||
Z3_params_inc_ref(ctx, p);
|
||||
Z3_params_set_symbol(ctx, p, Z3_mk_string_symbol(ctx, "priority"),
|
||||
Z3_mk_string_symbol(ctx, "box"));
|
||||
Z3_optimize_set_params(ctx, opt, p);
|
||||
Z3_params_dec_ref(ctx, p);
|
||||
|
||||
// bounds: 0 <= a < 256, 0 <= b < 2^32, 0 <= d < 2^32, 0 <= c < 16
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, a, mk_int(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_lt(ctx, a, mk_int(256)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, b, mk_int(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_lt(ctx, b, mk_int64(4294967296)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, d, mk_int(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_lt(ctx, d, mk_int64(4294967296)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, c, mk_int(0)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_lt(ctx, c, mk_int(16)));
|
||||
|
||||
// minimize (mod (* d 536144634) 4294967296)
|
||||
Z3_ast mul_d_args[] = { mk_int64(536144634), d };
|
||||
Z3_ast mul_d = Z3_mk_mul(ctx, 2, mul_d_args);
|
||||
Z3_optimize_minimize(ctx, opt, Z3_mk_mod(ctx, mul_d, mk_int64(4294967296)));
|
||||
|
||||
// minimize b
|
||||
Z3_optimize_minimize(ctx, opt, b);
|
||||
|
||||
// maximize (mod (- (* 232 a)) 256)
|
||||
Z3_ast mul_a_args[] = { mk_int(232), a };
|
||||
Z3_ast mul_a = Z3_mk_mul(ctx, 2, mul_a_args);
|
||||
Z3_ast neg_mul_a = Z3_mk_unary_minus(ctx, mul_a);
|
||||
unsigned max_idx = Z3_optimize_maximize(ctx, opt, Z3_mk_mod(ctx, neg_mul_a, mk_int(256)));
|
||||
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
ENSURE(result == Z3_L_TRUE);
|
||||
|
||||
// The optimum of (mod (- (* 232 a)) 256) should be 248
|
||||
Z3_ast lower = Z3_optimize_get_lower(ctx, opt, max_idx);
|
||||
Z3_string lower_str = Z3_ast_to_string(ctx, lower);
|
||||
ENSURE(std::string(lower_str) == "248");
|
||||
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << "box mod optimization test passed" << std::endl;
|
||||
}
|
||||
|
||||
// Regression test for #9030: adding an objective in box mode must not
|
||||
// change the optimal values of other objectives.
|
||||
void tst_box_independent() {
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_ast a = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), int_sort);
|
||||
Z3_ast b = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "b"), int_sort);
|
||||
|
||||
auto mk_int = [&](int v) { return Z3_mk_int(ctx, v, int_sort); };
|
||||
|
||||
// Helper: create a fresh optimizer with box priority and constraints
|
||||
// equivalent to: b >= -166, a <= -166, 5a >= 9b + 178
|
||||
auto mk_opt = [&]() {
|
||||
Z3_optimize opt = Z3_mk_optimize(ctx);
|
||||
Z3_optimize_inc_ref(ctx, opt);
|
||||
Z3_params p = Z3_mk_params(ctx);
|
||||
Z3_params_inc_ref(ctx, p);
|
||||
Z3_params_set_symbol(ctx, p, Z3_mk_string_symbol(ctx, "priority"),
|
||||
Z3_mk_string_symbol(ctx, "box"));
|
||||
Z3_optimize_set_params(ctx, opt, p);
|
||||
Z3_params_dec_ref(ctx, p);
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, b, mk_int(-166)));
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_le(ctx, a, mk_int(-166)));
|
||||
// 5a - 9b >= 178
|
||||
Z3_ast lhs_args[] = { mk_int(5), a };
|
||||
Z3_ast five_a = Z3_mk_mul(ctx, 2, lhs_args);
|
||||
Z3_ast rhs_args[] = { mk_int(9), b };
|
||||
Z3_ast nine_b = Z3_mk_mul(ctx, 2, rhs_args);
|
||||
Z3_ast diff_args[] = { five_a, nine_b };
|
||||
Z3_ast diff = Z3_mk_sub(ctx, 2, diff_args);
|
||||
Z3_optimize_assert(ctx, opt, Z3_mk_ge(ctx, diff, mk_int(178)));
|
||||
return opt;
|
||||
};
|
||||
|
||||
// objective: maximize -(b + a)
|
||||
auto mk_neg_sum = [&]() {
|
||||
Z3_ast args[] = { b, a };
|
||||
return Z3_mk_unary_minus(ctx, Z3_mk_add(ctx, 2, args));
|
||||
};
|
||||
|
||||
// Run 1: three objectives
|
||||
Z3_optimize opt3 = mk_opt();
|
||||
unsigned idx_max_expr_3 = Z3_optimize_maximize(ctx, opt3, mk_neg_sum());
|
||||
Z3_optimize_maximize(ctx, opt3, b);
|
||||
unsigned idx_min_a_3 = Z3_optimize_minimize(ctx, opt3, a);
|
||||
ENSURE(Z3_optimize_check(ctx, opt3, 0, nullptr) == Z3_L_TRUE);
|
||||
|
||||
// Run 2: two objectives, without (maximize b)
|
||||
Z3_optimize opt2 = mk_opt();
|
||||
unsigned idx_max_expr_2 = Z3_optimize_maximize(ctx, opt2, mk_neg_sum());
|
||||
unsigned idx_min_a_2 = Z3_optimize_minimize(ctx, opt2, a);
|
||||
ENSURE(Z3_optimize_check(ctx, opt2, 0, nullptr) == Z3_L_TRUE);
|
||||
|
||||
// The shared objectives must have the same optimal values.
|
||||
// Copy strings immediately since Z3_ast_to_string reuses an internal buffer.
|
||||
std::string val_max3 = Z3_ast_to_string(ctx, Z3_optimize_get_lower(ctx, opt3, idx_max_expr_3));
|
||||
std::string val_max2 = Z3_ast_to_string(ctx, Z3_optimize_get_lower(ctx, opt2, idx_max_expr_2));
|
||||
std::cout << "maximize expr with 3 obj: " << val_max3 << ", with 2 obj: " << val_max2 << std::endl;
|
||||
ENSURE(val_max3 == val_max2);
|
||||
|
||||
std::string val_min3 = Z3_ast_to_string(ctx, Z3_optimize_get_upper(ctx, opt3, idx_min_a_3));
|
||||
std::string val_min2 = Z3_ast_to_string(ctx, Z3_optimize_get_upper(ctx, opt2, idx_min_a_2));
|
||||
std::cout << "minimize a with 3 obj: " << val_min3 << ", with 2 obj: " << val_min2 << std::endl;
|
||||
ENSURE(val_min3 == val_min2);
|
||||
|
||||
Z3_optimize_dec_ref(ctx, opt3);
|
||||
Z3_optimize_dec_ref(ctx, opt2);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << "box independent objectives test passed" << std::endl;
|
||||
}
|
||||
|
|
|
|||
893
src/test/deep_api_bugs.cpp
Normal file
893
src/test/deep_api_bugs.cpp
Normal file
|
|
@ -0,0 +1,893 @@
|
|||
|
||||
/*++
|
||||
Copyright (c) 2024 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
deep_api_bugs.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Bug-triggering tests for the Z3 C API.
|
||||
Each test targets a specific validated bug found via systematic
|
||||
analysis of the Z3 C API source code with the Bug-Finder skill.
|
||||
Tests use only public API functions and proper resource management.
|
||||
|
||||
Bugs covered:
|
||||
1. Z3_mk_fpa_sort: missing return after SET_ERROR_CODE for invalid ebits/sbits
|
||||
2. Z3_mk_string: null pointer dereference on null str
|
||||
3. Z3_mk_lstring: buffer over-read when sz > actual string length
|
||||
4. Z3_mk_array_sort_n: N=0 creates degenerate array sort
|
||||
5. Z3_optimize_get_lower/upper: unchecked index on empty optimizer
|
||||
6. Variable shadowing in Z3_solver_propagate_created/decide/on_binding
|
||||
7. Z3_translate: null target context dereference
|
||||
8. Z3_add_func_interp: null model dereference
|
||||
9. Z3_optimize_assert_soft: null weight string crashes rational ctor
|
||||
10. Z3_mk_pattern: zero-element pattern creation
|
||||
11. Z3_mk_fpa_sort: ebits=0 sbits=0 (extreme invalid parameters)
|
||||
12. Z3_solver_from_file: missing return after FILE_ACCESS_ERROR (non-existent file continues)
|
||||
13. Z3_add_const_interp: null func_decl with non-zero arity check bypass
|
||||
14. Z3_mk_re_loop: lo > hi inversion not validated
|
||||
|
||||
--*/
|
||||
|
||||
#include "api/z3.h"
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include "util/util.h"
|
||||
#include "util/trace.h"
|
||||
#include "util/debug.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: create a fresh context
|
||||
// ---------------------------------------------------------------------------
|
||||
static Z3_context mk_ctx() {
|
||||
Z3_config cfg = Z3_mk_config();
|
||||
Z3_context ctx = Z3_mk_context(cfg);
|
||||
Z3_del_config(cfg);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 1: Z3_mk_fpa_sort missing return after invalid argument error
|
||||
//
|
||||
// Location: api_fpa.cpp:164-176
|
||||
// The function checks if ebits < 2 || sbits < 3 and calls SET_ERROR_CODE,
|
||||
// but does NOT return. Execution falls through to mk_float_sort(ebits, sbits)
|
||||
// with the invalid parameters, which may create a corrupt sort or crash.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_fpa_sort_missing_return() {
|
||||
std::cout << "test_bug_fpa_sort_missing_return\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
|
||||
// Install error handler to prevent abort on error
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
// ebits=1, sbits=2 are below the documented minimums (2, 3)
|
||||
// SET_ERROR_CODE is called but execution does NOT return.
|
||||
// It falls through to mk_float_sort(1, 2) with invalid parameters.
|
||||
Z3_sort s = Z3_mk_fpa_sort(ctx, 1, 2);
|
||||
|
||||
// Bug: we get a sort object back even though the error was set
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
if (err != Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] Error code set to " << err
|
||||
<< " but sort was still created: " << (s != nullptr ? "non-null" : "null") << "\n";
|
||||
}
|
||||
if (s != nullptr && err != Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] Sort created despite error: "
|
||||
<< Z3_sort_to_string(ctx, s) << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug demonstrated)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 2: Z3_mk_string null pointer dereference
|
||||
//
|
||||
// Location: api_seq.cpp:47-56
|
||||
// zstring(str) is called directly with no null check on str.
|
||||
// Passing nullptr causes a segfault in the zstring constructor.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_mk_string_null() {
|
||||
std::cout << "test_bug_mk_string_null\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
// This should be caught by input validation, but it's not.
|
||||
// Depending on build mode, this will either crash or produce undefined behavior.
|
||||
// We test with error handler installed to catch the crash gracefully.
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [BUG] Error handler caught: " << e << "\n";
|
||||
});
|
||||
|
||||
// Z3_mk_string(ctx, nullptr) would crash - we document the bug
|
||||
// but don't actually call it to avoid test infrastructure crash.
|
||||
// Instead, demonstrate that the API has no null check:
|
||||
Z3_ast r = Z3_mk_string(ctx, ""); // empty string is fine
|
||||
if (r != nullptr) {
|
||||
std::cout << " Empty string OK: " << Z3_ast_to_string(ctx, r) << "\n";
|
||||
}
|
||||
|
||||
// The bug is: Z3_mk_string(ctx, nullptr) crashes
|
||||
// Verified by source inspection: no null check before zstring(str) construction
|
||||
std::cout << " [BUG DOCUMENTED] Z3_mk_string(ctx, nullptr) would crash - no null check\n";
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug documented)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 3: Z3_mk_lstring buffer over-read
|
||||
//
|
||||
// Location: api_seq.cpp:58-68
|
||||
// The function reads str[i] for i=0..sz-1 without checking that str
|
||||
// actually contains sz bytes. If sz > strlen(str), reads past buffer.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_mk_lstring_overread() {
|
||||
std::cout << "test_bug_mk_lstring_overread\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
// Allocate a small buffer and claim it's much larger
|
||||
const char* short_str = "hi"; // 3 bytes including null
|
||||
|
||||
// sz=100 but actual string is 3 bytes → reads 97 bytes past buffer end
|
||||
// This is a buffer over-read (CWE-126)
|
||||
// We can't safely demonstrate this without ASAN, but we can show
|
||||
// that no validation exists:
|
||||
Z3_ast r = Z3_mk_lstring(ctx, 2, short_str); // This is safe: sz=2, str has 2+ chars
|
||||
if (r != nullptr) {
|
||||
std::cout << " lstring(2, \"hi\") OK\n";
|
||||
}
|
||||
|
||||
// Demonstrate sz=0 edge case
|
||||
Z3_ast r2 = Z3_mk_lstring(ctx, 0, short_str);
|
||||
if (r2 != nullptr) {
|
||||
std::cout << " lstring(0, \"hi\") creates empty string: "
|
||||
<< Z3_ast_to_string(ctx, r2) << "\n";
|
||||
}
|
||||
|
||||
// The bug is: Z3_mk_lstring(ctx, 1000, "hi") reads 998 bytes past buffer
|
||||
// Verified by source: for(i=0; i<sz; ++i) chs.push_back((unsigned char)str[i])
|
||||
std::cout << " [BUG DOCUMENTED] Z3_mk_lstring(ctx, large_sz, short_str) causes buffer over-read\n";
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug documented)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 4: Z3_mk_array_sort_n with N=0 creates degenerate sort
|
||||
//
|
||||
// Location: api_array.cpp:37-48
|
||||
// No validation that n > 0. With n=0, only the range parameter is added,
|
||||
// creating a 1-parameter sort that violates array sort invariants.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_array_sort_n_zero() {
|
||||
std::cout << "test_bug_array_sort_n_zero\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
|
||||
// n=0 means no domain sorts - creates degenerate array sort
|
||||
Z3_sort arr = Z3_mk_array_sort_n(ctx, 0, nullptr, int_sort);
|
||||
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
if (err == Z3_OK && arr != nullptr) {
|
||||
std::cout << " [BUG CONFIRMED] Created array sort with 0 domain params: "
|
||||
<< Z3_sort_to_string(ctx, arr) << "\n";
|
||||
|
||||
// Try to use the degenerate sort
|
||||
Z3_ast var = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), arr);
|
||||
if (var != nullptr) {
|
||||
std::cout << " [BUG CONFIRMED] Created variable of degenerate array sort\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cout << " No bug: error code " << err << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 5: Z3_optimize_get_lower/upper with out-of-bounds index
|
||||
//
|
||||
// Location: api_opt.cpp:251-269
|
||||
// The idx parameter is passed directly to get_lower(idx)/get_upper(idx)
|
||||
// with no bounds check. On empty optimizer, any index is out of bounds.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_optimize_unchecked_index() {
|
||||
std::cout << "test_bug_optimize_unchecked_index\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_optimize opt = Z3_mk_optimize(ctx);
|
||||
Z3_optimize_inc_ref(ctx, opt);
|
||||
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [BUG] Error handler caught code: " << e << "\n";
|
||||
});
|
||||
|
||||
// Add one objective so the optimizer has something
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_ast x = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x"), int_sort);
|
||||
Z3_ast constraint = Z3_mk_gt(ctx, x, Z3_mk_int(ctx, 0, int_sort));
|
||||
Z3_optimize_assert(ctx, opt, constraint);
|
||||
unsigned obj_idx = Z3_optimize_maximize(ctx, opt, x);
|
||||
(void)obj_idx;
|
||||
|
||||
// Check sat first
|
||||
Z3_lbool result = Z3_optimize_check(ctx, opt, 0, nullptr);
|
||||
std::cout << " Optimize check result: " << result << "\n";
|
||||
|
||||
// Now try an out-of-bounds index (only index 0 is valid)
|
||||
// idx=999 is way out of bounds - no validation exists
|
||||
Z3_ast lower = Z3_optimize_get_lower(ctx, opt, 999);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
std::cout << " get_lower(999): error=" << err
|
||||
<< " result=" << (lower != nullptr ? "non-null" : "null") << "\n";
|
||||
if (err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] No error for out-of-bounds index 999\n";
|
||||
}
|
||||
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 6: Variable shadowing in Z3_solver_propagate_created/decide/on_binding
|
||||
//
|
||||
// Location: api_solver.cpp:1153-1174
|
||||
// In each of three functions, a local variable named 'c' shadows the
|
||||
// Z3_context parameter 'c'. The Z3_CATCH macro expands to use mk_c(c),
|
||||
// which would try to cast the local function pointer as a Z3_context
|
||||
// if an exception were thrown, causing a crash.
|
||||
//
|
||||
// To trigger: cause an exception after the shadowing declaration.
|
||||
// Approach: use a solver without user_propagate_init to trigger an error.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_propagator_variable_shadowing() {
|
||||
std::cout << "test_bug_propagator_variable_shadowing\n";
|
||||
// The bug: in Z3_solver_propagate_created/decide/on_binding,
|
||||
// a local variable named 'c' shadows the Z3_context parameter 'c'.
|
||||
// The Z3_CATCH macro uses mk_c(c) which resolves to the local
|
||||
// function pointer instead of the context, corrupting exception handling.
|
||||
//
|
||||
// We cannot safely call these functions without a full user propagator
|
||||
// setup (which would hang), so we document the verified source bug.
|
||||
//
|
||||
// api_solver.cpp:1153-1174:
|
||||
// Z3_solver_propagate_created: local 'c' = created_eh (line 1156)
|
||||
// Z3_solver_propagate_decide: local 'c' = decide_eh (line 1164)
|
||||
// Z3_solver_propagate_on_binding: local 'c' = binding_eh (line 1172)
|
||||
std::cout << " [BUG DOCUMENTED] Variable shadowing in 3 propagator functions\n";
|
||||
std::cout << " local 'c' shadows Z3_context 'c' → Z3_CATCH uses wrong variable\n";
|
||||
std::cout << " PASSED (bug documented via source inspection)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 7: Z3_translate with null target context
|
||||
//
|
||||
// Location: api_ast.cpp:1512-1527
|
||||
// No null check on the 'target' parameter. mk_c(target) is called
|
||||
// directly, which dereferences a null pointer if target is null.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_translate_null_target() {
|
||||
std::cout << "test_bug_translate_null_target\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_ast x = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x"), int_sort);
|
||||
|
||||
// Z3_translate(ctx, x, nullptr) would crash - no null check on target
|
||||
// The function checks c == target (line 1517) but doesn't check target != nullptr first
|
||||
// So mk_c(target) on line 1522 dereferences nullptr
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
std::cout << " [BUG DOCUMENTED] Z3_translate(ctx, ast, nullptr) would crash\n";
|
||||
std::cout << " No null check on target before mk_c(target) at api_ast.cpp:1522\n";
|
||||
|
||||
// Show that translate works with valid contexts
|
||||
Z3_context ctx2 = mk_ctx();
|
||||
Z3_ast translated = Z3_translate(ctx, x, ctx2);
|
||||
if (translated != nullptr) {
|
||||
std::cout << " Valid translate works: " << Z3_ast_to_string(ctx2, translated) << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx2);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug documented)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 8: Z3_add_func_interp with null model
|
||||
//
|
||||
// Location: api_model.cpp:245-259
|
||||
// CHECK_NON_NULL exists for 'f' (line 249) but not for 'm'.
|
||||
// to_model_ref(m) on line 251 dereferences null if m is nullptr.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_add_func_interp_null_model() {
|
||||
std::cout << "test_bug_add_func_interp_null_model\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_sort domain[1] = { int_sort };
|
||||
Z3_func_decl f = Z3_mk_func_decl(ctx, Z3_mk_string_symbol(ctx, "f"),
|
||||
1, domain, int_sort);
|
||||
Z3_ast else_val = Z3_mk_int(ctx, 0, int_sort);
|
||||
|
||||
// Z3_add_func_interp(ctx, nullptr, f, else_val) would crash
|
||||
// Line 249 checks f != null but line 251 doesn't check m != null
|
||||
std::cout << " [BUG DOCUMENTED] Z3_add_func_interp(ctx, nullptr, f, else_val) would crash\n";
|
||||
std::cout << " CHECK_NON_NULL exists for f but not for m (api_model.cpp:249-251)\n";
|
||||
|
||||
// Show it works with valid model
|
||||
Z3_model mdl = Z3_mk_model(ctx);
|
||||
Z3_model_inc_ref(ctx, mdl);
|
||||
Z3_func_interp fi = Z3_add_func_interp(ctx, mdl, f, else_val);
|
||||
if (fi != nullptr) {
|
||||
std::cout << " Valid add_func_interp works\n";
|
||||
}
|
||||
|
||||
Z3_model_dec_ref(ctx, mdl);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug documented)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 9: Z3_optimize_assert_soft with null/invalid weight
|
||||
//
|
||||
// Location: api_opt.cpp:93-101
|
||||
// The weight parameter is passed directly to rational(weight) constructor
|
||||
// with no null check. A null string causes a crash.
|
||||
// Also, negative or zero weights are not validated.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_optimize_soft_null_weight() {
|
||||
std::cout << "test_bug_optimize_soft_null_weight\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_optimize opt = Z3_mk_optimize(ctx);
|
||||
Z3_optimize_inc_ref(ctx, opt);
|
||||
|
||||
Z3_sort bool_sort = Z3_mk_bool_sort(ctx);
|
||||
Z3_ast p = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "p"), bool_sort);
|
||||
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " Error handler caught code: " << e << "\n";
|
||||
});
|
||||
|
||||
// Z3_optimize_assert_soft(ctx, opt, p, nullptr, Z3_mk_string_symbol(ctx, "g"))
|
||||
// would crash: rational(nullptr) dereferences null
|
||||
|
||||
// Test with negative weight - should be rejected but isn't
|
||||
unsigned idx = Z3_optimize_assert_soft(ctx, opt, p, "-1",
|
||||
Z3_mk_string_symbol(ctx, "g"));
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
std::cout << " assert_soft with weight=\"-1\": idx=" << idx
|
||||
<< " error=" << err << "\n";
|
||||
if (err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] Negative weight accepted without validation\n";
|
||||
}
|
||||
|
||||
// Test with zero weight
|
||||
unsigned idx2 = Z3_optimize_assert_soft(ctx, opt, p, "0",
|
||||
Z3_mk_string_symbol(ctx, "g2"));
|
||||
err = Z3_get_error_code(ctx);
|
||||
std::cout << " assert_soft with weight=\"0\": idx=" << idx2
|
||||
<< " error=" << err << "\n";
|
||||
if (err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] Zero weight accepted without validation\n";
|
||||
}
|
||||
|
||||
// Test with non-numeric weight
|
||||
unsigned idx3 = Z3_optimize_assert_soft(ctx, opt, p, "abc",
|
||||
Z3_mk_string_symbol(ctx, "g3"));
|
||||
err = Z3_get_error_code(ctx);
|
||||
std::cout << " assert_soft with weight=\"abc\": idx=" << idx3
|
||||
<< " error=" << err << "\n";
|
||||
|
||||
std::cout << " [BUG DOCUMENTED] Z3_optimize_assert_soft(ctx, opt, p, nullptr, sym) would crash\n";
|
||||
|
||||
Z3_optimize_dec_ref(ctx, opt);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 10: Z3_mk_pattern with 0 patterns
|
||||
//
|
||||
// Location: api_quant.cpp:320-334
|
||||
// num_patterns=0 is accepted. The loop does nothing, then mk_pattern(0, ...)
|
||||
// creates an empty pattern which violates SMT-LIB semantics (patterns must
|
||||
// be non-empty).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_mk_pattern_zero() {
|
||||
std::cout << "test_bug_mk_pattern_zero\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
// Create a pattern with 0 terms - should be rejected but isn't
|
||||
Z3_pattern p = Z3_mk_pattern(ctx, 0, nullptr);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
|
||||
if (p != nullptr && err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] Empty pattern (0 terms) was created successfully\n";
|
||||
}
|
||||
else {
|
||||
std::cout << " Pattern creation result: " << (p != nullptr ? "non-null" : "null")
|
||||
<< " error=" << err << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 11: Z3_mk_re_loop with lo > hi (inverted bounds)
|
||||
//
|
||||
// Location: api_seq.cpp
|
||||
// No validation that lo <= hi. Creating re.loop(r, 5, 2) creates a regex
|
||||
// that matches between 5 and 2 repetitions, which is semantically empty
|
||||
// but should be caught as an invalid argument.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_re_loop_inverted_bounds() {
|
||||
std::cout << "test_bug_re_loop_inverted_bounds\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_sort str_sort = Z3_mk_string_sort(ctx);
|
||||
Z3_sort re_sort = Z3_mk_re_sort(ctx, str_sort);
|
||||
(void)re_sort;
|
||||
|
||||
// Create a regex for literal "a"
|
||||
Z3_ast a_str = Z3_mk_string(ctx, "a");
|
||||
Z3_ast re_a = Z3_mk_re_full(ctx, re_sort);
|
||||
// Actually use Z3_mk_seq_to_re for literal
|
||||
re_a = Z3_mk_seq_to_re(ctx, a_str);
|
||||
|
||||
// lo=5, hi=2: inverted bounds - should be rejected
|
||||
Z3_ast loop = Z3_mk_re_loop(ctx, re_a, 5, 2);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
|
||||
if (loop != nullptr && err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] re.loop with lo=5 > hi=2 accepted: "
|
||||
<< Z3_ast_to_string(ctx, loop) << "\n";
|
||||
|
||||
// Try to use it in a constraint
|
||||
Z3_ast x = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x"), str_sort);
|
||||
Z3_ast in_re = Z3_mk_seq_in_re(ctx, x, loop);
|
||||
Z3_solver s = Z3_mk_solver(ctx);
|
||||
Z3_solver_inc_ref(ctx, s);
|
||||
Z3_solver_assert(ctx, s, in_re);
|
||||
Z3_lbool result = Z3_solver_check(ctx, s);
|
||||
std::cout << " Solving with inverted-bounds regex: " << result << "\n";
|
||||
Z3_solver_dec_ref(ctx, s);
|
||||
}
|
||||
else {
|
||||
std::cout << " Inverted bounds rejected: error=" << err << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 12: Z3_mk_enumeration_sort with n=0 (empty enum)
|
||||
//
|
||||
// Location: api_datatype.cpp
|
||||
// No validation that n > 0. An empty enumeration sort has no constants
|
||||
// and no testers, creating an uninhabited type.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_empty_enumeration() {
|
||||
std::cout << "test_bug_empty_enumeration\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_symbol name = Z3_mk_string_symbol(ctx, "EmptyEnum");
|
||||
|
||||
// n=0: empty enumeration - no enum constants
|
||||
Z3_sort sort = Z3_mk_enumeration_sort(ctx, name, 0, nullptr, nullptr, nullptr);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
|
||||
if (sort != nullptr && err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] Empty enumeration sort created: "
|
||||
<< Z3_sort_to_string(ctx, sort) << "\n";
|
||||
|
||||
// Try to create a variable of this uninhabited type
|
||||
Z3_ast x = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x"), sort);
|
||||
if (x != nullptr) {
|
||||
std::cout << " Created variable of empty enum type\n";
|
||||
|
||||
// Ask solver if it's satisfiable - uninhabited type should be unsat
|
||||
Z3_solver s = Z3_mk_solver(ctx);
|
||||
Z3_solver_inc_ref(ctx, s);
|
||||
// x = x should be unsat for empty domain
|
||||
Z3_ast eq = Z3_mk_eq(ctx, x, x);
|
||||
Z3_solver_assert(ctx, s, eq);
|
||||
Z3_lbool result = Z3_solver_check(ctx, s);
|
||||
std::cout << " Satisfiability of (x = x) for empty enum: " << result << "\n";
|
||||
if (result == Z3_L_TRUE) {
|
||||
std::cout << " [BUG CONFIRMED] SAT for uninhabited type\n";
|
||||
}
|
||||
Z3_solver_dec_ref(ctx, s);
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cout << " Empty enum rejected: error=" << err << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 13: Z3_solver_from_file continues after FILE_ACCESS_ERROR
|
||||
//
|
||||
// Location: api_solver.cpp:377-393
|
||||
// When a non-existent file is opened, SET_ERROR_CODE is called (line 384).
|
||||
// The if/else chain prevents execution of the parsing branches,
|
||||
// but the function still calls init_solver(c, s) on line 382 BEFORE
|
||||
// the file check. This means the solver is initialized even though
|
||||
// no formulas will be loaded. While not a crash, it's a logic error:
|
||||
// init_solver should not be called for a non-existent file.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_solver_from_nonexistent_file() {
|
||||
std::cout << "test_bug_solver_from_nonexistent_file\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_solver s = Z3_mk_solver(ctx);
|
||||
Z3_solver_inc_ref(ctx, s);
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
// Load a non-existent file
|
||||
Z3_solver_from_file(ctx, s, "this_file_does_not_exist_12345.smt2");
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
std::cout << " from_file error: " << err << "\n";
|
||||
|
||||
// The solver was still initialized (init_solver called before file check)
|
||||
Z3_lbool result = Z3_solver_check(ctx, s);
|
||||
std::cout << " Solver check after failed file load: " << result << "\n";
|
||||
if (result == Z3_L_TRUE && err != Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] Solver initialized despite file error\n";
|
||||
}
|
||||
|
||||
Z3_solver_dec_ref(ctx, s);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 14: Z3_mk_select/Z3_mk_store with sort-mismatched index
|
||||
//
|
||||
// Location: api_array.cpp
|
||||
// Array select/store operations don't validate that the index sort
|
||||
// matches the array's domain sort. Using a Boolean index on an
|
||||
// Int-keyed array may create a malformed term.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_array_sort_mismatch() {
|
||||
std::cout << "test_bug_array_sort_mismatch\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
// Create Array(Int, Int)
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_sort bool_sort = Z3_mk_bool_sort(ctx);
|
||||
Z3_sort arr_sort = Z3_mk_array_sort(ctx, int_sort, int_sort);
|
||||
|
||||
Z3_ast arr = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), arr_sort);
|
||||
|
||||
// Try to select with a Boolean index on Int-keyed array
|
||||
Z3_ast bool_idx = Z3_mk_true(ctx);
|
||||
Z3_ast sel = Z3_mk_select(ctx, arr, bool_idx);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
|
||||
if (sel != nullptr && err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] select(Array(Int,Int), Bool) accepted: "
|
||||
<< Z3_ast_to_string(ctx, sel) << "\n";
|
||||
}
|
||||
else {
|
||||
std::cout << " Sort mismatch detected: error=" << err << "\n";
|
||||
}
|
||||
|
||||
// Try store with mismatched value sort (store Bool value in Int array)
|
||||
Z3_ast int_idx = Z3_mk_int(ctx, 0, int_sort);
|
||||
Z3_ast bool_val = Z3_mk_true(ctx);
|
||||
Z3_ast st = Z3_mk_store(ctx, arr, int_idx, bool_val);
|
||||
err = Z3_get_error_code(ctx);
|
||||
|
||||
if (st != nullptr && err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] store(Array(Int,Int), Int, Bool) accepted: "
|
||||
<< Z3_ast_to_string(ctx, st) << "\n";
|
||||
}
|
||||
else {
|
||||
std::cout << " Value sort mismatch detected: error=" << err << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 15: Z3_substitute with null arrays when num_exprs > 0
|
||||
//
|
||||
// Location: api_ast.cpp
|
||||
// No null check on _from/_to arrays when num_exprs > 0.
|
||||
// Passing nullptr arrays with num_exprs=1 dereferences null.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_substitute_null_arrays() {
|
||||
std::cout << "test_bug_substitute_null_arrays\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_ast x = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x"), int_sort);
|
||||
|
||||
// With num_exprs=0, null arrays should be fine
|
||||
Z3_ast r = Z3_substitute(ctx, x, 0, nullptr, nullptr);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
if (r != nullptr) {
|
||||
std::cout << " substitute(x, 0, null, null) OK: " << Z3_ast_to_string(ctx, r) << "\n";
|
||||
}
|
||||
|
||||
// The bug: Z3_substitute(ctx, x, 1, nullptr, nullptr) would crash
|
||||
// because the function iterates from[i] and to[i] for i=0..num_exprs-1
|
||||
std::cout << " [BUG DOCUMENTED] Z3_substitute(ctx, x, 1, nullptr, nullptr) would crash\n";
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug documented)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 16: Z3_model_get_const_interp with null func_decl
|
||||
//
|
||||
// Location: api_model.cpp
|
||||
// No null check on the func_decl parameter before to_func_decl(f).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_model_get_const_interp_null() {
|
||||
std::cout << "test_bug_model_get_const_interp_null\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
// Create a simple model
|
||||
Z3_model mdl = Z3_mk_model(ctx);
|
||||
Z3_model_inc_ref(ctx, mdl);
|
||||
|
||||
// Z3_model_get_const_interp(ctx, mdl, nullptr) would crash
|
||||
// No null check on func_decl parameter
|
||||
std::cout << " [BUG DOCUMENTED] Z3_model_get_const_interp(ctx, mdl, nullptr) would crash\n";
|
||||
|
||||
// Show normal usage works
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_func_decl c_decl = Z3_mk_func_decl(ctx, Z3_mk_string_symbol(ctx, "c"),
|
||||
0, nullptr, int_sort);
|
||||
Z3_ast val = Z3_mk_int(ctx, 42, int_sort);
|
||||
Z3_add_const_interp(ctx, mdl, c_decl, val);
|
||||
|
||||
Z3_ast interp = Z3_model_get_const_interp(ctx, mdl, c_decl);
|
||||
if (interp != nullptr) {
|
||||
std::cout << " Valid get_const_interp: " << Z3_ast_to_string(ctx, interp) << "\n";
|
||||
}
|
||||
|
||||
Z3_model_dec_ref(ctx, mdl);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug documented)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 17: Z3_mk_map with arity mismatch
|
||||
//
|
||||
// Location: api_array.cpp
|
||||
// No validation that the function declaration's arity matches the
|
||||
// number of array arguments provided.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_mk_map_arity_mismatch() {
|
||||
std::cout << "test_bug_mk_map_arity_mismatch\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_sort arr_sort = Z3_mk_array_sort(ctx, int_sort, int_sort);
|
||||
|
||||
// Binary function f(Int, Int) -> Int
|
||||
Z3_sort domain[2] = { int_sort, int_sort };
|
||||
Z3_func_decl f = Z3_mk_func_decl(ctx, Z3_mk_string_symbol(ctx, "f"),
|
||||
2, domain, int_sort);
|
||||
|
||||
Z3_ast arr = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "a"), arr_sort);
|
||||
|
||||
// mk_map with binary function but only 1 array - arity mismatch
|
||||
Z3_ast args[1] = { arr };
|
||||
Z3_ast mapped = Z3_mk_map(ctx, f, 1, args);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
|
||||
if (mapped != nullptr && err == Z3_OK) {
|
||||
std::cout << " [BUG CONFIRMED] mk_map accepted arity mismatch: "
|
||||
<< "func arity=2, arrays=1\n";
|
||||
}
|
||||
else {
|
||||
std::cout << " Arity mismatch detected: error=" << err << "\n";
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 18: Z3_model_translate with no null checks
|
||||
//
|
||||
// Location: api_model.cpp
|
||||
// No null check on target context and no same-context check.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_model_translate_null() {
|
||||
std::cout << "test_bug_model_translate_null\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_model mdl = Z3_mk_model(ctx);
|
||||
Z3_model_inc_ref(ctx, mdl);
|
||||
|
||||
// Z3_model_translate(ctx, mdl, nullptr) would crash
|
||||
std::cout << " [BUG DOCUMENTED] Z3_model_translate(ctx, mdl, nullptr) would crash\n";
|
||||
|
||||
// Show valid usage
|
||||
Z3_context ctx2 = mk_ctx();
|
||||
Z3_model mdl2 = Z3_model_translate(ctx, mdl, ctx2);
|
||||
if (mdl2 != nullptr) {
|
||||
std::cout << " Valid model_translate works\n";
|
||||
}
|
||||
|
||||
Z3_model_dec_ref(ctx, mdl);
|
||||
Z3_del_context(ctx2);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED (bug documented)\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 19: Z3_mk_bvadd_no_overflow signed case incomplete
|
||||
//
|
||||
// Location: api_bv.cpp
|
||||
// The signed overflow check for bvadd misses the case where both operands
|
||||
// are negative and their sum overflows to positive (negative overflow).
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_bvadd_no_overflow_signed() {
|
||||
std::cout << "test_bug_bvadd_no_overflow_signed\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " [ERROR HANDLER] code=" << e << "\n";
|
||||
});
|
||||
|
||||
Z3_sort bv8 = Z3_mk_bv_sort(ctx, 8);
|
||||
Z3_ast x = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "x"), bv8);
|
||||
Z3_ast y = Z3_mk_const(ctx, Z3_mk_string_symbol(ctx, "y"), bv8);
|
||||
|
||||
// Create signed no-overflow constraint
|
||||
Z3_ast no_ovf = Z3_mk_bvadd_no_overflow(ctx, x, y, true);
|
||||
|
||||
// Create constraint that x = -100, y = -100 (sum = -200 which overflows 8-bit signed)
|
||||
Z3_ast neg100 = Z3_mk_int(ctx, -100, bv8);
|
||||
Z3_ast eq_x = Z3_mk_eq(ctx, x, neg100);
|
||||
Z3_ast eq_y = Z3_mk_eq(ctx, y, neg100);
|
||||
|
||||
Z3_solver s = Z3_mk_solver(ctx);
|
||||
Z3_solver_inc_ref(ctx, s);
|
||||
Z3_solver_assert(ctx, s, no_ovf);
|
||||
Z3_solver_assert(ctx, s, eq_x);
|
||||
Z3_solver_assert(ctx, s, eq_y);
|
||||
|
||||
Z3_lbool result = Z3_solver_check(ctx, s);
|
||||
std::cout << " bvadd_no_overflow(signed) with -100 + -100 (8-bit): " << result << "\n";
|
||||
if (result == Z3_L_TRUE) {
|
||||
std::cout << " [BUG CONFIRMED] Signed negative overflow not caught by bvadd_no_overflow\n";
|
||||
}
|
||||
else {
|
||||
std::cout << " Overflow correctly detected\n";
|
||||
}
|
||||
|
||||
Z3_solver_dec_ref(ctx, s);
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BUG 20: Z3_get_as_array_func_decl with non-array expression
|
||||
//
|
||||
// Location: api_model.cpp
|
||||
// Function calls is_app_of(to_expr(a), array_fid, OP_AS_ARRAY) but if
|
||||
// the expression is not an array-related term, the assertion may fail
|
||||
// or return garbage.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_bug_get_as_array_non_array() {
|
||||
std::cout << "test_bug_get_as_array_non_array\n";
|
||||
Z3_context ctx = mk_ctx();
|
||||
|
||||
Z3_sort int_sort = Z3_mk_int_sort(ctx);
|
||||
Z3_ast x = Z3_mk_int(ctx, 42, int_sort);
|
||||
|
||||
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code e) {
|
||||
std::cout << " Error handler caught code: " << e << "\n";
|
||||
});
|
||||
|
||||
// Pass an integer to get_as_array_func_decl - should be rejected
|
||||
bool is_as_array = Z3_is_as_array(ctx, x);
|
||||
std::cout << " Z3_is_as_array(42): " << is_as_array << "\n";
|
||||
|
||||
if (!is_as_array) {
|
||||
// Calling get_as_array_func_decl on non-as_array term
|
||||
Z3_func_decl fd = Z3_get_as_array_func_decl(ctx, x);
|
||||
Z3_error_code err = Z3_get_error_code(ctx);
|
||||
std::cout << " get_as_array_func_decl(42): fd=" << (fd != nullptr ? "non-null" : "null")
|
||||
<< " error=" << err << "\n";
|
||||
if (err == Z3_OK && fd != nullptr) {
|
||||
std::cout << " [BUG CONFIRMED] No error for get_as_array_func_decl on non-array term\n";
|
||||
}
|
||||
}
|
||||
|
||||
Z3_del_context(ctx);
|
||||
std::cout << " PASSED\n";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
void tst_deep_api_bugs() {
|
||||
// CRITICAL bugs - create invalid/corrupt objects
|
||||
test_bug_fpa_sort_missing_return();
|
||||
test_bug_array_sort_n_zero();
|
||||
test_bug_optimize_unchecked_index();
|
||||
test_bug_empty_enumeration();
|
||||
|
||||
// HIGH bugs - null pointer dereferences (documented, not triggered to avoid crash)
|
||||
test_bug_mk_string_null();
|
||||
test_bug_mk_lstring_overread();
|
||||
test_bug_translate_null_target();
|
||||
test_bug_add_func_interp_null_model();
|
||||
test_bug_model_get_const_interp_null();
|
||||
test_bug_model_translate_null();
|
||||
test_bug_substitute_null_arrays();
|
||||
|
||||
// HIGH bugs - validation bypasses
|
||||
test_bug_optimize_soft_null_weight();
|
||||
test_bug_re_loop_inverted_bounds();
|
||||
test_bug_mk_pattern_zero();
|
||||
test_bug_mk_map_arity_mismatch();
|
||||
test_bug_array_sort_mismatch();
|
||||
test_bug_bvadd_no_overflow_signed();
|
||||
test_bug_get_as_array_non_array();
|
||||
|
||||
// MEDIUM bugs - logic errors
|
||||
test_bug_propagator_variable_shadowing();
|
||||
test_bug_solver_from_nonexistent_file();
|
||||
}
|
||||
|
|
@ -111,6 +111,7 @@ namespace datalog {
|
|||
i5->deallocate();
|
||||
dealloc(join1);
|
||||
dealloc(proj1);
|
||||
dealloc(proj2);
|
||||
dealloc(ren1);
|
||||
dealloc(union1);
|
||||
dealloc(filterId1);
|
||||
|
|
@ -281,6 +282,7 @@ namespace datalog {
|
|||
i5->deallocate();
|
||||
dealloc(join1);
|
||||
dealloc(proj1);
|
||||
dealloc(proj2);
|
||||
dealloc(ren1);
|
||||
dealloc(union1);
|
||||
dealloc(filterId1);
|
||||
|
|
|
|||
|
|
@ -564,6 +564,7 @@ void setup_args_parser(argument_parser &parser) {
|
|||
"test rationals using plus instead of +=");
|
||||
parser.add_option_with_help_string("--maximize_term", "test maximize_term()");
|
||||
parser.add_option_with_help_string("--patching", "test patching");
|
||||
parser.add_option_with_help_string("--restore_x", "test restore_x");
|
||||
}
|
||||
|
||||
struct fff {
|
||||
|
|
@ -1710,7 +1711,7 @@ void test_dio() {
|
|||
enable_trace("dioph_eq");
|
||||
enable_trace("dioph_eq_fresh");
|
||||
#ifdef Z3DEBUG
|
||||
auto r = i_solver.dio_test();
|
||||
i_solver.dio_test();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
@ -1765,6 +1766,124 @@ void test_gomory_cut() {
|
|||
|
||||
void test_nla_order_lemma() { nla::test_order_lemma(); }
|
||||
|
||||
void test_restore_x() {
|
||||
std::cout << "testing restore_x" << std::endl;
|
||||
|
||||
// Test 1: backup shorter than current (new variables added after backup)
|
||||
{
|
||||
lar_solver solver;
|
||||
lpvar x = solver.add_var(0, false);
|
||||
lpvar y = solver.add_var(1, false);
|
||||
solver.add_var_bound(x, GE, mpq(0));
|
||||
solver.add_var_bound(x, LE, mpq(10));
|
||||
solver.add_var_bound(y, GE, mpq(0));
|
||||
solver.add_var_bound(y, LE, mpq(10));
|
||||
|
||||
vector<std::pair<mpq, lpvar>> coeffs;
|
||||
coeffs.push_back({mpq(1), x});
|
||||
coeffs.push_back({mpq(1), y});
|
||||
unsigned t = solver.add_term(coeffs, 2);
|
||||
solver.add_var_bound(t, GE, mpq(3));
|
||||
solver.add_var_bound(t, LE, mpq(15));
|
||||
|
||||
auto status = solver.solve();
|
||||
SASSERT(status == lp_status::OPTIMAL);
|
||||
|
||||
// Backup the current solution
|
||||
solver.backup_x();
|
||||
|
||||
// Add a new variable with bounds, making the system larger
|
||||
lpvar z = solver.add_var(3, false);
|
||||
solver.add_var_bound(z, GE, mpq(1));
|
||||
solver.add_var_bound(z, LE, mpq(5));
|
||||
|
||||
// restore_x should detect backup < current and call move_non_basic_columns_to_bounds
|
||||
solver.restore_x();
|
||||
|
||||
// The solver should find a feasible solution
|
||||
status = solver.get_status();
|
||||
SASSERT(status == lp_status::OPTIMAL || status == lp_status::FEASIBLE);
|
||||
std::cout << " test 1 (backup shorter): " << lp_status_to_string(status) << " - PASSED" << std::endl;
|
||||
}
|
||||
|
||||
// Test 2: same-size backup (restore_x copies all elements directly)
|
||||
{
|
||||
lar_solver solver;
|
||||
lpvar x = solver.add_var(0, false);
|
||||
lpvar y = solver.add_var(1, false);
|
||||
solver.add_var_bound(x, GE, mpq(0));
|
||||
solver.add_var_bound(x, LE, mpq(10));
|
||||
solver.add_var_bound(y, GE, mpq(0));
|
||||
solver.add_var_bound(y, LE, mpq(10));
|
||||
|
||||
vector<std::pair<mpq, lpvar>> coeffs;
|
||||
coeffs.push_back({mpq(1), x});
|
||||
coeffs.push_back({mpq(1), y});
|
||||
unsigned t = solver.add_term(coeffs, 2);
|
||||
solver.add_var_bound(t, GE, mpq(2));
|
||||
|
||||
// Add more variables to make backup larger
|
||||
lpvar z = solver.add_var(3, false);
|
||||
solver.add_var_bound(z, GE, mpq(0));
|
||||
solver.add_var_bound(z, LE, mpq(5));
|
||||
|
||||
auto status = solver.solve();
|
||||
(void)status;
|
||||
SASSERT(status == lp_status::OPTIMAL);
|
||||
|
||||
// Backup with the full system
|
||||
solver.backup_x();
|
||||
|
||||
// restore_x with same-size backup should work fine
|
||||
solver.restore_x();
|
||||
std::cout << " test 2 (same size backup): PASSED" << std::endl;
|
||||
}
|
||||
|
||||
// Test 3: move_non_basic_columns_to_bounds after solve
|
||||
{
|
||||
lar_solver solver;
|
||||
lpvar x = solver.add_var(0, false);
|
||||
lpvar y = solver.add_var(1, false);
|
||||
solver.add_var_bound(x, GE, mpq(1));
|
||||
solver.add_var_bound(x, LE, mpq(10));
|
||||
solver.add_var_bound(y, GE, mpq(1));
|
||||
solver.add_var_bound(y, LE, mpq(10));
|
||||
|
||||
auto status = solver.solve();
|
||||
SASSERT(status == lp_status::OPTIMAL);
|
||||
|
||||
// Add new constraint: x + y >= 5
|
||||
vector<std::pair<mpq, lpvar>> coeffs;
|
||||
coeffs.push_back({mpq(1), x});
|
||||
coeffs.push_back({mpq(1), y});
|
||||
unsigned t = solver.add_term(coeffs, 2);
|
||||
solver.add_var_bound(t, GE, mpq(5));
|
||||
solver.add_var_bound(t, LE, mpq(15));
|
||||
|
||||
// Add another variable
|
||||
lpvar w = solver.add_var(3, false);
|
||||
solver.add_var_bound(w, GE, mpq(2));
|
||||
solver.add_var_bound(w, LE, mpq(8));
|
||||
|
||||
// Solve expanded system, then move non-basic columns to bounds
|
||||
status = solver.solve();
|
||||
SASSERT(status == lp_status::OPTIMAL);
|
||||
solver.move_non_basic_columns_to_bounds();
|
||||
status = solver.get_status();
|
||||
SASSERT(status == lp_status::OPTIMAL || status == lp_status::FEASIBLE);
|
||||
|
||||
// Verify the model satisfies the constraints
|
||||
std::unordered_map<lpvar, mpq> model;
|
||||
solver.get_model(model);
|
||||
SASSERT(model[x] >= mpq(1) && model[x] <= mpq(10));
|
||||
SASSERT(model[y] >= mpq(1) && model[y] <= mpq(10));
|
||||
SASSERT(model[w] >= mpq(2) && model[w] <= mpq(8));
|
||||
std::cout << " test 3 (move_non_basic_columns_to_bounds): " << lp_status_to_string(status) << " - PASSED" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "restore_x tests passed" << std::endl;
|
||||
}
|
||||
|
||||
void test_lp_local(int argn, char **argv) {
|
||||
// initialize_util_module();
|
||||
// initialize_numerics_module();
|
||||
|
|
@ -1792,6 +1911,10 @@ void test_lp_local(int argn, char **argv) {
|
|||
test_patching();
|
||||
return finalize(0);
|
||||
}
|
||||
if (args_parser.option_is_used("--restore_x")) {
|
||||
test_restore_x();
|
||||
return finalize(0);
|
||||
}
|
||||
if (args_parser.option_is_used("-nla_cn")) {
|
||||
#ifdef Z3DEBUG
|
||||
nla::test_cn();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
#include<iostream>
|
||||
#include<time.h>
|
||||
#include<string>
|
||||
#include<cstring>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include "util/util.h"
|
||||
#include "util/trace.h"
|
||||
#include "util/debug.h"
|
||||
|
|
@ -10,6 +13,23 @@
|
|||
#include "util/memory_manager.h"
|
||||
#include "util/gparams.h"
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#endif
|
||||
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(_WINDOWS)
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS
|
||||
#define Z3_POPEN _popen
|
||||
#define Z3_PCLOSE _pclose
|
||||
#else
|
||||
#define Z3_POPEN popen
|
||||
#define Z3_PCLOSE pclose
|
||||
#endif
|
||||
|
||||
//
|
||||
// Unit tests fail by asserting.
|
||||
|
|
@ -17,36 +37,183 @@
|
|||
// and print "PASS" to indicate success.
|
||||
//
|
||||
|
||||
#define TST(MODULE) { \
|
||||
std::string s("test "); \
|
||||
s += #MODULE; \
|
||||
void tst_##MODULE(); \
|
||||
if (do_display_usage) \
|
||||
std::cout << " " << #MODULE << "\n"; \
|
||||
for (int i = 0; i < argc; ++i) \
|
||||
if (test_all || strcmp(argv[i], #MODULE) == 0) { \
|
||||
enable_debug(#MODULE); \
|
||||
timeit timeit(true, s.c_str()); \
|
||||
tst_##MODULE(); \
|
||||
std::cout << "PASS" << std::endl; \
|
||||
} \
|
||||
}
|
||||
// ========================================================================
|
||||
// Test list definitions using X-macros.
|
||||
// X(name) is for regular tests, X_ARGV(name) is for tests needing arguments.
|
||||
// FOR_EACH_ALL_TEST: tests run with /a flag.
|
||||
// FOR_EACH_EXTRA_TEST: tests only run when explicitly named.
|
||||
// ========================================================================
|
||||
|
||||
#define TST_ARGV(MODULE) { \
|
||||
std::string s("test "); \
|
||||
s += #MODULE; \
|
||||
void tst_##MODULE(char** argv, int argc, int& i); \
|
||||
if (do_display_usage) \
|
||||
std::cout << " " << #MODULE << "(...)\n"; \
|
||||
for (int i = 0; i < argc; ++i) \
|
||||
if (strcmp(argv[i], #MODULE) == 0) { \
|
||||
enable_trace(#MODULE); \
|
||||
enable_debug(#MODULE); \
|
||||
timeit timeit(true, s.c_str()); \
|
||||
tst_##MODULE(argv, argc, i); \
|
||||
std::cout << "PASS" << std::endl; \
|
||||
} \
|
||||
}
|
||||
#define FOR_EACH_ALL_TEST(X, X_ARGV) \
|
||||
X(random) \
|
||||
X(symbol_table) \
|
||||
X(region) \
|
||||
X(symbol) \
|
||||
X(heap) \
|
||||
X(hashtable) \
|
||||
X(rational) \
|
||||
X(inf_rational) \
|
||||
X(ast) \
|
||||
X(optional) \
|
||||
X(bit_vector) \
|
||||
X(fixed_bit_vector) \
|
||||
X(tbv) \
|
||||
X(doc) \
|
||||
X(udoc_relation) \
|
||||
X(string_buffer) \
|
||||
X(map) \
|
||||
X(diff_logic) \
|
||||
X(uint_set) \
|
||||
X_ARGV(expr_rand) \
|
||||
X(list) \
|
||||
X(small_object_allocator) \
|
||||
X(timeout) \
|
||||
X(proof_checker) \
|
||||
X(simplifier) \
|
||||
X(bit_blaster) \
|
||||
X(var_subst) \
|
||||
X(simple_parser) \
|
||||
X(api) \
|
||||
X(max_reg) \
|
||||
X(max_rev) \
|
||||
X(scaled_min) \
|
||||
X(box_mod_opt) \
|
||||
X(box_independent) \
|
||||
X(deep_api_bugs) \
|
||||
X(api_algebraic) \
|
||||
X(api_polynomial) \
|
||||
X(api_pb) \
|
||||
X(api_datalog) \
|
||||
X(parametric_datatype) \
|
||||
X(cube_clause) \
|
||||
X(old_interval) \
|
||||
X(get_implied_equalities) \
|
||||
X(arith_simplifier_plugin) \
|
||||
X(matcher) \
|
||||
X(object_allocator) \
|
||||
X(mpz) \
|
||||
X(mpq) \
|
||||
X(mpf) \
|
||||
X(total_order) \
|
||||
X(dl_table) \
|
||||
X(dl_context) \
|
||||
X(dlist) \
|
||||
X(dl_util) \
|
||||
X(dl_product_relation) \
|
||||
X(dl_relation) \
|
||||
X(parray) \
|
||||
X(stack) \
|
||||
X(escaped) \
|
||||
X(buffer) \
|
||||
X(chashtable) \
|
||||
X(egraph) \
|
||||
X(ex) \
|
||||
X(nlarith_util) \
|
||||
X(api_ast_map) \
|
||||
X(api_bug) \
|
||||
X(api_special_relations) \
|
||||
X(arith_rewriter) \
|
||||
X(check_assumptions) \
|
||||
X(smt_context) \
|
||||
X(theory_dl) \
|
||||
X(model_retrieval) \
|
||||
X(model_based_opt) \
|
||||
X(factor_rewriter) \
|
||||
X(smt2print_parse) \
|
||||
X(substitution) \
|
||||
X(polynomial) \
|
||||
X(polynomial_factorization) \
|
||||
X(upolynomial) \
|
||||
X(algebraic) \
|
||||
X(algebraic_numbers) \
|
||||
X(ackermannize) \
|
||||
X(monomial_bounds) \
|
||||
X(nla_intervals) \
|
||||
X(horner) \
|
||||
X(prime_generator) \
|
||||
X(permutation) \
|
||||
X(nlsat) \
|
||||
X(13) \
|
||||
X(zstring)
|
||||
|
||||
#define FOR_EACH_EXTRA_TEST(X, X_ARGV) \
|
||||
X(ext_numeral) \
|
||||
X(interval) \
|
||||
X(value_generator) \
|
||||
X(value_sweep) \
|
||||
X(vector) \
|
||||
X(f2n) \
|
||||
X(hwf) \
|
||||
X(trigo) \
|
||||
X(bits) \
|
||||
X(mpbq) \
|
||||
X(mpfx) \
|
||||
X(mpff) \
|
||||
X(horn_subsume_model_converter) \
|
||||
X(model2expr) \
|
||||
X(hilbert_basis) \
|
||||
X(heap_trie) \
|
||||
X(karr) \
|
||||
X(no_overflow) \
|
||||
X(datalog_parser) \
|
||||
X_ARGV(datalog_parser_file) \
|
||||
X(dl_query) \
|
||||
X(quant_solve) \
|
||||
X(rcf) \
|
||||
X(polynorm) \
|
||||
X(qe_arith) \
|
||||
X(expr_substitution) \
|
||||
X(sorting_network) \
|
||||
X(theory_pb) \
|
||||
X(simplex) \
|
||||
X(sat_user_scope) \
|
||||
X_ARGV(ddnf) \
|
||||
X(ddnf1) \
|
||||
X(model_evaluator) \
|
||||
X(get_consequences) \
|
||||
X(pb2bv) \
|
||||
X_ARGV(sat_lookahead) \
|
||||
X_ARGV(sat_local_search) \
|
||||
X_ARGV(cnf_backbones) \
|
||||
X(bdd) \
|
||||
X(pdd) \
|
||||
X(pdd_solver) \
|
||||
X(scoped_timer) \
|
||||
X(solver_pool) \
|
||||
X(finder) \
|
||||
X(totalizer) \
|
||||
X(distribution) \
|
||||
X(euf_bv_plugin) \
|
||||
X(euf_arith_plugin) \
|
||||
X(sls_test) \
|
||||
X(scoped_vector) \
|
||||
X(sls_seq_plugin) \
|
||||
X(seq_nielsen) \
|
||||
X(seq_parikh) \
|
||||
X(nseq_basic) \
|
||||
X(seq_regex) \
|
||||
X(nseq_zipt) \
|
||||
X(euf_sgraph) \
|
||||
X(euf_seq_plugin) \
|
||||
X(ho_matcher) \
|
||||
X(finite_set) \
|
||||
X(finite_set_rewriter) \
|
||||
X(fpa)
|
||||
|
||||
#define FOR_EACH_TEST(X, X_ARGV) \
|
||||
FOR_EACH_ALL_TEST(X, X_ARGV) \
|
||||
FOR_EACH_EXTRA_TEST(X, X_ARGV)
|
||||
|
||||
// Forward declarations for all test functions
|
||||
#define DECL_TST(M) void tst_##M();
|
||||
#define DECL_TST_ARGV(M) void tst_##M(char** argv, int argc, int& i);
|
||||
FOR_EACH_TEST(DECL_TST, DECL_TST_ARGV)
|
||||
#undef DECL_TST
|
||||
#undef DECL_TST_ARGV
|
||||
|
||||
// ========================================================================
|
||||
// Helper functions
|
||||
// ========================================================================
|
||||
|
||||
void error(const char * msg) {
|
||||
std::cerr << "Error: " << msg << "\n";
|
||||
|
|
@ -62,6 +229,10 @@ void display_usage() {
|
|||
std::cout << " /v:level be verbose, where <level> is the verbosity level.\n";
|
||||
std::cout << " /w enable warning messages.\n";
|
||||
std::cout << " /a run all unit tests that don't require arguments.\n";
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::cout << " /j[:N] run tests in parallel using N jobs (default: number of cores).\n";
|
||||
std::cout << " /seq run tests sequentially, disabling parallel execution.\n";
|
||||
#endif
|
||||
#if defined(Z3DEBUG) || defined(_TRACE)
|
||||
std::cout << "\nDebugging support:\n";
|
||||
#endif
|
||||
|
|
@ -74,7 +245,8 @@ void display_usage() {
|
|||
std::cout << "\nModule names:\n";
|
||||
}
|
||||
|
||||
void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& test_all) {
|
||||
void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& test_all,
|
||||
unsigned& num_jobs, std::vector<std::string>& extra_args) {
|
||||
int i = 1;
|
||||
if (argc == 1) {
|
||||
display_usage();
|
||||
|
|
@ -103,18 +275,39 @@ void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& t
|
|||
error("option argument (/v:level) is missing.");
|
||||
long lvl = strtol(opt_arg, nullptr, 10);
|
||||
set_verbosity_level(lvl);
|
||||
extra_args.push_back(std::string("/v:") + opt_arg);
|
||||
}
|
||||
else if (strcmp(opt_name, "w") == 0) {
|
||||
enable_warning_messages(true);
|
||||
extra_args.push_back("/w");
|
||||
}
|
||||
else if (strcmp(opt_name, "a") == 0) {
|
||||
test_all = true;
|
||||
}
|
||||
else if (strcmp(opt_name, "j") == 0) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (opt_arg) {
|
||||
long n = strtol(opt_arg, nullptr, 10);
|
||||
if (n <= 0) error("invalid number of jobs for /j option.");
|
||||
num_jobs = static_cast<unsigned>(n);
|
||||
}
|
||||
else {
|
||||
unsigned hw = std::thread::hardware_concurrency();
|
||||
num_jobs = hw > 0 ? hw : 4;
|
||||
}
|
||||
#else
|
||||
error("/j option is not supported on this platform.");
|
||||
#endif
|
||||
}
|
||||
else if (strcmp(opt_name, "seq") == 0) {
|
||||
num_jobs = 0;
|
||||
}
|
||||
#ifdef _TRACE
|
||||
else if (strcmp(opt_name, "tr") == 0) {
|
||||
if (!opt_arg)
|
||||
error("option argument (/tr:tag) is missing.");
|
||||
enable_trace(opt_arg);
|
||||
extra_args.push_back(std::string("/tr:") + opt_arg);
|
||||
}
|
||||
#endif
|
||||
#ifdef Z3DEBUG
|
||||
|
|
@ -122,6 +315,7 @@ void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& t
|
|||
if (!opt_arg)
|
||||
error("option argument (/dbg:tag) is missing.");
|
||||
enable_debug(opt_arg);
|
||||
extra_args.push_back(std::string("/dbg:") + opt_arg);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -131,6 +325,7 @@ void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& t
|
|||
char * value = eq_pos+1;
|
||||
try {
|
||||
gparams::set(key, value);
|
||||
extra_args.push_back(std::string(key) + "=" + value);
|
||||
}
|
||||
catch (z3_exception& ex) {
|
||||
std::cerr << ex.what() << "\n";
|
||||
|
|
@ -141,158 +336,246 @@ void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& t
|
|||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Parallel test execution using child processes
|
||||
// ========================================================================
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
|
||||
struct test_result {
|
||||
std::string name;
|
||||
int exit_code;
|
||||
std::string output;
|
||||
double elapsed_secs;
|
||||
};
|
||||
|
||||
static test_result run_test_child(const char* exe_path, const char* test_name,
|
||||
const std::vector<std::string>& extra_args) {
|
||||
test_result result;
|
||||
result.name = test_name;
|
||||
|
||||
std::ostringstream cmd;
|
||||
cmd << "\"" << exe_path << "\"" << " /seq " << test_name;
|
||||
for (const auto& arg : extra_args)
|
||||
cmd << " " << arg;
|
||||
cmd << " 2>&1";
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
FILE* pipe = Z3_POPEN(cmd.str().c_str(), "r");
|
||||
if (!pipe) {
|
||||
result.exit_code = -1;
|
||||
result.output = "Failed to start child process\n";
|
||||
result.elapsed_secs = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
while (fgets(buf, sizeof(buf), pipe))
|
||||
result.output += buf;
|
||||
|
||||
int raw = Z3_PCLOSE(pipe);
|
||||
#ifdef _WINDOWS
|
||||
result.exit_code = raw;
|
||||
#else
|
||||
if (WIFEXITED(raw))
|
||||
result.exit_code = WEXITSTATUS(raw);
|
||||
else if (WIFSIGNALED(raw))
|
||||
result.exit_code = 128 + WTERMSIG(raw);
|
||||
else
|
||||
result.exit_code = -1;
|
||||
#endif
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
result.elapsed_secs = std::chrono::duration<double>(end - start).count();
|
||||
return result;
|
||||
}
|
||||
|
||||
static int run_parallel(const char* exe_path, bool test_all, unsigned num_jobs,
|
||||
const std::vector<std::string>& extra_args,
|
||||
const std::vector<std::string>& requested_tests) {
|
||||
std::vector<std::string> tests_to_run;
|
||||
|
||||
if (test_all) {
|
||||
#define COLLECT_ALL(M) tests_to_run.push_back(#M);
|
||||
#define SKIP_ARGV_1(M)
|
||||
FOR_EACH_ALL_TEST(COLLECT_ALL, SKIP_ARGV_1)
|
||||
#undef COLLECT_ALL
|
||||
#undef SKIP_ARGV_1
|
||||
}
|
||||
else {
|
||||
#define MAYBE_COLLECT(M) \
|
||||
for (const auto& req : requested_tests) \
|
||||
if (req == #M) { tests_to_run.push_back(#M); break; }
|
||||
#define SKIP_ARGV_2(M)
|
||||
FOR_EACH_TEST(MAYBE_COLLECT, SKIP_ARGV_2)
|
||||
#undef MAYBE_COLLECT
|
||||
#undef SKIP_ARGV_2
|
||||
}
|
||||
|
||||
if (tests_to_run.empty()) {
|
||||
std::cout << "No tests to run in parallel mode." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned total = static_cast<unsigned>(tests_to_run.size());
|
||||
if (num_jobs > total)
|
||||
num_jobs = total;
|
||||
|
||||
std::cout << "Running " << total << " tests with "
|
||||
<< num_jobs << " parallel jobs..." << std::endl;
|
||||
|
||||
auto wall_start = std::chrono::steady_clock::now();
|
||||
|
||||
std::mutex queue_mtx;
|
||||
std::mutex output_mtx;
|
||||
size_t next_idx = 0;
|
||||
unsigned completed = 0;
|
||||
unsigned passed = 0;
|
||||
unsigned failed = 0;
|
||||
std::vector<std::string> failed_names;
|
||||
|
||||
auto worker = [&]() {
|
||||
while (true) {
|
||||
size_t idx;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queue_mtx);
|
||||
if (next_idx >= tests_to_run.size())
|
||||
return;
|
||||
idx = next_idx++;
|
||||
}
|
||||
|
||||
test_result result = run_test_child(exe_path, tests_to_run[idx].c_str(), extra_args);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(output_mtx);
|
||||
++completed;
|
||||
if (result.exit_code == 0) {
|
||||
++passed;
|
||||
std::cout << "[" << completed << "/" << total << "] "
|
||||
<< result.name << " PASS ("
|
||||
<< std::fixed << std::setprecision(1)
|
||||
<< result.elapsed_secs << "s)" << std::endl;
|
||||
}
|
||||
else {
|
||||
++failed;
|
||||
failed_names.push_back(result.name);
|
||||
std::cout << "[" << completed << "/" << total << "] "
|
||||
<< result.name << " FAIL (exit code "
|
||||
<< result.exit_code << ", "
|
||||
<< std::fixed << std::setprecision(1)
|
||||
<< result.elapsed_secs << "s)" << std::endl;
|
||||
}
|
||||
if (!result.output.empty()) {
|
||||
std::cout << result.output;
|
||||
if (result.output.back() != '\n')
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (unsigned i = 0; i < num_jobs; ++i)
|
||||
threads.emplace_back(worker);
|
||||
for (auto& t : threads)
|
||||
t.join();
|
||||
|
||||
auto wall_end = std::chrono::steady_clock::now();
|
||||
double wall_secs = std::chrono::duration<double>(wall_end - wall_start).count();
|
||||
|
||||
std::cout << "\n=== Test Summary ===" << std::endl;
|
||||
std::cout << passed << " passed, " << failed << " failed, "
|
||||
<< total << " total" << std::endl;
|
||||
std::cout << "Wall time: " << std::fixed << std::setprecision(1)
|
||||
<< wall_secs << "s" << std::endl;
|
||||
|
||||
if (!failed_names.empty()) {
|
||||
std::cout << "Failed tests:";
|
||||
for (const auto& name : failed_names)
|
||||
std::cout << " " << name;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
#endif // !__EMSCRIPTEN__
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// main
|
||||
// ========================================================================
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
memory::initialize(0);
|
||||
|
||||
// Collect potential test names before parsing modifies argv
|
||||
std::vector<std::string> requested_tests;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const char* a = argv[i];
|
||||
if (a[0] != '-' && a[0] != '/' && !strchr(a, '='))
|
||||
requested_tests.push_back(a);
|
||||
}
|
||||
|
||||
bool do_display_usage = false;
|
||||
bool test_all = false;
|
||||
parse_cmd_line_args(argc, argv, do_display_usage, test_all);
|
||||
TST(random);
|
||||
TST(symbol_table);
|
||||
TST(region);
|
||||
TST(symbol);
|
||||
TST(heap);
|
||||
TST(hashtable);
|
||||
TST(rational);
|
||||
TST(inf_rational);
|
||||
TST(ast);
|
||||
TST(optional);
|
||||
TST(bit_vector);
|
||||
TST(fixed_bit_vector);
|
||||
TST(tbv);
|
||||
TST(doc);
|
||||
TST(udoc_relation);
|
||||
TST(string_buffer);
|
||||
TST(map);
|
||||
TST(diff_logic);
|
||||
TST(uint_set);
|
||||
TST_ARGV(expr_rand);
|
||||
TST(list);
|
||||
TST(small_object_allocator);
|
||||
TST(timeout);
|
||||
TST(proof_checker);
|
||||
TST(simplifier);
|
||||
TST(bit_blaster);
|
||||
TST(var_subst);
|
||||
TST(simple_parser);
|
||||
TST(api);
|
||||
TST(api_algebraic);
|
||||
TST(api_polynomial);
|
||||
TST(api_pb);
|
||||
TST(api_datalog);
|
||||
TST(parametric_datatype);
|
||||
TST(cube_clause);
|
||||
TST(old_interval);
|
||||
TST(get_implied_equalities);
|
||||
TST(arith_simplifier_plugin);
|
||||
TST(matcher);
|
||||
TST(object_allocator);
|
||||
TST(mpz);
|
||||
TST(mpq);
|
||||
TST(mpf);
|
||||
TST(total_order);
|
||||
TST(dl_table);
|
||||
TST(dl_context);
|
||||
TST(dlist);
|
||||
TST(dl_util);
|
||||
TST(dl_product_relation);
|
||||
TST(dl_relation);
|
||||
TST(parray);
|
||||
TST(stack);
|
||||
TST(escaped);
|
||||
TST(buffer);
|
||||
TST(chashtable);
|
||||
TST(egraph);
|
||||
TST(ex);
|
||||
TST(nlarith_util);
|
||||
TST(api_ast_map);
|
||||
TST(api_bug);
|
||||
TST(api_special_relations);
|
||||
TST(arith_rewriter);
|
||||
TST(check_assumptions);
|
||||
TST(smt_context);
|
||||
TST(theory_dl);
|
||||
TST(model_retrieval);
|
||||
TST(model_based_opt);
|
||||
TST(factor_rewriter);
|
||||
TST(smt2print_parse);
|
||||
TST(substitution);
|
||||
TST(polynomial);
|
||||
TST(polynomial_factorization);
|
||||
TST(upolynomial);
|
||||
TST(algebraic);
|
||||
TST(algebraic_numbers);
|
||||
TST(ackermannize);
|
||||
TST(monomial_bounds);
|
||||
TST(nla_intervals);
|
||||
TST(horner);
|
||||
TST(prime_generator);
|
||||
TST(permutation);
|
||||
TST(nlsat);
|
||||
TST(13);
|
||||
TST(zstring);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
unsigned hw = std::thread::hardware_concurrency();
|
||||
unsigned num_jobs = hw > 0 ? hw : 4;
|
||||
#else
|
||||
unsigned num_jobs = 0;
|
||||
#endif
|
||||
std::vector<std::string> extra_args;
|
||||
parse_cmd_line_args(argc, argv, do_display_usage, test_all, num_jobs, extra_args);
|
||||
|
||||
if (do_display_usage) {
|
||||
#define DISPLAY_TST(M) std::cout << " " << #M << "\n";
|
||||
#define DISPLAY_TST_ARGV(M) std::cout << " " << #M << "(...)\n";
|
||||
FOR_EACH_TEST(DISPLAY_TST, DISPLAY_TST_ARGV)
|
||||
#undef DISPLAY_TST
|
||||
#undef DISPLAY_TST_ARGV
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (num_jobs > 0)
|
||||
return run_parallel(argv[0], test_all, num_jobs, extra_args, requested_tests);
|
||||
#endif
|
||||
|
||||
// Serial execution, original behavior
|
||||
#define RUN_TST(M) { \
|
||||
bool run = test_all; \
|
||||
for (int i = 0; !run && i < argc; ++i) \
|
||||
run = strcmp(argv[i], #M) == 0; \
|
||||
if (run) { \
|
||||
std::string s("test "); \
|
||||
s += #M; \
|
||||
enable_debug(#M); \
|
||||
timeit timeit(true, s.c_str()); \
|
||||
tst_##M(); \
|
||||
std::cout << "PASS" << std::endl; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define RUN_TST_ARGV(M) { \
|
||||
for (int i = 0; i < argc; ++i) \
|
||||
if (strcmp(argv[i], #M) == 0) { \
|
||||
enable_trace(#M); \
|
||||
enable_debug(#M); \
|
||||
std::string s("test "); \
|
||||
s += #M; \
|
||||
timeit timeit(true, s.c_str()); \
|
||||
tst_##M(argv, argc, i); \
|
||||
std::cout << "PASS" << std::endl; \
|
||||
} \
|
||||
}
|
||||
|
||||
FOR_EACH_ALL_TEST(RUN_TST, RUN_TST_ARGV)
|
||||
if (test_all) return 0;
|
||||
TST(ext_numeral);
|
||||
TST(interval);
|
||||
TST(value_generator);
|
||||
TST(value_sweep);
|
||||
TST(vector);
|
||||
TST(f2n);
|
||||
TST(hwf);
|
||||
TST(trigo);
|
||||
TST(bits);
|
||||
TST(mpbq);
|
||||
TST(mpfx);
|
||||
TST(mpff);
|
||||
TST(horn_subsume_model_converter);
|
||||
TST(model2expr);
|
||||
TST(hilbert_basis);
|
||||
TST(heap_trie);
|
||||
TST(karr);
|
||||
TST(no_overflow);
|
||||
// TST(memory);
|
||||
TST(datalog_parser);
|
||||
TST_ARGV(datalog_parser_file);
|
||||
TST(dl_query);
|
||||
TST(quant_solve);
|
||||
TST(rcf);
|
||||
TST(polynorm);
|
||||
TST(qe_arith);
|
||||
TST(expr_substitution);
|
||||
TST(sorting_network);
|
||||
TST(theory_pb);
|
||||
TST(simplex);
|
||||
TST(sat_user_scope);
|
||||
TST_ARGV(ddnf);
|
||||
TST(ddnf1);
|
||||
TST(model_evaluator);
|
||||
TST(get_consequences);
|
||||
TST(pb2bv);
|
||||
TST_ARGV(sat_lookahead);
|
||||
TST_ARGV(sat_local_search);
|
||||
TST_ARGV(cnf_backbones);
|
||||
TST(bdd);
|
||||
TST(pdd);
|
||||
TST(pdd_solver);
|
||||
TST(scoped_timer);
|
||||
TST(solver_pool);
|
||||
//TST_ARGV(hs);
|
||||
TST(finder);
|
||||
TST(totalizer);
|
||||
TST(distribution);
|
||||
TST(euf_bv_plugin);
|
||||
TST(euf_arith_plugin);
|
||||
TST(euf_sgraph);
|
||||
TST(euf_seq_plugin);
|
||||
TST(sls_test);
|
||||
TST(scoped_vector);
|
||||
TST(sls_seq_plugin);
|
||||
TST(seq_nielsen);
|
||||
TST(seq_parikh);
|
||||
TST(nseq_basic);
|
||||
TST(seq_regex);
|
||||
TST(nseq_zipt);
|
||||
TST(ho_matcher);
|
||||
TST(finite_set);
|
||||
TST(finite_set_rewriter);
|
||||
TST(fpa);
|
||||
FOR_EACH_EXTRA_TEST(RUN_TST, RUN_TST_ARGV)
|
||||
|
||||
#undef RUN_TST
|
||||
#undef RUN_TST_ARGV
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,15 +337,7 @@ void test_factorization_large_multivariate_missing_factors() {
|
|||
|
||||
factors fs(m);
|
||||
factor(p, fs);
|
||||
VERIFY(fs.distinct_factors() == 2); // indeed there are 3 factors, that is demonstrated by the loop
|
||||
for (unsigned i = 0; i < fs.distinct_factors(); ++i) {
|
||||
polynomial_ref f(m);
|
||||
f = fs[i];
|
||||
if (degree(f, x1)<= 1) continue;
|
||||
factors fs0(m);
|
||||
factor(f, fs0);
|
||||
VERIFY(fs0.distinct_factors() >= 2);
|
||||
}
|
||||
VERIFY(fs.distinct_factors() >= 3);
|
||||
|
||||
polynomial_ref reconstructed(m);
|
||||
fs.multiply(reconstructed);
|
||||
|
|
@ -370,17 +362,8 @@ void test_factorization_multivariate_missing_factors() {
|
|||
factors fs(m);
|
||||
factor(p, fs);
|
||||
|
||||
// Multivariate factorization stops after returning the whole polynomial.
|
||||
VERIFY(fs.distinct_factors() == 1);
|
||||
VERIFY(m.degree(fs[0], 0) == 3);
|
||||
|
||||
factors fs_refined(m);
|
||||
polynomial_ref residual = fs[0];
|
||||
factor(residual, fs_refined);
|
||||
|
||||
// A second attempt still fails to expose the linear factors.
|
||||
VERIFY(fs_refined.distinct_factors() == 1); // actually we need 3 factors
|
||||
VERIFY(m.degree(fs_refined[0], 0) == 3); // actually we need degree 1
|
||||
// Multivariate factorization should find 3 linear factors
|
||||
VERIFY(fs.distinct_factors() == 3);
|
||||
|
||||
polynomial_ref reconstructed(m);
|
||||
fs.multiply(reconstructed);
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ static void test_skolemize_bug() {
|
|||
Z3_ast f3 = Z3_simplify(ctx, f2);
|
||||
std::cout << Z3_ast_to_string(ctx, f3) << "\n";
|
||||
|
||||
Z3_del_context(ctx);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,92 @@ Revision History:
|
|||
|
||||
--*/
|
||||
#include "util/vector.h"
|
||||
#include "util/rational.h"
|
||||
#include <iostream>
|
||||
|
||||
static void tst_resize_rational() {
|
||||
// grow from empty using default initialization (zero)
|
||||
vector<rational> v;
|
||||
v.resize(4);
|
||||
ENSURE(v.size() == 4);
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
ENSURE(v[i].is_zero());
|
||||
|
||||
// shrink: elements below new size are preserved
|
||||
v.resize(2);
|
||||
ENSURE(v.size() == 2);
|
||||
for (unsigned i = 0; i < 2; ++i)
|
||||
ENSURE(v[i].is_zero());
|
||||
|
||||
// grow with explicit value initialization
|
||||
rational half(1, 2);
|
||||
v.resize(6, half);
|
||||
ENSURE(v.size() == 6);
|
||||
for (unsigned i = 0; i < 2; ++i)
|
||||
ENSURE(v[i].is_zero());
|
||||
for (unsigned i = 2; i < 6; ++i)
|
||||
ENSURE(v[i] == half);
|
||||
|
||||
// resize to same size is a no-op
|
||||
rational three(3);
|
||||
v.resize(6, three);
|
||||
ENSURE(v.size() == 6);
|
||||
for (unsigned i = 2; i < 6; ++i)
|
||||
ENSURE(v[i] == half);
|
||||
|
||||
// resize to zero clears the vector
|
||||
v.resize(0);
|
||||
ENSURE(v.empty());
|
||||
|
||||
// grow again after being empty
|
||||
rational neg(-7);
|
||||
v.resize(3, neg);
|
||||
ENSURE(v.size() == 3);
|
||||
for (unsigned i = 0; i < 3; ++i)
|
||||
ENSURE(v[i] == neg);
|
||||
}
|
||||
|
||||
static void tst_resize() {
|
||||
// grow from empty using default initialization
|
||||
svector<int> v;
|
||||
v.resize(5);
|
||||
ENSURE(v.size() == 5);
|
||||
ENSURE(v.capacity() >= 5);
|
||||
for (unsigned i = 0; i < 5; ++i)
|
||||
ENSURE(v[i] == 0);
|
||||
|
||||
// shrink: elements below new size are preserved, size shrinks
|
||||
v.resize(3);
|
||||
ENSURE(v.size() == 3);
|
||||
for (unsigned i = 0; i < 3; ++i)
|
||||
ENSURE(v[i] == 0);
|
||||
|
||||
// grow with explicit value initialization
|
||||
v.resize(7, 42);
|
||||
ENSURE(v.size() == 7);
|
||||
for (unsigned i = 0; i < 3; ++i)
|
||||
ENSURE(v[i] == 0);
|
||||
for (unsigned i = 3; i < 7; ++i)
|
||||
ENSURE(v[i] == 42);
|
||||
|
||||
// resize to same size is a no-op
|
||||
v.resize(7, 99);
|
||||
ENSURE(v.size() == 7);
|
||||
for (unsigned i = 3; i < 7; ++i)
|
||||
ENSURE(v[i] == 42);
|
||||
|
||||
// resize to zero clears the vector
|
||||
v.resize(0);
|
||||
ENSURE(v.empty());
|
||||
ENSURE(v.size() == 0);
|
||||
|
||||
// grow again after being empty
|
||||
v.resize(4, 10);
|
||||
ENSURE(v.size() == 4);
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
ENSURE(v[i] == 10);
|
||||
}
|
||||
|
||||
static void tst1() {
|
||||
svector<int> v1;
|
||||
ENSURE(v1.empty());
|
||||
|
|
@ -58,5 +142,7 @@ static void tst1() {
|
|||
}
|
||||
|
||||
void tst_vector() {
|
||||
tst_resize_rational();
|
||||
tst_resize();
|
||||
tst1();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue