From a308c7bea60838c36645775e3f6cd4acbf119cd7 Mon Sep 17 00:00:00 2001 From: esrrhs Date: Sat, 19 Sep 2026 00:26:59 +0800 Subject: [PATCH 1/2] fix: match Lua unary minus, for-in multi-return, and native range checks Keep minus as a unary op so 1-2 and mininteger stay correct, preserve 1.0 as float, and fail closed on HTTP/INI/YAML/Redis/net bounds. Co-authored-by: Cursor --- src/compile/c_gen.cpp | 109 ++++--- src/compile/c_gen.h | 4 +- src/compile/compile_common.h | 80 +++++ src/compile/flex/scanner.cpp | 276 +++++++++--------- src/compile/flex/scanner.l | 8 +- src/compile/semantic_analysis.cpp | 17 +- src/compile/type_inferencer.cpp | 39 +-- src/interp/codegen.cpp | 86 ++++-- src/jit/vm_function.h | 6 +- src/native/http/native_http.cpp | 12 +- src/native/ini/native_ini.cpp | 15 +- src/native/mysql/native_mysql_pool.cpp | 3 +- src/native/native_common.h | 7 + src/native/net/native_net.cpp | 11 +- src/native/net/net_event.cpp | 39 +++ src/native/redis/native_redis.cpp | 67 ++++- src/native/yaml/native_yaml.cpp | 52 ++++ test/lua/http/test_http.lua | 16 + test/lua/ini/test_ini_basic.lua | 14 + test/lua/jit/test_binop_no_space.lua | 16 + test/lua/jit/test_for_in_factory.lua | 77 +++++ test/lua/jit/test_method_eval_order.lua | 14 + test/lua/jit/test_table_float_key_precise.lua | 9 + test/lua/net/test_net_create_destroy.lua | 16 + test/lua/redis/test_redis.lua | 16 + test/lua/yaml/test_yaml_basic.lua | 10 + test/test_http.cpp | 11 + test/test_ini.cpp | 13 + test/test_jitter.cpp | 42 +++ test/test_net.cpp | 28 ++ test/test_redis.cpp | 22 ++ test/test_state.cpp | 26 ++ test/test_syntax_tree.cpp | 27 +- test/test_yaml.cpp | 13 + 34 files changed, 934 insertions(+), 267 deletions(-) create mode 100644 test/lua/jit/test_binop_no_space.lua create mode 100644 test/lua/jit/test_for_in_factory.lua create mode 100644 test/lua/jit/test_method_eval_order.lua create mode 100644 test/lua/jit/test_table_float_key_precise.lua diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp index 32fa028..5d6dfed 100644 --- a/src/compile/c_gen.cpp +++ b/src/compile/c_gen.cpp @@ -142,17 +142,16 @@ bool CGen::ClassifyLiteralKey(const SyntaxTreeInterfacePtr &exp, LiteralKeyInfo out.repr = exp_node->ExpValue(); return true; } - if (exp_node->GetExpKind() == ExpKind::kNumber) { + { TableKeyKind kind = TableKeyKind::kInt; std::string canonical; int64_t int_value = 0; double float_value = 0; - if (!ClassifyLuaNumberKey(exp_node->ExpValue(), kind, canonical, int_value, float_value)) { - return false; + if (ClassifyConstNumberExp(exp, kind, canonical, int_value, float_value)) { + out.kind = kind; + out.repr = canonical; + return true; } - out.kind = kind; - out.repr = canonical; - return true; } if (exp_node->GetExpKind() == ExpKind::kTrue) { out.kind = TableKeyKind::kBool; @@ -343,9 +342,9 @@ void CGen::EmitSpecAccessorBody(const SpecTypeMetadata &meta, bool is_get) { for (const auto &f: meta.fields) { if (f.key_kind == TableKeyKind::kFloat) { if (is_get) { - Out() << " if (__fval == " << f.float_value << ") { *__finish = true; return s->" << f.c_field_name << "; }\n"; + Out() << " if (__fval == " << FormatCDoubleLiteral(f.float_value) << ") { *__finish = true; return s->" << f.c_field_name << "; }\n"; } else { - Out() << " if (__fval == " << f.float_value << ") { s->" << f.c_field_name << " = v; tbl->spec_vals[" << f_idx << "] = v; tbl->spec_keys[" << f_idx + Out() << " if (__fval == " << FormatCDoubleLiteral(f.float_value) << ") { s->" << f.c_field_name << " = v; tbl->spec_vals[" << f_idx << "] = v; tbl->spec_keys[" << f_idx << "] = k; *__finish = true; return; }\n"; } } @@ -357,9 +356,9 @@ void CGen::EmitSpecAccessorBody(const SpecTypeMetadata &meta, bool is_get) { for (const auto &f: meta.fields) { if (f.key_kind == TableKeyKind::kFloat) { if (is_get) { - Out() << " if ((double)__ival == " << f.float_value << ") { *__finish = true; return s->" << f.c_field_name << "; }\n"; + Out() << " if ((double)__ival == " << FormatCDoubleLiteral(f.float_value) << ") { *__finish = true; return s->" << f.c_field_name << "; }\n"; } else { - Out() << " if ((double)__ival == " << f.float_value << ") { s->" << f.c_field_name << " = v; tbl->spec_vals[" << f_idx << "] = v; tbl->spec_keys[" << f_idx + Out() << " if ((double)__ival == " << FormatCDoubleLiteral(f.float_value) << ") { s->" << f.c_field_name << " = v; tbl->spec_vals[" << f_idx << "] = v; tbl->spec_keys[" << f_idx << "] = k; *__finish = true; return; }\n"; } } @@ -1653,17 +1652,23 @@ void CGen::CompileTypedNumericForLoop(const std::shared_ptr & is_constant_step = true; step_double_val = 1.0; step_int_val = 1; - } else if (const auto step_exp = std::dynamic_pointer_cast(for_stmt->ExpStep()); step_exp && step_exp->GetExpKind() == ExpKind::kNumber) { - is_constant_step = true; - if (loop_type == T_INT) { - step_int_val = ToInteger(step_exp->ExpValue()); - if (step_int_val == 0) { - ThrowError("'for' step is zero", for_stmt->ExpStep()); - } - } else { - step_double_val = (LookupNodeType(step_exp.get()) == T_INT) ? static_cast(ToInteger(step_exp->ExpValue())) : ToFloat(step_exp->ExpValue()); - if (step_double_val == 0.0) { - ThrowError("'for' step is zero", for_stmt->ExpStep()); + } else { + TableKeyKind step_kind = TableKeyKind::kInt; + std::string step_canonical; + int64_t step_ival = 0; + double step_fval = 0; + if (ClassifyConstNumberExp(for_stmt->ExpStep(), step_kind, step_canonical, step_ival, step_fval)) { + is_constant_step = true; + if (loop_type == T_INT) { + step_int_val = (step_kind == TableKeyKind::kInt) ? step_ival : static_cast(step_fval); + if (step_int_val == 0) { + ThrowError("'for' step is zero", for_stmt->ExpStep()); + } + } else { + step_double_val = (step_kind == TableKeyKind::kInt) ? static_cast(step_ival) : step_fval; + if (step_double_val == 0.0) { + ThrowError("'for' step is zero", for_stmt->ExpStep()); + } } } } @@ -2059,7 +2064,7 @@ void CGen::CompileStmtForIn(const SyntaxTreeInterfacePtr &stmt) { const auto &exps = explist_ptr->Exps(); if (exps.size() == 1) { - std::string exp0_compiled = CompileExp(exps[0]); + std::string exp0_compiled = CompileExp(exps[0], true); std::string tmp_exp0 = std::format("flua_fi_exp0_{}", tmp_var_counter_++); func_temp_decls_ << " CVar " << tmp_exp0 << ";\n"; Out() << GenTab() << tmp_exp0 << " = " << exp0_compiled << ";\n"; @@ -2073,13 +2078,26 @@ void CGen::CompileStmtForIn(const SyntaxTreeInterfacePtr &stmt) { Out() << GenTab() << " " << iter_s << " = kNil;\n"; Out() << GenTab() << " " << iter_var << " = kNil;\n"; Out() << GenTab() << "}\n"; + } else if (exps.size() == 2) { + std::string e0 = CompileExp(exps[0], false); + std::string e1 = CompileExp(exps[1], true); + std::string tmp_e1 = std::format("flua_fi_e1_{}", tmp_var_counter_++); + func_temp_decls_ << " CVar " << tmp_e1 << ";\n"; + Out() << GenTab() << iter_f << " = FlUnboxMulti(" << e0 << ", 0);\n"; + Out() << GenTab() << tmp_e1 << " = " << e1 << ";\n"; + Out() << GenTab() << iter_s << " = FlUnboxMulti(" << tmp_e1 << ", 0);\n"; + Out() << GenTab() << iter_var << " = FlUnboxMulti(" << tmp_e1 << ", 1);\n"; } else { - std::string e0 = CompileExp(exps[0]); - std::string e1 = (exps.size() >= 2) ? CompileExp(exps[1]) : "kNil"; - std::string e2 = (exps.size() >= 3) ? CompileExp(exps[2]) : "kNil"; + std::string e0 = CompileExp(exps[0], false); + std::string e1 = CompileExp(exps[1], false); + const bool last_of_three = exps.size() == 3; + std::string e2 = CompileExp(exps[2], last_of_three); Out() << GenTab() << iter_f << " = FlUnboxMulti(" << e0 << ", 0);\n"; Out() << GenTab() << iter_s << " = FlUnboxMulti(" << e1 << ", 0);\n"; Out() << GenTab() << iter_var << " = FlUnboxMulti(" << e2 << ", 0);\n"; + for (size_t i = 3; i < exps.size(); ++i) { + Out() << GenTab() << "(void)(" << CompileExp(exps[i], i + 1 == exps.size()) << ");\n"; + } } Out() << GenTab() << "while (true) {\n"; @@ -2877,6 +2895,20 @@ std::string CGen::CompileNumericExp(const SyntaxTreeInterfacePtr &exp) { const auto e = std::dynamic_pointer_cast(exp); + { + TableKeyKind kind = TableKeyKind::kInt; + std::string canonical; + int64_t int_value = 0; + double float_value = 0; + if (ClassifyConstNumberExp(exp, kind, canonical, int_value, float_value)) { + if (ConstNumberExpIsIntValue(kind, exp)) { + return std::to_string(int_value); + } + const double d = (kind == TableKeyKind::kInt) ? static_cast(int_value) : float_value; + return FormatCDoubleLiteral(d); + } + } + if (const auto exp_kind = e->GetExpKind(); exp_kind == ExpKind::kNumber) { if (LookupNodeType(e.get()) == T_INT) { return std::to_string(ToInteger(e->ExpValue())); @@ -3114,7 +3146,6 @@ std::string CGen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncall std::string expansion_tmp; int expansion_start_idx = 0; - CompileCallArgs(args_ptr, args_kind, compiled_args, has_expansion, expansion_tmp, expansion_start_idx); std::string func_name; const SyntaxTreeVar *var_ptr = nullptr; ResolveCalleeName(pe_pre_ptr, func_name, var_ptr); @@ -3123,6 +3154,17 @@ std::string CGen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncall var = std::dynamic_pointer_cast(pe_pre_ptr->GetValue()); } + std::string method_obj_tmp; + if (!fc->Name().empty()) { + // Lua:先求 prefix(接收者),再求参数。obj():f(arg()) 必须先跑 obj()。 + std::string obj_expr = var ? CompileVar(var) : CompilePrefixexp(pe_pre_ptr); + method_obj_tmp = std::format("flua_obj_{}", tmp_var_counter_++); + func_temp_decls_ << " CVar " << method_obj_tmp << ";\n"; + Out() << GenTab() << method_obj_tmp << " = " << obj_expr << ";\n"; + } + + CompileCallArgs(args_ptr, args_kind, compiled_args, has_expansion, expansion_tmp, expansion_start_idx); + if (!func_name.empty() && func_name == "FAKELUA_SET_TABLE") { if (compiled_args.size() != 3) { ThrowError("FAKELUA_SET_TABLE expects exactly 3 arguments", functioncall); @@ -3157,7 +3199,7 @@ std::string CGen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncall if (local_func_names_.contains(func_name) && !is_local_callee) { call_expr = BuildLocalFunctionCall(func_name, compiled_args, has_expansion, expansion_tmp, expansion_start_idx); } else if (!fc->Name().empty()) { - call_expr = BuildMethodCall(fc, pe_pre, pe_pre_ptr, var, compiled_args, has_expansion, expansion_tmp); + call_expr = BuildMethodCall(fc, pe_pre_ptr, method_obj_tmp, compiled_args, has_expansion, expansion_tmp); } else { call_expr = BuildDynamicCall(func_name, pe_pre, pe_pre_ptr, var, compiled_args, has_expansion, expansion_tmp, is_local_callee); } @@ -4525,22 +4567,13 @@ std::string CGen::BuildLocalFunctionCall(const std::string &func_name, const std return call_expr; } -std::string CGen::BuildMethodCall(const std::shared_ptr &fc, SyntaxTreeInterfacePtr pe_pre, const std::shared_ptr &pe_pre_ptr, - const std::shared_ptr &var, const std::vector &compiled_args, bool has_expansion, const std::string &expansion_tmp) { +std::string CGen::BuildMethodCall(const std::shared_ptr &fc, const std::shared_ptr &pe_pre_ptr, const std::string &obj_tmp, + const std::vector &compiled_args, bool has_expansion, const std::string &expansion_tmp) { auto args = compiled_args; if (has_expansion) { args.push_back(expansion_tmp); } const std::string &method_name = fc->Name(); - std::string obj_expr; - if (var) { - obj_expr = CompileVar(var); - } else { - obj_expr = CompilePrefixexp(pe_pre_ptr); - } - std::string obj_tmp = std::format("flua_obj_{}", tmp_var_counter_++); - func_temp_decls_ << " CVar " << obj_tmp << ";\n"; - Out() << GenTab() << obj_tmp << " = " << obj_expr << ";\n"; // 构造参数列表(对象本身作为第一个参数) std::vector final_args; diff --git a/src/compile/c_gen.h b/src/compile/c_gen.h index 6166541..6a221a8 100644 --- a/src/compile/c_gen.h +++ b/src/compile/c_gen.h @@ -159,8 +159,8 @@ class CGen { int &expansion_start_idx); void ResolveCalleeName(const std::shared_ptr &pe_pre_ptr, std::string &func_name, const SyntaxTreeVar *&var_ptr); std::string BuildLocalFunctionCall(const std::string &func_name, const std::vector &compiled_args, bool has_expansion, const std::string &expansion_tmp, int expansion_start_idx); - std::string BuildMethodCall(const std::shared_ptr &fc, SyntaxTreeInterfacePtr pe_pre, const std::shared_ptr &pe_pre_ptr, - const std::shared_ptr &var, const std::vector &compiled_args, bool has_expansion, const std::string &expansion_tmp); + std::string BuildMethodCall(const std::shared_ptr &fc, const std::shared_ptr &pe_pre_ptr, const std::string &obj_tmp, + const std::vector &compiled_args, bool has_expansion, const std::string &expansion_tmp); std::string BuildDynamicCall(const std::string &func_name, SyntaxTreeInterfacePtr pe_pre, const std::shared_ptr &pe_pre_ptr, const std::shared_ptr &var, const std::vector &compiled_args, bool has_expansion, const std::string &expansion_tmp, bool is_local_callee); // EmitSpecAccessorBody:按 key kind 发射 get/set 的 if-else/switch 条件逻辑 diff --git a/src/compile/compile_common.h b/src/compile/compile_common.h index 0ea7787..44ff6a6 100644 --- a/src/compile/compile_common.h +++ b/src/compile/compile_common.h @@ -4,6 +4,7 @@ #include "jit/vm_function.h" #include "syntax_tree.h" #include "util/debug.h" +#include "util/string_util.h" #include #include #include @@ -345,6 +346,85 @@ inline bool ClassifyLuaNumberKey(const std::string &num_str, TableKeyKind &kind, } } +// 常量数字表达式:字面量,或一元减号叠在字面量上(lexer 不再把 -1 收成单个 NUMBER)。 +inline bool ClassifyConstNumberExp(const SyntaxTreeInterfacePtr &node, TableKeyKind &kind, std::string &canonical, int64_t &int_value, double &float_value) { + const auto exp = std::dynamic_pointer_cast(node); + if (!exp) { + return false; + } + if (exp->GetExpKind() == ExpKind::kNumber) { + return ClassifyLuaNumberKey(exp->ExpValue(), kind, canonical, int_value, float_value); + } + if (exp->GetExpKind() != ExpKind::kUnop) { + return false; + } + const auto op = std::dynamic_pointer_cast(exp->Op()); + if (!op || op->GetOpKind() != UnOpKind::kMinus) { + return false; + } + if (!ClassifyConstNumberExp(exp->Right(), kind, canonical, int_value, float_value)) { + return false; + } + if (kind == TableKeyKind::kInt) { + if (int_value == std::numeric_limits::min()) { + kind = TableKeyKind::kFloat; + float_value = -static_cast(int_value); + canonical = std::format("{:.17g}", float_value); + int_value = 0; + } else { + int_value = -int_value; + canonical = std::to_string(int_value); + } + } else { + float_value = -float_value; + // lexer 把 -9223372036854775808 拆成 UNMINUS + 2^63;2^63 只能先当 float, + // 取负后正好是 mininteger,应对齐 Lua 收成整数。 + constexpr double kTwo63 = 9223372036854775808.0; + if (float_value == -kTwo63) { + kind = TableKeyKind::kInt; + int_value = std::numeric_limits::min(); + canonical = std::to_string(int_value); + float_value = 0.0; + } else if (!canonical.empty() && canonical.front() == '-') { + canonical = canonical.substr(1); + } else { + canonical.insert(canonical.begin(), '-'); + } + } + return true; +} + +// 表键可以把 1.0 收成整数;表达式值必须跟 Lua 字面量语法走:1.0 / 1e0 仍是 float。 +inline bool ConstNumberExpHasFloatSyntax(const SyntaxTreeInterfacePtr &node) { + const auto exp = std::dynamic_pointer_cast(node); + if (!exp) { + return false; + } + if (exp->GetExpKind() == ExpKind::kNumber) { + return !IsInteger(exp->ExpValue()); + } + if (exp->GetExpKind() != ExpKind::kUnop) { + return false; + } + const auto op = std::dynamic_pointer_cast(exp->Op()); + if (!op || op->GetOpKind() != UnOpKind::kMinus) { + return false; + } + return ConstNumberExpHasFloatSyntax(exp->Right()); +} + +inline bool ConstNumberExpIsIntValue(TableKeyKind kind, const SyntaxTreeInterfacePtr &node) { + return kind == TableKeyKind::kInt && !ConstNumberExpHasFloatSyntax(node); +} + +inline std::string FormatCDoubleLiteral(double d) { + std::string s = std::format("{:.17g}", d); + if (s.find('.') == std::string::npos && s.find('e') == std::string::npos && s.find('E') == std::string::npos) { + s += ".0"; + } + return s; +} + inline std::string EscapeCStringLiteral(const std::string &s) { std::string out; out.reserve(s.size() + 8); diff --git a/src/compile/flex/scanner.cpp b/src/compile/flex/scanner.cpp index 3123d51..230865b 100644 --- a/src/compile/flex/scanner.cpp +++ b/src/compile/flex/scanner.cpp @@ -414,7 +414,7 @@ struct yy_trans_info flex_int32_t yy_verify; flex_int32_t yy_nxt; }; -static const flex_int16_t yy_accept[184] = +static const flex_int16_t yy_accept[182] = { 0, 0, 0, 0, 0, 0, 0, 74, 72, 9, 10, 11, 72, 18, 12, 13, 72, 38, 39, 36, 35, @@ -422,20 +422,20 @@ static const flex_int16_t yy_accept[184] = 16, 70, 42, 43, 19, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 40, 14, 41, 15, 8, 6, 7, 4, 3, 4, - 9, 10, 11, 0, 71, 0, 0, 0, 5, 68, - 68, 24, 67, 69, 68, 69, 69, 69, 32, 30, - 28, 26, 27, 31, 70, 0, 70, 70, 70, 47, - 70, 70, 70, 70, 70, 70, 55, 56, 70, 70, - - 70, 60, 70, 70, 70, 70, 70, 29, 0, 2, - 71, 71, 0, 25, 68, 0, 68, 68, 0, 0, - 44, 70, 70, 70, 50, 70, 52, 70, 70, 70, - 58, 59, 70, 70, 70, 70, 70, 70, 0, 1, - 68, 68, 69, 68, 69, 70, 70, 48, 70, 70, - 54, 70, 70, 70, 63, 64, 70, 70, 68, 0, - 68, 45, 70, 70, 51, 70, 57, 70, 70, 65, - 66, 68, 68, 70, 49, 70, 61, 62, 70, 70, - 46, 53, 0 + 9, 10, 11, 0, 71, 0, 0, 0, 5, 24, + 67, 69, 68, 69, 69, 69, 32, 30, 28, 26, + 27, 31, 70, 0, 70, 70, 70, 47, 70, 70, + 70, 70, 70, 70, 55, 56, 70, 70, 70, 60, + + 70, 70, 70, 70, 70, 29, 0, 2, 71, 71, + 0, 25, 68, 0, 68, 68, 0, 0, 44, 70, + 70, 70, 50, 70, 52, 70, 70, 70, 58, 59, + 70, 70, 70, 70, 70, 70, 0, 1, 68, 68, + 69, 68, 69, 70, 70, 48, 70, 70, 54, 70, + 70, 70, 63, 64, 70, 70, 68, 0, 68, 45, + 70, 70, 51, 70, 57, 70, 70, 65, 66, 68, + 68, 70, 49, 70, 61, 62, 70, 70, 46, 53, + 0 } ; static const YY_CHAR yy_ec[256] = @@ -480,59 +480,55 @@ static const YY_CHAR yy_meta[57] = 5, 5, 1, 1, 1, 1 } ; -static const flex_int16_t yy_base[193] = +static const flex_int16_t yy_base[191] = { 0, - 0, 0, 54, 56, 58, 59, 287, 288, 284, 282, - 280, 58, 288, 288, 288, 55, 288, 288, 288, 288, - 288, 50, 267, 265, 54, 58, 261, 288, 44, 257, - 55, 0, 249, 288, 288, 233, 229, 230, 229, 37, - 51, 228, 48, 227, 29, 224, 233, 47, 225, 228, - 288, 288, 288, 245, 288, 288, 288, 288, 288, 71, - 265, 263, 261, 93, 288, 94, 91, 92, 234, 88, - 234, 246, 288, 90, 232, 288, 97, 0, 288, 288, - 288, 288, 288, 288, 0, 228, 222, 220, 212, 0, - 207, 218, 210, 205, 207, 201, 0, 0, 214, 205, - - 198, 0, 71, 209, 195, 195, 202, 288, 88, 288, - 100, 104, 104, 288, 114, 110, 118, 128, 211, 210, - 0, 207, 190, 201, 0, 189, 0, 201, 190, 201, - 0, 0, 196, 182, 187, 193, 188, 172, 115, 288, - 122, 131, 0, 0, 155, 168, 151, 150, 153, 140, - 0, 145, 154, 139, 0, 0, 140, 145, 160, 133, - 139, 0, 137, 142, 0, 138, 0, 127, 124, 0, - 0, 0, 153, 110, 0, 113, 0, 0, 117, 97, - 0, 0, 288, 199, 204, 209, 214, 217, 219, 135, - 224, 115 - + 0, 0, 54, 56, 58, 59, 282, 283, 279, 277, + 275, 58, 283, 283, 283, 55, 283, 283, 283, 283, + 283, 263, 261, 259, 49, 54, 255, 283, 54, 251, + 58, 0, 243, 283, 283, 227, 223, 224, 223, 39, + 51, 222, 49, 221, 53, 218, 227, 52, 219, 222, + 283, 283, 283, 239, 283, 283, 283, 283, 283, 73, + 259, 257, 255, 93, 283, 96, 94, 95, 228, 241, + 283, 88, 92, 283, 101, 0, 283, 283, 283, 283, + 283, 283, 0, 224, 218, 216, 208, 0, 203, 214, + 206, 201, 203, 197, 0, 0, 210, 201, 194, 0, + + 19, 205, 191, 191, 198, 283, 89, 283, 104, 106, + 99, 283, 113, 115, 122, 136, 207, 206, 0, 190, + 169, 163, 0, 151, 0, 163, 152, 163, 0, 0, + 158, 144, 147, 153, 148, 145, 113, 283, 127, 129, + 0, 0, 163, 145, 143, 138, 141, 127, 0, 125, + 134, 119, 0, 0, 122, 125, 168, 131, 138, 0, + 115, 105, 0, 100, 0, 88, 83, 0, 0, 0, + 140, 72, 0, 71, 0, 0, 75, 34, 0, 0, + 283, 207, 212, 217, 222, 225, 227, 70, 232, 67 } ; -static const flex_int16_t yy_def[193] = +static const flex_int16_t yy_def[191] = { 0, - 183, 1, 184, 184, 185, 185, 183, 183, 183, 183, - 183, 186, 183, 183, 183, 187, 183, 183, 183, 183, - 183, 183, 183, 183, 188, 188, 183, 183, 183, 183, - 183, 189, 183, 183, 183, 189, 189, 189, 189, 189, - 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 186, 183, 186, 187, 187, 183, 188, - 70, 183, 183, 183, 70, 183, 183, 190, 183, 183, - 183, 183, 183, 183, 189, 191, 189, 189, 189, 189, - 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, - - 189, 189, 189, 189, 189, 189, 189, 183, 183, 183, - 186, 187, 183, 183, 70, 183, 188, 188, 191, 191, - 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, - 189, 189, 189, 189, 189, 189, 189, 189, 183, 183, - 188, 188, 192, 118, 183, 189, 189, 189, 189, 189, - 189, 189, 189, 189, 189, 189, 189, 189, 118, 183, - 188, 189, 189, 189, 189, 189, 189, 189, 189, 189, - 189, 159, 188, 189, 189, 189, 189, 189, 189, 189, - 189, 189, 0, 183, 183, 183, 183, 183, 183, 183, - 183, 183 - + 181, 1, 182, 182, 183, 183, 181, 181, 181, 181, + 181, 184, 181, 181, 181, 185, 181, 181, 181, 181, + 181, 181, 181, 181, 186, 186, 181, 181, 181, 181, + 181, 187, 181, 181, 181, 187, 187, 187, 187, 187, + 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 184, 181, 184, 185, 185, 181, 181, + 181, 181, 186, 181, 181, 188, 181, 181, 181, 181, + 181, 181, 187, 189, 187, 187, 187, 187, 187, 187, + 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + + 187, 187, 187, 187, 187, 181, 181, 181, 184, 185, + 181, 181, 186, 181, 186, 186, 189, 189, 187, 187, + 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + 187, 187, 187, 187, 187, 187, 181, 181, 186, 186, + 190, 116, 181, 187, 187, 187, 187, 187, 187, 187, + 187, 187, 187, 187, 187, 187, 116, 181, 186, 187, + 187, 187, 187, 187, 187, 187, 187, 187, 187, 157, + 186, 187, 187, 187, 187, 187, 187, 187, 187, 187, + 0, 181, 181, 181, 181, 181, 181, 181, 181, 181 } ; -static const flex_int16_t yy_nxt[345] = +static const flex_int16_t yy_nxt[340] = { 0, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, @@ -540,41 +536,40 @@ static const flex_int16_t yy_nxt[345] = 8, 34, 35, 36, 37, 38, 39, 40, 41, 42, 32, 43, 32, 44, 45, 46, 32, 47, 32, 48, 49, 50, 51, 52, 53, 54, 56, 57, 56, 57, - 59, 59, 65, 65, 69, 80, 81, 70, 71, 74, - 100, 75, 75, 74, 101, 75, 75, 83, 84, 77, - 91, 92, 78, 77, 93, 68, 97, 104, 66, 60, - 60, 77, 98, 109, 105, 77, 94, 65, 111, 65, - - 112, 95, 110, 74, 65, 75, 75, 115, 115, 116, - 109, 116, 65, 77, 117, 117, 78, 133, 159, 110, - 134, 68, 68, 66, 66, 77, 139, 117, 117, 76, - 66, 141, 141, 140, 68, 142, 142, 139, 118, 141, - 141, 182, 76, 143, 140, 144, 144, 77, 142, 142, - 161, 161, 144, 144, 181, 145, 173, 173, 180, 77, - 179, 144, 144, 144, 144, 144, 144, 160, 178, 160, - 173, 173, 161, 161, 145, 76, 177, 172, 172, 176, - 175, 174, 171, 170, 172, 172, 169, 168, 167, 166, - 165, 164, 163, 172, 172, 172, 172, 172, 172, 55, - - 55, 55, 55, 55, 58, 58, 58, 58, 58, 64, - 162, 64, 64, 64, 67, 158, 67, 67, 67, 76, - 76, 76, 85, 85, 119, 119, 119, 119, 119, 157, - 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, - 146, 65, 120, 138, 137, 136, 135, 132, 131, 130, - 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, - 76, 114, 76, 113, 63, 62, 61, 108, 107, 106, - 103, 102, 99, 96, 90, 89, 88, 87, 86, 82, - 79, 73, 72, 63, 62, 61, 183, 7, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183 + 59, 59, 65, 65, 72, 131, 73, 73, 132, 72, + 157, 73, 73, 116, 75, 78, 79, 76, 180, 75, + 81, 82, 89, 90, 91, 68, 75, 95, 66, 60, + 60, 75, 102, 96, 98, 107, 92, 65, 99, 103, + + 109, 93, 65, 110, 108, 113, 113, 72, 65, 73, + 73, 107, 179, 114, 65, 114, 178, 75, 115, 115, + 108, 137, 177, 66, 68, 68, 66, 176, 138, 75, + 139, 139, 115, 115, 66, 137, 68, 175, 75, 140, + 140, 174, 138, 173, 139, 139, 140, 140, 159, 159, + 75, 141, 75, 142, 142, 171, 171, 171, 171, 172, + 142, 142, 169, 143, 75, 168, 167, 166, 165, 142, + 142, 142, 142, 142, 142, 158, 164, 158, 163, 162, + 159, 159, 143, 74, 161, 170, 170, 160, 156, 155, + 154, 153, 170, 170, 152, 151, 150, 149, 148, 147, + + 146, 170, 170, 170, 170, 170, 170, 55, 55, 55, + 55, 55, 58, 58, 58, 58, 58, 64, 145, 64, + 64, 64, 67, 144, 67, 67, 67, 74, 74, 74, + 83, 83, 117, 117, 117, 117, 117, 65, 118, 136, + 135, 134, 133, 130, 129, 128, 127, 126, 125, 124, + 123, 122, 121, 120, 119, 118, 112, 111, 63, 62, + 61, 106, 105, 104, 101, 100, 97, 94, 88, 87, + 86, 85, 84, 80, 77, 71, 70, 69, 63, 62, + 61, 181, 7, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181 } ; -static const flex_int16_t yy_chk[345] = +static const flex_int16_t yy_chk[340] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -582,38 +577,37 @@ static const flex_int16_t yy_chk[345] = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 4, 4, - 5, 6, 12, 16, 22, 29, 29, 22, 22, 25, - 45, 25, 25, 26, 45, 26, 26, 31, 31, 25, - 40, 40, 25, 26, 41, 16, 43, 48, 12, 5, - 6, 25, 43, 60, 48, 26, 41, 64, 66, 67, - - 68, 41, 60, 70, 111, 70, 70, 74, 74, 77, - 109, 77, 112, 70, 77, 77, 70, 103, 192, 109, - 103, 67, 68, 64, 66, 70, 113, 116, 116, 115, - 111, 115, 115, 113, 112, 117, 117, 139, 190, 141, - 141, 180, 115, 118, 139, 118, 118, 141, 142, 142, - 160, 160, 118, 118, 179, 118, 161, 161, 176, 141, - 174, 118, 118, 118, 118, 118, 118, 145, 169, 145, - 173, 173, 145, 145, 118, 159, 168, 159, 159, 166, - 164, 163, 158, 157, 159, 159, 154, 153, 152, 150, - 149, 148, 147, 159, 159, 159, 159, 159, 159, 184, - - 184, 184, 184, 184, 185, 185, 185, 185, 185, 186, - 146, 186, 186, 186, 187, 138, 187, 187, 187, 188, - 188, 188, 189, 189, 191, 191, 191, 191, 191, 137, - 136, 135, 134, 133, 130, 129, 128, 126, 124, 123, - 122, 120, 119, 107, 106, 105, 104, 101, 100, 99, - 96, 95, 94, 93, 92, 91, 89, 88, 87, 86, - 75, 72, 71, 69, 63, 62, 61, 54, 50, 49, - 47, 46, 44, 42, 39, 38, 37, 36, 33, 30, - 27, 24, 23, 11, 10, 9, 7, 183, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, - 183, 183, 183, 183 + 5, 6, 12, 16, 25, 101, 25, 25, 101, 26, + 190, 26, 26, 188, 25, 29, 29, 25, 178, 26, + 31, 31, 40, 40, 41, 16, 25, 43, 12, 5, + 6, 26, 48, 43, 45, 60, 41, 64, 45, 48, + + 66, 41, 67, 68, 60, 72, 72, 73, 109, 73, + 73, 107, 177, 75, 110, 75, 174, 73, 75, 75, + 107, 111, 172, 64, 67, 68, 66, 167, 111, 73, + 113, 113, 114, 114, 109, 137, 110, 166, 113, 115, + 115, 164, 137, 162, 139, 139, 140, 140, 158, 158, + 113, 116, 139, 116, 116, 159, 159, 171, 171, 161, + 116, 116, 156, 116, 139, 155, 152, 151, 150, 116, + 116, 116, 116, 116, 116, 143, 148, 143, 147, 146, + 143, 143, 116, 157, 145, 157, 157, 144, 136, 135, + 134, 133, 157, 157, 132, 131, 128, 127, 126, 124, + + 122, 157, 157, 157, 157, 157, 157, 182, 182, 182, + 182, 182, 183, 183, 183, 183, 183, 184, 121, 184, + 184, 184, 185, 120, 185, 185, 185, 186, 186, 186, + 187, 187, 189, 189, 189, 189, 189, 118, 117, 105, + 104, 103, 102, 99, 98, 97, 94, 93, 92, 91, + 90, 89, 87, 86, 85, 84, 70, 69, 63, 62, + 61, 54, 50, 49, 47, 46, 44, 42, 39, 38, + 37, 36, 33, 30, 27, 24, 23, 22, 11, 10, + 9, 7, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 181, 181, 181, 181, 181, 181, 181, 181 } ; /* Table of booleans, true if rule could match eol. */ @@ -663,26 +657,26 @@ static const flex_int16_t yy_rule_linenum[73] = # define YY_DECL \ yy::parser::symbol_type fakelua::MyFlexer::MyYylex() -#line 666 "scanner.cpp" +#line 660 "scanner.cpp" #line 23 "scanner.l" #define FLEX_VERSION (YY_FLEX_MAJOR_VERSION * 100 + YY_FLEX_MINOR_VERSION) -#line 671 "scanner.cpp" +#line 665 "scanner.cpp" #define YY_NO_INPUT 1 #line 33 "scanner.l" -#line 676 "scanner.cpp" +#line 670 "scanner.cpp" /* 短字符串里不能出现裸换行(\n 和 \r 都算),与 Lua 的 "unfinished string" 一致。 需要跨行的内容用 [[ ]] 长字符串或转义写法。 */ -/* 字面量不带正号:Lua 没有一元加,+1 应该报 unexpected symbol near '+'。 - 去掉 + 后它会退回成 PLUS token,语法里没有对应产生式,于是和 Lua 一样被拒。 */ +/* 字面量不带符号:Lua 把 +1 / -1 拆成一元运算符 + 无符号数字。 + 若把 - 写进数字 token,x-1、1-1、x -1 都会变成 IDENT/NUMBER NUMBER,无法解析。 */ #line 50 "scanner.l" // Code run each time a pattern is matched. # define YY_USER_ACTION loc.columns (yyleng); -#line 684 "scanner.cpp" -#line 685 "scanner.cpp" +#line 678 "scanner.cpp" +#line 679 "scanner.cpp" #define INITIAL 0 #define SINGLE_LINE_COMMENT_STATE 1 @@ -888,7 +882,7 @@ YY_DECL /* 长注释按 Lua 的长括号层级处理:--[[ 配 ]],--[==[ 配 ]==],层级不匹配的 ]] 只是普通内容。传统的 --]] 收尾依然有效,因为其中的 ]] 就是 0 级闭括号。 */ -#line 891 "scanner.cpp" +#line 885 "scanner.cpp" while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ { @@ -917,13 +911,13 @@ YY_DECL while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 184 ) + if ( yy_current_state >= 182 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; ++yy_cp; } - while ( yy_current_state != 183 ); + while ( yy_current_state != 181 ); yy_cp = (yy_last_accepting_cpos); yy_current_state = (yy_last_accepting_state); @@ -1355,7 +1349,7 @@ YY_RULE_SETUP #line 153 "scanner.l" ECHO; YY_BREAK -#line 1358 "scanner.cpp" +#line 1352 "scanner.cpp" case YY_END_OF_BUFFER: { @@ -1793,7 +1787,7 @@ int yyFlexLexer::yy_get_next_buffer() while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 184 ) + if ( yy_current_state >= 182 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; @@ -1826,11 +1820,11 @@ int yyFlexLexer::yy_get_next_buffer() while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 184 ) + if ( yy_current_state >= 182 ) yy_c = yy_meta[yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; - yy_is_jam = (yy_current_state == 183); + yy_is_jam = (yy_current_state == 181); return yy_is_jam ? 0 : yy_current_state; } diff --git a/src/compile/flex/scanner.l b/src/compile/flex/scanner.l index f020e75..55408d0 100644 --- a/src/compile/flex/scanner.l +++ b/src/compile/flex/scanner.l @@ -40,10 +40,10 @@ string2 \'((\\'|[^'\n\r])*)\' string3 \[\[([^\]]|(\][^\]]))*\]\] string {string1}|{string2}|{string3} name [_a-zA-Z][_a-zA-Z0-9]* -/* 字面量不带正号:Lua 没有一元加,+1 应该报 unexpected symbol near '+'。 - 去掉 + 后它会退回成 PLUS token,语法里没有对应产生式,于是和 Lua 一样被拒。 */ -num1 -?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)? -num2 -?0[xX][0-9a-fA-F]+(\.[0-9a-fA-F]+)?([pP][+-]?[0-9]+)? +/* 字面量不带符号:Lua 把 +1 / -1 拆成一元运算符 + 无符号数字。 + 若把 - 写进数字 token,x-1、1-1、x -1 都会变成 IDENT/NUMBER NUMBER,无法解析。 */ +num1 [0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)? +num2 0[xX][0-9a-fA-F]+(\.[0-9a-fA-F]+)?([pP][+-]?[0-9]+)? number {num1}|{num2} %{ diff --git a/src/compile/semantic_analysis.cpp b/src/compile/semantic_analysis.cpp index 595398d..667493f 100644 --- a/src/compile/semantic_analysis.cpp +++ b/src/compile/semantic_analysis.cpp @@ -714,16 +714,13 @@ void SemanticAnalysis::CheckBlockReturnPosition(const SyntaxTreeInterfacePtr &no void SemanticAnalysis::CheckForLoop(const SyntaxTreeInterfacePtr &node) { const auto for_loop = std::dynamic_pointer_cast(node); if (const auto step_exp = std::dynamic_pointer_cast(for_loop->ExpStep())) { - if (step_exp->GetExpKind() == ExpKind::kNumber) { - const auto &val = step_exp->ExpValue(); - if (IsInteger(val)) { - if (ToInteger(val) == 0) { - ThrowError("'for' step is zero", step_exp); - } - } else { - if (ToFloat(val) == 0.0) { - ThrowError("'for' step is zero", step_exp); - } + TableKeyKind kind = TableKeyKind::kInt; + std::string canonical; + int64_t int_value = 0; + double float_value = 0; + if (ClassifyConstNumberExp(step_exp, kind, canonical, int_value, float_value)) { + if ((kind == TableKeyKind::kInt && int_value == 0) || (kind == TableKeyKind::kFloat && float_value == 0.0)) { + ThrowError("'for' step is zero", step_exp); } } } diff --git a/src/compile/type_inferencer.cpp b/src/compile/type_inferencer.cpp index 6115df4..84a2b65 100644 --- a/src/compile/type_inferencer.cpp +++ b/src/compile/type_inferencer.cpp @@ -827,6 +827,13 @@ InferredType TypeInferencer::InferExp(const std::shared_ptr &exp, DEBUG_ASSERT(op); switch (const auto op_kind = op->GetOpKind()) { case UnOpKind::kMinus: { + TableKeyKind kind = TableKeyKind::kInt; + std::string canonical; + int64_t int_value = 0; + double float_value = 0; + if (ClassifyConstNumberExp(exp, kind, canonical, int_value, float_value)) { + return RecordType(current_map, exp.get(), ConstNumberExpIsIntValue(kind, exp) ? T_INT : T_FLOAT); + } if (operand_type == T_INT) { return RecordType(current_map, exp.get(), T_INT); } @@ -1651,13 +1658,25 @@ bool TypeInferencer::BuildCtorFields(const SyntaxTreeInterfacePtr &tc, std::vect f.key_kind = TableKeyKind::kString; f.c_field_name = SpecStringFieldCName(f.key); desc = "S_" + f.key; - } else if (kind == ExpKind::kNumber) { + } else if (kind == ExpKind::kTrue) { + f.key = "true"; + f.key_kind = TableKeyKind::kBool; + f.c_field_name = "_bool_true"; + f.bool_value = true; + desc = "B_true"; + } else if (kind == ExpKind::kFalse) { + f.key = "false"; + f.key_kind = TableKeyKind::kBool; + f.c_field_name = "_bool_false"; + f.bool_value = false; + desc = "B_false"; + } else { TableKeyKind nkind = TableKeyKind::kInt; std::string canonical; int64_t int_value = 0; double float_value = 0; - if (!ClassifyLuaNumberKey(key_exp->ExpValue(), nkind, canonical, int_value, float_value)) { - return false; + if (!ClassifyConstNumberExp(key_exp, nkind, canonical, int_value, float_value)) { + return false;// 非静态 key } if (nkind == TableKeyKind::kInt) { f.key = canonical; @@ -1678,20 +1697,6 @@ bool TypeInferencer::BuildCtorFields(const SyntaxTreeInterfacePtr &tc, std::vect f.float_value = float_value; desc = "F_" + f.key; } - } else if (kind == ExpKind::kTrue) { - f.key = "true"; - f.key_kind = TableKeyKind::kBool; - f.c_field_name = "_bool_true"; - f.bool_value = true; - desc = "B_true"; - } else if (kind == ExpKind::kFalse) { - f.key = "false"; - f.key_kind = TableKeyKind::kBool; - f.c_field_name = "_bool_false"; - f.bool_value = false; - desc = "B_false"; - } else { - return false;// 非静态 key } } } diff --git a/src/interp/codegen.cpp b/src/interp/codegen.cpp index 31ad10e..b67d645 100644 --- a/src/interp/codegen.cpp +++ b/src/interp/codegen.cpp @@ -209,6 +209,20 @@ CVar InterpCodegen::LiteralFromExp(const SyntaxTreeInterfacePtr &exp) { return interp_rt::Int(ToInteger(e->ExpValue())); } return interp_rt::Float(ToFloat(e->ExpValue())); + case ExpKind::kUnop: { + TableKeyKind kind = TableKeyKind::kInt; + std::string canonical; + int64_t int_value = 0; + double float_value = 0; + if (ClassifyConstNumberExp(exp, kind, canonical, int_value, float_value)) { + if (ConstNumberExpIsIntValue(kind, exp)) { + return interp_rt::Int(int_value); + } + const double d = (kind == TableKeyKind::kInt) ? static_cast(int_value) : float_value; + return interp_rt::Float(d); + } + return interp_rt::Nil(); + } case ExpKind::kString: { CVar s{}; s.type_ = static_cast(VarType::StringId); @@ -791,12 +805,15 @@ void InterpCodegen::CompileStmtReturn(const SyntaxTreeInterfacePtr &stmt) { return; } const bool last_multi = LastPreservesMulti(exps.back()); - const int base = stack_top_; std::vector regs; for (size_t i = 0; i < exps.size(); ++i) { const bool pres = (i + 1 == exps.size()) && last_multi; regs.push_back(CompileExp(exps[i], pres)); } + // 必须在 CompileExp 之后再取 base:表达式临时槽从当时的 stack_top_ 往上长, + // 若先占 [base, base+n) 再 MoveTo,会把已经算好的字面量/临时量冲掉 + // (return f, t, 0 在 t 是参数时会变成 f, t, t)。 + const int base = stack_top_; EnsureStack(base + static_cast(regs.size())); for (size_t i = 0; i < regs.size(); ++i) { MoveTo(base + static_cast(i), regs[i]); @@ -1209,32 +1226,34 @@ void InterpCodegen::CompileStmtForIn(const SyntaxTreeInterfacePtr &stmt) { } const auto &exps = explist_ptr->Exps(); - const int iter_f = AllocReg(); - const int iter_s = AllocReg(); - const int iter_var = AllocReg(); + // 迭代器三元组必须是 local:CALL 的结果槽是 AllocReg 临时量, + // 若 f/s/var 也只是 temp,stack_top_ 回落后会和 CALL 帧重叠, + // 第一次迭代就把函数冲掉,或 MoveTo 把 state/ctrl 搅乱。 + const int iter_f = AllocLocal(); + const int iter_s = AllocLocal(); + const int iter_var = AllocLocal(); if (exps.size() == 1) { const int e0 = CompileExp(exps[0], true); - const int is_m = AllocReg(); // Always unbox 0,1,2 — UnboxMulti on non-multi returns the value for idx 0 and nil else. Emit(Op::UNBOX, iter_f, e0, 0); Emit(Op::UNBOX, iter_s, e0, 1); Emit(Op::UNBOX, iter_var, e0, 2); - (void)is_m; + } else if (exps.size() == 2) { + MoveTo(iter_f, CompileExp(exps[0], false)); + const int e1 = CompileExp(exps[1], true); + Emit(Op::UNBOX, iter_f, iter_f, 0); + Emit(Op::UNBOX, iter_s, e1, 0); + Emit(Op::UNBOX, iter_var, e1, 1); } else { MoveTo(iter_f, CompileExp(exps[0], false)); - if (exps.size() >= 2) { - MoveTo(iter_s, CompileExp(exps[1], false)); - } else { - Emit(Op::LOADNIL, iter_s); - } - if (exps.size() >= 3) { - MoveTo(iter_var, CompileExp(exps[2], false)); - } else { - Emit(Op::LOADNIL, iter_var); - } + MoveTo(iter_s, CompileExp(exps[1], false)); + const int e2 = CompileExp(exps[2], exps.size() == 3); Emit(Op::UNBOX, iter_f, iter_f, 0); Emit(Op::UNBOX, iter_s, iter_s, 0); - Emit(Op::UNBOX, iter_var, iter_var, 0); + Emit(Op::UNBOX, iter_var, e2, 0); + for (size_t i = 3; i < exps.size(); ++i) { + (void)CompileExp(exps[i], i + 1 == exps.size()); + } } LoopInfo loop; @@ -1303,6 +1322,22 @@ void InterpCodegen::CompileStmtLocalFunction(const SyntaxTreeInterfacePtr &stmt) int InterpCodegen::CompileExp(const SyntaxTreeInterfacePtr &exp, bool preserve_multi, int dest) { const auto e = std::dynamic_pointer_cast(exp); + if (e->GetExpKind() == ExpKind::kNumber || e->GetExpKind() == ExpKind::kUnop) { + TableKeyKind kind = TableKeyKind::kInt; + std::string canonical; + int64_t int_value = 0; + double float_value = 0; + if (ClassifyConstNumberExp(exp, kind, canonical, int_value, float_value)) { + const int r = dest >= 0 ? dest : AllocReg(); + if (ConstNumberExpIsIntValue(kind, exp)) { + LoadConstTo(r, interp_rt::Int(int_value)); + } else { + const double d = (kind == TableKeyKind::kInt) ? static_cast(int_value) : float_value; + LoadConstTo(r, interp_rt::Float(d)); + } + return r; + } + } switch (e->GetExpKind()) { case ExpKind::kNil: { const int r = dest >= 0 ? dest : AllocReg(); @@ -1512,17 +1547,20 @@ int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncal } } - EmitCallArgs(args_ptr, arg_regs, last_expand); - + int method_obj = -1; if (!fc->Name().empty()) { - int obj; if (pe_pre_ptr->GetPrefixKind() == PrefixExpKind::kVar) { - obj = CompileVar(pe_pre_ptr->GetValue()); + method_obj = CompileVar(pe_pre_ptr->GetValue()); } else { - obj = CompilePrefixexp(fc->prefixexp(), false); + method_obj = CompilePrefixexp(fc->prefixexp(), false); } + } + + EmitCallArgs(args_ptr, arg_regs, last_expand); + + if (!fc->Name().empty()) { std::vector final_args; - final_args.push_back(obj); + final_args.push_back(method_obj); for (int a: arg_regs) final_args.push_back(a); if (kStringLibraryMethods.contains(fc->Name())) { return place_callname("string." + fc->Name(), final_args); @@ -1533,7 +1571,7 @@ int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncal key.data_.i = s_->GetConstString().Alloc(fc->Name()); LoadConstTo(k, key); const int method = AllocReg(); - Emit(Op::GETTABLE, method, obj, k); + Emit(Op::GETTABLE, method, method_obj, k); return place_call(method, final_args); } diff --git a/src/jit/vm_function.h b/src/jit/vm_function.h index f91f3a3..f2cccd8 100644 --- a/src/jit/vm_function.h +++ b/src/jit/vm_function.h @@ -44,7 +44,11 @@ class VmFunction { } void Merge(const VmFunction &func) { - is_vararg_ = is_vararg_ || func.is_vararg_; + // 热更新以最新一次注册的签名为准。只 OR is_vararg_、不改 arg_count_ + // 会让 Call() 在 `function f()` 再定义成 `function f(...)` 时算出 + // fixed_count = -1,写穿 call_cvars[-1]。 + arg_count_ = func.arg_count_; + is_vararg_ = func.is_vararg_; for (int i = 0; i < JIT_MAX; ++i) { if (!func.func_addr_[i]) { continue; diff --git a/src/native/http/native_http.cpp b/src/native/http/native_http.cpp index 66289ad..0477654 100644 --- a/src/native/http/native_http.cpp +++ b/src/native/http/native_http.cpp @@ -1003,7 +1003,9 @@ static void ParseRequestOpts(State *s, CVar opts, std::string &method, std::stri CVar b = table::TableHelper::GetTableStrId(s, opts, "body"); if (b.type_ != static_cast(VarType::Nil)) body = CVarToString(b); CVar t = table::TableHelper::GetTableStrId(s, opts, "timeout_ms"); - if (t.type_ != static_cast(VarType::Nil)) timeout_ms = static_cast(inter::CVarToInteger(t, 10000)); + if (t.type_ != static_cast(VarType::Nil)) { + timeout_ms = CheckInt32Range(inter::CVarToInteger(t, 10000), "http.request", "timeout_ms"); + } CVar v = table::TableHelper::GetTableStrId(s, opts, "version"); if (v.type_ != static_cast(VarType::Nil)) { int64_t ver = inter::CVarToInteger(v, 11); @@ -1099,9 +1101,13 @@ static CVar HttpServerFn(State *s, CVar *args, int n) { port = static_cast(port_val); } CVar bl = table::TableHelper::GetTableStrId(s, a0, "backlog"); - if (bl.type_ != static_cast(VarType::Nil)) backlog = static_cast(inter::CVarToInteger(bl, 128)); + if (bl.type_ != static_cast(VarType::Nil)) { + backlog = CheckInt32Range(inter::CVarToInteger(bl, 128), "http.server", "backlog", 1); + } CVar t = table::TableHelper::GetTableStrId(s, a0, "timeout_ms"); - if (t.type_ != static_cast(VarType::Nil)) timeout_ms = static_cast(inter::CVarToInteger(t, 10000)); + if (t.type_ != static_cast(VarType::Nil)) { + timeout_ms = CheckInt32Range(inter::CVarToInteger(t, 10000), "http.server", "timeout_ms"); + } CVar tls_var = table::TableHelper::GetTableStrId(s, a0, "tls"); if (tls_var.type_ != static_cast(VarType::Nil)) tls = CVarToBoolFlag(tls_var, false); CVar cert_var = table::TableHelper::GetTableStrId(s, a0, "cert"); diff --git a/src/native/ini/native_ini.cpp b/src/native/ini/native_ini.cpp index 514d21d..a96249b 100644 --- a/src/native/ini/native_ini.cpp +++ b/src/native/ini/native_ini.cpp @@ -117,6 +117,19 @@ static std::string IniEscapeKey(const std::string &key) { return out.empty() ? "_" : out; } +static std::string IniEscapeValue(const std::string &val) { + std::string out; + out.reserve(val.size()); + for (unsigned char c: val) { + if (c == '\0' || c == '\n' || c == '\r') { + out.push_back('_'); + } else { + out.push_back(static_cast(c)); + } + } + return out; +} + static std::string CVarToIniValue(CVar v) { switch (v.type_) { case static_cast(VarType::Int): @@ -131,7 +144,7 @@ static std::string CVarToIniValue(CVar v) { case static_cast(VarType::Nil): return ""; default: - return inter::FakeluaToNativeString(nullptr, v); + return IniEscapeValue(inter::FakeluaToNativeString(nullptr, v)); } } diff --git a/src/native/mysql/native_mysql_pool.cpp b/src/native/mysql/native_mysql_pool.cpp index 352ebd4..fc21a6e 100644 --- a/src/native/mysql/native_mysql_pool.cpp +++ b/src/native/mysql/native_mysql_pool.cpp @@ -7,6 +7,7 @@ #include "var/var.h" #include +#include #include #include #include @@ -136,7 +137,7 @@ static CVar PoolCreate(State *s, CVar *args, int n) { CVar timeout_var = table::TableHelper::GetTableStrId(s, a0, "timeout_ms"); if (timeout_var.type_ != static_cast(VarType::Nil)) { - config.connect_timeout_ms = static_cast(inter::CVarToInteger(timeout_var, 5000)); + config.connect_timeout_ms = CheckInt32Range(inter::CVarToInteger(timeout_var, 5000), "mysql.pool", "timeout_ms"); config.read_timeout_ms = config.connect_timeout_ms; } diff --git a/src/native/native_common.h b/src/native/native_common.h index 2b67316..bd276ac 100644 --- a/src/native/native_common.h +++ b/src/native/native_common.h @@ -20,6 +20,13 @@ inline uint16_t CheckPortRange(int64_t port, const char *what, int64_t lo, int64 return static_cast(port); } +inline int CheckInt32Range(int64_t v, const char *what, const char *field, int64_t lo = 0, int64_t hi = std::numeric_limits::max()) { + if (v < lo || v > hi) { + ThrowFakeluaException(std::format("{}: {} {} out of range", what, field, v)); + } + return static_cast(v); +} + // Throw a standardized "bad argument #N to 'fname' (expected)" exception. [[noreturn]] inline void ThrowBadArgument(int argno, const char *fname, const char *expected) { std::string msg = std::string("bad argument #") + std::to_string(argno) + " to '" + fname + "' (" + expected + ")"; diff --git a/src/native/net/native_net.cpp b/src/native/net/native_net.cpp index b7fd0b7..1bb9999 100644 --- a/src/native/net/native_net.cpp +++ b/src/native/net/native_net.cpp @@ -548,7 +548,7 @@ static net::NetConfig ParseConfig(State *s, CVar *args, int n) { } cfg.max_conn = static_cast(maxc); } - cfg.backlog = static_cast(GetTableField(s, a0, "backlog", 128)); + cfg.backlog = CheckInt32Range(GetTableField(s, a0, "backlog", 128), "net", "backlog", 1); { int64_t sbs = GetTableField(s, a0, "send_buf_size", 0); if (sbs == 0) sbs = GetTableField(s, a0, "sendbuf", 0); @@ -576,9 +576,12 @@ static net::NetConfig ParseConfig(State *s, CVar *args, int n) { cfg.max_packet_len = static_cast(mpl); } } - cfg.fixed_packet_len = static_cast(GetTableField(s, a0, "fixed_len", 0)); - if (cfg.fixed_packet_len == 0) { - cfg.fixed_packet_len = static_cast(GetTableField(s, a0, "fixed_packet_len", 0)); + { + int64_t flen = GetTableField(s, a0, "fixed_len", 0); + if (flen == 0) flen = GetTableField(s, a0, "fixed_packet_len", 0); + if (flen != 0) { + cfg.fixed_packet_len = CheckInt32Range(flen, "net", "fixed_packet_len", 1); + } } cfg.no_delay = GetTableField(s, a0, "nodelay", 1) != 0; cfg.keep_alive = GetTableField(s, a0, "keepalive", 1) != 0; diff --git a/src/native/net/net_event.cpp b/src/native/net/net_event.cpp index 6946cbc..0885da1 100644 --- a/src/native/net/net_event.cpp +++ b/src/native/net/net_event.cpp @@ -70,6 +70,32 @@ std::string FindHeader(const std::string &raw, const std::string &name) { return {}; } +std::string HeaderContainsToken(const std::string &value, const std::string &token) { + auto tolower_copy = [](std::string s) { + for (char &c: s) { + c = static_cast(std::tolower(static_cast(c))); + } + return s; + }; + const std::string hay = tolower_copy(value); + const std::string needle = tolower_copy(token); + size_t pos = 0; + while (pos < hay.size()) { + auto comma = hay.find(',', pos); + const size_t end = comma == std::string::npos ? hay.size() : comma; + size_t start = pos; + while (start < end && (hay[start] == ' ' || hay[start] == '\t')) ++start; + size_t stop = end; + while (stop > start && (hay[stop - 1] == ' ' || hay[stop - 1] == '\t')) --stop; + if (hay.compare(start, stop - start, needle) == 0) { + return needle; + } + if (comma == std::string::npos) break; + pos = comma + 1; + } + return {}; +} + std::string RequestPath(const std::string &headers) { auto nl = headers.find("\r\n"); std::string line = headers.substr(0, nl == std::string::npos ? headers.size() : nl); @@ -328,6 +354,19 @@ class EventConn : public std::enable_shared_from_this { Close(); return; } + if (HeaderContainsToken(FindHeader(headers, "Upgrade"), "websocket").empty()) { + Close(); + return; + } + if (HeaderContainsToken(FindHeader(headers, "Connection"), "Upgrade").empty()) { + Close(); + return; + } + std::string version = FindHeader(headers, "Sec-WebSocket-Version"); + if (!version.empty() && version != "13") { + Close(); + return; + } std::string accept = WsAcceptKey(key); std::string resp = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + accept + "\r\n\r\n"; bufferevent_write(bev_, resp.data(), resp.size()); diff --git a/src/native/redis/native_redis.cpp b/src/native/redis/native_redis.cpp index 8644412..6b71411 100644 --- a/src/native/redis/native_redis.cpp +++ b/src/native/redis/native_redis.cpp @@ -43,28 +43,39 @@ static void CallNamed(State *s, const std::string &name, CVar *args, int n) { inter::DispatchCall(s, addr, args, n, jit_type); } -static CVar ReplyToLua(State *s, const redisReply *r); +static constexpr int kMaxRedisReplyDepth = 64; -static CVar ArrayToLua(State *s, const redisReply *r) { +static CVar ReplyToLua(State *s, const redisReply *r, int depth); + +static CVar ArrayToLua(State *s, const redisReply *r, int depth) { + if (depth > kMaxRedisReplyDepth) { + ThrowFakeluaException("redis: nested reply too deep"); + } CVar tbl = table::TableHelper::CreateTable(s); for (size_t i = 0; i < r->elements; ++i) { - table::TableHelper::SetTableInt(s, tbl, static_cast(i + 1), ReplyToLua(s, r->element[i])); + table::TableHelper::SetTableInt(s, tbl, static_cast(i + 1), ReplyToLua(s, r->element[i], depth + 1)); } return tbl; } -static CVar MapToLua(State *s, const redisReply *r) { +static CVar MapToLua(State *s, const redisReply *r, int depth) { + if (depth > kMaxRedisReplyDepth) { + ThrowFakeluaException("redis: nested reply too deep"); + } CVar tbl = table::TableHelper::CreateTable(s); for (size_t i = 0; i + 1 < r->elements; i += 2) { - CVar key = ReplyToLua(s, r->element[i]); - CVar val = ReplyToLua(s, r->element[i + 1]); + CVar key = ReplyToLua(s, r->element[i], depth + 1); + CVar val = ReplyToLua(s, r->element[i + 1], depth + 1); table::TableHelper::SetTable(s, tbl, key, val); } return tbl; } -static CVar ReplyToLua(State *s, const redisReply *r) { +static CVar ReplyToLua(State *s, const redisReply *r, int depth = 0) { if (!r) return inter::NativeToFakeluaNil(s); + if (depth > kMaxRedisReplyDepth) { + ThrowFakeluaException("redis: nested reply too deep"); + } switch (r->type) { case REDIS_REPLY_NIL: return inter::NativeToFakeluaNil(s); @@ -80,10 +91,10 @@ static CVar ReplyToLua(State *s, const redisReply *r) { case REDIS_REPLY_ARRAY: case REDIS_REPLY_SET: case REDIS_REPLY_PUSH: - return ArrayToLua(s, r); + return ArrayToLua(s, r, depth); case REDIS_REPLY_MAP: case REDIS_REPLY_ATTR: - return MapToLua(s, r); + return MapToLua(s, r, depth); case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: @@ -326,22 +337,44 @@ class RedisConnection { op->reply = CloneReply(reply); } - static redisReply *CloneReply(const redisReply *r) { + static redisReply *CloneReply(const redisReply *r, int depth = 0) { if (!r) return nullptr; + if (depth > kMaxRedisReplyDepth) { + ThrowFakeluaException("redis: nested reply too deep"); + } auto *out = static_cast(calloc(1, sizeof(redisReply))); + if (!out) { + ThrowFakeluaException("redis: out of memory"); + } out->type = r->type; out->integer = r->integer; out->dval = r->dval; out->len = r->len; if (r->str) { - out->str = static_cast(malloc(r->len + 1)); - std::memcpy(out->str, r->str, r->len); + out->str = static_cast(malloc(static_cast(r->len) + 1)); + if (!out->str) { + freeReplyObject(out); + ThrowFakeluaException("redis: out of memory"); + } + std::memcpy(out->str, r->str, static_cast(r->len)); out->str[r->len] = 0; } if (r->elements && r->element) { out->elements = r->elements; out->element = static_cast(calloc(r->elements, sizeof(redisReply *))); - for (size_t i = 0; i < r->elements; ++i) out->element[i] = CloneReply(r->element[i]); + if (!out->element) { + out->elements = 0; + freeReplyObject(out); + ThrowFakeluaException("redis: out of memory"); + } + for (size_t i = 0; i < r->elements; ++i) { + try { + out->element[i] = CloneReply(r->element[i], depth + 1); + } catch (...) { + freeReplyObject(out); + throw; + } + } } return out; } @@ -547,9 +580,13 @@ static CVar RedisConnect(State *s, CVar *args, int n) { v = table::TableHelper::GetTableStrId(s, a0, "password"); if (v.type_ != static_cast(VarType::Nil)) password = CVarToString(v); v = table::TableHelper::GetTableStrId(s, a0, "db"); - if (v.type_ != static_cast(VarType::Nil)) db = static_cast(inter::CVarToInteger(v, 0)); + if (v.type_ != static_cast(VarType::Nil)) { + db = CheckInt32Range(inter::CVarToInteger(v, 0), "redis.connect", "db"); + } v = table::TableHelper::GetTableStrId(s, a0, "timeout_ms"); - if (v.type_ != static_cast(VarType::Nil)) timeout_ms = static_cast(inter::CVarToInteger(v, 1000)); + if (v.type_ != static_cast(VarType::Nil)) { + timeout_ms = CheckInt32Range(inter::CVarToInteger(v, 1000), "redis.connect", "timeout_ms"); + } } else { ThrowBadArgument(1, "redis.connect", "config must be a table"); } diff --git a/src/native/yaml/native_yaml.cpp b/src/native/yaml/native_yaml.cpp index 72e1b36..519b805 100644 --- a/src/native/yaml/native_yaml.cpp +++ b/src/native/yaml/native_yaml.cpp @@ -80,6 +80,57 @@ static CVar ScalarToLua(State *s, const std::string &str) { static constexpr int kMaxYamlDepth = 64; +static void CheckYamlTextNesting(const std::string &str) { + int depth = 0; + bool in_single = false; + bool in_double = false; + for (size_t i = 0; i < str.size(); ++i) { + const char c = str[i]; + if (in_single) { + if (c == '\'') { + if (i + 1 < str.size() && str[i + 1] == '\'') { + ++i; + } else { + in_single = false; + } + } + continue; + } + if (in_double) { + if (c == '\\' && i + 1 < str.size()) { + ++i; + continue; + } + if (c == '"') { + in_double = false; + } + continue; + } + if (c == '#') { + while (i < str.size() && str[i] != '\n') { + ++i; + } + continue; + } + if (c == '\'') { + in_single = true; + continue; + } + if (c == '"') { + in_double = true; + continue; + } + if (c == '[' || c == '{') { + ++depth; + if (depth > kMaxYamlDepth) { + ThrowFakeluaException("yaml.decode: nesting too deep"); + } + } else if ((c == ']' || c == '}') && depth > 0) { + --depth; + } + } +} + // YAML::Node → CVar static CVar NodeToLua(State *s, const YAML::Node &node, int depth, std::vector &stack) { if (depth > kMaxYamlDepth) { @@ -209,6 +260,7 @@ static CVar YamlDecode(State *s, CVar *args, int n) { CVar a0 = inter::GetNativeArg(s, args, n, 0); std::string str = inter::FakeluaToNativeString(s, a0); try { + CheckYamlTextNesting(str); YAML::Node root = YAML::Load(str); std::vector stack; return NodeToLua(s, root, 0, stack); diff --git a/test/lua/http/test_http.lua b/test/lua/http/test_http.lua index 3e84cb0..95c3855 100644 --- a/test/lua/http/test_http.lua +++ b/test/lua/http/test_http.lua @@ -146,6 +146,22 @@ function test_bad_port() return 1 end +function test_timeout_ms_range() + local ok = pcall(function() + http.request({ url = "http://127.0.0.1:1/", timeout_ms = 3000000000 }, "HttpTest.on_client") + end) + if ok then return 0 end + local ok2 = pcall(function() + local cfg = {} + cfg["ip"] = "127.0.0.1" + cfg["port"] = 0 + cfg["timeout_ms"] = 3000000000 + http.server(cfg) + end) + if ok2 then return 0 end + return 1 +end + function test_method_inject() local ok = pcall(function() http.request({ diff --git a/test/lua/ini/test_ini_basic.lua b/test/lua/ini/test_ini_basic.lua index 753623e..e5977c4 100644 --- a/test/lua/ini/test_ini_basic.lua +++ b/test/lua/ini/test_ini_basic.lua @@ -58,3 +58,17 @@ function test_roundtrip() if t2.db.port ~= 3306 then return 0 end return 1 end + +function test_encode_value_newline() + local s = ini.encode({ sec = { k = "a\n[pwned]\nx=1" } }) + if type(s) ~= "string" then return 0 end + -- 换行被转义后不能再拆出新 section + if s:find("\n%[pwned%]") then return 0 end + if s:find("\r") then return 0 end + local t2 = ini.decode(s) + if t2.pwned ~= nil then return 0 end + if type(t2.sec) ~= "table" then return 0 end + if type(t2.sec.k) ~= "string" then return 0 end + if t2.sec.k:find("\n", 1, true) then return 0 end + return 1 +end diff --git a/test/lua/jit/test_binop_no_space.lua b/test/lua/jit/test_binop_no_space.lua new file mode 100644 index 0000000..888e331 --- /dev/null +++ b/test/lua/jit/test_binop_no_space.lua @@ -0,0 +1,16 @@ +function test_sub_no_space() + local x = 5 + if x-1 ~= 4 then return 0 end + if 1-1 ~= 0 then return 0 end + if x -1 ~= 4 then return 0 end + if 10-3 ~= 7 then return 0 end + if -2-3 ~= -5 then return 0 end + return 1 +end + +function test_unary_minus_still_works() + local a = -10 + if a ~= -10 then return 0 end + if -0xff ~= -255 then return 0 end + return 1 +end diff --git a/test/lua/jit/test_for_in_factory.lua b/test/lua/jit/test_for_in_factory.lua new file mode 100644 index 0000000..9bf6a61 --- /dev/null +++ b/test/lua/jit/test_for_in_factory.lua @@ -0,0 +1,77 @@ +-- Generic for-in explist adjustment (Lua 5.4): +-- 1 exp -> f, s, var from that exp's returns (missing become nil) +-- 2 exp -> f, plus s, var from last exp +-- 3 exp -> f, s, var; extras discarded +-- Avoid native ipairs/next as values (call-by-name only, not first-class). + +local function iter(t, i) + i = i + 1 + local v = t[i] + if v == nil then + return nil + end + return i, v +end + +function test_for_in_factory() + local function factory() + return iter, {10, 20, 30}, 0 + end + local s = 0 + for i, v in factory() do + s = s + v + end + if s ~= 60 then + return 0 + end + + local function factory_one() + return factory() + end + s = 0 + for i, v in factory_one() do + s = s + v + end + if s ~= 60 then + return 0 + end + return 1 +end + +function test_for_in_mypairs() + local function mypairs(t) + return iter, t, 0 + end + local s = 0 + for i, v in mypairs({10, 20, 30}) do + s = s + v + end + if s ~= 60 then + return 0 + end + return 1 +end + +function test_for_in_two_returns() + local function give_state() + return {7}, 0 + end + local s = 0 + for k, v in iter, give_state() do + s = s + v + end + if s ~= 7 then + return 0 + end + + s = 0 + local t = {4, 5} + local ctrl = 0 + for k, v in iter, t, ctrl do + s = s + v + end + if s ~= 9 then + return 0 + end + return 1 +end diff --git a/test/lua/jit/test_method_eval_order.lua b/test/lua/jit/test_method_eval_order.lua new file mode 100644 index 0000000..290551e --- /dev/null +++ b/test/lua/jit/test_method_eval_order.lua @@ -0,0 +1,14 @@ +function test_method_eval_order() + local t = { f = function(self, x) return x end } + local i = 0 + local function obj() + i = i + 1 + return t + end + local function arg() + return i + end + local r = obj():f(arg()) + if r ~= 1 then return 0 end + return 1 +end diff --git a/test/lua/jit/test_table_float_key_precise.lua b/test/lua/jit/test_table_float_key_precise.lua new file mode 100644 index 0000000..229ef16 --- /dev/null +++ b/test/lua/jit/test_table_float_key_precise.lua @@ -0,0 +1,9 @@ +function test_float_key_precise() + local t = { [0.123456789] = 42 } + local k = 0.123456789 + if t[k] ~= 42 then return 0 end + if t[0.123456789] ~= 42 then return 0 end + t[k] = 99 + if t[0.123456789] ~= 99 then return 0 end + return 1 +end diff --git a/test/lua/net/test_net_create_destroy.lua b/test/lua/net/test_net_create_destroy.lua index ebd8770..89e97d8 100644 --- a/test/lua/net/test_net_create_destroy.lua +++ b/test/lua/net/test_net_create_destroy.lua @@ -35,3 +35,19 @@ function test_ip_crlf() if ok then return 0 end return 1 end + +function test_bad_backlog() + local ok = pcall(function() + net.server({ port = 19994, backlog = 3000000000 }) + end) + if ok then return 0 end + return 1 +end + +function test_bad_fixed_len() + local ok = pcall(function() + net.server({ port = 19993, framer = "fixed", fixed_len = 3000000000 }) + end) + if ok then return 0 end + return 1 +end diff --git a/test/lua/redis/test_redis.lua b/test/lua/redis/test_redis.lua index c82b9b3..0bb9258 100644 --- a/test/lua/redis/test_redis.lua +++ b/test/lua/redis/test_redis.lua @@ -55,3 +55,19 @@ function test_bad_port() if ok then return 0 end return 1 end + +function test_bad_timeout_ms() + local ok = pcall(function() + redis.connect({ host = "127.0.0.1", port = 6379, timeout_ms = 3000000000 }, "RedisTest.on_connect") + end) + if ok then return 0 end + return 1 +end + +function test_bad_db() + local ok = pcall(function() + redis.connect({ host = "127.0.0.1", port = 6379, db = 4294967298 }, "RedisTest.on_connect") + end) + if ok then return 0 end + return 1 +end diff --git a/test/lua/yaml/test_yaml_basic.lua b/test/lua/yaml/test_yaml_basic.lua index c47bc48..a2a179c 100644 --- a/test/lua/yaml/test_yaml_basic.lua +++ b/test/lua/yaml/test_yaml_basic.lua @@ -96,3 +96,13 @@ function test_decode_cycle() if ok then return 0 end return 1 end + +function test_decode_too_deep() + local n = 80 + local s = string.rep("[", n) .. string.rep("]", n) + local ok = pcall(function() + yaml.decode(s) + end) + if ok then return 0 end + return 1 +end diff --git a/test/test_http.cpp b/test/test_http.cpp index 3740086..853871b 100644 --- a/test/test_http.cpp +++ b/test/test_http.cpp @@ -59,6 +59,17 @@ TEST(test_http, bad_port) { FakeluaDeleteState(s); } +TEST(test_http, timeout_ms_range) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + CompileFile(s, "./http/test_http.lua", config); + int64_t ret = 0; + CallAll(s, "HttpTest.test_timeout_ms_range", ret); + EXPECT_EQ(ret, 1); + FakeluaDeleteState(s); +} + TEST(test_http, method_inject) { State *s = FakeluaNewState(); ASSERT_NE(s, nullptr); diff --git a/test/test_ini.cpp b/test/test_ini.cpp index 4a578f5..a06ad2e 100644 --- a/test/test_ini.cpp +++ b/test/test_ini.cpp @@ -80,3 +80,16 @@ TEST(test_ini, roundtrip) { FakeluaDeleteState(s); } } + +TEST(test_ini, encode_value_newline) { + for (auto jit_type: AllJitTypes()) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + CompileFile(s, "./ini/test_ini_basic.lua", config); + int64_t ret = 0; + Call(s, jit_type, "IniTest.test_encode_value_newline", ret); + EXPECT_EQ(ret, 1); + FakeluaDeleteState(s); + } +} diff --git a/test/test_jitter.cpp b/test/test_jitter.cpp index 00b8ab2..8bc47e4 100644 --- a/test/test_jitter.cpp +++ b/test/test_jitter.cpp @@ -3027,3 +3027,45 @@ TEST(jitter, tail_call) { ASSERT_EQ(ret, 1); }); } + +TEST(jitter, test_binop_no_space) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_binop_no_space.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test_sub_no_space", ret); + ASSERT_EQ(ret, 1); + Call(s, type, "test_unary_minus_still_works", ret); + ASSERT_EQ(ret, 1); + }); +} + +TEST(jitter, test_for_in_factory) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_for_in_factory.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test_for_in_factory", ret); + ASSERT_EQ(ret, 1); + Call(s, type, "test_for_in_mypairs", ret); + ASSERT_EQ(ret, 1); + Call(s, type, "test_for_in_two_returns", ret); + ASSERT_EQ(ret, 1); + }); +} + +TEST(jitter, test_method_eval_order) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_method_eval_order.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test_method_eval_order", ret); + ASSERT_EQ(ret, 1); + }); +} + +TEST(jitter, test_table_float_key_precise) { + JitterRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./jit/test_table_float_key_precise.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test_float_key_precise", ret); + ASSERT_EQ(ret, 1); + }); +} diff --git a/test/test_net.cpp b/test/test_net.cpp index 02d93c4..6164838 100644 --- a/test/test_net.cpp +++ b/test/test_net.cpp @@ -279,6 +279,34 @@ TEST(test_net, test_ip_crlf) { FakeluaDeleteState(s); } +TEST(test_net, test_bad_backlog) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + + CompileConfig config; + CompileFile(s, "./net/test_net_create_destroy.lua", config); + + int64_t ret = 0; + CallAll(s, "NetCreate.test_bad_backlog", ret); + EXPECT_EQ(ret, 1); + + FakeluaDeleteState(s); +} + +TEST(test_net, test_bad_fixed_len) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + + CompileConfig config; + CompileFile(s, "./net/test_net_create_destroy.lua", config); + + int64_t ret = 0; + CallAll(s, "NetCreate.test_bad_fixed_len", ret); + EXPECT_EQ(ret, 1); + + FakeluaDeleteState(s); +} + // 测试 12: 反复 connect + close,验证 slot 自动释放并复用(maxconn=2 时成功服务 6 次客户端连接) TEST(test_net, test_slot_reuse_repeated_connect) { State *s = FakeluaNewState(); diff --git a/test/test_redis.cpp b/test/test_redis.cpp index 42865aa..3e31d21 100644 --- a/test/test_redis.cpp +++ b/test/test_redis.cpp @@ -100,3 +100,25 @@ TEST(test_redis, bad_port) { EXPECT_EQ(ret, 1); FakeluaDeleteState(s); } + +TEST(test_redis, bad_timeout_ms) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + CompileFile(s, "./redis/test_redis.lua", config); + int64_t ret = 0; + CallAll(s, "RedisTest.test_bad_timeout_ms", ret); + EXPECT_EQ(ret, 1); + FakeluaDeleteState(s); +} + +TEST(test_redis, bad_db) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + CompileFile(s, "./redis/test_redis.lua", config); + int64_t ret = 0; + CallAll(s, "RedisTest.test_bad_db", ret); + EXPECT_EQ(ret, 1); + FakeluaDeleteState(s); +} diff --git a/test/test_state.cpp b/test/test_state.cpp index fe63b1e..bc55065 100644 --- a/test/test_state.cpp +++ b/test/test_state.cpp @@ -306,6 +306,32 @@ TEST(state, vm_function_merge) { ASSERT_EQ(f.GetAddr(JIT_GCC), reinterpret_cast(&VmfB)); } +TEST(state, vm_function_merge_updates_arity) { + VmFunction f("merge_fn", 0, JIT_TCC, reinterpret_cast(&VmfA), {}, false); + ASSERT_EQ(f.GetArgCount(), 0); + ASSERT_FALSE(f.IsVararg()); + + VmFunction vararg_func("merge_fn", 1, JIT_GCC, reinterpret_cast(&VmfB), {}, true); + f.Merge(vararg_func); + + ASSERT_EQ(f.GetArgCount(), 1); + ASSERT_TRUE(f.IsVararg()); + ASSERT_EQ(f.GetAddr(JIT_TCC), reinterpret_cast(&VmfA)); + ASSERT_EQ(f.GetAddr(JIT_GCC), reinterpret_cast(&VmfB)); +} + +TEST(state, recompile_vararg_call) { + FakeluaStateGuard guard; + auto *s = guard.GetState(); + ASSERT_NO_THROW(CompileString(s, "function f() return 0 end", {})); + ASSERT_NO_THROW(CompileString(s, "function f(...) return select('#', ...) end", {})); + int64_t ret = 0; + CallAll(s, "f", ret, 7); + ASSERT_EQ(ret, 1); + CallAll(s, "f", ret, 1, 2, 3); + ASSERT_EQ(ret, 3); +} + TEST(state, vm_register_and_get_function) { State s; ASSERT_TRUE(s.GetVM().GetFunction("absent").Empty()); diff --git a/test/test_syntax_tree.cpp b/test/test_syntax_tree.cpp index 4df3711..680c49b 100644 --- a/test/test_syntax_tree.cpp +++ b/test/test_syntax_tree.cpp @@ -808,8 +808,13 @@ TEST(syntax_tree, number) { " name: f\n" " (explist)[6:5]\n" " (exp)[6:5]\n" - " type: number\n" - " value: -2.234\n" + " type: unop\n" + " value: \n" + " (unop)[6:5]\n" + " op: MINUS\n" + " (exp)[6:6]\n" + " type: number\n" + " value: 2.234\n" " (assign)[7:3]\n" " (varlist)[7:1]\n" " (var)[7:1]\n" @@ -826,8 +831,13 @@ TEST(syntax_tree, number) { " name: h\n" " (explist)[8:5]\n" " (exp)[8:5]\n" - " type: number\n" - " value: -1.1e-10\n" + " type: unop\n" + " value: \n" + " (unop)[8:5]\n" + " op: MINUS\n" + " (exp)[8:6]\n" + " type: number\n" + " value: 1.1e-10\n" " (assign)[9:3]\n" " (varlist)[9:1]\n" " (var)[9:1]\n" @@ -853,8 +863,13 @@ TEST(syntax_tree, number) { " name: k\n" " (explist)[11:5]\n" " (exp)[11:5]\n" - " type: number\n" - " value: -0x3.1ap-4\n"; + " type: unop\n" + " value: \n" + " (unop)[11:5]\n" + " op: MINUS\n" + " (exp)[11:6]\n" + " type: number\n" + " value: 0x3.1ap-4\n"; ASSERT_EQ(dumpstr, wantstr); } diff --git a/test/test_yaml.cpp b/test/test_yaml.cpp index b5da486..7f75cd9 100644 --- a/test/test_yaml.cpp +++ b/test/test_yaml.cpp @@ -171,3 +171,16 @@ TEST(test_yaml, decode_cycle) { FakeluaDeleteState(s); } } + +TEST(test_yaml, decode_too_deep) { + for (auto jit_type: AllJitTypes()) { + State *s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig config; + CompileFile(s, "./yaml/test_yaml_basic.lua", config); + int64_t ret = 0; + Call(s, jit_type, "YamlTest.test_decode_too_deep", ret); + EXPECT_EQ(ret, 1); + FakeluaDeleteState(s); + } +} From 31a4ab0938dd4c4b6dd638fbf68228a1d6479ca0 Mon Sep 17 00:00:00 2001 From: esrrhs Date: Sat, 19 Sep 2026 00:58:49 +0800 Subject: [PATCH 2/2] fix: expect 1.0/0.0 C literals in typed-float infer tests Float source tokens now stay float in generated C, so the string checks must look for 1.0 and 0.0 instead of integer 1/0. Co-authored-by: Cursor --- test/test_infer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_infer.cpp b/test/test_infer.cpp index 8061198..c99920b 100644 --- a/test/test_infer.cpp +++ b/test/test_infer.cpp @@ -59,7 +59,7 @@ TEST(infer, test_infer_typed_int_for) { TEST(infer, test_infer_typed_float_local) { const auto code = InferGetCCode("./infer/test_infer_typed_float_local.lua"); // Both locals must be typed as double. - ASSERT_NE(code.find("double x = 1;"), std::string::npos); + ASSERT_NE(code.find("double x = 1.0;"), std::string::npos); ASSERT_NE(code.find("double y = ((x) + (0.5));"), std::string::npos); // No CVar declarations for x or y. ASSERT_EQ(code.find("CVar x"), std::string::npos); @@ -1294,7 +1294,7 @@ TEST(infer, test_native_bool_elseif) { TEST(infer, test_native_bool_float) { const auto code = InferGetCCode("./infer/test_native_bool_float.lua"); // float specialization: direct C comparison with >= and float literal. - ASSERT_NE(code.find("((x) >= (0)"), std::string::npos); + ASSERT_NE(code.find("((x) >= (0.0)"), std::string::npos); // No IsTrue or flua_ibt_ for the if condition. ASSERT_EQ(code.find("flua_ibt_"), std::string::npos);