Skip to content

Fixes multiple heishamon rules bug - #15

Merged
CurlyMoo merged 7 commits into
CurlyMoo:mainfrom
IgorYbema:fix-894-mathcnt-reset
Jul 31, 2026
Merged

Fixes multiple heishamon rules bug#15
CurlyMoo merged 7 commits into
CurlyMoo:mainfrom
IgorYbema:fix-894-mathcnt-reset

Conversation

@IgorYbema

@IgorYbema IgorYbema commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

See commits for reasons

IgorYbema and others added 6 commits June 5, 2026 18:39
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.
@IgorYbema IgorYbema closed this Jul 30, 2026
@IgorYbema IgorYbema changed the title Fix heishamon bug 946 mathcnt reset Fixes multiple heishamon rules bug Jul 30, 2026
@IgorYbema IgorYbema reopened this Jul 30, 2026
@CurlyMoo

Copy link
Copy Markdown
Owner

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 },

@IgorYbema

Copy link
Copy Markdown
Contributor Author

that is why 218 should now read 219

@CurlyMoo

CurlyMoo commented Jul 30, 2026 via email

Copy link
Copy Markdown
Owner

@IgorYbema

Copy link
Copy Markdown
Contributor Author

Confirmed the rule text never assigns to $b at all — $b doesn't appear anywhere in on foo then max(1, 2); end on timer=1 then if $a > 0 then foo(); end max(1, 2); end — and $a is only ever read (if $a > 0), never written. So no implementation, ours or otherwise, can produce $b = 2/$a = 2 (or = 1) from this exact rule text. The expected-output strings look like they were pulled from a different/adjacent line in your fixture when pasting.

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?

@CurlyMoo

CurlyMoo commented Jul 30, 2026 via email

Copy link
Copy Markdown
Owner

@IgorYbema

Copy link
Copy Markdown
Contributor Author

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?

@CurlyMoo

Copy link
Copy Markdown
Owner

Two additions:

  1. Can you add these two unittests:
{ "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 },
  1. Can you also set the slots debug for non ESP devices:
    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>
@IgorYbema

Copy link
Copy Markdown
Contributor Author

Added both — the two unittests and the varstack slots debug output for non-ESP devices. Pushed in b7257f0.

@CurlyMoo
CurlyMoo merged commit e26c4e9 into CurlyMoo:main Jul 31, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants