/* test_guard.c -- the 5% safety boundary. * * A guard band whose only assertion is "the patterns still look right" has not * been shown to catch anything. These tests therefore do three things a * structural test cannot: * * 1. pin the sizing arithmetic, including the case that makes a 5% margin * evaporate -- a short list, where 5% rounds down to zero elements and a * zero-width guard is a comment rather than a boundary; * 2. index past each end of a real stack on purpose, and require the check to * fail, and require it to say *which* end was hit; * 3. run the real push/pop paths and require the boundary to survive them, so * a boundary that fires on ordinary use is caught as a false alarm rather * than being tuned into uselessness. */ #include "v4/guard.h" #include "v4/stack.h" #include #include #include static int failures = 0; static int checks = 0; #define CHECK(cond, ...) \ do { \ checks++; \ if (!(cond)) { \ failures++; \ printf(" FAIL %s:%d: ", __FILE__, __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ } \ } while (0) static void test_sizing_rounds_up(void) { /* 5% of each, rounded up. */ CHECK(V4_GUARD_ELEMS(20) == 1, "5%% of 20 is 1, got %u", V4_GUARD_ELEMS(20)); CHECK(V4_GUARD_ELEMS(40) == 2, "5%% of 40 is 2, got %u", V4_GUARD_ELEMS(40)); CHECK(V4_GUARD_ELEMS(100) == 5, "5%% of 100 is 5, got %u", V4_GUARD_ELEMS(100)); CHECK(V4_GUARD_ELEMS(1000) == 50, "5%% of 1000 is 50, got %u", V4_GUARD_ELEMS(1000)); CHECK(V4_GUARD_ELEMS(2000) == 100, "5%% of 2000 is 100, got %u", V4_GUARD_ELEMS(2000)); } static void test_sizing_never_zero(void) { /* The case that matters. 5% of anything under 20 rounds to 0 by * truncation, and a guard of zero elements catches nothing at all while * still reading as "5% boundary" in the source. */ for (unsigned n = 1; n < 20u; n++) { unsigned g = V4_GUARD_ELEMS(n); CHECK(g >= 1u, "a %u-element list got a %u-element boundary", n, g); } CHECK(V4_GUARD_ELEMS(1) == 1, "1-element list boundary"); CHECK(V4_GUARD_ELEMS(3) == 1, "3-element list boundary"); CHECK(V4_GUARD_ELEMS(8) == 1, "8-element list boundary"); CHECK(V4_GUARD_ELEMS(19) == 1, "19-element list boundary"); CHECK(V4_GUARD_ELEMS(V4_DATA_RING) == 1, "data ring boundary at depth 8"); CHECK(V4_GUARD_ELEMS(V4_RET_RING) == 1, "return ring boundary at depth 8"); } static void test_patterns_are_distinct_at_this_width(void) { /* The compile-time assertion in guard.h covers this, but the point of * distinct patterns is "which end", and that is only meaningful if it * survives truncation to the cell width. */ CHECK(V4_GUARD_PATTERN_HEAD != V4_GUARD_PATTERN_TAIL, "head and tail patterns are equal at V4_CELL_BITS=%d", V4_CELL_BITS); CHECK((v4_ucell)V4_GUARD_PATTERN_HEAD != 0u, "head pattern truncated to 0"); CHECK((v4_ucell)V4_GUARD_PATTERN_TAIL != 0u, "tail pattern truncated to 0"); } static void test_fill_and_intact(void) { v4_cell band[7]; v4_guard_fill(band, 7, V4_GUARD_PATTERN_HEAD); CHECK(v4_guard_intact(band, 7, V4_GUARD_PATTERN_HEAD), "filled band intact"); CHECK(!v4_guard_intact(band, 7, V4_GUARD_PATTERN_TAIL), "a head-filled band must not read as a tail band"); /* Every element is checked, not just the ends. */ for (unsigned i = 0; i < 7; i++) { band[i] = 0; CHECK(!v4_guard_intact(band, 7, V4_GUARD_PATTERN_HEAD), "band intact after corrupting interior element %u", i); v4_guard_fill(band, 7, V4_GUARD_PATTERN_HEAD); } } static void test_reset_installs_boundaries(void) { v4_dstack d; v4_rstack r; /* A struct that has never been reset holds whatever was on the stack, so * the boundary cannot be assumed present. Poison both first: that the * check fails before reset is the point, since it is what proves the * check is reading the boundary and not returning a constant. */ memset(&d, 0xA5, sizeof d); memset(&r, 0xA5, sizeof r); CHECK(!v4_dstack_guards_intact(&d), "data boundaries must not read as intact before reset"); CHECK(!v4_rstack_guards_intact(&r), "return boundaries must not read as intact before reset"); v4_dstack_reset(&d); v4_rstack_reset(&r); CHECK(v4_dstack_guards_intact(&d), "data boundaries intact after reset"); CHECK(v4_rstack_guards_intact(&r), "return boundaries intact after reset"); } static void test_head_overrun_is_detected(void) { /* Deliberately index one element before the ring: exactly what a lost * modulo or a sign error on the head arithmetic would do. */ v4_dstack d; v4_dstack_reset(&d); CHECK(v4_dstack_guards_intact(&d), "intact before the deliberate overrun"); d.guard_head[0] = (v4_cell)V4_GUARD_PATTERN_TAIL; /* wrong pattern too */ CHECK(!v4_dstack_guards_intact(&d), "head overrun past the data ring was not detected"); /* Which end is answerable because the two patterns differ. */ CHECK((v4_ucell)d.guard_head[0] != V4_GUARD_PATTERN_HEAD, "head boundary no longer holds its own pattern"); v4_dstack_reset(&d); CHECK(v4_dstack_guards_intact(&d), "intact after re-reset"); } static void test_tail_overrun_is_detected(void) { v4_dstack d; v4_dstack_reset(&d); d.guard_tail[0] = 0; CHECK(!v4_dstack_guards_intact(&d), "tail overrun past the data ring was not detected"); v4_dstack_reset(&d); CHECK(v4_dstack_guards_intact(&d), "intact after re-reset"); } static void test_return_stack_boundaries(void) { v4_rstack r; v4_rstack_reset(&r); CHECK(v4_rstack_guards_intact(&r), "return boundaries intact after reset"); r.guard_head[0] = 0; CHECK(!v4_rstack_guards_intact(&r), "return head overrun not detected"); v4_rstack_reset(&r); r.guard_tail[V4_RET_BOUND - 1u] = 0; CHECK(!v4_rstack_guards_intact(&r), "return tail overrun not detected"); v4_rstack_reset(&r); CHECK(v4_rstack_guards_intact(&r), "return intact after re-reset"); } static void test_ordinary_use_never_touches_the_boundaries(void) { /* The other half of the contract: a boundary that fires on legitimate use * is worse than none, because it trains you to ignore it. Drive both * stacks hard, well past their depths, and require the boundaries to * survive every operation. */ v4_dstack d; v4_rstack r; v4_dstack_reset(&d); v4_rstack_reset(&r); uint64_t s = 0x243F6A8885A308D3ull; for (int i = 0; i < 50000; i++) { s ^= s << 13; s ^= s >> 7; s ^= s << 17; v4_dstack_push(&d, (v4_cell)s); v4_rstack_push(&r, (v4_cell)(s >> 11)); if (i & 1) { (void)v4_dstack_pop(&d); (void)v4_rstack_pop(&r); } } CHECK(v4_dstack_guards_intact(&d), "data boundary damaged by ordinary push/pop"); CHECK(v4_rstack_guards_intact(&r), "return boundary damaged by ordinary push/pop"); /* head must still be in range, which is the invariant the boundary exists * to police. */ CHECK(d.head < V4_DATA_RING, "data head %u out of range", d.head); CHECK(r.head < V4_RET_RING, "return head %u out of range", r.head); } int main(void) { printf("v4 guard tests: V4_CELL_BITS=%d, data bound %d, return bound %d\n", V4_CELL_BITS, V4_DATA_BOUND, V4_RET_BOUND); test_sizing_rounds_up(); test_sizing_never_zero(); test_patterns_are_distinct_at_this_width(); test_fill_and_intact(); test_reset_installs_boundaries(); test_head_overrun_is_detected(); test_tail_overrun_is_detected(); test_return_stack_boundaries(); test_ordinary_use_never_touches_the_boundaries(); printf(" %d checks, %d failures\n", checks, failures); return failures ? 1 : 0; }