Fix QA findings: FRE GC safety, WAIT mask=0, ENVIRON leak, sentinel collision

- 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
This commit is contained in:
Eremey Valetov
2026-03-29 06:15:16 -04:00
parent a510154405
commit eaa1166d8c
4 changed files with 23 additions and 16 deletions
+9 -8
View File
@@ -34,8 +34,8 @@ from ipykernel.kernelbase import Kernel
from . import __version__
_SENTINEL = '<<<GWDONE>>>'
_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."""
+4 -5
View File
@@ -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) {
+7 -2
View File
@@ -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() == ',') {
+3 -1
View File
@@ -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)