16 KiB
16 KiB
Phase 32: Hakorune Static Analyzer - 実装計画(改訂版)
期間: 2025-10-20 → 2025-10-27 (7日間) 配置: selfhost/analyzer/(Ring-1: Meta/自己ホスト) 構造: 責務分離(driver/ast_scan/mir_scan/report)
Day 1: スケルトン作成 + README (2025-10-20)
🎯 目標
- ディレクトリ構造作成
- 4つのBoxスケルトン
- hako_module.toml設定
- README.md(責務/入出力/ENV)
タスク
ディレクトリ作成 (10分)
mkdir -p selfhost/analyzer/{boxes,smokes}
hako_module.toml作成 (20分)
# selfhost/analyzer/hako_module.toml
[package]
name = "selfhost.analyzer"
version = "0.1.0"
[exports]
driver = "boxes/driver_box.hako"
ast_scan = "boxes/ast_scan_box.hako"
mir_scan = "boxes/mir_scan_box.hako"
report = "boxes/report_box.hako"
[dependencies]
BoxHelpers = "selfhost.shared.box_helpers"
MirSchema = "selfhost.shared.mir.schema"
JsonEmit = "selfhost.shared.json.emit"
driver_box.hako スケルトン (30分)
# selfhost/analyzer/boxes/driver_box.hako
using selfhost.analyzer.mir_scan as MirScanBox
using selfhost.analyzer.report as ReportBox
static box DriverBox {
birth() {
# 空のbirth(static boxなので状態なし)
}
# 文字列入力→文字列出力
run_from_string(input, kind) {
# TODO: Day 2-3で実装
return "[DriverBox] Not implemented yet"
}
# MapBox入力→MapBox出力
run_from_box(input, kind) {
# TODO: Day 2-3で実装
local result
result = new MapBox()
return result
}
}
mir_scan_box.hako スケルトン (30分)
# selfhost/analyzer/boxes/mir_scan_box.hako
using selfhost.shared.box_helpers as BoxHelpers
using selfhost.shared.mir.schema as MirSchema
static box MirScanBox {
birth() {
# 空のbirth
}
# MIR JSON → 関数リスト抽出
extract_functions(mir_json) {
# TODO: Day 2で実装
local result
result = new ArrayBox()
return result
}
# 呼び出しグラフ構築
build_call_graph(mir_json) {
# TODO: Day 3で実装
local result
result = new MapBox()
return result
}
# BFS到達可能性分析
find_dead_code(call_graph, entry_points) {
# TODO: Day 4で実装
local result
result = new ArrayBox()
return result
}
}
report_box.hako スケルトン (20分)
# selfhost/analyzer/boxes/report_box.hako
static box ReportBox {
birth() {
# 空のbirth
}
# 診断サマリ文字列生成
generate_text_report(dead_functions, all_functions) {
# TODO: Day 5で実装
return "[ReportBox] Analysis complete"
}
# MapBox診断生成
generate_map_report(dead_functions, reachable_functions) {
# TODO: Day 5で実装
local result
result = new MapBox()
return result
}
}
ast_scan_box.hako スケルトン (10分)
# selfhost/analyzer/boxes/ast_scan_box.hako
static box AstScanBox {
birth() {
# 空のbirth
}
# AST走査(Phase 32外、将来実装)
scan(ast_json) {
return "[AstScanBox] Not implemented in Phase 32"
}
}
README.md作成 (40分)
# selfhost/analyzer - Hakorune Static Analyzer
## 責務
HakoruneでHakoruneコードを解析する静的解析ツール(Phase 32: デッドコード検出MVP)
## 入出力
### 入力
- MIR JSON文字列(Gate-C Strict前提)
- またはMapBox(MIR構造)
### 出力
- テキストレポート(診断サマリ)
- またはMapBox(構造化診断)
## ENV変数
```bash
# 追加チェックON
NYASH_ANALYZER_STRICT=1
# 将来の切替(既定=mir)
HAKO_ANALYZER_ENTRY=ast|mir
# Gate-C/AST Strict(既定ON)
NYASH_GATE_C_STRICT=1
NYASH_AST_STRICT=1
非対象
- 外部I/O(ファイル読み書き)
- プラグイン化(当面VM経由のみ)
- CLI(将来tools/に追加可能)
使用例
using selfhost.analyzer.driver as DriverBox
local mir_json = "{ ... }" // MIR JSON文字列
local result = DriverBox.run_from_string(mir_json, "mir")
print(result)
### 成果物
- ✅ selfhost/analyzer/hako_module.toml: 20行
- ✅ driver_box.hako: 30行
- ✅ mir_scan_box.hako: 40行
- ✅ report_box.hako: 25行
- ✅ ast_scan_box.hako: 15行
- ✅ README.md: 50行
- **合計**: 180行
### 完了基準
- [ ] 全ファイル作成完了
- [ ] hako_module.toml が正しく読み込まれる
- [ ] スケルトンBoxがインポート可能
---
## Day 2: MIR走査基盤(関数抽出) (2025-10-21)
### 🎯 目標
- extract_functions 実装
- MIR JSON → 関数リスト変換
- 基本的な走査パターン確立
### タスク
#### extract_functions 実装 (2時間)
```hako
# selfhost/analyzer/boxes/mir_scan_box.hako
extract_functions(mir_json) {
local functions_key
functions_key = "functions"
local has_key
has_key = mir_json.has(functions_key)
if has_key == 0 {
print("[MirScanBox] ERROR: Missing 'functions' key")
return null
}
local functions_array
functions_array = mir_json.get(functions_key)
local result
result = new ArrayBox()
local i
i = 0
loop(i < functions_array.size()) {
local func
func = functions_array.get(i)
local name
name = me.extract_function_name(func)
if name != null {
result.push(name)
}
i = i + 1
}
return result
}
extract_function_name(func_obj) {
local name_key
name_key = "name"
local has_name
has_name = func_obj.has(name_key)
if has_name == 0 {
return null
}
local name
name = func_obj.get(name_key)
local arity_key
arity_key = "arity"
local arity
arity = func_obj.get(arity_key)
# "Main.main/0" 形式
local slash
slash = "/"
local arity_str
arity_str = arity.to_string()
local qualified
qualified = name.concat(slash)
qualified = qualified.concat(arity_str)
return qualified
}
テストケース作成 (1時間)
- 簡単なMIR JSON(2関数)
- 関数リスト抽出確認
成果物
- ✅ mir_scan_box.hako拡張: 100行
- ✅ テストケース: 50行
- 合計: 150行
完了基準
- extract_functions が動作
- 実際のMIR JSONから関数リスト取得可能
Day 3: 呼び出しグラフ構築 (2025-10-22)
🎯 目標
- build_call_graph 実装
- mir_call命令から呼び出し先抽出
- caller → [callees] マップ生成
タスク
build_call_graph 実装 (3時間)
- 各関数のinstructionsをスキャン
- mir_call命令のcalleeを抽出
- MapBox(caller → ArrayBox)構築
Callee型パース実装 (1時間)
parse_callee(callee_obj) {
# "function:Main.helper/1"
# "extern:env.console.log"
# "constructor:ArrayBox"
local callee_type
callee_type = callee_obj.get("type")
if callee_type == "ModuleFunction" {
local module
module = callee_obj.get("module")
local func
func = callee_obj.get("function")
local arity
arity = callee_obj.get("arity")
local dot
dot = "."
local slash
slash = "/"
local result
result = module.concat(dot)
result = result.concat(func)
result = result.concat(slash)
local arity_str
arity_str = arity.to_string()
result = result.concat(arity_str)
return result
}
# TODO: Constructor/Extern対応
return null
}
成果物
- ✅ mir_scan_box.hako拡張: 150行
- ✅ テストケース: 100行
- 合計: 250行
完了基準
- build_call_graph が動作
- 実際のMIR JSONから呼び出しグラフ生成可能
Day 4: BFS到達可能性分析 (2025-10-23)
🎯 目標
- find_dead_code 実装
- BFS(幅優先探索)アルゴリズム
- デッドコード検出
タスク
find_dead_code 実装 (2時間)
find_dead_code(call_graph, entry_points) {
local reachable
reachable = me.mark_reachable(call_graph, entry_points)
local all_funcs
all_funcs = call_graph.keys()
local dead
dead = new ArrayBox()
local i
i = 0
loop(i < all_funcs.size()) {
local func
func = all_funcs.get(i)
local is_reachable
is_reachable = reachable.has(func)
if is_reachable == 0 {
dead.push(func)
}
i = i + 1
}
return dead
}
mark_reachable(graph, entry_points) {
local visited
visited = new MapBox() # Set代わり
local queue
queue = new ArrayBox() # 簡易キュー
# エントリポイント追加
local i
i = 0
loop(i < entry_points.size()) {
local entry
entry = entry_points.get(i)
queue.push(entry)
i = i + 1
}
# BFS
loop(queue.size() > 0) {
local current
current = queue.get(0)
# 簡易shift(先頭削除の代わりに新しいArrayBoxを作る)
local new_queue
new_queue = new ArrayBox()
local j
j = 1
loop(j < queue.size()) {
local item
item = queue.get(j)
new_queue.push(item)
j = j + 1
}
queue = new_queue
local already_visited
already_visited = visited.has(current)
if already_visited == 1 {
continue # ✅ Phase 12.7で実装済み
}
visited.set(current, 1)
local callees
callees = graph.get(current)
if callees != null {
local ci
ci = 0
loop(ci < callees.size()) {
local callee
callee = callees.get(ci)
local callee_visited
callee_visited = visited.has(callee)
if callee_visited == 0 {
queue.push(callee)
}
ci = ci + 1
}
}
}
return visited
}
成果物
- ✅ mir_scan_box.hako完成: 200行
- ✅ テストケース: 100行
- 合計: 300行
完了基準
- find_dead_code が動作
- BFS正確性確認(既知デッドコード検出)
Day 5: レポート生成 (2025-10-24)
🎯 目標
- generate_text_report 実装
- generate_map_report 実装
- 人間可読・機械可読両対応
タスク
generate_text_report 実装 (1.5時間)
generate_text_report(dead_functions, all_functions) {
local header
header = "Hakorune Dead Code Analyzer\n"
local separator
separator = "=============================\n"
local result
result = header.concat(separator)
local all_count
all_count = all_functions.size()
local all_str
all_str = all_count.to_string()
local all_line
all_line = "Functions found: "
all_line = all_line.concat(all_str)
all_line = all_line.concat("\n")
result = result.concat(all_line)
local dead_count
dead_count = dead_functions.size()
local dead_str
dead_str = dead_count.to_string()
local dead_line
dead_line = "Dead functions: "
dead_line = dead_line.concat(dead_str)
dead_line = dead_line.concat("\n")
result = result.concat(dead_line)
# 個別リスト表示
local i
i = 0
loop(i < dead_functions.size()) {
local func
func = dead_functions.get(i)
local prefix
prefix = " [DEAD] "
local line
line = prefix.concat(func)
line = line.concat("\n")
result = result.concat(line)
i = i + 1
}
return result
}
generate_map_report 実装 (1時間)
generate_map_report(dead_functions, reachable_functions) {
local result
result = new MapBox()
result.set("dead_functions", dead_functions)
result.set("reachable_functions", reachable_functions)
local dead_count
dead_count = dead_functions.size()
result.set("dead_count", dead_count)
local reachable_count
reachable_count = reachable_functions.size()
result.set("reachable_count", reachable_count)
return result
}
成果物
- ✅ report_box.hako完成: 120行
- 合計: 120行
完了基準
- レポートが見やすい
- 統計情報が正確
Day 6: スモークテスト整備 (2025-10-25)
🎯 目標
- 3本のスモークテスト作成
- happy path 2本 + 負例1本
- quick-lite/quick/integrationプロファイル対応
タスク
happy path スモーク1 (1時間)
# selfhost/analyzer/smokes/analyzer_mir_gatec_ok_vm.sh
#!/bin/bash
set -euo pipefail
# Gate-C MIR JSON入力(正常系)
MIR_JSON='{"functions":[{"name":"Main.main","arity":0,"blocks":[]}]}'
RESULT=$(NYASH_GATE_C_STRICT=1 ./target/release/hakorune -c "
using selfhost.analyzer.driver as DriverBox
local mir = \"$MIR_JSON\"
local result = DriverBox.run_from_string(mir, \"mir\")
print(result)
")
if echo "$RESULT" | grep -q "Functions found: 1"; then
echo "✅ PASS: analyzer_mir_gatec_ok_vm"
exit 0
else
echo "❌ FAIL: Expected 'Functions found: 1'"
exit 1
fi
負例スモーク (1時間)
# selfhost/analyzer/smokes/analyzer_bad_mir_fail_vm.sh
#!/bin/bash
set -euo pipefail
# 不正MIR: 余剰キー/型不一致
BAD_MIR='{"functions":[], "extra_key": "should fail"}'
set +e
RESULT=$(NYASH_GATE_C_STRICT=1 ./target/release/hakorune -c "
using selfhost.analyzer.driver as DriverBox
local mir = \"$BAD_MIR\"
local result = DriverBox.run_from_string(mir, \"mir\")
print(result)
" 2>&1)
EXIT_CODE=$?
set -e
if [ $EXIT_CODE -ne 0 ]; then
echo "✅ PASS: analyzer_bad_mir_fail_vm (expected failure)"
exit 0
else
echo "❌ FAIL: Should have failed on bad MIR"
exit 1
fi
プロファイル統合 (1時間)
- quick-lite: 負例のみ
- quick: 全3本
- integration: セルフホスト解析追加
成果物
- ✅ スモークテスト3本: 150行
- ✅ プロファイル設定: 30行
- 合計: 180行
完了基準
- 全スモークテストPASS
- quick-liteで負例Fail-Fast確認
Day 7: セルフホスト解析 + ドキュメント (2025-10-26)
🎯 目標
- セルフホストコードでの実用検証
- 精度95%以上確認
- ドキュメント整備
タスク
セルフホスト解析 (2時間)
# selfhost/compiler/builder/rewrite/known.hako を解析
NYASH_GATE_C_STRICT=1 ./target/release/hakorune --emit-mir-json /tmp/known.json \
apps/selfhost-compiler/builder/rewrite/known.hako
# Analyzerで解析
./target/release/hakorune -c "
using selfhost.analyzer.driver as DriverBox
# /tmp/known.json の内容を読み込み(簡易版)
local result = DriverBox.run_from_string(mir_json, \"mir\")
print(result)
"
ドキュメント整備 (1時間)
- Phase 32 README.md 更新(実装完了マーク)
- PLAN.md 実績LOC記録
精度検証 (30分)
- 手動でデッドコード候補確認
- 偽陽性/偽陰性チェック
成果物
- ✅ 実用検証レポート: 50行
- ✅ ドキュメント更新
- 合計: 50行
完了基準
- セルフホストコードで95%以上の精度
- ドキュメント完備
- 1,000行ファイルを5秒以内で解析
📊 進捗トラッキング
| Day | 計画LOC | 実績LOC | 状態 | 備考 |
|---|---|---|---|---|
| Day 1 | 180 | - | 🟡 | スケルトン + README |
| Day 2 | 150 | - | 🟡 | MIR走査基盤 |
| Day 3 | 250 | - | 🟡 | 呼び出しグラフ |
| Day 4 | 300 | - | 🟡 | BFS到達可能性 |
| Day 5 | 120 | - | 🟡 | レポート生成 |
| Day 6 | 180 | - | 🟡 | スモークテスト |
| Day 7 | 50 | - | 🟡 | セルフホスト解析 |
| Total | 1,230 | - |
🎯 成功基準(最終確認)
- 精度: >95%(セルフホストコードで手動検証)
- 再現率: >90%(既知デッドコード検出)
- 処理速度: <5秒(1,000行ファイル)
- 使いやすさ: 1コマンドで実行可能
- テストカバレッジ: 3件のスモークテスト全PASS
🚨 リスク & 対策
リスク1: selfhost/shared依存の複雑さ
- 対策: Day 1でimport確認、早期検証
- バックアップ: 簡易JSON解析を自前実装
リスク2: BFS パフォーマンス
- 対策: ArrayBox簡易キュー(小規模グラフで十分)
- バックアップ: 将来QueueBox追加可能
リスク3: セルフホスト解析で想定外の問題
- 対策: Day 7に余裕を持たせる
- バックアップ: 簡単な例で先に検証
作成日: 2025-10-19 改訂日: 2025-10-19(構造改訂版) 開始予定: 2025-10-20 完了予定: 2025-10-27