Skip to content

Commit 7555e10

Browse files
committed
Make OP_MUL_ASSIGN in rvalue context a runtime error. Also fix want_result semantics
1 parent 90c33d3 commit 7555e10

File tree

5 files changed

+86
-14
lines changed

5 files changed

+86
-14
lines changed

Compiler/src/LSLCompiler.cpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,8 +1042,6 @@ bool LuauVisitor::visit(LSLUnaryExpression *un_expr)
10421042
}
10431043

10441044
// Everything below here should be a (post|pre)(incr|decr) operator operating on an lvalue.
1045-
auto *parent = un_expr->getParent();
1046-
10471045
LUAU_ASSERT(un_expr->getChildExpr()->getNodeSubType() == NODE_LVALUE_EXPRESSION);
10481046
auto *lvalue = (LSLLValueExpression *)un_expr->getChildExpr();
10491047
auto *lvalue_sym = lvalue->getSymbol();
@@ -1069,7 +1067,7 @@ bool LuauVisitor::visit(LSLUnaryExpression *un_expr)
10691067
// Especially important of mutating unary operators like `++bar` where
10701068
// they may be their own statement, and we don't necessarily care about
10711069
// pushing the result.
1072-
const bool want_result = (parent && parent->getNodeType() != NODE_STATEMENT);
1070+
const bool want_result = un_expr->getResultNeeded();
10731071

10741072
// Pre-allocated by buildFunction() to ensure index < 256 for LOP_ADDK/SUBK
10751073
const auto one_const_idx = addConstantUnder(lvalue->getType()->getOneValue(), UINT8_MAX);
@@ -1146,12 +1144,11 @@ bool LuauVisitor::visit(LSLBinaryExpression* bin_expr)
11461144
// assignment is very special
11471145
if (op == '=')
11481146
{
1149-
auto *parent = bin_expr->getParent();
11501147
// Basically, do we have to move this result for whoever asked for it.
11511148
// Often `=` is used as if it were a statement, but it's also an expression
11521149
// in LSL, which means that it can be used like `foo = bar = baz = 1`;
11531150
// Only MOVE the result to the target register if we'll actually end up using it.
1154-
const bool want_result = (parent && parent->getNodeType() != NODE_STATEMENT);
1151+
const bool want_result = bin_expr->getResultNeeded();
11551152

11561153
// The left-hand side of `=` _must_ be an lvalue
11571154
LUAU_ASSERT(lhs->getNodeSubType() == NODE_LVALUE_EXPRESSION);
@@ -1160,7 +1157,7 @@ bool LuauVisitor::visit(LSLBinaryExpression* bin_expr)
11601157

11611158
uint8_t source_reg;
11621159
bool have_truncated_float = false;
1163-
if (auto *member = lval->getMember())
1160+
if (lval->getMember() != nullptr)
11641161
{
11651162
// Evaluate this first, it may be re-used if we need to use the result of the
11661163
// assignment expression.
@@ -1223,8 +1220,7 @@ bool LuauVisitor::visit(LSLBinaryExpression* bin_expr)
12231220
// An evil, demonic operator, only exists for int *= float
12241221
if (op == OP_MUL_ASSIGN)
12251222
{
1226-
auto *parent = bin_expr->getParent();
1227-
const bool want_result = (parent && parent->getNodeType() != NODE_STATEMENT);
1223+
const bool want_result = bin_expr->getResultNeeded();
12281224

12291225
// assignment is special
12301226
LUAU_ASSERT(lhs->getNodeSubType() == NODE_LVALUE_EXPRESSION);
@@ -1261,11 +1257,14 @@ bool LuauVisitor::visit(LSLBinaryExpression* bin_expr)
12611257
mBuilder->emitABC(LOP_MOVE, source_reg, func_reg, 0);
12621258

12631259
// Something actually wants to capture the result of the `*=`,
1264-
// pass it along.
1260+
// but that's not allowed in LSL. Throw a runtime error.
12651261
if (want_result)
12661262
{
1267-
mBuilder->emitABC(LOP_LSL_DOUBLE2FLOAT, floaty_reg, floaty_reg, 0);
1268-
maybeMove(expected_target, floaty_reg);
1263+
auto err_func_reg = allocReg(bin_expr);
1264+
pushImport(err_func_reg, "error");
1265+
const auto msg_const_idx = addConstantString(sref("cannot take result of integer *= float"), INT16_MAX);
1266+
mBuilder->emitAD(LOP_LOADK, allocReg(bin_expr), msg_const_idx);
1267+
mBuilder->emitABC(LOP_CALL, err_func_reg, 1 + 1, 0 + 1);
12691268
}
12701269
return false;
12711270
}

tests/LSL.test.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ static void checkStatus(lua_State *L, int status)
8787
}
8888
}
8989

90-
static StateRef runConformance(const char* name, int32_t (*yield)(lua_State* L) = nullptr, void (*setup)(lua_State* L) = nullptr, lua_CompileOptions* options = nullptr)
90+
static StateRef runConformance(const char* name, int32_t (*yield)(lua_State* L) = nullptr, void (*setup)(lua_State* L) = nullptr, lua_CompileOptions* options = nullptr, const char* expectedError = nullptr)
9191
{
9292
#ifdef LUAU_CONFORMANCE_SOURCE_DIR
9393
std::string path = LUAU_CONFORMANCE_SOURCE_DIR;
@@ -209,11 +209,26 @@ static StateRef runConformance(const char* name, int32_t (*yield)(lua_State* L)
209209

210210
if (status == 0)
211211
{
212-
// Do nothing for now.
212+
// Script succeeded - fail if we expected an error
213+
if (expectedError)
214+
{
215+
FAIL("Expected error '" << expectedError << "' but script succeeded");
216+
}
213217
}
214218
else
215219
{
216-
checkStatus(Lhandler, status);
220+
// Script errored
221+
if (expectedError)
222+
{
223+
// Check if the error matches what we expected
224+
REQUIRE(lua_isstring(Lhandler, -1));
225+
std::string error_msg = lua_tostring(Lhandler, -1);
226+
CHECK(error_msg.find(expectedError) != std::string::npos);
227+
}
228+
else
229+
{
230+
checkStatus(Lhandler, status);
231+
}
217232
}
218233

219234
return globalState;
@@ -1047,6 +1062,21 @@ static std::string mono_to_upper_string(const std::string &str)
10471062
return to_upper_mono(str.c_str(), str.length());
10481063
}
10491064

1065+
TEST_CASE("Integer MulAssign Float Result Error")
1066+
{
1067+
runConformance("mul_assign_error.lsl", nullptr, nullptr, nullptr, "cannot take result of integer *= float");
1068+
}
1069+
1070+
TEST_CASE("Integer MulAssign Float Valid Contexts")
1071+
{
1072+
runConformance("mul_assign_valid.lsl");
1073+
}
1074+
1075+
TEST_CASE("Integer Division By Zero Error")
1076+
{
1077+
runConformance("div_by_zero.lsl", nullptr, nullptr, nullptr, "Math error");
1078+
}
1079+
10501080
TEST_CASE("Mono Strings")
10511081
{
10521082
// upper end of BMP without case change (3-byte)

tests/conformance/div_by_zero.lsl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Test that integer division by zero throws a runtime error
2+
default {
3+
state_entry() {
4+
integer a = 1;
5+
integer b = 0;
6+
integer c = a / b; // This should error
7+
print((string)c);
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Test that using the result of `integer *= float` throws a runtime error
2+
default {
3+
state_entry() {
4+
integer a = 1;
5+
float b = 7.0;
6+
float t = a *= b; // This should error
7+
print((string)t);
8+
}
9+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Tests all the cases where `<int> MUL_ASSIGN <float>` won't cause an errors.
2+
// Essentially, all the cases where it's not treated as an rvalue.
3+
// The error case is in `mul_assign_error.lsl`.
4+
default {
5+
state_entry() {
6+
integer a;
7+
float b = 2.0;
8+
9+
// Expression statement
10+
a = 3;
11+
a *= b;
12+
13+
// For loop increment
14+
a = 1;
15+
integer i;
16+
for (i = 0; i < 3; a *= b) {
17+
++i;
18+
}
19+
20+
// For loop init
21+
a = 5;
22+
for (a *= b; i < 6; ++i) {
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)