From eaa1166d8ceb6b1b7b470101901cd85a1e53bcce Mon Sep 17 00:00:00 2001 From: Eremey Valetov Date: Sun, 29 Mar 2026 06:15:16 -0400 Subject: [PATCH] Fix QA findings: FRE GC safety, WAIT mask=0, ENVIRON leak, sentinel collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FRE(): remove strpool_gc() call during expression evaluation — temporaries on the C stack would become dangling pointers after compaction - WAIT: reject mask=0 (would infinite-loop since AND 0 is always 0) - ENVIRON: use setenv()+free() instead of putenv() to avoid memory leak and dangling pointer if pool is compacted - portio: add bounds check to com1_inp() (com1_out already had one) - kernel: use CHR$(1) sentinel prefix to avoid collision with user output - kernel: fix INPUT prompt rfind() edge case when no newline in buffer - eval.c: enlarge DATE$ format buffer to silence -Wformat-truncation --- gwbasickernel/kernel.py | 17 +++++++++-------- src/eval.c | 9 ++++----- src/interp.c | 9 +++++++-- src/portio.c | 4 +++- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/gwbasickernel/kernel.py b/gwbasickernel/kernel.py index bd3370d..d0a23a2 100644 --- a/gwbasickernel/kernel.py +++ b/gwbasickernel/kernel.py @@ -34,8 +34,8 @@ from ipykernel.kernelbase import Kernel from . import __version__ -_SENTINEL = '<<>>' -_SENTINEL_CMD = f'PRINT "{_SENTINEL}"\n' +_SENTINEL = '\x01GWDONE\x01' +_SENTINEL_CMD = 'PRINT CHR$(1)+"GWDONE"+CHR$(1)\n' _ERROR_RE = re.compile( r'(?:Syntax error|Illegal function call|Type mismatch|Out of |' @@ -378,13 +378,14 @@ class GWBasicKernel(Kernel): return before, 'done' # Check for INPUT prompt: "? " at end of buffer (no newline after) - if allow_stdin and buf.endswith(b'? ') and b'\n' not in buf[buf.rfind(b'\n') + 1:]: - # Extract text before the "? " to show as prompt + if allow_stdin and buf.endswith(b'? '): last_nl = buf.rfind(b'\n') - prompt_line = buf[last_nl + 1:].decode('utf-8', errors='replace') - reply = self.raw_input(prompt_line) - self._proc.stdin.write((reply + '\n').encode()) - self._proc.stdin.flush() + tail = buf[last_nl + 1:] if last_nl >= 0 else buf + if b'\n' not in tail[:-2]: # no embedded newline before "? " + prompt_line = tail.decode('utf-8', errors='replace') + reply = self.raw_input(prompt_line) + self._proc.stdin.write((reply + '\n').encode()) + self._proc.stdin.flush() def _split_sixel(self, raw): """Split raw output into (text_parts, sixel_blobs) interleaved.""" diff --git a/src/eval.c b/src/eval.c index 59aa3b3..27cb883 100644 --- a/src/eval.c +++ b/src/eval.c @@ -641,10 +641,9 @@ static gw_value_t eval_function(uint8_t prefix, uint8_t func_tok) gw_expect('('); arg = gw_eval(); /* can be string or numeric */ gw_expect_rparen(); - if (arg.type == VT_STR) { - gw_str_free(&arg.sval); - strpool_gc(); - } + if (arg.type == VT_STR) gw_str_free(&arg.sval); + /* No GC here — unsafe during expression eval (temps on C stack). + * The run loop compacts at statement boundaries instead. */ v.type = VT_SNG; v.fval = (float)strpool_free(); return v; @@ -996,7 +995,7 @@ static gw_value_t eval_atom(void) gw_chrget(); gw_value_t v; v.type = VT_STR; - char tbuf[16]; + char tbuf[40]; time_t now = time(NULL); struct tm *tm = localtime(&now); if (xtok == XSTMT_DATE) { diff --git a/src/interp.c b/src/interp.c index 96b1e11..8cec72e 100644 --- a/src/interp.c +++ b/src/interp.c @@ -1377,7 +1377,12 @@ void gw_exec_stmt(void) gw_value_t val = gw_eval_str(); char *s = gw_str_to_cstr(&val.sval); gw_str_free(&val.sval); - putenv(s); /* s is intentionally not freed — putenv requires it */ + char *eq = strchr(s, '='); + if (eq) { + *eq = '\0'; + setenv(s, eq + 1, 1); + } + free(s); return; } @@ -2617,7 +2622,7 @@ void gw_exec_stmt(void) gw_skip_spaces(); gw_expect(','); int mask = gw_eval_int(); - if (mask < 0 || mask > 255) gw_error(ERR_FC); + if (mask < 0 || mask > 255 || mask == 0) gw_error(ERR_FC); int xor_mask = 0; gw_skip_spaces(); if (gw_chrgot() == ',') { diff --git a/src/portio.c b/src/portio.c index cf3d571..2fe7cec 100644 --- a/src/portio.c +++ b/src/portio.c @@ -122,7 +122,9 @@ static uint8_t com1_inp(uint16_t port) int reg = port - 0x3F8; if (reg == 5) /* LSR: transmitter ready */ return 0x60; - return com1_regs[reg]; + if (reg >= 0 && reg < 7) + return com1_regs[reg]; + return 0xFF; } static void com1_out(uint16_t port, uint8_t value)