From 1149d86c5804c13a516452021d560ea69a06e9bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:18:34 +0000 Subject: [PATCH 1/5] Fix math max/min precision and validation Co-authored-by: esrrhs <4083816+esrrhs@users.noreply.github.com> --- src/native/math/native_math.cpp | 56 +++++++++++++++---- test/lua/math/test_math_max_min_precision.lua | 27 +++++++++ test/test_math.cpp | 20 +++++++ 3 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 test/lua/math/test_math_max_min_precision.lua diff --git a/src/native/math/native_math.cpp b/src/native/math/native_math.cpp index 036c91b..1f4952a 100644 --- a/src/native/math/native_math.cpp +++ b/src/native/math/native_math.cpp @@ -18,6 +18,46 @@ static CVar PushNumInt(State *state, double d) { return inter::NativeToFakeluaFloat(state, d); } +static int CompareIntDouble(int64_t integer, double floating) { + constexpr double kInt64LowerBound = -9223372036854775808.0; + constexpr double kInt64UpperBound = 9223372036854775808.0; + if (std::isnan(floating)) return 0; + if (floating >= kInt64UpperBound) return -1; + if (floating < kInt64LowerBound) return 1; + + const int64_t truncated = static_cast(floating); + if (integer < truncated) return -1; + if (integer > truncated) return 1; + const double truncated_as_double = static_cast(truncated); + if (floating > truncated_as_double) return -1; + if (floating < truncated_as_double) return 1; + return 0; +} + +static int CompareNumbers(CVar lhs, double lhs_value, CVar rhs, double rhs_value) { + const int int_type = static_cast(VarType::Int); + if (lhs.type_ == int_type && rhs.type_ == int_type) { + if (lhs.data_.i < rhs.data_.i) return -1; + if (lhs.data_.i > rhs.data_.i) return 1; + return 0; + } + if (lhs.type_ == int_type) return CompareIntDouble(lhs.data_.i, rhs_value); + if (rhs.type_ == int_type) return -CompareIntDouble(rhs.data_.i, lhs_value); + if (lhs_value < rhs_value) return -1; + if (lhs_value > rhs_value) return 1; + return 0; +} + +static double CheckMathNumberArg(const CVar &arg, int argno, const char *fname) { + CheckNumberArg(arg, argno, fname); + const double value = inter::CVarToNumber(arg, std::numeric_limits::quiet_NaN()); + if ((arg.type_ == static_cast(VarType::String) || arg.type_ == static_cast(VarType::StringId)) && + std::isnan(value)) { + ThrowBadArgument(argno, fname, "number expected"); + } + return value; +} + // Use shared CheckNumberArg from native_common.h void RegisterMathLibraryApi(State *s) { @@ -63,13 +103,11 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.max", 1, true, [](State *state, CVar *args, int n) -> CVar { if (n < 1) return inter::NativeToFakeluaNil(state); CVar max_cvar = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(max_cvar, 1, "math.max"); - double max_v = inter::CVarToNumber(max_cvar, -std::numeric_limits::infinity()); + double max_v = CheckMathNumberArg(max_cvar, 1, "math.max"); for (int i = 1; i < n; ++i) { CVar arg_i = inter::GetNativeArg(state, args, n, i); - CheckNumberArg(arg_i, i + 1, "math.max"); - double v_i = inter::CVarToNumber(arg_i, -std::numeric_limits::infinity()); - if (v_i > max_v) { + double v_i = CheckMathNumberArg(arg_i, i + 1, "math.max"); + if (CompareNumbers(max_cvar, max_v, arg_i, v_i) < 0) { max_v = v_i; max_cvar = arg_i; } @@ -87,13 +125,11 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.min", 1, true, [](State *state, CVar *args, int n) -> CVar { if (n < 1) return inter::NativeToFakeluaNil(state); CVar min_cvar = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(min_cvar, 1, "math.min"); - double min_v = inter::CVarToNumber(min_cvar, std::numeric_limits::infinity()); + double min_v = CheckMathNumberArg(min_cvar, 1, "math.min"); for (int i = 1; i < n; ++i) { CVar arg_i = inter::GetNativeArg(state, args, n, i); - CheckNumberArg(arg_i, i + 1, "math.min"); - double v_i = inter::CVarToNumber(arg_i, std::numeric_limits::infinity()); - if (v_i < min_v) { + double v_i = CheckMathNumberArg(arg_i, i + 1, "math.min"); + if (CompareNumbers(min_cvar, min_v, arg_i, v_i) > 0) { min_v = v_i; min_cvar = arg_i; } diff --git a/test/lua/math/test_math_max_min_precision.lua b/test/lua/math/test_math_max_min_precision.lua new file mode 100644 index 0000000..33966e6 --- /dev/null +++ b/test/lua/math/test_math_max_min_precision.lua @@ -0,0 +1,27 @@ +function test_math_max_min_precision() + local positive_int = 9007199254740993 + local positive_float = 9007199254740992.0 + if math.max(positive_float, positive_int) ~= positive_int then return 1 end + if math.type(math.max(positive_float, positive_int)) ~= "integer" then return 2 end + if math.min(positive_int, positive_float) ~= positive_float then return 3 end + if math.type(math.min(positive_int, positive_float)) ~= "float" then return 4 end + + local negative_int = -9007199254740993 + local negative_float = -9007199254740992.0 + if math.max(negative_int, negative_float) ~= negative_float then return 5 end + if math.type(math.max(negative_int, negative_float)) ~= "float" then return 6 end + if math.min(negative_float, negative_int) ~= negative_int then return 7 end + if math.type(math.min(negative_float, negative_int)) ~= "integer" then return 8 end + + return 5000 +end + +function test_math_max_invalid_string() + local ok = pcall(math.max, "not a number") + return ok and 0 or 5000 +end + +function test_math_min_invalid_string() + local ok = pcall(math.min, 1, "not a number") + return ok and 0 or 5000 +end diff --git a/test/test_math.cpp b/test/test_math.cpp index 080a84c..105766c 100644 --- a/test/test_math.cpp +++ b/test/test_math.cpp @@ -406,6 +406,26 @@ TEST(test_math, test_math_max_min) { FakeluaDeleteState(s); } +TEST(test_math, test_math_max_min_precision) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + + CompileFile(s, "./math/test_math_max_min_precision.lua", config); + for (auto jit_type: AllJitTypes()) { + SCOPED_TRACE(::testing::Message() << "jit=" << JitTypeName(jit_type)); + int64_t res = 0; + Call(s, jit_type, "test_math_max_min_precision", res); + EXPECT_EQ(res, 5000); + Call(s, jit_type, "test_math_max_invalid_string", res); + EXPECT_EQ(res, 5000); + Call(s, jit_type, "test_math_min_invalid_string", res); + EXPECT_EQ(res, 5000); + } + + FakeluaDeleteState(s); +} + TEST(test_math, test_math_constants_full) { State *s = FakeluaNewState(); ASSERT_NE(s, nullptr); From 8dbb70d20b5bb13769f9c885fb0ee46207221e06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:30:54 +0000 Subject: [PATCH 2/5] Fix remaining math edge cases Co-authored-by: esrrhs <4083816+esrrhs@users.noreply.github.com> --- src/compile/c_gen.cpp | 2 +- src/compile/c_runtime_header.h | 5 + src/native/math/native_math.cpp | 121 +++++++----------- test/lua/math/test_math_max_min_precision.lua | 23 ++++ test/test_math.cpp | 2 + 5 files changed, 80 insertions(+), 73 deletions(-) diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp index f5f373b..a6a4506 100644 --- a/src/compile/c_gen.cpp +++ b/src/compile/c_gen.cpp @@ -3603,7 +3603,7 @@ std::string CGen::TryCompileBuiltinMathCall(const std::shared_ptr 2147483647LL) exponent = 2147483647LL; + if (exponent < (-2147483647LL - 1)) exponent = (-2147483647LL - 1); + return ldexp(value, (int)exponent); +} // 整数 for 步进:用无符号加法探测有符号溢出。溢出返回 0 且不改 *ctrl //(避免 maxinteger++ 变成 mininteger 后死循环)。continue 仍走 for 的 incr 子句。 diff --git a/src/native/math/native_math.cpp b/src/native/math/native_math.cpp index 1f4952a..8bf2e8b 100644 --- a/src/native/math/native_math.cpp +++ b/src/native/math/native_math.cpp @@ -3,6 +3,7 @@ #include "util/number_util.h" #include "var/var.h" #include +#include #include #include #include @@ -65,7 +66,7 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.abs", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.abs"); + CheckMathNumberArg(a0, 1, "math.abs"); if (a0.type_ == static_cast(VarType::Int)) { int64_t n = a0.data_.i; if (n < 0) n = static_cast(0ull - static_cast(n)); @@ -86,7 +87,7 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.floor", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.floor"); + CheckMathNumberArg(a0, 1, "math.floor"); if (a0.type_ == static_cast(VarType::Int)) return a0; double f = inter::CVarToNumber(a0, std::numeric_limits::quiet_NaN()); return PushNumInt(state, std::floor(f)); @@ -94,7 +95,7 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.ceil", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.ceil"); + CheckMathNumberArg(a0, 1, "math.ceil"); if (a0.type_ == static_cast(VarType::Int)) return a0; double f = inter::CVarToNumber(a0, std::numeric_limits::quiet_NaN()); return PushNumInt(state, std::ceil(f)); @@ -146,28 +147,28 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.sqrt", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.sqrt"); + CheckMathNumberArg(a0, 1, "math.sqrt"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::sqrt(v0)); }); RegisterNativeFunction(s, "math.sin", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.sin"); + CheckMathNumberArg(a0, 1, "math.sin"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::sin(v0)); }); RegisterNativeFunction(s, "math.cos", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.cos"); + CheckMathNumberArg(a0, 1, "math.cos"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::cos(v0)); }); RegisterNativeFunction(s, "math.tan", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.tan"); + CheckMathNumberArg(a0, 1, "math.tan"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::tan(v0)); }); @@ -175,8 +176,8 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.pow", 2, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); CVar a1 = inter::GetNativeArg(state, args, n, 1); - CheckNumberArg(a0, 1, "math.pow"); - CheckNumberArg(a1, 2, "math.pow"); + CheckMathNumberArg(a0, 1, "math.pow"); + CheckMathNumberArg(a1, 2, "math.pow"); double v0 = inter::CVarToNumber(a0, 0.0); double v1 = inter::CVarToNumber(a1, 0.0); return inter::NativeToFakeluaFloat(state, std::pow(v0, v1)); @@ -184,25 +185,25 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.asin", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.asin"); + CheckMathNumberArg(a0, 1, "math.asin"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::asin(v0)); }); RegisterNativeFunction(s, "math.acos", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.acos"); + CheckMathNumberArg(a0, 1, "math.acos"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::acos(v0)); }); RegisterNativeFunction(s, "math.atan", 1, true, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.atan"); + CheckMathNumberArg(a0, 1, "math.atan"); double v0 = inter::CVarToNumber(a0, 0.0); if (n >= 2) { CVar a1 = inter::GetNativeArg(state, args, n, 1); - CheckNumberArg(a1, 2, "math.atan"); + CheckMathNumberArg(a1, 2, "math.atan"); double v1 = inter::CVarToNumber(a1, 0.0); return inter::NativeToFakeluaFloat(state, std::atan2(v0, v1)); } @@ -212,8 +213,8 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.atan2", 2, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); CVar a1 = inter::GetNativeArg(state, args, n, 1); - CheckNumberArg(a0, 1, "math.atan2"); - CheckNumberArg(a1, 2, "math.atan2"); + CheckMathNumberArg(a0, 1, "math.atan2"); + CheckMathNumberArg(a1, 2, "math.atan2"); double v0 = inter::CVarToNumber(a0, 0.0); double v1 = inter::CVarToNumber(a1, 0.0); return inter::NativeToFakeluaFloat(state, std::atan2(v0, v1)); @@ -222,8 +223,8 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.copysign", 2, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); CVar a1 = inter::GetNativeArg(state, args, n, 1); - CheckNumberArg(a0, 1, "math.copysign"); - CheckNumberArg(a1, 2, "math.copysign"); + CheckMathNumberArg(a0, 1, "math.copysign"); + CheckMathNumberArg(a1, 2, "math.copysign"); double v0 = inter::CVarToNumber(a0, 0.0); double v1 = inter::CVarToNumber(a1, 0.0); return inter::NativeToFakeluaFloat(state, std::copysign(v0, v1)); @@ -231,18 +232,18 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.exp", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.exp"); + CheckMathNumberArg(a0, 1, "math.exp"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::exp(v0)); }); RegisterNativeFunction(s, "math.log", 1, true, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.log"); + CheckMathNumberArg(a0, 1, "math.log"); double v0 = inter::CVarToNumber(a0, 0.0); if (n >= 2) { CVar a1 = inter::GetNativeArg(state, args, n, 1); - CheckNumberArg(a1, 2, "math.log"); + CheckMathNumberArg(a1, 2, "math.log"); double base = inter::CVarToNumber(a1, 1.0); return inter::NativeToFakeluaFloat(state, std::log(v0) / std::log(base)); } @@ -251,28 +252,28 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.log10", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.log10"); + CheckMathNumberArg(a0, 1, "math.log10"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::log10(v0)); }); RegisterNativeFunction(s, "math.sinh", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.sinh"); + CheckMathNumberArg(a0, 1, "math.sinh"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::sinh(v0)); }); RegisterNativeFunction(s, "math.cosh", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.cosh"); + CheckMathNumberArg(a0, 1, "math.cosh"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::cosh(v0)); }); RegisterNativeFunction(s, "math.tanh", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.tanh"); + CheckMathNumberArg(a0, 1, "math.tanh"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::tanh(v0)); }); @@ -280,8 +281,8 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.fmod", 2, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); CVar a1 = inter::GetNativeArg(state, args, n, 1); - CheckNumberArg(a0, 1, "math.fmod"); - CheckNumberArg(a1, 2, "math.fmod"); + CheckMathNumberArg(a0, 1, "math.fmod"); + CheckMathNumberArg(a1, 2, "math.fmod"); // Lua 5.4:两个参数都是 integer 时走整数取模(C 的截断向零语义) if (a0.type_ == static_cast(VarType::Int) && a1.type_ == static_cast(VarType::Int)) { const int64_t b = a1.data_.i; @@ -303,11 +304,12 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.ldexp", 2, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); CVar a1 = inter::GetNativeArg(state, args, n, 1); - CheckNumberArg(a0, 1, "math.ldexp"); + CheckMathNumberArg(a0, 1, "math.ldexp"); double v0 = inter::CVarToNumber(a0, 0.0); // 指数必须是整数;2^63 经 CVarToInteger 会变成 0(ldexp(x,0)),属错误结果。 int64_t exp = CheckIntegerArg(a1, 2, "math.ldexp"); - return inter::NativeToFakeluaFloat(state, std::ldexp(v0, static_cast(exp))); + const int exp_int = static_cast(std::clamp(exp, std::numeric_limits::min(), std::numeric_limits::max())); + return inter::NativeToFakeluaFloat(state, std::ldexp(v0, exp_int)); }); RegisterNativeFunction(s, "math.type", 1, false, [](State *state, CVar *args, int n) -> CVar { @@ -340,14 +342,14 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.deg", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.deg"); + CheckMathNumberArg(a0, 1, "math.deg"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, v0 * (180.0 / 3.14159265358979323846)); }); RegisterNativeFunction(s, "math.rad", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.rad"); + CheckMathNumberArg(a0, 1, "math.rad"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, v0 * (3.14159265358979323846 / 180.0)); }); @@ -359,16 +361,7 @@ void RegisterMathLibraryApi(State *s) { } else if (n == 1) { CVar a0 = inter::GetNativeArg(state, args, n, 0); // 标准 Lua:math.random 要求 number 参数,Bool/Table/String/Nil 不合法 - if (a0.type_ != static_cast(VarType::Int) && a0.type_ != static_cast(VarType::Float)) { - ThrowFakeluaException("bad argument #1 to 'math.random' (number expected)"); - } - int64_t u = 0; - if (a0.type_ == static_cast(VarType::Int)) { - u = a0.data_.i; - } else if (!DoubleFitsInt64(a0.data_.f, &u)) { - // 2^63 / 1.5 等:CVarToInteger 会截断或回落到 0(变成 random(0)),属错误结果。 - ThrowBadArgument(1, "math.random", "number has no integer representation"); - } + const int64_t u = CheckIntegerArg(a0, 1, "math.random"); if (u == 0) { // Lua 5.4:math.random(0) 特殊情况,返回全范围随机整数。 // 拼 4 个 rand 填满 64 位,避免 Windows 上 RAND_MAX=32767 导致位数不足。 @@ -385,24 +378,8 @@ void RegisterMathLibraryApi(State *s) { CVar a0 = inter::GetNativeArg(state, args, n, 0); CVar a1 = inter::GetNativeArg(state, args, n, 1); // 标准 Lua:math.random 要求 number 参数,Bool/Table/String/Nil 不合法 - if (a0.type_ != static_cast(VarType::Int) && a0.type_ != static_cast(VarType::Float)) { - ThrowFakeluaException("bad argument #1 to 'math.random' (number expected)"); - } - if (a1.type_ != static_cast(VarType::Int) && a1.type_ != static_cast(VarType::Float)) { - ThrowFakeluaException("bad argument #2 to 'math.random' (number expected)"); - } - int64_t l = 0; - int64_t u = 0; - if (a0.type_ == static_cast(VarType::Int)) { - l = a0.data_.i; - } else if (!DoubleFitsInt64(a0.data_.f, &l)) { - ThrowBadArgument(1, "math.random", "number has no integer representation"); - } - if (a1.type_ == static_cast(VarType::Int)) { - u = a1.data_.i; - } else if (!DoubleFitsInt64(a1.data_.f, &u)) { - ThrowBadArgument(2, "math.random", "number has no integer representation"); - } + const int64_t l = CheckIntegerArg(a0, 1, "math.random"); + const int64_t u = CheckIntegerArg(a1, 2, "math.random"); if (l > u) { // Lua 5.4:空区间报 "interval is empty" ThrowFakeluaException("bad argument #1 to 'math.random' (interval is empty)"); @@ -412,7 +389,10 @@ void RegisterMathLibraryApi(State *s) { // 与 Lua 5.4 project() 思路一致:range 最大到 2^64(此时回绕为 0), // 回绕仅发生在 l=INT64_MIN, u=INT64_MAX 的极端情况,此时直接返回 l。 uint64_t range = static_cast(u) - static_cast(l) + 1; - if (range == 0) return inter::NativeToFakeluaInt(state, l);// 区间覆盖整个 int64,任意值都可 + if (range == 0) { + const uint64_t rv = (static_cast(std::rand()) << 32) | static_cast(std::rand()); + return inter::NativeToFakeluaInt(state, std::bit_cast(rv)); + } uint64_t rv = (static_cast(std::rand()) << 32) | static_cast(std::rand()); int64_t r = static_cast(static_cast(l) + (rv % range)); return inter::NativeToFakeluaInt(state, r); @@ -424,10 +404,7 @@ void RegisterMathLibraryApi(State *s) { if (n >= 1) { CVar a0 = inter::GetNativeArg(state, args, n, 0); // 标准 Lua:math.randomseed 要求 number 参数,Bool/Table/String/Nil 不合法 - if (a0.type_ != static_cast(VarType::Int) && a0.type_ != static_cast(VarType::Float)) { - ThrowFakeluaException("bad argument #1 to 'math.randomseed' (number expected)"); - } - seed = static_cast(inter::CVarToInteger(a0, seed)); + seed = static_cast(CheckIntegerArg(a0, 1, "math.randomseed")); } std::srand(seed); return inter::NativeToFakeluaNil(state); @@ -435,7 +412,7 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.modf", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.modf"); + CheckMathNumberArg(a0, 1, "math.modf"); if (a0.type_ == static_cast(VarType::Int)) { CVar multi = inter::AllocMultiCVar(state, 2); inter::SetMultiCVarElement(multi, 0, a0); @@ -455,7 +432,7 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.frexp", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.frexp"); + CheckMathNumberArg(a0, 1, "math.frexp"); double val = inter::CVarToNumber(a0, 0.0); int exp_val = 0; double frac = std::frexp(val, &exp_val); @@ -467,28 +444,28 @@ void RegisterMathLibraryApi(State *s) { RegisterNativeFunction(s, "math.erf", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.erf"); + CheckMathNumberArg(a0, 1, "math.erf"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::erf(v0)); }); RegisterNativeFunction(s, "math.erfc", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.erfc"); + CheckMathNumberArg(a0, 1, "math.erfc"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::erfc(v0)); }); RegisterNativeFunction(s, "math.gamma", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.gamma"); + CheckMathNumberArg(a0, 1, "math.gamma"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::tgamma(v0)); }); RegisterNativeFunction(s, "math.lgamma", 1, false, [](State *state, CVar *args, int n) -> CVar { CVar a0 = inter::GetNativeArg(state, args, n, 0); - CheckNumberArg(a0, 1, "math.lgamma"); + CheckMathNumberArg(a0, 1, "math.lgamma"); double v0 = inter::CVarToNumber(a0, 0.0); return inter::NativeToFakeluaFloat(state, std::lgamma(v0)); }); @@ -501,9 +478,9 @@ void RegisterMathLibraryApi(State *s) { CVar ax = inter::GetNativeArg(state, args, n, 0); CVar alo = inter::GetNativeArg(state, args, n, 1); CVar ahi = inter::GetNativeArg(state, args, n, 2); - CheckNumberArg(ax, 1, "math.clamp"); - CheckNumberArg(alo, 2, "math.clamp"); - CheckNumberArg(ahi, 3, "math.clamp"); + CheckMathNumberArg(ax, 1, "math.clamp"); + CheckMathNumberArg(alo, 2, "math.clamp"); + CheckMathNumberArg(ahi, 3, "math.clamp"); if (ax.type_ == static_cast(VarType::Int) && alo.type_ == static_cast(VarType::Int) && ahi.type_ == static_cast(VarType::Int)) { if (alo.data_.i > ahi.data_.i) { diff --git a/test/lua/math/test_math_max_min_precision.lua b/test/lua/math/test_math_max_min_precision.lua index 33966e6..eaa2672 100644 --- a/test/lua/math/test_math_max_min_precision.lua +++ b/test/lua/math/test_math_max_min_precision.lua @@ -25,3 +25,26 @@ function test_math_min_invalid_string() local ok = pcall(math.min, 1, "not a number") return ok and 0 or 5000 end + +function test_math_other_edge_cases() + local ok = pcall(function() return math.sqrt("not a number") end) + if ok then return 11 end + + ok = pcall(function() math.randomseed(1e308) end) + if ok then return 12 end + ok = pcall(function() math.randomseed(1.5) end) + if ok then return 13 end + if not pcall(function() math.randomseed("2") end) then return 14 end + if not pcall(function() math.random("2") end) then return 15 end + + if math.ldexp(1.0, 2147483648) ~= math.huge then return 16 end + if math.ldexp(1.0, -2147483649) ~= 0.0 then return 17 end + + math.randomseed(42) + for _ = 1, 8 do + if math.random(math.mininteger, math.maxinteger) ~= math.mininteger then + return 5000 + end + end + return 18 +end diff --git a/test/test_math.cpp b/test/test_math.cpp index 105766c..727a00e 100644 --- a/test/test_math.cpp +++ b/test/test_math.cpp @@ -421,6 +421,8 @@ TEST(test_math, test_math_max_min_precision) { EXPECT_EQ(res, 5000); Call(s, jit_type, "test_math_min_invalid_string", res); EXPECT_EQ(res, 5000); + Call(s, jit_type, "test_math_other_edge_cases", res); + EXPECT_EQ(res, 5000); } FakeluaDeleteState(s); From 8609f76baf47aee3979364eef61fefdb5b45ecae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:33:01 +0000 Subject: [PATCH 3/5] Fix regression expectations for integer validation Co-authored-by: esrrhs <4083816+esrrhs@users.noreply.github.com> --- test/lua/math/test_math_max_min_precision.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/lua/math/test_math_max_min_precision.lua b/test/lua/math/test_math_max_min_precision.lua index eaa2672..223928a 100644 --- a/test/lua/math/test_math_max_min_precision.lua +++ b/test/lua/math/test_math_max_min_precision.lua @@ -42,9 +42,9 @@ function test_math_other_edge_cases() math.randomseed(42) for _ = 1, 8 do - if math.random(math.mininteger, math.maxinteger) ~= math.mininteger then - return 5000 + if math.random(math.mininteger, math.maxinteger) == math.mininteger then + return 18 end end - return 18 + return 5000 end From 6578642a5f8214655cf17491d6cabe21d71771dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:37:56 +0000 Subject: [PATCH 4/5] Fix JIT random edge cases and regressions Co-authored-by: esrrhs <4083816+esrrhs@users.noreply.github.com> --- src/compile/c_gen.cpp | 14 ++++++++----- src/compile/c_runtime_header.h | 8 +++++++ src/native/math/native_math.cpp | 15 ++++++++----- test/lua/math/test_math_jit_guard.lua | 19 ++++++++++++++--- test/lua/math/test_math_max_min_precision.lua | 21 +++++++------------ test/test_math.cpp | 20 +++++++++--------- 6 files changed, 61 insertions(+), 36 deletions(-) diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp index a6a4506..6f09c51 100644 --- a/src/compile/c_gen.cpp +++ b/src/compile/c_gen.cpp @@ -3513,6 +3513,7 @@ std::string CGen::TryCompileBuiltinMathCall(const std::shared_ptr 全范围随机整数(拼 4 个 rand 填满 64 位,跨平台一致) // random(<0) -> 抛异常 "interval is empty" @@ -3693,11 +3695,12 @@ std::string CGen::TryCompileBuiltinMathCall(const std::shared_ptr u -> 抛异常 "interval is empty" // l == u -> 返回 l @@ -3731,9 +3734,10 @@ std::string CGen::TryCompileBuiltinMathCall(const std::shared_ptr(std::rand()) & 0x7fff); + } + return std::bit_cast(value); +} + // Use shared CheckNumberArg from native_common.h void RegisterMathLibraryApi(State *s) { @@ -364,9 +372,7 @@ void RegisterMathLibraryApi(State *s) { const int64_t u = CheckIntegerArg(a0, 1, "math.random"); if (u == 0) { // Lua 5.4:math.random(0) 特殊情况,返回全范围随机整数。 - // 拼 4 个 rand 填满 64 位,避免 Windows 上 RAND_MAX=32767 导致位数不足。 - uint64_t rv = (static_cast(std::rand()) << 48) | (static_cast(std::rand()) << 32) | (static_cast(std::rand()) << 16) | static_cast(std::rand()); - return inter::NativeToFakeluaInt(state, static_cast(rv)); + return inter::NativeToFakeluaInt(state, RandomInt64()); } if (u < 0) { // Lua 5.4:math.random(负数) 报 "interval is empty" @@ -390,8 +396,7 @@ void RegisterMathLibraryApi(State *s) { // 回绕仅发生在 l=INT64_MIN, u=INT64_MAX 的极端情况,此时直接返回 l。 uint64_t range = static_cast(u) - static_cast(l) + 1; if (range == 0) { - const uint64_t rv = (static_cast(std::rand()) << 32) | static_cast(std::rand()); - return inter::NativeToFakeluaInt(state, std::bit_cast(rv)); + return inter::NativeToFakeluaInt(state, RandomInt64()); } uint64_t rv = (static_cast(std::rand()) << 32) | static_cast(std::rand()); int64_t r = static_cast(static_cast(l) + (rv % range)); diff --git a/test/lua/math/test_math_jit_guard.lua b/test/lua/math/test_math_jit_guard.lua index bb7bad3..27f92bc 100644 --- a/test/lua/math/test_math_jit_guard.lua +++ b/test/lua/math/test_math_jit_guard.lua @@ -18,10 +18,23 @@ function test_math_modf_bad_arg() math.modf({}) end --- NaN / 2^63 以前 JIT 会 (unsigned)float,属 UB。现在走 native,不得崩。 +function test_math_sqrt_bad_string() + math.sqrt("not a number") +end + +function test_math_randomseed_out_of_range() + math.randomseed(1e308) +end + +function test_math_randomseed_fractional() + math.randomseed(1.5) +end + +-- Invalid floating-point seeds must be rejected without an invalid conversion. function test_math_randomseed_nan() math.randomseed(0 / 0) +end + +function test_math_randomseed_2pow63() math.randomseed(2 ^ 63) - local _ = math.random() - return 5000 end diff --git a/test/lua/math/test_math_max_min_precision.lua b/test/lua/math/test_math_max_min_precision.lua index 223928a..039b947 100644 --- a/test/lua/math/test_math_max_min_precision.lua +++ b/test/lua/math/test_math_max_min_precision.lua @@ -17,25 +17,17 @@ function test_math_max_min_precision() end function test_math_max_invalid_string() - local ok = pcall(math.max, "not a number") - return ok and 0 or 5000 + math.max("not a number") end function test_math_min_invalid_string() - local ok = pcall(math.min, 1, "not a number") - return ok and 0 or 5000 + math.min(1, "not a number") end function test_math_other_edge_cases() - local ok = pcall(function() return math.sqrt("not a number") end) - if ok then return 11 end - - ok = pcall(function() math.randomseed(1e308) end) - if ok then return 12 end - ok = pcall(function() math.randomseed(1.5) end) - if ok then return 13 end - if not pcall(function() math.randomseed("2") end) then return 14 end - if not pcall(function() math.random("2") end) then return 15 end + math.randomseed("2") + math.random("2") + math.random("1", "2") if math.ldexp(1.0, 2147483648) ~= math.huge then return 16 end if math.ldexp(1.0, -2147483649) ~= 0.0 then return 17 end @@ -45,6 +37,9 @@ function test_math_other_edge_cases() if math.random(math.mininteger, math.maxinteger) == math.mininteger then return 18 end + if math.random(0) == math.mininteger then + return 19 + end end return 5000 end diff --git a/test/test_math.cpp b/test/test_math.cpp index 727a00e..bb3a97a 100644 --- a/test/test_math.cpp +++ b/test/test_math.cpp @@ -238,6 +238,9 @@ TEST(test_math, test_math_jit_guard) { CallThrow(s, "test_math_fmod_bad_arg", res); CallThrow(s, "test_math_randomseed_bad_table", res); CallThrow(s, "test_math_modf_bad_arg", res); + CallThrow(s, "test_math_sqrt_bad_string", res); + CallThrow(s, "test_math_randomseed_out_of_range", res); + CallThrow(s, "test_math_randomseed_fractional", res); FakeluaDeleteState(s); } @@ -247,12 +250,10 @@ TEST(test_math, test_math_randomseed_nan) { ASSERT_NE(s, nullptr); CompileConfig config; - for (auto jit_type: AllJitTypes()) { - CompileFile(s, "./math/test_math_jit_guard.lua", config); - double res = 0; - Call(s, jit_type, "test_math_randomseed_nan", res); - EXPECT_NEAR(res, 5000, 0.5); - } + CompileFile(s, "./math/test_math_jit_guard.lua", config); + double res = 0; + CallThrow(s, "test_math_randomseed_nan", res); + CallThrow(s, "test_math_randomseed_2pow63", res); FakeluaDeleteState(s); } @@ -417,13 +418,12 @@ TEST(test_math, test_math_max_min_precision) { int64_t res = 0; Call(s, jit_type, "test_math_max_min_precision", res); EXPECT_EQ(res, 5000); - Call(s, jit_type, "test_math_max_invalid_string", res); - EXPECT_EQ(res, 5000); - Call(s, jit_type, "test_math_min_invalid_string", res); - EXPECT_EQ(res, 5000); Call(s, jit_type, "test_math_other_edge_cases", res); EXPECT_EQ(res, 5000); } + int64_t res = 0; + CallThrow(s, "test_math_max_invalid_string", res); + CallThrow(s, "test_math_min_invalid_string", res); FakeluaDeleteState(s); } From 001579930229418495ae9f63a521a87b3e7561be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Oct 2026 02:50:00 +0000 Subject: [PATCH 5/5] Fix malformed math.pow JIT guard Co-authored-by: esrrhs <4083816+esrrhs@users.noreply.github.com> --- src/compile/c_gen.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp index 6f09c51..3166058 100644 --- a/src/compile/c_gen.cpp +++ b/src/compile/c_gen.cpp @@ -3513,7 +3513,6 @@ std::string CGen::TryCompileBuiltinMathCall(const std::shared_ptr