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:
rajames
2026-10-04 12:04:59 -04:00
co-authored by Claude Opus 5.5
parent b5d644d498
commit b3d2d56d11
11 changed files with 277 additions and 74 deletions
+26 -3
View File
@@ -13,6 +13,30 @@ void v4_node_reset(v4_node *n)
v4_rstack_reset(&n->rs);
v4_node_console_attach(n, -1);
v4_node_console_input_attach(n, -1, -1);
v4_node_fault_attach(n, -1);
}
int v4_node_addr_ok(v4_cell addr)
{
/* One unsigned compare covers both ends: a negative address is a very
* large unsigned one. */
return (v4_ucell)addr < (v4_ucell)V4_NODE_WORDS;
}
void v4_node_fault_attach(v4_node *n, v4_cell handler)
{
n->fault_vector = v4_node_addr_ok(handler) ? handler : (v4_cell)-1;
n->fault_addr = 0;
n->faults = 0;
n->stopped = 0;
}
void v4_node_fault(v4_node *n, v4_cell addr)
{
n->fault_addr = addr;
n->faults++;
if (n->fault_vector >= 0) n->p = n->fault_vector;
else n->stopped = 1;
}
int v4_node_guards_intact(const v4_node *n)
@@ -25,9 +49,7 @@ int v4_node_guards_intact(const v4_node *n)
v4_cell v4_node_load(const v4_node *n, v4_cell addr)
{
/* Precondition checked by the caller; see node.h. The unsigned compare is
* deliberate: `addr` is signed, and a negative address must not wrap into
* a large positive one and read the top of memory instead. */
if (!v4_node_addr_ok(addr)) return 0;
return n->mem[(unsigned)addr];
}
@@ -42,6 +64,7 @@ void v4_node_store(v4_node *n, v4_cell addr, v4_cell value)
n->console_dropped++;
return;
}
if (!v4_node_addr_ok(addr)) return;
n->mem[(unsigned)addr] = value;
}