Skip to content

Commit f3417f5

Browse files
committed
mcsat: avoid eval_in_model longjmp on unmapped uninterpreted blasted leaf
Fixes the windows-latest|release|--enable-thread-safety --enable-mcsat failure in tests/api/mcsat_tuples.c::test_partial_tuple_model_no_keep_subst. Root cause (confirmed by the unbuffered Windows CI trace): In preprocessor_build_tuple_model, when reconstructing the value of a tuple-blasted atom, we call model_get_term_value() on each blasted leaf. With keep_subst=0 the model has no alias table (model->has_alias == 0), so eval_uninterpreted -- invoked transitively on an *unmapped* uninterpreted leaf -- raises MDL_EVAL_UNKNOWN_TERM via longjmp() to the setjmp() inside eval_in_model(). That longjmp path returns cleanly on macOS/Linux, but on MinGW x86_64 under -O3 -fomit-frame-pointer with THREAD_SAFE (TLS) enabled it crashes inside the unwind. setjmp/longjmp on mingw-w64 x86_64 is implemented via SEH and is sensitive to omitted frame pointers and TLS interactions; this configuration is exactly the one that breaks. The crash is reproducible and Windows-only because only this test exercises all three triggers simultaneously: keep_subst=0 (no alias table), an *unmapped* tuple-blasted leaf (the second component of the unconstrained tuple x is never assigned during search), and that leaf being an UNINTERPRETED_TERM (never substituted away by an equality). Captured Windows CI trace (excerpt, before this fix): [mcsat_tuples] START test_partial_tuple_model_no_keep_subst [pp_build_model] enter, has_alias=0, eqs=1 ... [pp_build_tuple] i=0 atom=774 tau=21 [pp_build_tuple] n_leaves=2 [pp_build_tuple] j=0 leaf=780 [pp_build_tuple] model_get_term_value -> 2 <- mapped, fine [pp_build_tuple] j=1 leaf=782 <- unmapped uninterp <crash inside model_get_term_value> Fix: In the per-leaf loop of preprocessor_build_tuple_model, first try the direct lookup model_find_term_value(). Only fall through to model_get_term_value() (which can invoke eval_in_model) when there is either an alias table to consult or the leaf is not an uninterpreted term -- i.e., when the evaluator can plausibly produce a value. For the remaining case (no alias + unmapped + uninterpreted), v stays at null_value (< 0) and the existing fallback path mints a fresh default via vtbl_make_object(leaf_tau), which is sound: an unmapped uninterpreted leaf is unconstrained, so any value of the right type preserves model correctness. This avoids the longjmp/SEH unwind path entirely on the only execution path where it could fire, fixing the Windows crash without changing semantics on Linux/macOS. The accompanying revert of TEMP-DIAG stderr prints (in preprocessor.c and tests/api/mcsat_tuples.c) removes the now-unneeded instrumentation that pinned the bug.
1 parent 0a049bd commit f3417f5

2 files changed

Lines changed: 26 additions & 150 deletions

File tree

src/mcsat/preprocessor.c

Lines changed: 8 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,6 @@
4040
#include "yices.h"
4141
#include "api/yices_api_lock_free.h"
4242

43-
#include <stdlib.h>
44-
45-
/* TEMP DIAG: stderr trace macro, gated on env var YICES_PP_TRACE so the
46-
* diagnostic prints don't pollute regression-test stderr (regression
47-
* tests diff stderr against expected output). Set YICES_PP_TRACE=1 in CI
48-
* runs that need to debug the Windows MinGW release+thread-safety
49-
* failure. To be removed along with every PP_DIAG call site once the
50-
* failure is pinpointed. */
51-
static int pp_diag_enabled(void) {
52-
static int cached = -1;
53-
if (cached < 0) {
54-
const char* s = getenv("YICES_PP_TRACE");
55-
cached = (s != NULL && s[0] != '\0' && s[0] != '0') ? 1 : 0;
56-
}
57-
return cached;
58-
}
59-
#define PP_DIAG(...) do { if (pp_diag_enabled()) { fprintf(stderr, __VA_ARGS__); fflush(stderr); } } while (0)
60-
6143
void preprocessor_construct(preprocessor_t* pre, term_table_t* terms, jmp_buf* handler, const mcsat_options_t* options) {
6244
pre->terms = terms;
6345
init_term_manager(&pre->tm, terms);
@@ -2479,35 +2461,26 @@ static value_t merge_blasted_function_value(preprocessor_t* pre, value_table_t*
24792461
static
24802462
value_t build_value_from_flat(preprocessor_t* pre, value_table_t* vtbl, type_t tau, const value_t* flat, uint32_t* idx) {
24812463
type_table_t* types = pre->terms->types;
2482-
PP_DIAG("[build_value_from_flat] enter tau=%d idx=%u\n", (int) tau, (unsigned) *idx);
24832464

24842465
if (type_kind(types, tau) == TUPLE_TYPE) {
24852466
tuple_type_t* tuple = tuple_type_desc(types, tau);
24862467
uint32_t i, n = tuple->nelem;
2487-
PP_DIAG("[build_value_from_flat] TUPLE n=%u\n", (unsigned) n);
24882468
value_t elem[n];
24892469
for (i = 0; i < n; ++i) {
24902470
elem[i] = build_value_from_flat(pre, vtbl, tuple->elem[i], flat, idx);
24912471
if (elem[i] == null_value) {
2492-
PP_DIAG("[build_value_from_flat] TUPLE elem %u null\n", (unsigned) i);
24932472
return null_value;
24942473
}
24952474
}
2496-
PP_DIAG("[build_value_from_flat] TUPLE calling vtbl_mk_tuple\n");
2497-
value_t r = vtbl_mk_tuple(vtbl, n, elem);
2498-
PP_DIAG("[build_value_from_flat] TUPLE vtbl_mk_tuple -> %d\n", (int) r);
2499-
return r;
2475+
return vtbl_mk_tuple(vtbl, n, elem);
25002476
} else if (type_kind(types, tau) == FUNCTION_TYPE) {
25012477
uint32_t n = type_leaf_count(pre, tau);
2502-
PP_DIAG("[build_value_from_flat] FUNCTION n_leaves=%u\n", (unsigned) n);
25032478
value_t v = merge_blasted_function_value(pre, vtbl, tau, flat + *idx, n);
25042479
*idx += n;
2505-
PP_DIAG("[build_value_from_flat] FUNCTION merge -> %d new_idx=%u\n", (int) v, (unsigned) *idx);
25062480
return v;
25072481
} else {
25082482
value_t v = flat[*idx];
25092483
(*idx)++;
2510-
PP_DIAG("[build_value_from_flat] LEAF v=%d new_idx=%u\n", (int) v, (unsigned) *idx);
25112484
return v;
25122485
}
25132486
}
@@ -2705,27 +2678,19 @@ void preprocessor_build_tuple_model(preprocessor_t* pre, model_t* model) {
27052678
type_table_t* types = pre->terms->types;
27062679
uint32_t i;
27072680

2708-
/* TEMP DIAG (windows release+thread-safety mcsat_tuples failure) */
2709-
PP_DIAG("[pp_build_tuple] enter, atoms=%u\n",
2710-
(unsigned) pre->tuple_blast_atoms.size);
27112681
for (i = 0; i < pre->tuple_blast_atoms.size; ++i) {
27122682
term_t atom = pre->tuple_blast_atoms.data[i];
27132683
ivector_t leaves;
27142684
type_t tau = term_type(pre->terms, atom);
27152685
uint32_t n, j;
27162686

2717-
PP_DIAG("[pp_build_tuple] i=%u atom=%d tau=%d\n",
2718-
(unsigned) i, (int) atom, (int) tau);
2719-
27202687
if (model_find_term_value(model, atom) != null_value) {
2721-
PP_DIAG("[pp_build_tuple] atom already mapped, continue\n");
27222688
continue;
27232689
}
27242690

27252691
init_ivector(&leaves, 0);
27262692
tuple_blast_get(pre, atom, &leaves);
27272693
n = leaves.size;
2728-
PP_DIAG("[pp_build_tuple] n_leaves=%u\n", (unsigned) n);
27292694
if (n == 0) {
27302695
delete_ivector(&leaves);
27312696
continue;
@@ -2734,10 +2699,12 @@ void preprocessor_build_tuple_model(preprocessor_t* pre, model_t* model) {
27342699
value_t leaf_vals[n];
27352700
bool ok = true;
27362701
for (j = 0; j < n; ++j) {
2737-
PP_DIAG("[pp_build_tuple] j=%u leaf=%d\n",
2738-
(unsigned) j, (int) leaves.data[j]);
2739-
value_t v = model_get_term_value(model, leaves.data[j]);
2740-
PP_DIAG("[pp_build_tuple] model_get_term_value -> %d\n", (int) v);
2702+
term_t leaf = leaves.data[j];
2703+
value_t v = model_find_term_value(model, leaf);
2704+
if (v == null_value &&
2705+
(model->has_alias || term_kind(pre->terms, leaf) != UNINTERPRETED_TERM)) {
2706+
v = model_get_term_value(model, leaf);
2707+
}
27412708
if (v < 0) {
27422709
/* The blasted leaf was never assigned a value by the mcsat
27432710
* search (typical for unconstrained tuple components) and
@@ -2748,19 +2715,15 @@ void preprocessor_build_tuple_model(preprocessor_t* pre, model_t* model) {
27482715
* so the original tuple atom can still be reconstructed.
27492716
* vtbl_make_object handles bool / arith / bv / tuple /
27502717
* function / scalar uniformly. */
2751-
type_t leaf_tau = term_type(pre->terms, leaves.data[j]);
2752-
PP_DIAG("[pp_build_tuple] leaf_tau=%d, calling vtbl_make_object\n",
2753-
(int) leaf_tau);
2718+
type_t leaf_tau = term_type(pre->terms, leaf);
27542719
v = vtbl_make_object(vtbl, leaf_tau);
2755-
PP_DIAG("[pp_build_tuple] vtbl_make_object -> %d\n", (int) v);
27562720
if (v < 0) {
27572721
ok = false;
27582722
break;
27592723
}
27602724
}
27612725
leaf_vals[j] = v;
27622726
}
2763-
PP_DIAG("[pp_build_tuple] leaf loop done, ok=%d\n", (int) ok);
27642727
if (!ok) {
27652728
/* vtbl_make_object failed for some leaf: the type cannot be
27662729
* inhabited concretely (extremely unusual -- e.g. a malformed
@@ -2779,9 +2742,7 @@ void preprocessor_build_tuple_model(preprocessor_t* pre, model_t* model) {
27792742
}
27802743

27812744
if (type_kind(types, tau) == FUNCTION_TYPE) {
2782-
PP_DIAG("[pp_build_tuple] FUNCTION_TYPE branch\n");
27832745
value_t f = merge_blasted_function_value(pre, vtbl, tau, leaf_vals, n);
2784-
PP_DIAG("[pp_build_tuple] merge_blasted_function_value -> %d\n", (int) f);
27852746
if (f >= 0) {
27862747
model_map_term(model, atom, f);
27872748
} else if (trace_enabled(pre->tracer, "mcsat::preprocess")) {
@@ -2792,16 +2753,10 @@ void preprocessor_build_tuple_model(preprocessor_t* pre, model_t* model) {
27922753
trace_term_ln(pre->tracer, pre->terms, atom);
27932754
}
27942755
} else {
2795-
PP_DIAG("[pp_build_tuple] non-FUNCTION branch, calling build_value_from_flat\n");
27962756
uint32_t idx = 0;
27972757
value_t v = build_value_from_flat(pre, vtbl, tau, leaf_vals, &idx);
2798-
PP_DIAG("[pp_build_tuple] build_value_from_flat -> %d (idx=%u)\n",
2799-
(int) v, (unsigned) idx);
28002758
if (v >= 0) {
2801-
PP_DIAG("[pp_build_tuple] model_map_term(atom=%d, v=%d)\n",
2802-
(int) atom, (int) v);
28032759
model_map_term(model, atom, v);
2804-
PP_DIAG("[pp_build_tuple] model_map_term OK\n");
28052760
} else if (trace_enabled(pre->tracer, "mcsat::preprocess")) {
28062761
mcsat_trace_printf(pre->tracer,
28072762
"preprocessor_build_tuple_model: dropping tuple atom ");
@@ -2812,9 +2767,7 @@ void preprocessor_build_tuple_model(preprocessor_t* pre, model_t* model) {
28122767
}
28132768

28142769
delete_ivector(&leaves);
2815-
PP_DIAG("[pp_build_tuple] end of iteration i=%u\n", (unsigned) i);
28162770
}
2817-
PP_DIAG("[pp_build_tuple] exit\n");
28182771
}
28192772

28202773
static term_t build_term_from_flat(preprocessor_t* pre, type_t tau, const term_t* flat, uint32_t* idx);
@@ -3057,14 +3010,9 @@ void preprocessor_build_model(preprocessor_t* pre, model_t* model) {
30573010
* touched and the alias-based lazy resolution handles everything. */
30583011
evaluator_t eval;
30593012
bool eval_inited = false;
3060-
/* TEMP DIAG (windows release+thread-safety mcsat_tuples failure) */
3061-
PP_DIAG("[pp_build_model] enter, has_alias=%d, eqs=%u\n",
3062-
(int) model->has_alias, (unsigned) pre->equalities_list.size);
30633013
for (i = 0; i < pre->equalities_list.size; ++ i) {
30643014
term_t eq = pre->equalities_list.data[i];
30653015
term_t eq_var = preprocessor_get_eq_solved_var(pre, eq);
3066-
PP_DIAG("[pp_build_model] i=%u eq=%d eq_var=%d\n",
3067-
(unsigned) i, (int) eq, (int) eq_var);
30683016
if (trace_enabled(pre->tracer, "mcsat::preprocess")) {
30693017
mcsat_trace_printf(pre->tracer, "eq = ");
30703018
trace_term_ln(pre->tracer, pre->terms, eq);
@@ -3075,24 +3023,18 @@ void preprocessor_build_model(preprocessor_t* pre, model_t* model) {
30753023
// Some equalities are solved, but then reasserted in the solver
30763024
// these already have a model
30773025
if (model_find_term_value(model, eq_var) != null_value) {
3078-
PP_DIAG("[pp_build_model] already mapped, continue\n");
30793026
continue;
30803027
}
30813028
// Some equalities are marked, but not solved. These we skip as they
30823029
// are already set in the model
30833030
if (preprocessor_get(pre, eq_var) == eq_var) {
3084-
PP_DIAG("[pp_build_model] not solved, continue\n");
30853031
continue;
30863032
}
30873033
term_kind_t eq_kind = term_kind(pre->terms, eq);
3088-
PP_DIAG("[pp_build_model] eq_kind=%d\n", (int) eq_kind);
30893034
composite_term_t* eq_desc = get_composite(pre->terms, eq_kind, eq);
3090-
PP_DIAG("[pp_build_model] eq_desc=%p arity=%u\n",
3091-
(void*) eq_desc, eq_desc ? (unsigned) eq_desc->arity : 0u);
30923035
term_t eq_subst = eq_desc->arity > 1
30933036
? (eq_desc->arg[0] == eq_var ? eq_desc->arg[1] : eq_desc->arg[0])
30943037
: zero_term;
3095-
PP_DIAG("[pp_build_model] eq_subst=%d\n", (int) eq_subst);
30963038
if (model->has_alias) {
30973039
/* Standard path: record the substitution and let the model
30983040
* evaluator resolve eq_var lazily via eval_term(eq_subst). */
@@ -3111,25 +3053,16 @@ void preprocessor_build_model(preprocessor_t* pre, model_t* model) {
31113053
* If eval fails (e.g., eq_subst transitively depends on another
31123054
* not-yet-mapped eq_var), fall back to a fresh default value. */
31133055
if (!eval_inited) {
3114-
PP_DIAG("[pp_build_model] init_evaluator\n");
31153056
init_evaluator(&eval, model);
31163057
eval_inited = true;
3117-
PP_DIAG("[pp_build_model] init_evaluator OK\n");
31183058
}
3119-
PP_DIAG("[pp_build_model] eval_in_model(%d)\n", (int) eq_subst);
31203059
value_t v = eval_in_model(&eval, eq_subst);
3121-
PP_DIAG("[pp_build_model] eval_in_model -> v=%d\n", (int) v);
31223060
if (v < 0) {
31233061
type_t eq_var_tau = term_type(pre->terms, eq_var);
3124-
PP_DIAG("[pp_build_model] vtbl_make_object(tau=%d)\n", (int) eq_var_tau);
31253062
v = vtbl_make_object(model_get_vtbl(model), eq_var_tau);
3126-
PP_DIAG("[pp_build_model] vtbl_make_object -> v=%d\n", (int) v);
31273063
}
31283064
if (v >= 0) {
3129-
PP_DIAG("[pp_build_model] model_map_term(eq_var=%d, v=%d)\n",
3130-
(int) eq_var, (int) v);
31313065
model_map_term(model, eq_var, v);
3132-
PP_DIAG("[pp_build_model] model_map_term OK\n");
31333066
} else if (trace_enabled(pre->tracer, "mcsat::preprocess")) {
31343067
mcsat_trace_printf(pre->tracer,
31353068
"preprocessor_build_model: failed to bind eq_var ");
@@ -3139,14 +3072,10 @@ void preprocessor_build_model(preprocessor_t* pre, model_t* model) {
31393072
}
31403073

31413074
if (eval_inited) {
3142-
PP_DIAG("[pp_build_model] delete_evaluator\n");
31433075
delete_evaluator(&eval);
3144-
PP_DIAG("[pp_build_model] delete_evaluator OK\n");
31453076
}
31463077

3147-
PP_DIAG("[pp_build_model] calling preprocessor_build_tuple_model\n");
31483078
preprocessor_build_tuple_model(pre, model);
3149-
PP_DIAG("[pp_build_model] exit\n");
31503079
}
31513080

31523081

0 commit comments

Comments
 (0)