Three-inspection (structural / type / behavior) static analysis for MoonBit programs, every toolchain file kind (.mbt / .mbtx / .mbti / .mbt.md / .mbtp) and state-machine tables
Dependencies
| 文件 | 解析 | actionable | 未定义名 | |
|---|---|---|---|---|
| 0.4.6 | 1029 | 320 | 49 | 17* |
| 0.4.7 | 1029 | 320 | 36 | 17 |
moon add riantr/moonbit_static_analysis@0.4.7// 用途一:修订一段 MoonBit(当前子集)程序
let result : @pipeline.PipelineResult = @pipeline.run(source, "main.mbt")
println(@pipeline.render_result(result))
// 用途二:审计一台状态机(机器表以纯数据进来)
let spec : @statecheck.MachineSpec = { name: "主体", states: [...], ... }
println(@statecheck.render(spec))| 鉴 | 包 | 职责 | 产出 |
|---|---|---|---|
| 结构 | src/walk | 赋值即绑定、未用/未定义/参数被改、常量折叠剪枝 | 绑定表 fn_bindings |
| 类型 | src/types | 注解即契约、推断、赋值/实参/条件检查(声明取自绑定表) | 签名表 sigs |
| 行为 | src/interp | 格上抽象解释、按签名调度、虚栈(方法表取自签名表) | 行为发现 |
| 合并 | src/pipeline | 同一缺陷的多鉴回声 → 一条报告,lens 并集 | render_result |
moon run src/cli # demo:6 个样例 × 三鉴=== undefined.mbt ===
2:10 - error: undefined variable 'missing' (UndefinedName) [structural+type+behavior]
in main() at undefined.mbt:4
in g at undefined.mbt:1
Summary: 1 finding(s) (before merge: structural 1, type 2, behavior 1)| 鉴 | 机器侧语义 | 检查内容 |
|---|---|---|
| 结构 | 状态即绑定("赋值即绑定"推广到机器表) | 任何状态都必须被至少一条迁移绑定(出或入);只出不进 → never entered;从初始位置的可达性闭包;终点必须可达(机器必须能完成) |
| 类型 | 驱动槽契约("注解即契约") | 每个触发必须归位已知槽、无空槽;Block 必须带理由——无路必须说出口,不能沉默 |
| 行为 | 轨迹抽象执行(虚栈) | 修习历程每一步都必须有触发承载;断链的轨迹步带入口帧→出错帧的虚栈 |
let spec : @statecheck.MachineSpec = { name: "主体", states: [...], ... }
let findings : Array[@report.Report] = @statecheck.audit(spec)
let text : String = @statecheck.render(spec) // 合并后的文本报告规格 | 计数
---|---
状态 | 034
迁移 | 053
触发→槽 | 049 → 8
无路组合(带理由) | 228
修习历程 | 031 站
已知设计(4 条,出处层:设计使然——0.2.0 审计已验证):
- state '无忆' is never entered: it appears only as a transition source
- state '无筹' is never entered: it appears only as a transition source
- state '无忆' is unreachable from the initial position '立位'
- state '无筹' is unreachable from the initial position '立位'
未预期发现:**0 条**moon run src/cli 末尾那台 pyroduct 形状示例机器 是本模块内嵌的 8 状态玩具机 (statecheck.pyroduct_flavored()),不是 pyroduct 的真表;它带 182:12 这类 行:列,那是 state_span() 按状态名哈希出来的稳定伪 span(同一名字永远同一坐标), 不是任何源文件的位置——别拿它去 pyroduct 里对行号。
let root = /* 已解析到的源码根目录 */
let prov = @sa.provenance_for("my/module@1.2.3", root)
let text = @sa.mermaid_states(verdicts, @sa.aggregate(verdicts), prov, 8, 25)
println(scan_artifact_name(prov, root))moonbit_static_analysis_0.4.7_0.1.20260920_20261009T155222Z.mmd
moonbitlang-core_0.1.20260920+7d59c7ec9_0.1.20260920_20261009T051925Z.mmd
tree_unknown_0.1.20260920_20261009T051511Z.mmd # 版本没找到| 字段 | 回答 | 来源 |
|---|---|---|
| package | 读的是什么 | 坐标,或解析后的目录名 |
| version | 它的哪个修订 | 目标自己的 moon.mod(moon.mod.json 也读) |
| moonbit | 是哪套工具链读的 | 子进程跑 moon version |
| stamp | 什么时候 | @async.now(),UTC |
moon 0.1.20260920 (914d7da 2026-09-20)本轮更正。 上一版这一节写着「CLI 根本没法打时间戳,因为唯一的时钟在 internal 包后面」。那是错的:moonbitlang/async 把它公开重导出了 (pub using @event_loop {now}),而且 provenance_for 从 0.4.5 起就在调 @async.now()。CLI 之所以写 unknown,只是因为它被塞了一个手写的空时间戳,而没有去调 那个本来就能用的函数。
moonx riantr/moonbit_static_analysis@latest <target> --mmd out/scan.mmd
moonx riantr/moonbit_static_analysis@latest <target> --mmd-autoMMD _mbcore_0.1.20260920+7d59c7ec9_0.1.20260920_20261009T132547Z.mmdsrc/core Span/Severity/Lens/Family(merge_group 契约)/Frame + Family::advice
src/report Report 结构与渲染(lens 并集标签、vst 框架)
src/sa 扫描入口 + aggregate() + mermaid_states()(对外 library)
src/lexer MoonBit 词法前端
src/parser MoonBit 语法 → ast(含 .mbtx 脚本的 import 块)
src/ast MoonBit 抽象语法
src/walk 结构鉴:绑定表 + 结构发现
src/types 类型鉴:Ty/Ty?/Sig 推断
src/interp 行为鉴:AbsVal 格 + 调度 + 虚栈
src/pipeline 组装:跑三鉴 + merge_group 合并
src/moonfiles 其余文件种类:.mbt.md 逐块三鉴 / .mbti 接口审计 / .mbtp 证明 lint
src/statecheck 用途二:通用机器表审计(MachineSpec 纯数据桥)
src/samples demo 样例(内嵌 MoonBit 源)
src/cli 可执行入口(程序 demo + 文件种类 demo + 示例机器审计)| 后缀 | 本项目静态分析 |
|---|---|
| .mbt | 三鉴程序分析 |
| .mbtx | 三鉴程序分析 + 导入块审计(条目文法 "path" [@alias] [*];重复路径报 FParse;导入清单随报告回显) |
| .mbti | 接口审计:畸形行 / 重复签名 / 未知类型引用。行文法与 moon info 实际输出一致,生成文件不会报畸形行——但它的未知类型检查完全没有跨文件感知,在生成文件上仍会误报(实测 moonbitlang/core 上 93 条,而那棵树 moon check --target all 是 0 错误通过的)。这类发现按「未验证」对待,不要当缺陷报给用户 |
| .mbt.md | literate:只分析工具链确实编译的围栏 —— mbt check / mbt test 及其 moonbit 写法;第二个词是 check 或 test 才使块成为活代码。行号对齐 .md 真实行。裸 mbt、裸 moonbit 与 nocheck 是展示块,跳过——按工具链实测,见 EXTENSIONS.md |
| .mbtp | 证明文件逻辑侧 lint(体内字符串常量、!/↔ 禁形、跨包调用、lemma 缺 proof_ensure)——不替代 moon prove |
| moon.mod / moon.pkg / workspace | 记录在案,不做静态分析(配置不是代码——官方也把两者放在 parser 的 moon_config 子包里,与 syntax / mbti_parser 并列而独立;理由见 EXTENSIONS.md「配置文件的边界」) |
moon check --target all --deny-warn # 0 错 0 警(js / native / wasm / wasm-gc)
moon test --deny-warn # wasm 192/192;js 与 wasm-gc 139/139
moon run src/cli # 程序 demo + 文件种类 demo + pyroduct 形状示例机器审计CI 只跑 --target js;上面「四个 target」是本地跑的结论,CI 不覆盖 native/wasm。
moon prove src/core --why3-config .why3.conf # 生成 19 个 VC 并交 cvc5/alt-ergo| 渠道 | 地址 |
|---|---|
| Gitee(主仓) | https://gitee.com/ren-yongxiang/moonbit_static_analysis.git |
| GitHub(镜像) | https://github.com/riantr/moonbit_static_analysis |
| mooncakes.io(包注册表) | https://mooncakes.io/docs/riantr/moonbit_static_analysis |
moonx riantr/moonbit_static_analysis@latest riantr/moonbit_doubleML@latestSCANNED 107 files under ...\ML\CI
CLEAN .mbti ...\CI/pof/pkg.generated.mbti
PARTIAL .mbt defects=2 notices=1 read up to line 36, 57 after it not shown ...\CI/pof/aggregator.mbt
12:3 - error: ... (UndefinedName) [behavior]
PARSED 32/107 files understood (29.9%); 75 more read only up to their first parse error
SUMMARY files=107 parsed=32 actionable=57 notices=211 unreliable=15759 withheld=6769 total=16027 (actionable+notices+unreliable = total; withheld ⊆ unreliable)| 目标 | 文件 | 解析 | actionable(旧 → 新) |
|---|---|---|---|
| riantr/moonbit_doubleML@latest → 0.113.0 | 180 | 73(40.5%) | 0 → 123 |
| 本仓库自身(.repos/0.3.5) | 39 | 22(56.4%) | 0 → 7 |
| moonbit_linalg_gpu | 8 | 4(50.0%) | 1 → 5 |
| ML/CI(--exclude _qa_verify) | 107 | 32(29.9%) | 11 → 57 |
与 moon check 的关系要说清楚:在 .mbt 上,本工具不与工具链的编译器 竞争——moon check 有真正的解析器和约 90 条内建告警,覆盖率恒为 100%, 实测 2.3 秒 / 128 条,且每条都带 file:line:col、源码片段与修复建议。本工具的 .mbt 前端只读一个子集:在 moonbitlang/core 上,882 个 .mbt 解析了 173 个(19.6%),而 .mbti 与 .mbt.md 是 100%。原先最大的单一原因 方法调用在 AST 里根本没有位置——ECall 的被调名是 String,所以 m.set(k, v) 无处安放,解析就停在那里——本轮已修(新增 EMethodCall 节点, 三个 lens 都接上),该阻塞从 34 降到 0。这才是诚实的头条数字,也是 PARSED 要印在第一行的原因。本工具能说「编译器做不到」的地方是 .mbti / .mbt.md / .mbtp 三类文件与跨包语义。早先版本在同一份 ML/CI 代码上报过 529 条「未使用局部」,而编译器报 8 条。 那不是两种意见,而是没解析的 struct 函数体里那些字段被当成了没被读的绑定。 现在它报不出来了——被前端截断的函数会整函数退出未使用局部 / 未使用参数 / 参数被改写这三族审计,因为「从未被读」是关于整个函数的结论,半个函数 支撑不了这个结论。仅此一项修复就把 moonbitlang/core 从 270 条 actionable 降到 147 条,把本仓库从 11 条降到 0 条。再往下读,同一条原则还适用于模块帧,也适用于一类解析器从没学过怎么解析的 名字:前端没读完的文件,模块帧是可证不完整的,所以那里「找不到可见绑定」 什么也不能证明;而 Type::member 路径(@debug.Repr::opaque_(v))是成员引用 不是绑定,跟字段名、方法名本来的处理一致。再把 extern "c" fn 当成声明建模 (原先整条跳过,名字就丢了,于是每个 extern 函数的调用点都报未绑定),补上数值 字面量与 enum 构造器,moonbitlang/core 从 270 条降到 49 条,undefined name 从 64 条降到 17 条(发布 0.4.6 时误记为 0,与该版扫描产物不符,后按实测勘正), 本仓库仍是 0;0.4.7 再把 .mbti 审计的未知类型检查接到模块级接口符号表上, 其余 13 条误报清零,降到 36。
moonbitlang/core 文件 解析 actionable 未定义名 0.4.5 1029 305 270 64 0.4.6 1029 320 49 17 0.4.7 1029 320 36 17
Install
Download zipThree-inspection (structural / type / behavior) static analysis for MoonBit programs, every toolchain file kind (.mbt / .mbtx / .mbti / .mbt.md / .mbtp) and state-machine tables
Dependencies