# 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分) ```bash mkdir -p selfhost/analyzer/{boxes,smokes} ``` #### hako_module.toml作成 (20分) ```toml # 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分) ```hako # 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分) ```hako # 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分) ```hako # 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分) ```hako # 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分) ```markdown # 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/に追加可能) ## 使用例 ```hako 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時間) ```hako 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時間) ```hako 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時間) ```hako 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時間) ```hako 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時間) ```bash # 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時間) ```bash # 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時間) ```bash # 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