Fixes multiple heishamon rules bug - #15
Conversation
rule_initialize already rejects a single ruleset whose own varsize exceeds INT8_MAX variables, but the varstack accumulates across all rulesets. A ruleset that fits on its own could still push the running varstack total past 127 slots, overflowing the int8 variable index. Add a second guard on (varstack->nrbytes + varsize) so the combined total is checked, with a matching error message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two consecutive `if <compound condition with && or ||> then <call> end` blocks miscompiled: the first block's trailing OP_AND/OP_OR was relocated into the second block, so both bodies fired with the wrong condition. The assignment-statement path in rule_create resets mathcnt to 0, but the call/expression-statement terminator did not. mathcnt then leaked across the block boundary, so the second block's first operand no longer had a == 1, and bc_parse_math_order's backward limit search overshot past the first block's call body and JMP into its first OP_GETVAL. The operator-reorder window then spanned both blocks. Reset mathcnt to 0 in the TEND branch of the TSEMICOLON handler, i.e. only when a call/expression statement is the last in its block. Doing it at every OP_CLEAR instead changes slot reuse for multi-statement bodies and regresses byte counts. Fixes #894 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a standalone call (e.g. foo();) is the only statement in an if-block, the TSEMICOLON handler sets OP_CALL.a to 0. bc_assign_slots' segment detection only advances `start` when it finds a node with a > 0, so with OP_CALL.a = 0 `start` stayed at its previous value and the slot-assignment loops scanned preceding bytecode, in some cases corrupting OP_JMP jump offsets and over-allocating heap slots. Fix 1: initialise start = end after the skip-JMPs step, so when no positive-slot node is found the loops scan only the actual segment. Fix 2: add the same OP_CALL/a==0 continue to loop2 that loop1 already has, so loop2 does not overwrite the no-return-value sentinel. This yields a tighter (4 bytes smaller) layout for 10 standalone-call unittests with identical runtime output; their expected byte counts are updated accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Append "varstack slots: %d/127" to the two bytecode debug log lines so the running variable-slot count is visible alongside the new total varstack guard. Cosmetic; debug logging only, no functional change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/function Extends the #894 fix, which only reset mathcnt when a call/expression statement closed an if-block via `end`. The same leak happens when the call statement is instead followed by a sibling statement (nested if, event, or function call) at the same level - bc_parse_math_order's backward slot search can still overshoot into the call's slots and corrupt the next statement's condition, notably a following && compound condition (HeishaMon issue #946). Updates the one existing test fixture whose expected bytecode size grew by the extra reset (283 -> 291); all other outputs/sizes are unchanged.
Covers the exact shape that was crashing: an if-block containing a call statement immediately followed by a sibling if whose condition uses a compound && operator. Verified this test FATALs on the parent commit (mathcnt reset missing) and passes with it applied.
|
This fails this unittests i made for another Heishamon issue before: { "on foo then max(1, 2); end on timer=1 then if $a > 0 then foo(); end max(1, 2); end", { { "", 108 }, { "[1]$b = 2[1]$a = 2", 218 } }, { { "", 167 }, { "[1]$b = 1[1]$a = 1", 151 } }, 0 }, |
|
that is why 218 should now read 219 |
|
That’s not the only issue with that unittest. The output is also wrong.
|
|
Confirmed the rule text never assigns to Could you paste the exact full line from your test file (or a small diff), so I can verify against the real fixture instead of a possibly-mismatched copy? |
|
True, it possibly lead to a parsing error before.
|
|
Good to know — thanks for confirming. Since the mismatch was on the pasted fixture rather than the fix itself, is there anything else blocking this PR, or is it good to merge? |
|
Two additions:
{ "on foo then max(1, 2); end on timer=1 then if $a > 0 then foo(); end max(1, 2); end", { { "", 108 }, { "", 219 } }, { { "", 167 }, { "", 151 } }, 0 },
{ "if 1 == 1 then if 2 == 2 then foo(1); else foo(2); end end on foo($b) then $a = $b; end ", { { "", 156 }, { "[2]$b = NULL[2]$a = NULL", 194 } }, { { "", 167 }, { "[2]$b = NULL[2]$a = NULL", 151 } }, 0 },
printf("bytecode: %d/%d, heap: %d/%d, stack: %d/%d bytes, varstack: %d/%d bytes, varstack slots: %lu/127\n",
getval(obj->bc.nrbytes),
getval(obj->bc.bufsize),
getval(obj->heap->nrbytes),
getval(obj->heap->bufsize),
((stack == NULL) ? 0 : getval(stack->nrbytes)),
((stack == NULL) ? 0 : getval(stack->bufsize)),
((varstack->nrbytes == 0) ? 0 : varstack->nrbytes),
(varstack->bufsize),
varstack->nrbytes / sizeof(struct vm_vchar_t) |
Adds the two regression tests and extends the non-ESP printf debug lines with varstack slot usage, per CurlyMoo's review comment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Added both — the two unittests and the varstack slots debug output for non-ESP devices. Pushed in b7257f0. |
See commits for reasons