/* * test_sim_cache_clear_isolation.c - scheduler-allocation spec, task 5.2 * round 2 (Req 7.2 regression, mandated by the round-1 review that * REJECTED this task for a CRITICAL memory-corruption defect). * * ── The bug this guards against ────────────────────────────────────────── * core_begin_session_internal() (core_session.c) is reached from BOTH * driver.c's xce_sched_driver_run_normal_mode_round() (a real * xce_scheduler_t*, which HAS an unsatisfiable_cache field, scheduler.h) * and sim_loop.c's sched_sim_driver_run() (a structurally different * xce_sched_sim_driver_t*, sim.h -- Req 5 AC 17 "Simulation isolation" -- * which has NO unsatisfiable_cache field at all). Round 1 called * alloc_cache.c's real, strong cache_clear(xce_scheduler_t*) via a * same-named WEAK fallback declared in core_session.c, relying on * link-time weak/strong symbol resolution. Since that resolution picks * ONE winner for the whole binary (not per call site), BOTH the driver.c * call (correct) and the sim_loop.c call (WRONG -- there is no cache to * clear) resolved to the same real cache_clear(xce_scheduler_t*), which * reinterpreted whatever field sits at unsatisfiable_cache's byte offset * in xce_sched_sim_driver_t as a kvtab_t* and clobbered it. That offset is * `alt_events_file` (sim.h) -- a live FILE* every --sim-alt-events run * depends on. Empirically confirmed by this task's round-1 reviewer, and * reproduced by this exact test file before round 2's fix (see this * task's own Status Report for the before/after run). * * ── The fix this proves ────────────────────────────────────────────────── * core_begin_session_internal() now calls a driver-registered hook * (core->cache_clear_cb, core.h's xce_core_cache_clear_cb_t) ONLY if * non-NULL, instead of calling cache_clear() unconditionally. * sim_loop.c never calls xce_core_set_cache_clear_cb() -- a simulation- * mode xce_sched_core_t's hook therefore stays NULL (xce_sched_core_ * init()'s memset() default), and core_begin_session_internal() treats * NULL as "this driver has no cache to clear" and skips the call * entirely. This test constructs a real xce_sched_sim_driver_t, points * alt_events_file at a real, open FILE*, deliberately does NOT register * any cache_clear_cb (mirroring production sim_loop.c exactly), calls the * REAL xce_core_begin_session() (core_session.c's public entry point), * and asserts alt_events_file is bit-for-bit unchanged across the call. * * ── Link technique ─────────────────────────────────────────────────────── * Same technique as the sibling test_core_session_cache_clear.c (round 1): * this fixture's meson.build entry lists core.c/core_clock.c/ * core_session.c/decision.c/alloc_cache.c as genuinely SEPARATE compiled * sources rather than white-box #include-ing them, specifically so * alloc_cache.c's real, strong cache_clear(xce_scheduler_t*) IS present * in this binary -- if a future regression reintroduced an unconditional * call to it, this test would still catch it (a white-box #include of * core_session.c alone, without also linking alloc_cache.c, could not * have caught round 1's bug at all -- that is exactly why round 1's bug * was invisible to the existing suite). * * kill_sched_* / plugin_unregister / sched_worker_release_host_ctx stubs * below are identical, for identical reasons, to test_core_session_ * cache_clear.c's own (see that file's header comment). */ #include #include #include #include #include #include #include "sched/alloc_cache.h" #include "sim.h" void kill_sched_job(void* arg) { (void)arg; } void kill_sched_queue(void* arg) { (void)arg; } void kill_sched_worker(void* arg) { (void)arg; } void kill_sched_user(void* arg) { (void)arg; } void kill_sched_project(void* arg) { (void)arg; } void kill_sched_resval(xce_val_t* val) { (void)val; } void plugin_unregister(void* arg) { (void)arg; } /* sched_worker_release_host_ctx (resource.c) is outside this test's linked * source set (only core.c/core_clock.c/core_session.c/decision.c/ * alloc_cache.c, meson.build) -- this fixture never adds a worker, so it * is never actually invoked at runtime either. */ void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; } void setUp(void) { return; } void tearDown(void) { return; } /* * THE Observable this regression guards (Req 7.2, round-2 review * requirement): a simulation-mode xce_sched_core_t, which never registers * a cache_clear_cb, must never have any of its sibling xce_sched_sim_ * driver_t fields touched by xce_core_begin_session() -- even with the * real, strong alloc_cache.c cache_clear(xce_scheduler_t*) linked into * the very same binary. */ static void test_sim_begin_session_never_corrupts_alt_events_file(void) { xce_sched_sim_driver_t sim; FILE* f; int rc; memset(&sim, 0, sizeof(sim)); rc = xce_sched_core_init(&(sim.core)); TEST_ASSERT_EQUAL_INT(0, rc); /* A real, live FILE* -- exactly what a --sim-alt-events run holds * here in production. tmpfile() needs no cleanup path/permissions * setup and is auto-removed on fclose()/process exit. */ f = tmpfile(); TEST_ASSERT_NOT_NULL(f); sim.alt_events_file = f; /* Deliberately NOT calling xce_core_set_cache_clear_cb() here -- * mirrors sim_loop.c exactly, which never registers this hook (Req 5 * AC 17 "Simulation isolation": sim mode has no cache to clear). This * is the crux of the regression: before round 2's fix, * core_begin_session_internal() called cache_clear() unconditionally, * so linking alloc_cache.c's real, strong cache_clear(xce_scheduler_t*) * into this binary was enough to corrupt alt_events_file below even * though nothing here ever asked for a cache to be cleared. */ rc = xce_core_begin_session(&(sim.core)); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_PTR_MESSAGE( f, sim.alt_events_file, "xce_core_begin_session() corrupted xce_sched_sim_driver_t's " "alt_events_file -- core_begin_session_internal() must treat a " "NULL cache_clear_cb as a true no-op (Req 7.2 round 2), never " "fall through to alloc_cache.c's xce_scheduler_t*-typed " "cache_clear() for a simulation-mode core"); rc = xce_core_end_session(&(sim.core)); TEST_ASSERT_EQUAL_INT(0, rc); fclose(f); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_sim_begin_session_never_corrupts_alt_events_file); return UNITY_END(); }