3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-03-07 05:44:51 +00:00

Add C API unit test for user propagator mk_fresh callback and memory leak detection

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-03 02:00:13 +00:00
parent c1b41d4ed0
commit c4e5b79b48
3 changed files with 111 additions and 0 deletions

View file

@ -152,6 +152,7 @@ add_executable(test-z3
value_generator.cpp
value_sweep.cpp
var_subst.cpp
user_propagator_fresh.cpp
vector.cpp
lp/lp.cpp
lp/nla_solver_test.cpp

View file

@ -288,4 +288,5 @@ int main(int argc, char ** argv) {
TST(finite_set);
TST(finite_set_rewriter);
TST(fpa);
TST(user_propagator_fresh);
}

View file

@ -0,0 +1,109 @@
/*++
Copyright (c) 2024 Microsoft Corporation
Module Name:
user_propagator_fresh.cpp
Abstract:
Unit test for user propagator mk_fresh callback via C API.
Checks that the fresh callback is invoked and that there are no memory leaks.
--*/
#include "api/z3.h"
#include "util/memory_manager.h"
#include "util/debug.h"
#include <iostream>
static unsigned g_fresh_count = 0;
static void* test_fresh_eh(void* user_ctx, Z3_context new_ctx) {
g_fresh_count++;
return nullptr;
}
static void test_push_eh(void* user_ctx, Z3_solver_callback cb) {}
static void test_pop_eh(void* user_ctx, Z3_solver_callback cb, unsigned num_scopes) {}
static void run_propagator_fresh_once() {
Z3_config cfg = Z3_mk_config();
Z3_context ctx = Z3_mk_context(cfg);
Z3_del_config(cfg);
Z3_solver s = Z3_mk_solver(ctx);
Z3_solver_inc_ref(ctx, s);
Z3_solver_propagate_init(ctx, s, nullptr,
test_push_eh,
test_pop_eh,
test_fresh_eh);
// Build: forall (x:Int). p(x)
// This is SAT and triggers model-based quantifier instantiation,
// which creates an auxiliary context and invokes mk_fresh.
Z3_sort int_sort = Z3_mk_int_sort(ctx);
Z3_sort bool_sort = Z3_mk_bool_sort(ctx);
Z3_sort domain[1] = { int_sort };
Z3_func_decl p = Z3_mk_func_decl(ctx,
Z3_mk_string_symbol(ctx, "p"),
1, domain, bool_sort);
Z3_ast var_x = Z3_mk_bound(ctx, 0, int_sort);
Z3_ast px = Z3_mk_app(ctx, p, 1, &var_x);
Z3_sort bound_sorts[1] = { int_sort };
Z3_symbol bound_names[1] = { Z3_mk_string_symbol(ctx, "x") };
Z3_ast forall_px = Z3_mk_forall(ctx, 0,
0, nullptr,
1, bound_sorts, bound_names,
px);
Z3_solver_assert(ctx, s, forall_px);
Z3_lbool result = Z3_solver_check(ctx, s);
// forall x. p(x) is SAT: p can be interpreted as constantly true.
SASSERT(result == Z3_L_TRUE);
(void)result;
Z3_solver_dec_ref(ctx, s);
Z3_del_context(ctx);
}
/*
* Test that the mk_fresh callback is invoked when the solver creates an
* auxiliary context (e.g., for model-based quantifier instantiation), and that
* no memory is leaked by the C API wrapper allocations made during the callback.
*
* When mk_fresh is called, the C API wrapper allocates api::context (~100 KB+)
* and api_context_obj. If those were leaked, we would see hundreds of KB of
* growth per run. The test verifies that no such systematic growth occurs.
*/
void tst_user_propagator_fresh() {
// Warm up global one-time Z3 state (family-id tables, symbol caches, etc.)
// so that subsequent measurements are not polluted by first-time init costs.
run_propagator_fresh_once();
// Measure memory before the actual test run.
int64_t mem_before = (int64_t)memory::get_allocation_size();
g_fresh_count = 0;
run_propagator_fresh_once();
int64_t mem_after = (int64_t)memory::get_allocation_size();
int64_t delta = mem_after - mem_before;
std::cout << "fresh callback invoked " << g_fresh_count << " time(s)\n";
std::cout << "net memory delta: " << delta << " bytes\n";
// The fresh callback must have been invoked at least once.
SASSERT(g_fresh_count > 0);
// An api::context (allocated in the C API fresh wrapper and stored as
// m_api_context in theory_user_propagator) is at least ~100 KB as observed
// in the first-run initialization cost. If it were not freed by
// ~theory_user_propagator(), delta would be orders of magnitude larger.
// The threshold of 4096 bytes tolerates minor fluctuations from Z3's
// internal memory pools while still catching any real per-call leak.
SASSERT(delta < 4096);
}