mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
## 问题(实测) `channels.lua` 的主 chunk **顶到 Lua 上限 200 个 local** —— 往文件顶部加**哪怕 1 个**顶层 local 就报: `main function has more than 200 local variables` 实测(LuaJIT 与 luac 5.4 一致,**响亮失败**,不是静默降级)。 后果不是"某个功能出错",而是**整个渠道插件编译失败 ⇒ 全渠道不可用**。 换句话说:这个文件此前**一个顶层 local 都加不了**,任何后续改动都会立刻炸。 ## 修法 把 20 个**纯常量**(字符串/数字/自闭合字面量)从裸 `local` 收进 `plugin.K` 表 —— **挂表字段不占 local 槽位**。只搬常量,**函数一律保留 local**: 函数之间存在真实的调用顺序依赖(前向引用),搬迁风险与收益不成比例。 搬迁是纯机械的,且脚本带**长字符串保护**:替换时逐行跳过 `[===[ ... ]===]` 区间,绝不改动适配器源码文本 (本文件里那 7 段长字符串是适配器代码,误改会直接弄坏适配器)。 ### 实测结果 | 指标 | 前 | 后 | |---|---|---| | 加 1 个顶层 local | ✗ 编译失败 | ✓ 通过 | | 加 10 / 20 个 | ✗ | ✓ 通过 | | 字节数 | 589195 | 590042 | ## 判据(2 条 + 变异 2/2) - `TestChannelsLuaHasLocalHeadroom`:注入 1 与 10 个顶层 local 后**仍能编译**。 为什么用 **luajit** 而不是 `luac`:内核链接 `libluajit-5.1.so.2`, 而 luac(Lua 5.4)对同一文件给出**不同结论**,用错工具会得到假绿。 为什么不"数一遍 local":主 chunk 的 local 数受作用域嵌套影响, 静态计数不可靠(同一文件被不同脚本数出 228/232/248 三个数); 而"能不能再加"是**决定性**的,也正是我们需要的性质。 - `TestChannelsConstantsLiveInPluginK`(配对):抽样断言这些常量确实在 `plugin.K` 内、且**没有**变回裸 local —— 否则余量会再次归零。 - 变异:用重构前的文件 → 报红;把常量改回裸 local → 报红。 ## ★ 顺带更正两处我先前的错误说法(本轮实测推翻) 1. **不是静默失败**。我先前说"超出的 local 静默退化为全局" —— 错。 LuaJIT 与 luac 都在第 201 个报**编译错误**。危险度远低于我所说。 2. **`adaptersDir` 为 nil 的真实原因不是"local 被顶掉"** —— 它定义在 **`ZCODE_ADAPTER_SRC` 长字符串内部**(适配器源码文本), 在**适配器沙箱**里执行,而那里没有 `__llmsproxy_plugin_dir`、没有文件系统。 所以 zcode 的 `loadAnthropic` 必然返回 nil、BASE=nil (它自写了全部转换所以功能没问题,但"复用 anthropic、协议不漂移"的 设计意图从未生效)。 ⇒ **教训**:读 10000+ 行的 Lua 文件时,先扫出所有长字符串区间; 落在里面的"函数定义"只是文本,不属于插件作用域。
74 lines
2.9 KiB
Go
74 lines
2.9 KiB
Go
package core
|
||
|
||
import (
|
||
"os/exec"
|
||
"regexp"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// ★ channels.lua 必须留出顶层 local 余量 —— 否则**下一次顶层改动就编译失败**。
|
||
//
|
||
// 实测事实(2026-10-16):该文件的主 chunk 曾**顶到 Lua 上限 200**,
|
||
// 往顶部加**哪怕 1 个**顶层 local 就报
|
||
//
|
||
// main function has more than 200 local variables
|
||
//
|
||
// 而这炸的不是某个功能 —— 是**整个渠道插件**(插件加载失败 ⇒ 全渠道不可用)。
|
||
//
|
||
// ⚠️ 判据为什么用 luajit 而不是 luac:
|
||
//
|
||
// 内核链接的是 libluajit-5.1.so.2;而 `luac`(Lua 5.4)对同一文件给出
|
||
// **不同的**结论。用错工具会得到假绿。
|
||
//
|
||
// ⚠️ 判据为什么是「注入 N 个 local 仍能编译」而不是「数一遍 local」:
|
||
//
|
||
// 主 chunk 的 local 数受**作用域嵌套**影响,静态计数不可靠(同一个文件
|
||
// 用不同脚本数出 228/232/248 三个数)。而"能不能再加"是**决定性**的,
|
||
// 也正是我们真正需要的性质。
|
||
func TestChannelsLuaHasLocalHeadroom(t *testing.T) {
|
||
src, err := readBundledChannels(t)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := exec.LookPath("luajit"); err != nil {
|
||
t.Skip("luajit 不在 PATH(判据需要与内核相同的 Lua 实现)")
|
||
}
|
||
|
||
const headroom = 10
|
||
for _, n := range []int{1, headroom} {
|
||
probe := strings.Repeat("local __headroom = 1\n", n) + src
|
||
cmd := exec.Command("luajit", "-e", "local f,e=loadfile(arg[1]); if not f then io.stderr:write(tostring(e)) os.exit(1) end", "/dev/stdin")
|
||
cmd.Stdin = strings.NewReader(probe)
|
||
if out, err := cmd.CombinedOutput(); err != nil {
|
||
t.Fatalf("★ 顶层 local 余量不足:加 %d 个 local 就无法编译 ⇒ "+
|
||
"下一次顶层改动会**整插件加载失败**(全渠道不可用)\n %s\n"+
|
||
"修法:把纯常量收进 plugin.K 表(挂表字段不占 local 槽位)", n, strings.TrimSpace(string(out)))
|
||
}
|
||
}
|
||
t.Logf("✓ channels.lua 仍有 ≥%d 个顶层 local 余量", headroom)
|
||
}
|
||
|
||
// 配对判据:常量必须确实在 plugin.K 里(防止有人把余量"省"出来的同时又
|
||
// 把常量改回裸 local —— 那会让下一个改动的余量再次归零)。
|
||
func TestChannelsConstantsLiveInPluginK(t *testing.T) {
|
||
src, err := readBundledChannels(t)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
s := string(src)
|
||
if !strings.Contains(s, "plugin.K = {") {
|
||
t.Fatal("必须有 plugin.K 常量表(它是 local 余量的来源)")
|
||
}
|
||
// 抽样:这些曾占用 local 槽位的纯常量必须已挂表
|
||
for _, n := range []string{"STORE_NAME", "SESSION_TTL", "B64_ALPHABET", "MODELS_DEV_URL"} {
|
||
if !regexp.MustCompile(`(?m)^\s+` + n + ` = `).MatchString(s) {
|
||
t.Errorf("%s 应定义在 plugin.K 表内", n)
|
||
}
|
||
if regexp.MustCompile(`(?m)^local ` + n + ` = `).MatchString(s) {
|
||
t.Errorf("%s 又变回裸 local 了(会吃掉 local 余量)", n)
|
||
}
|
||
}
|
||
t.Log("✓ 纯常量已集中在 plugin.K")
|
||
}
|