mruby-compiler: fix JMPNOT-to-MATCHERR rewriting in pattern match codegen

The MATCHERR optimization replaced JMPNOT (BS, 4 bytes) with
MATCHERR (B, 2 bytes) and rewound s->pc by 2. When pattern
alternation (e.g. a|B) dispatched a success jump to s->pc before
the optimization, the rewind shifted subsequent instructions and
the jump landed in the middle of the next instruction, causing
out-of-bounds access at runtime.

Replace JMPNOT in-place with MATCHERR+NOP+NOP to keep the same
4-byte size, so s->pc does not change and jump targets stay valid.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-02-07 19:10:15 +09:00
parent 6afff1c3eb
commit 4617263030
+6 -5
View File
@@ -6701,11 +6701,12 @@ codegen(codegen_scope *s, node *tree, int val)
fail_pos + 2 == s->pc &&
s->iseq[fail_pos - 2] == OP_JMPNOT) {
if (mp->raise_on_fail) {
/* Single failure point with raise - replace JMPNOT with MATCHERR */
int reg = s->iseq[fail_pos - 1]; /* Register from JMPNOT */
s->pc = fail_pos - 2; /* Rewind past JMPNOT */
s->lastpc = s->pc;
genop_1(s, OP_MATCHERR, reg); /* Emit MATCHERR with the register */
/* Replace JMPNOT(BS,4bytes) with MATCHERR(B,2bytes)+NOP+NOP;
* keep the same size so that any jump targeting s->pc stays valid */
s->iseq[fail_pos - 2] = OP_MATCHERR;
/* fail_pos-1 already holds the register operand */
s->iseq[fail_pos] = OP_NOP;
s->iseq[fail_pos + 1] = OP_NOP;
s->sp = saved_sp - 1;
if (val) push();
break; /* Pattern matching complete */