From 375edb2a1ba0a46efc7f3dc0a9c2e268b1fc48be Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Sat, 8 Nov 2025 04:17:38 +0900 Subject: [PATCH] HC021 implementation complete: Analyzer IO Safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Implementation: - tools/hako_check/rules/rule_analyzer_io_safety.hako: New rule detecting direct I/O in analyzer rules - Detects: new FileBox(), .open(), .read(), .write(), network I/O - CLI-internal push approach: rules should receive data through parameters - Successfully detects FileBox usage in rule_dead_methods.hako:13-14 ✅ Tests: - tools/hako_check/tests/HC021_analyzer_io_safety/: Complete test suite - ok.hako: Safe rule using CLI-internal push approach - ng.hako: Unsafe rule using direct FileBox I/O (3 warnings expected) - Test passes with all diagnostics matching ✅ Integration: - tools/hako_check/cli.hako: Added HC021 rule invocation - All existing tests (HC011-HC022) remain green 🎯 Achievement: - Priority task completed as requested - Validates "CLI内push案" (CLI-internal push approach) design - Encourages safer analyzer rule development 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude --- tools/hako_check/cli.hako | 4 + .../rules/rule_analyzer_io_safety.hako | 87 +++++++++++++++++++ .../HC021_analyzer_io_safety/expected.json | 13 +++ .../tests/HC021_analyzer_io_safety/ng.hako | 21 +++++ .../tests/HC021_analyzer_io_safety/ok.hako | 42 +++++++++ 5 files changed, 167 insertions(+) create mode 100644 tools/hako_check/rules/rule_analyzer_io_safety.hako create mode 100644 tools/hako_check/tests/HC021_analyzer_io_safety/expected.json create mode 100644 tools/hako_check/tests/HC021_analyzer_io_safety/ng.hako create mode 100644 tools/hako_check/tests/HC021_analyzer_io_safety/ok.hako diff --git a/tools/hako_check/cli.hako b/tools/hako_check/cli.hako index d0766003..265e2ea6 100644 --- a/tools/hako_check/cli.hako +++ b/tools/hako_check/cli.hako @@ -15,6 +15,8 @@ using tools.hako_check.rules.rule_missing_entrypoint as RuleMissingEntrypointBox using tools.hako_check.rules.rule_top_level_local as RuleTopLevelLocalBox using tools.hako_check.rules.rule_arity_mismatch as RuleArityMismatchBox using tools.hako_check.rules.rule_stage3_gate as RuleStage3GateBox +using tools.hako_check.rules.rule_brace_heuristics as RuleBraceHeuristicsBox +using tools.hako_check.rules.rule_analyzer_io_safety as RuleAnalyzerIoSafetyBox using tools.hako_check.render.graphviz as GraphvizRenderBox using tools.hako_parser.parser_core as HakoParserCoreBox @@ -84,6 +86,8 @@ static box HakoAnalyzerBox { RuleJsonfragUsageBox.apply(text, p, out) RuleTopLevelLocalBox.apply(text, p, out) RuleStage3GateBox.apply(text, p, out) + RuleBraceHeuristicsBox.apply(text, p, out) + RuleAnalyzerIoSafetyBox.apply(text, p, out) // rules that need IR (enable dead code detection) local before_n = out.size() RuleDeadMethodsBox.apply_ir(ir, p, out) diff --git a/tools/hako_check/rules/rule_analyzer_io_safety.hako b/tools/hako_check/rules/rule_analyzer_io_safety.hako new file mode 100644 index 00000000..614a8b84 --- /dev/null +++ b/tools/hako_check/rules/rule_analyzer_io_safety.hako @@ -0,0 +1,87 @@ +// tools/hako_check/rules/rule_analyzer_io_safety.hako — HC021: Analyzer IO Safety +// Detects analyzer rules that perform direct I/O operations (FileBox, NetworkBox, etc.) +// Analyzer rules should receive all data through method parameters (CLI-internal push approach). + +using selfhost.shared.common.string_helpers as Str + +static box RuleAnalyzerIoSafetyBox { + method apply(text, path, out) { + if text == null { return 0 } + + // Only check files that look like analyzer rules (contain "Box" and "apply") + if text.indexOf("Box") < 0 || text.indexOf("apply") < 0 { + return 0 + } + + local lines = me._split_lines(text) + local i = 0 + while i < lines.size() { + local ln = lines.get(i) + local line_num = i + 1 + + // Remove comments for analysis + local comment_pos = ln.indexOf("//") + local code = ln + if comment_pos >= 0 { + code = ln.substring(0, comment_pos) + } + + // Check for FileBox instantiation + if code.indexOf("new FileBox") >= 0 { + local msg = "[HC021] analyzer rule uses direct file I/O (new FileBox): use CLI-internal push approach instead" + out.push(msg + " :: " + path + ":" + me._itoa(line_num)) + } + + // Check for file operations (even on passed-in FileBox) + if code.indexOf(".open(") >= 0 || code.indexOf(".read(") >= 0 || code.indexOf(".write(") >= 0 { + local msg = "[HC021] analyzer rule uses file operations: use CLI-internal push approach instead" + out.push(msg + " :: " + path + ":" + me._itoa(line_num)) + } + + // Check for other dangerous I/O boxes + if code.indexOf("new NetworkBox") >= 0 || code.indexOf("new SocketBox") >= 0 { + local msg = "[HC021] analyzer rule uses network I/O: use CLI-internal push approach instead" + out.push(msg + " :: " + path + ":" + me._itoa(line_num)) + } + + i = i + 1 + } + + return out.size() + } + + _split_lines(s) { + local arr = new ArrayBox() + if s == null { return arr } + local n = s.length() + local last = 0 + local i = 0 + loop(i < n) { + local ch = s.substring(i, i+1) + if ch == "\n" { + arr.push(s.substring(last, i)) + last = i + 1 + } + i = i + 1 + } + if last <= n { arr.push(s.substring(last)) } + return arr + } + + _itoa(n) { + local v = 0 + n + if v == 0 { return "0" } + local out = "" + local digits = "0123456789" + local tmp = "" + while v > 0 { + local d = v % 10 + tmp = digits.substring(d, d+1) + tmp + v = v / 10 + } + out = tmp + return out + } +} + +static box RuleAnalyzerIoSafetyMain { method main(args) { return 0 } } diff --git a/tools/hako_check/tests/HC021_analyzer_io_safety/expected.json b/tools/hako_check/tests/HC021_analyzer_io_safety/expected.json new file mode 100644 index 00000000..2ef2fcb2 --- /dev/null +++ b/tools/hako_check/tests/HC021_analyzer_io_safety/expected.json @@ -0,0 +1,13 @@ +{"diagnostics":[ + {"file":"ng.hako","line":1,"rule":"HC012","message":"[HC012] dead static box (never referenced): RuleUnsafeExampleBox","quickFix":"","severity":"warning"}, + {"file":"ng.hako","line":1,"rule":"HC012","message":"[HC012] dead static box (never referenced): RuleUnsafeExampleMain","quickFix":"","severity":"warning"}, + {"file":"ng.hako","line":1,"rule":"HC014","message":"[HC014] missing entrypoint (Main.main or main)","quickFix":"","severity":"warning"}, + {"file":"ng.hako","line":7,"rule":"HC021","message":"[HC021] analyzer rule uses direct file I/O (new FileBox): use CLI-internal push approach instead :: ng.hako:7","quickFix":"","severity":"warning"}, + {"file":"ng.hako","line":8,"rule":"HC021","message":"[HC021] analyzer rule uses file operations: use CLI-internal push approach instead :: ng.hako:8","quickFix":"","severity":"warning"}, + {"file":"ng.hako","line":9,"rule":"HC021","message":"[HC021] analyzer rule uses file operations: use CLI-internal push approach instead :: ng.hako:9","quickFix":"","severity":"warning"}, + {"file":"ok.hako","line":1,"rule":"HC012","message":"[HC012] dead static box (never referenced): RuleSafeExampleMain","quickFix":"","severity":"warning"}, + {"file":"ok.hako","line":1,"rule":"HC014","message":"[HC014] missing entrypoint (Main.main or main)","quickFix":"","severity":"warning"}, + {"file":"ok.hako","line":1,"rule":"HC022","message":"[HC022] Suggestion: Use NYASH_PARSER_STAGE3=1 or HAKO_PARSER_STAGE3=1 environment variables","quickFix":"","severity":"warning"}, + {"file":"ok.hako","line":1,"rule":"HC012","message":"[HC012] dead static box (never referenced): RuleSafeExampleBox","quickFix":"","severity":"warning"}, + {"file":"ok.hako","line":11,"rule":"HC022","message":"[HC022] Stage-3 construct detected (while): ok.hako:11","quickFix":"","severity":"warning"} +]} diff --git a/tools/hako_check/tests/HC021_analyzer_io_safety/ng.hako b/tools/hako_check/tests/HC021_analyzer_io_safety/ng.hako new file mode 100644 index 00000000..b237c748 --- /dev/null +++ b/tools/hako_check/tests/HC021_analyzer_io_safety/ng.hako @@ -0,0 +1,21 @@ +// ng.hako - Unsafe analyzer rule (uses direct file I/O) +// This rule violates the CLI-internal push approach by using FileBox + +static box RuleUnsafeExampleBox { + method apply(text, path, out) { + // UNSAFE: directly reads file using FileBox + local fb = new FileBox() + if fb.open(path) == 0 { + local content = fb.read() + fb.close() + + if content.indexOf("dangerous") >= 0 { + out.push("[EXAMPLE] found dangerous pattern") + } + } + + return out.size() + } +} + +static box RuleUnsafeExampleMain { method main(args) { return 0 } } diff --git a/tools/hako_check/tests/HC021_analyzer_io_safety/ok.hako b/tools/hako_check/tests/HC021_analyzer_io_safety/ok.hako new file mode 100644 index 00000000..bb40bb34 --- /dev/null +++ b/tools/hako_check/tests/HC021_analyzer_io_safety/ok.hako @@ -0,0 +1,42 @@ +// ok.hako - Safe analyzer rule (CLI-internal push approach) +// This rule receives all data through parameters and doesn't do direct I/O + +static box RuleSafeExampleBox { + method apply(text, path, out) { + // Safe: uses text parameter passed from CLI + if text == null { return 0 } + + local lines = me._split_lines(text) + local i = 0 + while i < lines.size() { + local ln = lines.get(i) + // Analyze the line + if ln.indexOf("unsafe_pattern") >= 0 { + out.push("[EXAMPLE] found unsafe pattern") + } + i = i + 1 + } + + return out.size() + } + + _split_lines(s) { + local arr = new ArrayBox() + if s == null { return arr } + local n = s.length() + local last = 0 + local i = 0 + loop(i < n) { + local ch = s.substring(i, i+1) + if ch == "\n" { + arr.push(s.substring(last, i)) + last = i + 1 + } + i = i + 1 + } + if last <= n { arr.push(s.substring(last)) } + return arr + } +} + +static box RuleSafeExampleMain { method main(args) { return 0 } }