fix(v4): a manage request with too few values, or no room, is refused (#169)

v4_manage_serve took its arguments with v4_dstack_pop and never checked the
stack's depth: a request with too few values acted on stale ring values, and
one whose results did not fit dropped the oldest silently -- around the D-16
guards. Now each request's arity (manage.h) is checked first: fewer values
than it takes, or no room for what it leaves, is error 12 with the stack as it
was -- as a block request is refused (blocks.c).

test_manage.c, written first and seen to fail (12 of 13 requests at both cell
widths; NODE-BORN passed only because the stale value read was below 1): every
request with one value fewer than it takes, and NODE-ME and CAPSULE-CELL on a
stack with no room -- refused, the stack unchanged. After: 78 checks, 0
failures at 32 and 64 bits; all v4 tests at 64 bits, 40 programs, 1,871,313
checks, 0 failures (logs/20261112-152602/v4-test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
Claude
2026-11-12 15:26:02 +00:00
parent c28cc0191b
commit c642af541b
3 changed files with 49 additions and 0 deletions
Binary file not shown.
+10
View File
@@ -87,15 +87,25 @@ int v4_manage_enter(const v4_manage *m, v4_cell number, unsigned place, v4_cell
return 0;
}
/* The values each request takes from the asker's stack and leaves there, from NODE-ME (-6) to
* NODE-PLACE (-18), as manage.h gives them: a request with fewer, or with no room for what it
* leaves, is refused before anything is taken (#169) -- as a block request is (blocks.c). */
static const unsigned char req_in[13] = { 0, 1, 4, 2, 1, 1, 1, 2, 2, 0, 1, 2, 1 };
static const unsigned char req_out[13] = { 1, 1, 1, 0, 0, 0, 0, 0, 1, 2, 1, 1, 1 };
int v4_manage_serve(const v4_manage *m, unsigned place, v4_cell request)
{
v4_fabric_node *at = v4_fabric_node_at(m->f, place);
v4_manage_entry *me, *e;
v4_node *n;
v4_cell a, b, c, d;
unsigned in, out;
if (!at || request > V4_REQ_NODE_ME || request < V4_REQ_NODE_PLACE) return 0;
n = &at->n;
in = req_in[V4_REQ_NODE_ME - request];
out = req_out[V4_REQ_NODE_ME - request];
if (n->ds.depth < in || n->ds.depth - in + out > V4_DATA_DEPTH) return refuse(n);
me = by_place(m, place);
switch (request) {
+36
View File
@@ -93,6 +93,42 @@ int main(void)
CHECK(v4_manage_enter(&m, 10, h, 0) == 0 && v4_manage_enter(&m, 0, h, 0) == 0, "a number that is live, or 0, is not entered again");
CHECK(v4_manage_serve(&m, h, V4_REQ_NODE_ME) == 1 && pop(h) == (v4_cell)h, "NODE-ME is her place");
/* #169: a request with fewer values than it takes, or with no room for what it leaves, is
* refused, error 12, the stack as it was -- as a block request is (v4/system/blocks.c). The
* values a request takes and leaves, from manage.h. */
{
static const struct { v4_cell req; unsigned in, out; const char *name; } arity[] = {
{ V4_REQ_NODE_ME, 0, 1, "NODE-ME" }, { V4_REQ_NODE_BORN, 1, 1, "NODE-BORN" },
{ V4_REQ_NODE_WIRE, 4, 1, "NODE-WIRE" }, { V4_REQ_NODE_UNWIRE, 2, 0, "NODE-UNWIRE" },
{ V4_REQ_NODE_SLEEP, 1, 0, "NODE-SLEEP" }, { V4_REQ_NODE_WAKE, 1, 0, "NODE-WAKE" },
{ V4_REQ_NODE_KILL, 1, 0, "NODE-KILL" }, { V4_REQ_NODE_PARITY, 2, 0, "NODE-PARITY" },
{ V4_REQ_CAPSULE_OPEN, 2, 1, "CAPSULE-OPEN" }, { V4_REQ_CAPSULE_CELL, 0, 2, "CAPSULE-CELL" },
{ V4_REQ_CAPSULE_LINE, 1, 1, "CAPSULE-LINE" }, { V4_REQ_NODE_CHILD, 2, 1, "NODE-CHILD" },
{ V4_REQ_NODE_PLACE, 1, 1, "NODE-PLACE" },
};
unsigned k, j;
for (k = 0; k < sizeof arity / sizeof arity[0]; k++) {
unsigned have;
if (arity[k].in > 0) { /* one value fewer than it takes */
v4_dstack_reset(&N(h)->ds);
for (j = 0; j + 1 < arity[k].in; j++) push(h, 1);
have = depth(h);
CHECK(refused(h, arity[k].req) && depth(h) == have,
"%s with %u value(s), not %u: refused, the stack as it was (depth %u)",
arity[k].name, have, arity[k].in, depth(h));
}
if (arity[k].out > arity[k].in) { /* no room for what it leaves */
v4_dstack_reset(&N(h)->ds);
while (depth(h) + (arity[k].out - arity[k].in) <= V4_DATA_DEPTH) push(h, 1);
have = depth(h);
CHECK(refused(h, arity[k].req) && depth(h) == have,
"%s on a stack with no room for its %u: refused, the stack as it was (depth %u of %u)",
arity[k].name, arity[k].out - arity[k].in, depth(h), have);
}
}
v4_dstack_reset(&N(h)->ds);
}
/* births */
r = birth(h, 11);
CHECK(r >= 0 && place_of(h, 11) == r && child(h, 10, 0) == 11 && child(h, 10, 1) == 0, "Hera births 11: the registry has it, with her as its parent");