fix: ops.cc compound-assign efuns don't clear the lvalue's subtype

"undefined" (the value read back from a missing mapping key) is
represented as {T_NUMBER, subtype=T_UNDEFINED, 0}. The T_NUMBER cases
of &= /= %= *= |= ^= <<= >>= -= (src/packages/ops/ops.cc) modify the
lvalue's u.number in place via a compound C operator but never reset
its subtype, so a variable that started out undefined kept reporting
undefinedp() == 1 after a real arithmetic result had been computed
into it. f_xor_eq() didn't even clear the pushed result's subtype
either. The VM's own F_ADD_EQ (+=) already got this right for
comparison.

Also cleared f_or()'s result subtype to match its sibling f_and() --
same root cause (a plain `|` on an undefined left operand reused that
operand's stack slot, carrying its subtype into the real result).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTPLudxra3u1andNyWJEQG
This commit is contained in:
Claude 2026-07-14 07:16:00 +00:00 committed by Yucong Sun
parent cdb1e9e923
commit 7a47120207
2 changed files with 73 additions and 0 deletions

View file

@ -37,6 +37,7 @@ void f_and_eq() {
error("Bad right type to &=\n");
}
sp->u.number = argp->u.number &= sp->u.number;
argp->subtype = 0;
sp->subtype = 0;
}
@ -54,6 +55,7 @@ void f_div_eq() {
} else {
argp->u.number /= sp->u.number;
}
argp->subtype = 0;
sp->u.number = argp->u.number;
sp->subtype = 0;
break;
@ -380,6 +382,7 @@ void f_lsh_eq() {
error("Illegal shift amount to <<=.\n");
}
sp->u.number = argp->u.number <<= sp->u.number;
argp->subtype = 0;
sp->subtype = 0;
}
@ -400,6 +403,7 @@ void f_mod_eq() {
} else {
argp->u.number %= sp->u.number;
}
argp->subtype = 0;
sp->u.number = argp->u.number;
sp->subtype = 0;
}
@ -410,6 +414,7 @@ void f_mult_eq() {
switch (argp->type | sp->type) {
case T_NUMBER: {
sp->u.number = argp->u.number *= sp->u.number;
argp->subtype = 0;
sp->subtype = 0;
break;
}
@ -554,6 +559,7 @@ void f_or() {
CHECK_TYPES(sp, T_NUMBER, 2, F_OR);
sp--;
sp->u.number |= (sp + 1)->u.number;
sp->subtype = 0;
}
void f_or_eq() {
@ -573,6 +579,7 @@ void f_or_eq() {
error("Bad right type to |=\n");
}
sp->u.number = argp->u.number |= sp->u.number;
argp->subtype = 0;
sp->subtype = 0;
}
@ -906,6 +913,7 @@ void f_rsh_eq() {
error("Illegal shift amount to >>=.\n");
}
sp->u.number = argp->u.number >>= sp->u.number;
argp->subtype = 0;
sp->subtype = 0;
}
@ -915,6 +923,7 @@ void f_sub_eq() {
switch (argp->type | sp->type) {
case T_NUMBER: {
sp->u.number = argp->u.number -= sp->u.number;
argp->subtype = 0;
sp->subtype = 0;
break;
}
@ -1213,6 +1222,8 @@ void f_xor_eq() {
error("Bad right type to ^=\n");
}
sp->u.number = argp->u.number ^= sp->u.number;
argp->subtype = 0;
sp->subtype = 0;
}
void f_function_constructor() {

View file

@ -0,0 +1,62 @@
// Compound-assign efuns (src/packages/ops/ops.cc) for &= /= %= *= |= ^=
// <<= >>= -= modify an lvalue's u.number in place but used to leave its
// subtype untouched. "undefined" (the value read back from a missing
// mapping key) is represented as {T_NUMBER, subtype=T_UNDEFINED, 0} --
// so a variable that started out undefined kept reporting undefinedp()
// == 1 after a real arithmetic compound-assign computed a genuine
// result. (Plain += already got this right, in the VM's F_ADD_EQ.)
void do_tests() {
mapping m = ([ "k": 0 ]);
mixed u;
u = m["missing"];
ASSERT_EQ(1, undefinedp(u));
u &= 5;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(0, u);
u = m["missing"];
u |= 5;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(5, u);
u = m["missing"];
u ^= 5;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(5, u);
u = m["missing"];
u -= 5;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(-5, u);
u = m["missing"];
u *= 5;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(0, u);
u = m["missing"];
u /= 5;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(0, u);
u = m["missing"];
u %= 5;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(0, u);
u = m["missing"];
u <<= 3;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(0, u);
u = m["missing"];
u >>= 3;
ASSERT_EQ(0, undefinedp(u));
ASSERT_EQ(0, u);
// Plain (non-assign) `|` on an undefined operand must not carry the
// "undefined" subtype into a real computed result either.
u = m["missing"];
ASSERT_EQ(0, undefinedp(u | 5));
}