static box RuleDeadMethodsBox { // IR expects: methods(Array), calls(Array), entrypoints(Array) apply_ir(ir, path, out) { local methods = ir.get("methods") // If IR has no methods, or methods is empty, rebuild from source file. if methods == null || methods.size() == 0 { // Prefer in-memory source if provided (avoids FileBox/plugin dependency) local src = ir.get("source") if src != null { methods = me._scan_methods_from_text(src) } else { // Fallback to FileBox only when no source text provided local fb = new FileBox() if fb.open(path) == 0 { local text = fb.read(); fb.close(); methods = me._scan_methods_from_text(text) } else { methods = new ArrayBox() } } } if methods == null || methods.size() == 0 { return } local calls = ir.get("calls"); if (calls == null || calls.size() == 0) { // build minimal calls from source text (avoid plugin) local src = ir.get("source"); if src != null { calls = me._scan_calls_from_text(src) } else { calls = new ArrayBox() } } local eps = ir.get("entrypoints"); if eps == null { eps = new ArrayBox() } // build graph local adj = new MapBox() local i = 0; while i < methods.size() { adj.set(methods.get(i), new ArrayBox()); i = i + 1 } i = 0; while i < calls.size() { local c=calls.get(i); local f=c.get("from"); local t=c.get("to") // normalize from: prefer exact, otherwise try adding "/0" suffix local ff = f if adj.has(ff) == 0 { local f0 = f + "/0"; if adj.has(f0) == 1 { ff = f0 } } if adj.has(ff) == 1 { adj.get(ff).push(t) } i = i + 1 } // DFS from entrypoints local seen = new MapBox(); // resolve seeds: accept exact or prefix ("name/arity") matches for entrypoint names local seeds = new ArrayBox() // collect keys local keys = new ArrayBox(); i = 0; while i < methods.size() { keys.push(methods.get(i)); i = i + 1 } local j = 0 while j < eps.size() { local ep = eps.get(j) // exact match if adj.has(ep) == 1 { seeds.push(ep) } // prefix match: ep + "/" local pref = ep + "/" local k = 0; while k < keys.size() { local key = keys.get(k); if key.indexOf(pref) == 0 { seeds.push(key) } k = k + 1 } j = j + 1 } // fallback: common Main.main/0 if still empty if seeds.size() == 0 { if adj.has("Main.main/0") == 1 { seeds.push("Main.main/0") } } // run DFS from seeds j = 0; while j < seeds.size() { me._dfs(adj, seeds.get(j), seen); j = j + 1 } // report dead = methods not seen (filter with simple call-text heuristic) local src_text = ir.get("source") local cands = new ArrayBox() i = 0; while i < methods.size() { local m=methods.get(i); if seen.has(m)==0 { cands.push(m) }; i = i + 1 } i = 0; while i < cands.size() { local m = cands.get(i) local keep = 1 if src_text != null { // If source text contains a call like ".methodName(", consider it reachable local slash = m.lastIndexOf("/") local dotp = m.lastIndexOf(".") if dotp >= 0 { local meth = (slash>dotp)? m.substring(dotp+1, slash) : m.substring(dotp+1) if src_text.indexOf("." + meth + "(") >= 0 { keep = 0 } } } if keep == 1 { out.push("[HC011] unreachable method (dead code): PLACEHOLDER :: " + m) } i = i + 1 } } _scan_methods_from_text(text) { local res = new ArrayBox() if text == null { return res } // use local implementation to avoid external static calls local lines = me._split_lines(text) local cur = null local depth = 0 local i = 0 while i < lines.size() { local ln = me._ltrim(lines.get(i)) if ln.indexOf("static box ") == 0 { local rest = ln.substring("static box ".length()) local p = rest.indexOf("{") if p > 0 { cur = me._rstrip(rest.substring(0,p)) } else { cur = me._rstrip(rest) } depth = depth + 1 i = i + 1; continue } if cur != null && ln.indexOf("method ") == 0 { local rest = ln.substring("method ".length()) local p1 = rest.indexOf("(") local name = (p1>0)? me._rstrip(rest.substring(0,p1)) : me._rstrip(rest) local ar = 0 local p2 = rest.indexOf(")", (p1>=0)?(p1+1):0) if p1>=0 && p2>p1+1 { local inside = rest.substring(p1+1,p2) // count commas + 1 if any non-space local any = 0; local cnt = 1; local k=0; while k < inside.length() { local c=inside.substring(k,k+1); if c=="," { cnt = cnt + 1 }; if c!=" "&&c!="\t" { any=1 }; k=k+1 } if any == 1 { ar = cnt } } res.push(cur + "." + name + "/" + me._itoa(ar)) } // adjust depth by braces on the line local j=0; while j < ln.length() { local ch=ln.substring(j,j+1); if ch=="{" { depth = depth + 1 } else { if ch=="}" { depth = depth - 1; if depth < 0 { depth = 0 } } } j=j+1 } if depth == 0 { cur = null } i = i + 1 } return res } _ltrim(s) { return me._ltrim_chars(s, " \t") } _rstrip(s) { local n = s.length() local last = n local r = 0 while r < n { local i4 = n-1-r local c = s.substring(i4,i4+1) if c != " " && c != "\t" { last = i4+1; break } if r == n-1 { last = 0 } r = r + 1 } return s.substring(0,last) } _ltrim_chars(s, cs) { local n = s.length(); local head = 0 local idx = 0 while idx < n { local ch = s.substring(idx, idx+1) if ch != " " && ch != "\t" { head = idx; break } if idx == n-1 { head = n } idx = idx + 1 } return s.substring(head) } _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 } _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 } _scan_calls_from_text(text) { local arr = new ArrayBox(); if text == null { return arr } local lines = me._split_lines(text) local src_m = "Main.main/0" local i=0; while i < lines.size() { local ln = lines.get(i) // naive: detect patterns like "Main.foo(" local pos = 0; local n = ln.length() loop (pos < n) { local k = ln.indexOf(".", pos); if k < 0 { break } // scan ident before '.' local lhs = me._scan_ident_rev(ln, k-1) // scan ident after '.' local rhs = me._scan_ident_fwd(ln, k+1) if lhs != null && rhs != null { local to = lhs + "." + rhs + "/0" local rec = new MapBox(); rec.set("from", src_m); rec.set("to", to); arr.push(rec) } pos = k + 1 } i = i + 1 } return arr } _scan_ident_rev(s, i) { if i<0 { return null } local n = i local start = 0 local rr = 0 while rr <= n { local j = i - rr local c = s.substring(j, j+1) if me._is_ident_char(c) == 0 { start = j+1; break } if j == 0 { start = 0; break } rr = rr + 1 } if start>i { return null } return s.substring(start, i+1) } _scan_ident_fwd(s, i) { local n=s.length(); if i>=n { return null } local endp = i local off = 0 while off < n { local j = i + off if j >= n { break } local c = s.substring(j, j+1) if me._is_ident_char(c) == 0 { endp = j; break } if j == n-1 { endp = n; break } off = off + 1 } if endp == i { return null } return s.substring(i, endp) } _is_ident_char(c) { if c == "_" { return 1 } if c >= "A" && c <= "Z" { return 1 } if c >= "a" && c <= "z" { return 1 } if c >= "0" && c <= "9" { return 1 } return 0 } _dfs(adj, node, seen) { if node == null { return } if seen.has(node) == 1 { return } seen.set(node, 1) if adj.has(node) == 0 { return } local arr = adj.get(node) local k = 0; while k < arr.size() { me._dfs(adj, arr.get(k), seen); k = k + 1 } } } static box RuleDeadMethodsMain { method main(args) { return 0 } }