Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ local z = (x + y) / 2.0
## 当前已知限制

### 语法限制
- 不支持 `label` / `goto`
- 泛型 `for in` 仅支持 `pairs()` / `ipairs()`
- 脚本侧函数调用仅支持简单函数名调用(不支持复杂前缀表达式调用,如 `obj:method()` 需通过间接方式实现)

Expand Down
18 changes: 18 additions & 0 deletions src/compile/c_gen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,12 @@ void CGen::CompileStmt(const SyntaxTreeInterfacePtr &stmt) {
break;
case SyntaxTreeType::Empty:
break;
case SyntaxTreeType::Goto:
CompileStmtGoto(stmt);
break;
case SyntaxTreeType::Label:
CompileStmtLabel(stmt);
break;
default:
ThrowError(std::format("not support stmt type: {}", SyntaxTreeTypeToString(stmt->Type())), stmt);
}
Expand Down Expand Up @@ -1207,6 +1213,18 @@ void CGen::CompileStmtBreak(const SyntaxTreeInterfacePtr &stmt) {
Out() << GenTab() << "break;\n";
}

void CGen::CompileStmtGoto(const SyntaxTreeInterfacePtr &stmt) {
DEBUG_ASSERT(stmt->Type() == SyntaxTreeType::Goto);
const auto goto_stmt = std::dynamic_pointer_cast<SyntaxTreeGoto>(stmt);
Out() << GenTab() << "goto flua_L_" << goto_stmt->GetLabel() << ";\n";
}

void CGen::CompileStmtLabel(const SyntaxTreeInterfacePtr &stmt) {
DEBUG_ASSERT(stmt->Type() == SyntaxTreeType::Label);
const auto label_stmt = std::dynamic_pointer_cast<SyntaxTreeLabel>(stmt);
Out() << "flua_L_" << label_stmt->GetName() << ": ;\n";
}

void CGen::CompileStmtForLoop(const SyntaxTreeInterfacePtr &stmt) {
DEBUG_ASSERT(stmt->Type() == SyntaxTreeType::ForLoop);
const auto for_stmt = std::dynamic_pointer_cast<SyntaxTreeForLoop>(stmt);
Expand Down
2 changes: 2 additions & 0 deletions src/compile/c_gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ class CGen {
void CompileDynamicForLoop(const std::shared_ptr<SyntaxTreeForLoop> &for_stmt);
// 编译泛型 for-in 遍历循环(如 ipairs/pairs 循环)
void CompileStmtForIn(const SyntaxTreeInterfacePtr &stmt);
void CompileStmtGoto(const SyntaxTreeInterfacePtr &stmt);
void CompileStmtLabel(const SyntaxTreeInterfacePtr &stmt);
// 编译具有独立原生强类型作用域的局部的花括号作用域块
void CompileScopedBlock(const SyntaxTreeInterfacePtr &block);
// 编译条件布尔表达式(用于处理逻辑运算的短路特性与分支预测优化)
Expand Down
88 changes: 87 additions & 1 deletion src/compile/semantic_analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ void SemanticAnalysis::CheckUnsupportedSyntax(const SyntaxTreeInterfacePtr &chun
}

WalkSyntaxTree(chunk, [this, &ar](const SyntaxTreeInterfacePtr &node) { CheckNode(node, ar); });
std::unordered_map<std::string, SyntaxTreeInterfacePtr> visible_labels;
ValidateGotoInBlock(chunk, visible_labels);
}

void SemanticAnalysis::CheckNode(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar) {
Expand Down Expand Up @@ -338,7 +340,91 @@ void SemanticAnalysis::CheckNode(const SyntaxTreeInterfacePtr &node, const Analy
}

void SemanticAnalysis::CheckGotoOrLabel(const SyntaxTreeInterfacePtr &node) {
ThrowError(node->Type() == SyntaxTreeType::Goto ? "goto is not supported" : "label is not supported", node);
// 不再直接拒绝,具体验证在 ValidateGotoInBlock 中完成
}

void SemanticAnalysis::CollectBlockLabels(const SyntaxTreeInterfacePtr &block, std::unordered_map<std::string, SyntaxTreeInterfacePtr> &labels) {
const auto blk = std::dynamic_pointer_cast<SyntaxTreeBlock>(block);
if (!blk) return;
for (const auto &stmt : blk->Stmts()) {
if (stmt->Type() == SyntaxTreeType::Label) {
const auto label = std::dynamic_pointer_cast<SyntaxTreeLabel>(stmt);
labels[label->GetName()] = stmt;
}
}
}

void SemanticAnalysis::ValidateGotoInBlock(const SyntaxTreeInterfacePtr &chunk, std::unordered_map<std::string, SyntaxTreeInterfacePtr> &visible_labels) {
const auto blk = std::dynamic_pointer_cast<SyntaxTreeBlock>(chunk);
if (!blk) return;

// 收集当前 block 自身的 label 并加入可见集合
CollectBlockLabels(chunk, visible_labels);

// 收集当前 block 的局部变量声明位置
std::vector<size_t> local_positions;
for (size_t i = 0; i < blk->Stmts().size(); ++i) {
if (blk->Stmts()[i]->Type() == SyntaxTreeType::LocalVar) {
local_positions.push_back(i);
}
}

// 检查 goto
for (size_t i = 0; i < blk->Stmts().size(); ++i) {
const auto &stmt = blk->Stmts()[i];
if (stmt->Type() == SyntaxTreeType::Goto) {
const auto goto_stmt = std::dynamic_pointer_cast<SyntaxTreeGoto>(stmt);
const auto &target_name = goto_stmt->GetLabel();
auto it = visible_labels.find(target_name);
if (it == visible_labels.end()) {
ThrowError(std::format("goto target '{}' not found", target_name), stmt);
}
// 检查 label 是否在当前 block 内(若在,则检查是否跳过局部变量)
size_t label_pos = blk->Stmts().size();
for (size_t j = 0; j < blk->Stmts().size(); ++j) {
if (blk->Stmts()[j].get() == it->second.get()) {
label_pos = j;
break;
}
}
if (i < label_pos && label_pos < blk->Stmts().size()) {
for (auto lp : local_positions) {
if (lp > i && lp <= label_pos) {
ThrowError(std::format("goto '{}' jumps over local variable declaration", target_name), stmt);
}
}
}
}
}

// 递归检查嵌套 block,传递可见 label 集合
for (const auto &stmt : blk->Stmts()) {
if (stmt->Type() == SyntaxTreeType::Block) {
ValidateGotoInBlock(stmt, visible_labels);
} else if (stmt->Type() == SyntaxTreeType::While) {
ValidateGotoInBlock(std::dynamic_pointer_cast<SyntaxTreeWhile>(stmt)->Block(), visible_labels);
} else if (stmt->Type() == SyntaxTreeType::Repeat) {
ValidateGotoInBlock(std::dynamic_pointer_cast<SyntaxTreeRepeat>(stmt)->Block(), visible_labels);
} else if (stmt->Type() == SyntaxTreeType::If) {
const auto if_stmt = std::dynamic_pointer_cast<SyntaxTreeIf>(stmt);
ValidateGotoInBlock(if_stmt->Block(), visible_labels);
if (if_stmt->ElseBlock()) ValidateGotoInBlock(if_stmt->ElseBlock(), visible_labels);
} else if (stmt->Type() == SyntaxTreeType::ForLoop) {
ValidateGotoInBlock(std::dynamic_pointer_cast<SyntaxTreeForLoop>(stmt)->Block(), visible_labels);
} else if (stmt->Type() == SyntaxTreeType::ForIn) {
ValidateGotoInBlock(std::dynamic_pointer_cast<SyntaxTreeForIn>(stmt)->Block(), visible_labels);
} else if (stmt->Type() == SyntaxTreeType::Function) {
std::unordered_map<std::string, SyntaxTreeInterfacePtr> func_labels;
const auto fb = std::dynamic_pointer_cast<SyntaxTreeFuncbody>(
std::dynamic_pointer_cast<SyntaxTreeFunction>(stmt)->Funcbody());
if (fb) ValidateGotoInBlock(fb->Block(), func_labels);
} else if (stmt->Type() == SyntaxTreeType::LocalFunction) {
std::unordered_map<std::string, SyntaxTreeInterfacePtr> func_labels;
const auto fb = std::dynamic_pointer_cast<SyntaxTreeFuncbody>(
std::dynamic_pointer_cast<SyntaxTreeLocalFunction>(stmt)->Funcbody());
if (fb) ValidateGotoInBlock(fb->Block(), func_labels);
}
}
}

void SemanticAnalysis::CheckFunctionCall(const SyntaxTreeInterfacePtr &node) {
Expand Down
2 changes: 2 additions & 0 deletions src/compile/semantic_analysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class SemanticAnalysis {
void CheckUnsupportedSyntax(const SyntaxTreeInterfacePtr &chunk, const AnalysisResult &ar);
void CheckNode(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar);
void CheckGotoOrLabel(const SyntaxTreeInterfacePtr &node);
void ValidateGotoInBlock(const SyntaxTreeInterfacePtr &chunk, std::unordered_map<std::string, SyntaxTreeInterfacePtr> &visible_labels);
void CollectBlockLabels(const SyntaxTreeInterfacePtr &block, std::unordered_map<std::string, SyntaxTreeInterfacePtr> &labels);
void CheckFunctionCall(const SyntaxTreeInterfacePtr &node);
void CheckParList(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar);
void CheckLocalVar(const SyntaxTreeInterfacePtr &node, const AnalysisResult &ar);
Expand Down
4 changes: 4 additions & 0 deletions src/compile/syntax_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,10 @@ class SyntaxTreeGoto final : public SyntaxTreeInterface {
label_ = label;
}

[[nodiscard]] const std::string &GetLabel() const {
return label_;
}

private:
std::string label_;
};
Expand Down
10 changes: 10 additions & 0 deletions test/lua/exception/test_goto_cross_function.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- goto 跳转到另一个函数的 label(不可见)
function test_goto_cross_function_a()
goto cross_label
return 1
end

function test_goto_cross_function_b()
::cross_label::
return 2
end
5 changes: 5 additions & 0 deletions test/lua/exception/test_goto_nonexistent_label.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- goto 跳转到不存在的 label
function test_goto_nonexistent_label()
goto nonexistent
return 1
end
8 changes: 8 additions & 0 deletions test/lua/exception/test_goto_skip_local.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- goto 跳过多个局部变量
function test_goto_skip_multiple_locals()
goto my_label
local a = 1
local b = 2
::my_label::
return a
end
7 changes: 7 additions & 0 deletions test/lua/exception/test_goto_skip_single_local.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- goto 跳过局部变量声明(单个)
function test_goto_skip_single_local()
goto my_label
local x = 1
::my_label::
return x
end
9 changes: 9 additions & 0 deletions test/lua/jit/test_goto_backward_loop.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function test_goto_backward_loop()
local i = 0
::loop::
i = i + 1
if i < 5 then
goto loop
end
return i
end
14 changes: 14 additions & 0 deletions test/lua/jit/test_goto_break_out.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- goto 跳出多层嵌套循环
function test_goto_break_out()
local result = 0
for i = 1, 10 do
for j = 1, 10 do
if i * j > 20 then
result = i * j
goto done
end
end
end
::done::
return result
end
12 changes: 12 additions & 0 deletions test/lua/jit/test_goto_continue.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- goto 模拟 continue:跳过循环体剩余部分
function test_goto_continue()
local sum = 0
for i = 1, 10 do
if i % 2 == 0 then
goto continue
end
sum = sum + i
::continue::
end
return sum
end
7 changes: 7 additions & 0 deletions test/lua/jit/test_goto_forward_skip.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
function test_goto_forward_skip()
local x = 100
goto skip
x = 999
::skip::
return x
end
12 changes: 12 additions & 0 deletions test/lua/jit/test_goto_in_if_else.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- goto 在 if/else 分支中
function test_goto_in_if_else()
local x = 0
if x == 0 then
goto skip
x = 99
else
x = 50
end
::skip::
return x
end
4 changes: 4 additions & 0 deletions test/lua/jit/test_goto_label_only.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function test_label_no_goto()
::unused::
return 42
end
34 changes: 18 additions & 16 deletions test/test_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,34 +732,36 @@ TEST(exception, test_const_unop_bitnot_error) {
}


TEST(exception, stmt_support_error) {
TEST(exception, goto_skip_local) {
FakeluaStateGuard sg;
auto s = sg.GetState();
ASSERT_NE(s, nullptr);
SetDebugLogLevel(0);
EXPECT_THROW(CompileFile(s, "./exception/test_goto_skip_single_local.lua", {}), std::exception);
}

try {
CompileFile(s, "./exception/test_stmt_support_error.lua", {});
ASSERT_TRUE(false);
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
ASSERT_TRUE(std::string(e.what()).find("goto is not supported") != std::string::npos);
}
TEST(exception, goto_skip_multiple_locals) {
FakeluaStateGuard sg;
auto s = sg.GetState();
ASSERT_NE(s, nullptr);
SetDebugLogLevel(0);
EXPECT_THROW(CompileFile(s, "./exception/test_goto_skip_local.lua", {}), std::exception);
}

TEST(exception, label_support_error) {
TEST(exception, goto_nonexistent_label) {
FakeluaStateGuard sg;
auto s = sg.GetState();
ASSERT_NE(s, nullptr);
SetDebugLogLevel(0);
EXPECT_THROW(CompileFile(s, "./exception/test_goto_nonexistent_label.lua", {}), std::exception);
}

try {
CompileFile(s, "./exception/test_label_support_error.lua", {});
ASSERT_TRUE(false);
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
ASSERT_TRUE(std::string(e.what()).find("label is not supported") != std::string::npos);
}
TEST(exception, goto_cross_function) {
FakeluaStateGuard sg;
auto s = sg.GetState();
ASSERT_NE(s, nullptr);
SetDebugLogLevel(0);
EXPECT_THROW(CompileFile(s, "./exception/test_goto_cross_function.lua", {}), std::exception);
}

TEST(exception, const_func_call_error) {
Expand Down
59 changes: 59 additions & 0 deletions test/test_jitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2885,3 +2885,62 @@ TEST(jitter, test_const_complex_expr) {
ASSERT_EQ(t->Get(key_val).GetInt(), -20);
});
}

// ============================================================================
// goto/label 合法场景
// ============================================================================

TEST(jitter, goto_forward_skip) {
JitterRunHelper([](State *s, JITType type, bool debug_mode) {
CompileFile(s, "./jit/test_goto_forward_skip.lua", {.debug_mode = debug_mode});
int64_t ret = 0;
Call(s, type, "test_goto_forward_skip", ret);
ASSERT_EQ(ret, 100);
});
}

TEST(jitter, goto_backward_loop) {
JitterRunHelper([](State *s, JITType type, bool debug_mode) {
CompileFile(s, "./jit/test_goto_backward_loop.lua", {.debug_mode = debug_mode});
int64_t ret = 0;
Call(s, type, "test_goto_backward_loop", ret);
ASSERT_EQ(ret, 5);
});
}

TEST(jitter, goto_label_only) {
JitterRunHelper([](State *s, JITType type, bool debug_mode) {
CompileFile(s, "./jit/test_goto_label_only.lua", {.debug_mode = debug_mode});
int64_t ret = 0;
Call(s, type, "test_label_no_goto", ret);
ASSERT_EQ(ret, 42);
});
}

TEST(jitter, goto_continue_simulation) {
JitterRunHelper([](State *s, JITType type, bool debug_mode) {
CompileFile(s, "./jit/test_goto_continue.lua", {.debug_mode = debug_mode});
int64_t ret = 0;
Call(s, type, "test_goto_continue", ret);
// 奇数之和 1+3+5+7+9 = 25
ASSERT_EQ(ret, 25);
});
}

TEST(jitter, goto_break_out_of_nested_loops) {
JitterRunHelper([](State *s, JITType type, bool debug_mode) {
CompileFile(s, "./jit/test_goto_break_out.lua", {.debug_mode = debug_mode});
int64_t ret = 0;
Call(s, type, "test_goto_break_out", ret);
ASSERT_EQ(ret, 21);
});
}

TEST(jitter, goto_in_if_else) {
JitterRunHelper([](State *s, JITType type, bool debug_mode) {
CompileFile(s, "./jit/test_goto_in_if_else.lua", {.debug_mode = debug_mode});
int64_t ret = 0;
Call(s, type, "test_goto_in_if_else", ret);
ASSERT_EQ(ret, 0);
});
}
Loading