From 5f98f5526dfd70f94fb8ab5a9536cca5b5681e96 Mon Sep 17 00:00:00 2001
From: esrrhs <esrrhs@163.com>
Date: Wed, 24 Jun 2026 18:41:07 +0800
Subject: [PATCH 1/3] feat: implement goto/label support

- Semantic analysis: replace rejection with proper scope validation
  - Goto targets must exist in visible scope
  - Goto cannot jump over local variable declarations
  - Recursive validation for nested blocks (if/while/repeat/for)
- CGen: add CompileStmtGoto and CompileStmtLabel
  - Labels emit C labels with flua_L_ prefix
  - Gotos emit C goto statements
- SyntaxTreeGoto: add GetLabel() method
- Remove old exception tests for unsupported goto/label
- Add 4 new test cases: forward skip, backward loop, label-only, invalid goto
---
 src/compile/c_gen.cpp              | 18 ++++++
 src/compile/c_gen.h                |  2 +
 src/compile/semantic_analysis.cpp  | 88 +++++++++++++++++++++++++++++-
 src/compile/semantic_analysis.h    |  2 +
 src/compile/syntax_tree.h          |  4 ++
 test/lua/jit/test_goto_basic.lua   | 23 ++++++++
 test/lua/jit/test_goto_invalid.lua |  7 +++
 test/test_exception.cpp            | 30 ----------
 test/test_jitter.cpp               | 40 ++++++++++++++
 9 files changed, 183 insertions(+), 31 deletions(-)
 create mode 100644 test/lua/jit/test_goto_basic.lua
 create mode 100644 test/lua/jit/test_goto_invalid.lua

diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp
index a5b35ae1..907b23e1 100644
--- a/src/compile/c_gen.cpp
+++ b/src/compile/c_gen.cpp
@@ -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);
     }
@@ -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);
diff --git a/src/compile/c_gen.h b/src/compile/c_gen.h
index bffef194..1467a2cf 100644
--- a/src/compile/c_gen.h
+++ b/src/compile/c_gen.h
@@ -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);
     // 编译条件布尔表达式（用于处理逻辑运算的短路特性与分支预测优化）
diff --git a/src/compile/semantic_analysis.cpp b/src/compile/semantic_analysis.cpp
index 0a750b20..ef8b8a64 100644
--- a/src/compile/semantic_analysis.cpp
+++ b/src/compile/semantic_analysis.cpp
@@ -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) {
@@ -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) {
diff --git a/src/compile/semantic_analysis.h b/src/compile/semantic_analysis.h
index 352cae65..7962119f 100644
--- a/src/compile/semantic_analysis.h
+++ b/src/compile/semantic_analysis.h
@@ -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);
diff --git a/src/compile/syntax_tree.h b/src/compile/syntax_tree.h
index 964a48d4..875c0114 100644
--- a/src/compile/syntax_tree.h
+++ b/src/compile/syntax_tree.h
@@ -722,6 +722,10 @@ class SyntaxTreeGoto final : public SyntaxTreeInterface {
         label_ = label;
     }
 
+    [[nodiscard]] const std::string &GetLabel() const {
+        return label_;
+    }
+
 private:
     std::string label_;
 };
diff --git a/test/lua/jit/test_goto_basic.lua b/test/lua/jit/test_goto_basic.lua
new file mode 100644
index 00000000..eefb325b
--- /dev/null
+++ b/test/lua/jit/test_goto_basic.lua
@@ -0,0 +1,23 @@
+-- goto/label 合法用法
+function test_goto_forward()
+    local x = 100
+    goto skip
+    x = 999
+    ::skip::
+    return x
+end
+
+function test_goto_backward()
+    local i = 0
+    ::loop::
+    i = i + 1
+    if i < 5 then
+        goto loop
+    end
+    return i
+end
+
+function test_label_no_goto()
+    ::unused::
+    return 42
+end
diff --git a/test/lua/jit/test_goto_invalid.lua b/test/lua/jit/test_goto_invalid.lua
new file mode 100644
index 00000000..5768336f
--- /dev/null
+++ b/test/lua/jit/test_goto_invalid.lua
@@ -0,0 +1,7 @@
+-- goto 跳过局部变量声明，应该报错
+function test_invalid()
+    goto my_label
+    local x = 1
+    ::my_label::
+    return x
+end
diff --git a/test/test_exception.cpp b/test/test_exception.cpp
index 7bda4776..5ab66f35 100644
--- a/test/test_exception.cpp
+++ b/test/test_exception.cpp
@@ -732,36 +732,6 @@ TEST(exception, test_const_unop_bitnot_error) {
 }
 
 
-TEST(exception, stmt_support_error) {
-    FakeluaStateGuard sg;
-    auto s = sg.GetState();
-    ASSERT_NE(s, nullptr);
-    SetDebugLogLevel(0);
-
-    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, label_support_error) {
-    FakeluaStateGuard sg;
-    auto s = sg.GetState();
-    ASSERT_NE(s, nullptr);
-    SetDebugLogLevel(0);
-
-    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, const_func_call_error) {
     FakeluaStateGuard sg;
     auto s = sg.GetState();
diff --git a/test/test_jitter.cpp b/test/test_jitter.cpp
index 71c2bf9d..65b12c1a 100644
--- a/test/test_jitter.cpp
+++ b/test/test_jitter.cpp
@@ -2885,3 +2885,43 @@ 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_basic.lua", {.debug_mode = debug_mode});
+        int64_t ret = 0;
+        Call(s, type, "test_goto_forward", ret);
+        ASSERT_EQ(ret, 100);
+    });
+}
+
+TEST(jitter, goto_backward_loop) {
+    JitterRunHelper([](State *s, JITType type, bool debug_mode) {
+        CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode});
+        int64_t ret = 0;
+        Call(s, type, "test_goto_backward", ret);
+        ASSERT_EQ(ret, 5);
+    });
+}
+
+TEST(jitter, goto_label_no_goto) {
+    JitterRunHelper([](State *s, JITType type, bool debug_mode) {
+        CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode});
+        int64_t ret = 0;
+        Call(s, type, "test_label_no_goto", ret);
+        ASSERT_EQ(ret, 42);
+    });
+}
+
+TEST(jitter, goto_invalid_jumps_over_local) {
+    JitterRunHelper([](State *s, JITType type, bool debug_mode) {
+        EXPECT_THROW(
+            CompileFile(s, "./jit/test_goto_invalid.lua", {.debug_mode = debug_mode}),
+            std::exception
+        );
+    });
+}

From 5ddabb0334728a1c9516b2d9875e43cd409ac42a Mon Sep 17 00:00:00 2001
From: esrrhs <esrrhs@163.com>
Date: Thu, 25 Jun 2026 10:19:08 +0800
Subject: [PATCH 2/3] refactor: split goto tests into individual files, add
 more scenarios

Valid goto tests (test/lua/jit/):
- test_goto_forward_skip: forward goto skipping a reassignment
- test_goto_backward_loop: backward goto forming a loop
- test_goto_label_only: label with no goto
- test_goto_continue: goto simulating continue in for loop
- test_goto_break_out: goto breaking out of nested for loops
- test_goto_in_if_else: goto within if/else branches

Invalid goto tests (test/lua/exception/):
- test_goto_skip_single_local: goto jumps over single local
- test_goto_skip_local: goto jumps over multiple locals
- test_goto_nonexistent_label: goto to nonexistent label
- test_goto_cross_function: goto targeting label in another function

Removed old combined test files.
---
 .../exception/test_goto_cross_function.lua    | 10 +++++
 .../exception/test_goto_nonexistent_label.lua |  5 +++
 test/lua/exception/test_goto_skip_local.lua   |  8 ++++
 .../exception/test_goto_skip_single_local.lua |  7 +++
 test/lua/jit/test_goto_backward_loop.lua      |  9 ++++
 test/lua/jit/test_goto_basic.lua              | 23 ----------
 test/lua/jit/test_goto_break_out.lua          | 14 ++++++
 test/lua/jit/test_goto_continue.lua           | 12 ++++++
 test/lua/jit/test_goto_forward_skip.lua       |  7 +++
 test/lua/jit/test_goto_in_if_else.lua         | 12 ++++++
 test/lua/jit/test_goto_invalid.lua            |  7 ---
 test/lua/jit/test_goto_label_only.lua         |  4 ++
 test/test_exception.cpp                       | 32 ++++++++++++++
 test/test_jitter.cpp                          | 43 +++++++++++++------
 14 files changed, 151 insertions(+), 42 deletions(-)
 create mode 100644 test/lua/exception/test_goto_cross_function.lua
 create mode 100644 test/lua/exception/test_goto_nonexistent_label.lua
 create mode 100644 test/lua/exception/test_goto_skip_local.lua
 create mode 100644 test/lua/exception/test_goto_skip_single_local.lua
 create mode 100644 test/lua/jit/test_goto_backward_loop.lua
 delete mode 100644 test/lua/jit/test_goto_basic.lua
 create mode 100644 test/lua/jit/test_goto_break_out.lua
 create mode 100644 test/lua/jit/test_goto_continue.lua
 create mode 100644 test/lua/jit/test_goto_forward_skip.lua
 create mode 100644 test/lua/jit/test_goto_in_if_else.lua
 delete mode 100644 test/lua/jit/test_goto_invalid.lua
 create mode 100644 test/lua/jit/test_goto_label_only.lua

diff --git a/test/lua/exception/test_goto_cross_function.lua b/test/lua/exception/test_goto_cross_function.lua
new file mode 100644
index 00000000..184c58c0
--- /dev/null
+++ b/test/lua/exception/test_goto_cross_function.lua
@@ -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
diff --git a/test/lua/exception/test_goto_nonexistent_label.lua b/test/lua/exception/test_goto_nonexistent_label.lua
new file mode 100644
index 00000000..49a814e2
--- /dev/null
+++ b/test/lua/exception/test_goto_nonexistent_label.lua
@@ -0,0 +1,5 @@
+-- goto 跳转到不存在的 label
+function test_goto_nonexistent_label()
+    goto nonexistent
+    return 1
+end
diff --git a/test/lua/exception/test_goto_skip_local.lua b/test/lua/exception/test_goto_skip_local.lua
new file mode 100644
index 00000000..75ba7fba
--- /dev/null
+++ b/test/lua/exception/test_goto_skip_local.lua
@@ -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
diff --git a/test/lua/exception/test_goto_skip_single_local.lua b/test/lua/exception/test_goto_skip_single_local.lua
new file mode 100644
index 00000000..7a3ea1bd
--- /dev/null
+++ b/test/lua/exception/test_goto_skip_single_local.lua
@@ -0,0 +1,7 @@
+-- goto 跳过局部变量声明（单个）
+function test_goto_skip_single_local()
+    goto my_label
+    local x = 1
+    ::my_label::
+    return x
+end
diff --git a/test/lua/jit/test_goto_backward_loop.lua b/test/lua/jit/test_goto_backward_loop.lua
new file mode 100644
index 00000000..b34bd0e7
--- /dev/null
+++ b/test/lua/jit/test_goto_backward_loop.lua
@@ -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
diff --git a/test/lua/jit/test_goto_basic.lua b/test/lua/jit/test_goto_basic.lua
deleted file mode 100644
index eefb325b..00000000
--- a/test/lua/jit/test_goto_basic.lua
+++ /dev/null
@@ -1,23 +0,0 @@
--- goto/label 合法用法
-function test_goto_forward()
-    local x = 100
-    goto skip
-    x = 999
-    ::skip::
-    return x
-end
-
-function test_goto_backward()
-    local i = 0
-    ::loop::
-    i = i + 1
-    if i < 5 then
-        goto loop
-    end
-    return i
-end
-
-function test_label_no_goto()
-    ::unused::
-    return 42
-end
diff --git a/test/lua/jit/test_goto_break_out.lua b/test/lua/jit/test_goto_break_out.lua
new file mode 100644
index 00000000..b15d7129
--- /dev/null
+++ b/test/lua/jit/test_goto_break_out.lua
@@ -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
diff --git a/test/lua/jit/test_goto_continue.lua b/test/lua/jit/test_goto_continue.lua
new file mode 100644
index 00000000..bc011c2e
--- /dev/null
+++ b/test/lua/jit/test_goto_continue.lua
@@ -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
diff --git a/test/lua/jit/test_goto_forward_skip.lua b/test/lua/jit/test_goto_forward_skip.lua
new file mode 100644
index 00000000..9e170eb7
--- /dev/null
+++ b/test/lua/jit/test_goto_forward_skip.lua
@@ -0,0 +1,7 @@
+function test_goto_forward_skip()
+    local x = 100
+    goto skip
+    x = 999
+    ::skip::
+    return x
+end
diff --git a/test/lua/jit/test_goto_in_if_else.lua b/test/lua/jit/test_goto_in_if_else.lua
new file mode 100644
index 00000000..abf419af
--- /dev/null
+++ b/test/lua/jit/test_goto_in_if_else.lua
@@ -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
diff --git a/test/lua/jit/test_goto_invalid.lua b/test/lua/jit/test_goto_invalid.lua
deleted file mode 100644
index 5768336f..00000000
--- a/test/lua/jit/test_goto_invalid.lua
+++ /dev/null
@@ -1,7 +0,0 @@
--- goto 跳过局部变量声明，应该报错
-function test_invalid()
-    goto my_label
-    local x = 1
-    ::my_label::
-    return x
-end
diff --git a/test/lua/jit/test_goto_label_only.lua b/test/lua/jit/test_goto_label_only.lua
new file mode 100644
index 00000000..d6553363
--- /dev/null
+++ b/test/lua/jit/test_goto_label_only.lua
@@ -0,0 +1,4 @@
+function test_label_no_goto()
+    ::unused::
+    return 42
+end
diff --git a/test/test_exception.cpp b/test/test_exception.cpp
index 5ab66f35..58536dac 100644
--- a/test/test_exception.cpp
+++ b/test/test_exception.cpp
@@ -732,6 +732,38 @@ TEST(exception, test_const_unop_bitnot_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);
+}
+
+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, 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);
+}
+
+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) {
     FakeluaStateGuard sg;
     auto s = sg.GetState();
diff --git a/test/test_jitter.cpp b/test/test_jitter.cpp
index 65b12c1a..64b248b0 100644
--- a/test/test_jitter.cpp
+++ b/test/test_jitter.cpp
@@ -2887,41 +2887,60 @@ TEST(jitter, test_const_complex_expr) {
 }
 
 // ============================================================================
-// goto/label 测试
+// goto/label 合法场景
 // ============================================================================
 
 TEST(jitter, goto_forward_skip) {
     JitterRunHelper([](State *s, JITType type, bool debug_mode) {
-        CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = debug_mode});
+        CompileFile(s, "./jit/test_goto_forward_skip.lua", {.debug_mode = debug_mode});
         int64_t ret = 0;
-        Call(s, type, "test_goto_forward", ret);
+        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_basic.lua", {.debug_mode = debug_mode});
+        CompileFile(s, "./jit/test_goto_backward_loop.lua", {.debug_mode = debug_mode});
         int64_t ret = 0;
-        Call(s, type, "test_goto_backward", ret);
+        Call(s, type, "test_goto_backward_loop", ret);
         ASSERT_EQ(ret, 5);
     });
 }
 
-TEST(jitter, goto_label_no_goto) {
+TEST(jitter, goto_label_only) {
     JitterRunHelper([](State *s, JITType type, bool debug_mode) {
-        CompileFile(s, "./jit/test_goto_basic.lua", {.debug_mode = 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_invalid_jumps_over_local) {
+TEST(jitter, goto_continue_simulation) {
     JitterRunHelper([](State *s, JITType type, bool debug_mode) {
-        EXPECT_THROW(
-            CompileFile(s, "./jit/test_goto_invalid.lua", {.debug_mode = debug_mode}),
-            std::exception
-        );
+        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);
     });
 }

From 44f546b26d7163437a6161d69957fcd4d09cb855 Mon Sep 17 00:00:00 2001
From: esrrhs <esrrhs@163.com>
Date: Thu, 25 Jun 2026 11:23:13 +0800
Subject: [PATCH 3/3] docs: remove goto/label from limitations list

---
 README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/README.md b/README.md
index 0d2be7d5..071a2ca2 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,6 @@ local z = (x + y) / 2.0
 ## 当前已知限制
 
 ### 语法限制
-- 不支持 `label` / `goto`
 - 泛型 `for in` 仅支持 `pairs()` / `ipairs()`
 - 脚本侧函数调用仅支持简单函数名调用（不支持复杂前缀表达式调用，如 `obj:method()` 需通过间接方式实现）
 
