fix(v4): overlong lines are refused whole; a refused store moves neither A nor P

- hosted (v4/tools/hosted.c): a line longer than the buffer is read to its
  end and refused as too long. Before, fgets handed it over in 1025-byte
  pieces and the last piece ran as a line of its own.
- bare metal (kernel/src/v4/sk_v4.c): the line editor takes 1024
  characters, as the boot does, and a line typed past that is refused as
  too long instead of being cut at 255 and run. V4_BOOT_LINE_TOO_LONG is
  now reported at the prompt and while a line waits.
- engine (v4/src/exec.c): a store the node itself refuses (to "any port",
  "which port", How or Have) left the opcode running on: !+ advanced A,
  against node.h's FAULTS, and !p advanced P past the handler, into the
  next fault kind's entry. A refused store now does nothing more; a store
  that raises an error (D-18) still counts as made, but P stays the
  handler's.
- test_port.c: both cases; they fail on the old exec.c (A 1017, P 901).

Engine-level tests (all v4/tests/test_*.c but test_host_*) pass at 32 and
64 bits; test_port also under ASan/UBSan. Not yet run: make -C v4 test,
test-32, hosted-check, and the three QEMU acceptance boots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
This commit is contained in:
rajames
2026-11-06 00:40:48 +00:00
co-authored by Claude Opus 5.5
parent 728b0a54eb
commit fd2088255d
4 changed files with 86 additions and 14 deletions
+17 -7
View File
@@ -29,7 +29,7 @@
#include "v4/boot.h"
#include "block_subsystem.h"
#define SK_V4_LINE 255u /* what the line editor here takes */
#define SK_V4_LINE 1024u /* what the line editor here takes: a line, as the boot takes one (v4/system/boot.c) */
/* v3's prompt (repl.c's SK_PROMPT_TEXT): "ok> " in bright cyan, after the
* console's own "[zuse@Hera] " line prefix. */
@@ -82,12 +82,15 @@ static void sk_v4_serve(v4_node *node, unsigned request)
* Zuse's session has ended (her drive was pulled): the caller goes headless
* (not in a build with EMERGENCY_CONSOLE_ENABLED, whose console needs no
* login: v3's sk_console_readline() the same). While nothing is typed the
* idle beat runs, as v3's. */
* idle beat runs, as v3's. A line typed past SK_V4_LINE characters is not
* cut short and run: what is typed after that is not kept, and
* SK_V4_LINE + 1 is returned, which the boot refuses as too long
* (V4_BOOT_LINE_TOO_LONG), running nothing. */
#define SK_V4_CURSOR_BLINK 50u /* ticks between blinks, as v3's (500 ms at 100 Hz) */
static int sk_v4_readline(void)
{
int len = 0;
int len = 0, over = 0;
int cursor_on = 1;
uint64_t blink = heartbeat_ticks();
@@ -113,11 +116,16 @@ static int sk_v4_readline(void)
arch_relax();
continue;
}
if (c == '\r' || c == '\n') { console_fb_erase_cursor(); console_putc('\n'); return len; }
if (c == '\r' || c == '\n') {
console_fb_erase_cursor(); console_putc('\n');
return over ? (int)SK_V4_LINE + 1 : len;
}
if (c == 8 || c == 127) {
if (len > 0) { len--; console_putc(8); console_putc(' '); console_putc(8); }
} else if (c >= 32 && c < 127 && len < (int)SK_V4_LINE) {
sk_v4_text[len++] = (char)c;
if (over > 0) { over--; console_putc(8); console_putc(' '); console_putc(8); }
else if (len > 0) { len--; console_putc(8); console_putc(' '); console_putc(8); }
} else if (c >= 32 && c < 127) {
if (len < (int)SK_V4_LINE) sk_v4_text[len++] = (char)c;
else over++;
console_putc((char)c);
}
cursor_on = 1; /* typing shows a solid cursor, as v3's */
@@ -196,6 +204,7 @@ void sk_v4_run(void (*chain)(void), const BootDisk *boot_disk)
break;
}
how = v4_boot_line(&boot, sk_v4_text, (unsigned)len);
if (how == V4_BOOT_LINE_TOO_LONG) { console_puts("Line too long\n ERROR\n"); how = V4_BOOT_LINE_WAITING; continue; }
if (how == V4_TEXT_ERROR) console_puts(" ERROR\n" SK_V4_PROMPT);
else if (how == V4_TEXT_COMPLETED) console_puts(" ok\n" SK_V4_PROMPT);
else break;
@@ -204,6 +213,7 @@ void sk_v4_run(void (*chain)(void), const BootDisk *boot_disk)
if (logged_out && (how == V4_TEXT_ERROR || how == V4_TEXT_COMPLETED)) continue;
if (how == V4_TEXT_COMPLETED) console_puts(" ok\n");
else if (how == V4_TEXT_ERROR) console_puts(" ERROR\n");
else if (how == V4_BOOT_LINE_TOO_LONG) console_puts("Line too long\n ERROR\n");
else if (how == V4_BOOT_LINE_REFUSED) console_puts("Message refused\n ERROR\n"); /* the node's wire is full: error 19's words */
else if (how == V4_BOOT_LINE_STOPPED) {
console_println("StarForth v4: the node stopped on a fault");
+12
View File
@@ -107,6 +107,11 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
v4_dstack *ds = &n->ds;
v4_rstack *rs = &n->rs;
v4_cell x;
/* A store can be refused by the node itself -- to "any port", "which
* port", How or Have (node.c) -- or raise an error (D-18). Refused, the
* opcode did nothing more (node.h, FAULTS); raised, the store was made,
* but P is the handler's. */
unsigned faults = n->faults;
if (op <= V4_OP_BANG_A && stack_faulted(n, op)) return;
@@ -180,20 +185,27 @@ void v4_exec_op(v4_node *n, v4_exec_state *es, v4_heat *h,
case V4_OP_STORE_P:
if (faulted(n, n->p)) return;
v4_node_store(n, n->p, v4_dstack_pop(ds));
if (n->faults != faults) { /* P is the handler now: it is not advanced */
if (n->fault_kind != V4_FAULT_RAISED) return;
break;
}
n->p = add_wrap(n->p, 1);
break;
case V4_OP_STORE_INC:
if (faulted(n, n->a)) return;
v4_node_store(n, n->a, v4_dstack_pop(ds));
if (n->faults != faults && n->fault_kind != V4_FAULT_RAISED) return; /* refused: A is untouched */
n->a = add_wrap(n->a, 1);
break;
case V4_OP_STORE_B:
if (faulted(n, n->b)) return;
v4_node_store(n, n->b, v4_dstack_pop(ds));
if (n->faults != faults && n->fault_kind != V4_FAULT_RAISED) return;
break;
case V4_OP_STORE_A:
if (faulted(n, n->a)) return;
v4_node_store(n, n->a, v4_dstack_pop(ds));
if (n->faults != faults && n->fault_kind != V4_FAULT_RAISED) return;
break;
case V4_OP_MUL_STEP:
+36
View File
@@ -133,6 +133,42 @@ int main(void)
v4_node_fault(&n, V4_FAULT_RAISED, 12); /* no handler attached: the node stops */
CHECK(v4_exec_step_word(&n, &es, &h) == 0 && n.stopped, "a node its neighbour faults does not go on with the word");
/* ---- a store the node refuses moves neither A nor P (node.h, FAULTS) ---- */
{
const v4_cell table = 900, any = PORT + (v4_cell)V4_PORT_ANY;
v4_cell w_inc, w_storep;
v4_asm_begin(&as, &n, 200);
w_inc = v4_asm_label(&as);
LIT(any); O(BANG_A); LIT(9); O(STORE_INC); O(SEMI);
w_storep = v4_asm_label(&as); /* a word a neighbour sends: !p ; */
O(STORE_P); O(SEMI);
CHECK(v4_asm_ok(&as), "the refused-store words assemble");
v4_node_fault_attach(&n, table);
start(w_inc);
{
unsigned words; /* to the fault and no further: the table is empty */
for (words = 0; words < 100 && n.faults == 0; words++) (void)v4_exec_step_word(&n, &es, &h);
}
CHECK(n.faults == 1 && n.fault_kind == V4_FAULT_ADDRESS && n.fault_addr == any,
"!+ to \"any port\" is an address fault");
CHECK(n.a == any, "and A is untouched: %ld", (long)n.a);
CHECK(n.p == table + (v4_cell)V4_FAULT_ADDRESS, "P is the address fault's handler: %ld", (long)n.p);
/* P at "any port", as a node newly born executes what it is sent
* (node.h, v4_node_born): the `!p` it is sent stores at P */
v4_node_fault_attach(&n, table);
start(any);
v4_dstack_push(&n.ds, 9);
v4_node_port_give(&n, 0, n.mem[w_storep]);
(void)v4_exec_step_word(&n, &es, &h);
CHECK(n.faults == 1 && n.fault_kind == V4_FAULT_ADDRESS && n.fault_addr == any,
"!p with P at \"any port\" is an address fault");
CHECK(n.p == table + (v4_cell)V4_FAULT_ADDRESS,
"and P is the address fault's handler, not the next one's: %ld", (long)n.p);
}
/* ---- detaching ---- */
v4_node_reset(&n);
CHECK(n.port == -1 && !n.asking, "reset detaches the port");
+21 -7
View File
@@ -52,6 +52,24 @@ static void serve(v4_node *node, unsigned request)
}
}
/* A line from stdin, its new line taken off. Returns its length, or -1 when
* the input has ended. A line longer than the buffer is read to its end and
* the rest thrown away, so that no part of it is run as a line of its own:
* the length returned is then the buffer's, which is more than a line may be,
* and the boot refuses it as too long. */
static long read_line(char *line, unsigned cap)
{
size_t len;
int c;
if (!fgets(line, (int)cap, stdin)) return -1;
len = strlen(line);
if (len && line[len - 1] == '\n') return (long)(len - 1);
if (len == cap - 1u)
while ((c = getchar()) != EOF && c != '\n') { }
return (long)len;
}
/* The prompt is the host's, as it is the kernel's REPL's for a v3 VM: read a
* line, hand it to the node, say how it ended. */
int main(void)
@@ -68,23 +86,19 @@ int main(void)
if (!v4_boot_run(&boot)) { fflush(stdout); return 1; }
for (;;) {
size_t len;
long len;
int how;
fputs("ok> ", stdout);
fflush(stdout);
if (!fgets(line, sizeof line, stdin)) { putchar('\n'); return 0; }
len = strlen(line);
if (len && line[len - 1] == '\n') len--;
if ((len = read_line(line, sizeof line)) < 0) { putchar('\n'); return 0; }
how = v4_boot_line(&boot, line, (unsigned)len);
/* The line is waiting for an answer no one here can send. The next
* line typed breaks the wait: the line that was waiting ends in an
* error, and then the line just typed is done (boot.h). */
while (how == V4_BOOT_LINE_WAITING) {
fflush(stdout);
if (!fgets(line, sizeof line, stdin)) { putchar('\n'); return 0; }
len = strlen(line);
if (len && line[len - 1] == '\n') len--;
if ((len = read_line(line, sizeof line)) < 0) { putchar('\n'); return 0; }
how = v4_boot_line(&boot, line, (unsigned)len);
if (how == V4_BOOT_LINE_TOO_LONG) { fputs("Line too long\n ERROR\n", stdout); how = V4_BOOT_LINE_WAITING; continue; }
if (how == V4_TEXT_ERROR) fputs(" ERROR\nok> ", stdout);