feat(v4.0.0): address faults -- an address outside memory is guarded (D-14)
Ruled 2026-10-04: guarded, an error shown, back to the prompt. - The executor checks every address a programme uses (P, A, B) before using it. Outside memory the opcode does nothing, the rest of its word is not executed, and P becomes the node's fault handler; a node with no handler stops. v4_node_load/store never index outside memory. - capsule/quit.v4: (FAULT), the host node's handler, prints "Address out of range", ends an open definition, prints ERROR and returns to the prompt. - tests: every memory opcode and P in test_exec.c; from the prompt, from inside nested words and loops, in test_host_quit.c. This closes the hole node.h described: a wild address used to index the model's own memory gigabytes out of bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b5d644d498
commit
b3d2d56d11
+28
-32
@@ -4,12 +4,10 @@
|
||||
* place for a *linear* index error -- mem[-1], mem[V4_NODE_WORDS] -- which is
|
||||
* inside the struct and therefore invisible to AddressSanitizer.
|
||||
*
|
||||
* It explicitly does NOT cover an out-of-range *word address*, where P, A or B
|
||||
* has left the address space. That lands gigabytes away, outside any band.
|
||||
* DECOMPOSITION.md does not say what such an access should do, so no policy is
|
||||
* invented here; instead the limitation is measured and asserted, so the claim
|
||||
* in node.h cannot rot. Closing that hole is a ruling on out-of-range
|
||||
* addressing, not a wider band.
|
||||
* It does not cover an out-of-range *word address*, where P, A or B has left
|
||||
* the address space: as an index that would land gigabytes away, outside any
|
||||
* band. D-14 guards that with a range check, tested here for the C accessors
|
||||
* and in test_exec.c for a running programme.
|
||||
*/
|
||||
#include "v4/node.h"
|
||||
|
||||
@@ -171,32 +169,30 @@ static void test_linear_overrun_high_is_caught(void)
|
||||
CHECK(v4_node_guards_intact(&n), "intact after re-reset");
|
||||
}
|
||||
|
||||
static void test_out_of_range_word_address_is_not_covered_by_the_band(void)
|
||||
static void test_out_of_range_word_address_touches_nothing(void)
|
||||
{
|
||||
/* What the band does NOT catch, asserted so the limitation stays visible
|
||||
* and cannot be quietly forgotten.
|
||||
*
|
||||
* An out-of-range *word address* is a different animal from a linear index
|
||||
* error. A v4_cell address of -1 becomes unsigned 0xFFFFFFFF, which is
|
||||
* 2^32 words past mem -- 16 GB at 32-bit cells. That is far outside the
|
||||
* struct, so no boundary can see it. The band was briefly documented as
|
||||
* catching this; it does not, and the protection is a range check whose
|
||||
* policy DECOMPOSITION.md does not define. This test records the gap by
|
||||
* measuring it, so the number in node.h stays true. */
|
||||
v4_cell neg = (v4_cell)-1;
|
||||
uint64_t words_out = (uint64_t)(v4_ucell)neg;
|
||||
uint64_t far = words_out * (uint64_t)sizeof(v4_cell);
|
||||
printf(" out-of-range word address -1 lands %llu bytes past mem "
|
||||
"(%.1f GB at this width): outside the band, as documented\n",
|
||||
(unsigned long long)far, (double)far / 1073741824.0);
|
||||
CHECK(far > (uint64_t)sizeof(v4_node),
|
||||
"an out-of-range address should land outside the node");
|
||||
|
||||
/* The band is only V4_MEM_BOUND words, so even a modest overrun is caught
|
||||
* only while it is within the band. Past that it is not, which is why the
|
||||
* open question is a range check and not a wider band. */
|
||||
CHECK(V4_MEM_BOUND < V4_NODE_WORDS / 2u,
|
||||
"if the band were most of memory, this reasoning would be wrong");
|
||||
/* A word address outside memory is a different thing from a linear index
|
||||
* error: -1 as an index would be 2^32 words past mem, far outside the
|
||||
* struct and any band. D-14 (2026-10-04) rules it guarded: load gives 0
|
||||
* and store does nothing. (That a running programme faults there is the
|
||||
* executor's business: test_exec.c.) */
|
||||
static const v4_cell bad[] = { -1, -2, (v4_cell)V4_NODE_WORDS, (v4_cell)V4_NODE_WORDS + (v4_cell)V4_MEM_BOUND,
|
||||
(v4_cell)V4_MSB, (v4_cell)(V4_MSB - 1u) };
|
||||
v4_node *n = malloc(sizeof *n);
|
||||
unsigned i;
|
||||
if (!n) { CHECK(0, "malloc"); return; }
|
||||
v4_node_reset(n);
|
||||
for (i = 0; i < V4_NODE_WORDS; i++) n->mem[i] = (v4_cell)(i + 1000u);
|
||||
for (i = 0; i < sizeof bad / sizeof bad[0]; i++) {
|
||||
CHECK(!v4_node_addr_ok(bad[i]), "%lld is not an address", (long long)bad[i]);
|
||||
CHECK(v4_node_load(n, bad[i]) == 0, "a load at %lld gives 0", (long long)bad[i]);
|
||||
v4_node_store(n, bad[i], 0x5A5A);
|
||||
}
|
||||
for (i = 0; i < V4_NODE_WORDS; i++)
|
||||
if (n->mem[i] != (v4_cell)(i + 1000u)) { CHECK(0, "a store outside memory changed word %u", i); break; }
|
||||
CHECK(v4_node_guards_intact(n), "and the boundaries are intact");
|
||||
CHECK(v4_node_addr_ok(0) && v4_node_addr_ok((v4_cell)(V4_NODE_WORDS - 1u)), "the first and last words are addresses");
|
||||
free(n);
|
||||
}
|
||||
|
||||
static void test_ends_are_distinguishable(void)
|
||||
@@ -257,7 +253,7 @@ int main(void)
|
||||
test_boundary_is_adjacent_to_memory();
|
||||
test_linear_overrun_low_is_caught();
|
||||
test_linear_overrun_high_is_caught();
|
||||
test_out_of_range_word_address_is_not_covered_by_the_band();
|
||||
test_out_of_range_word_address_touches_nothing();
|
||||
test_ends_are_distinguishable();
|
||||
test_workload_never_false_alarms();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user