3
0
Fork 0
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:
Nikolaj Bjorner 2026-03-24 17:44:48 -07:00
commit 6a6f9b1892
185 changed files with 16422 additions and 5692 deletions

View file

@ -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

View file

@ -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
View 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();
}

View 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);

View file

@ -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();

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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();
}