From 77a1fd66b82dddd994d650eb78896a612a4dfc2c Mon Sep 17 00:00:00 2001 From: squid-protocol Date: Tue, 22 Sep 2026 18:48:08 -0400 Subject: [PATCH] feat: C-family calls_out precision via _calls_out_ignore sets (#3286) perl (my/local/unless/elsif...), ruby, lua, matlab, ada (casefolded pragma/volatile/suppress), m4 (ifelse/ifdef control macros): per-language _calls_out_ignore frozensets + strict tests. No regex changes. Empties the dead-key baseline (writers ship here; also shipped in #3284/#3285). Co-Authored-By: Claude Fable 5 --- .../language_standards/languages/ada.py | 16 ++++++ .../language_standards/languages/lua.py | 14 ++++++ .../language_standards/languages/m4.py | 16 ++++++ .../language_standards/languages/matlab.py | 17 +++++++ .../language_standards/languages/perl.py | 22 +++++++++ .../language_standards/languages/ruby.py | 15 ++++++ tests/dead_key_audit_baseline.json | 4 +- tests/extraction/languages/test_ada_strict.py | 43 ++++++++++++++++ tests/extraction/languages/test_lua_strict.py | 45 +++++++++++++++++ tests/extraction/languages/test_m4_strict.py | 41 ++++++++++++++++ .../languages/test_matlab_strict.py | 49 +++++++++++++++++++ .../extraction/languages/test_perl_strict.py | 41 ++++++++++++++++ .../extraction/languages/test_ruby_strict.py | 47 ++++++++++++++++++ 13 files changed, 367 insertions(+), 3 deletions(-) diff --git a/gitgalaxy/standards/language_standards/languages/ada.py b/gitgalaxy/standards/language_standards/languages/ada.py index 1d7e6b839b..efc69256ee 100644 --- a/gitgalaxy/standards/language_standards/languages/ada.py +++ b/gitgalaxy/standards/language_standards/languages/ada.py @@ -50,6 +50,22 @@ "rules": { # Epic #3264: Explicitly declare the structural invocation paradigm "calls_out": CALLS_OUT_C_STYLE, + "_calls_out_ignore": frozenset( + { + "pragma", + "volatile", + "suppress", + "delay", + "accept", + "entry", + "raise", + "abs", + "mod", + "rem", + "others", + "aliased", + } + ), # --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE --- # branch: Ada's short-circuit forms are the two-word "and then"/ # "or else" (no && / || symbols exist in Ada). diff --git a/gitgalaxy/standards/language_standards/languages/lua.py b/gitgalaxy/standards/language_standards/languages/lua.py index 7e25ed0fd4..552795ae46 100644 --- a/gitgalaxy/standards/language_standards/languages/lua.py +++ b/gitgalaxy/standards/language_standards/languages/lua.py @@ -41,6 +41,20 @@ "rules": { # Epic #3264: Explicitly declare the structural invocation paradigm "calls_out": CALLS_OUT_C_STYLE, + "_calls_out_ignore": frozenset( + { + "pairs", + "ipairs", + "tostring", + "tonumber", + "type", + "unpack", + "rawequal", + "rawlen", + "next", + "select", + } + ), # --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE --- # 1. branch: decisions that split flow. Includes standard loops and Lua 5.2+ goto. "branch": re.compile(r"\b(if|elseif|else|for|while|repeat|and|or|not)\b"), diff --git a/gitgalaxy/standards/language_standards/languages/m4.py b/gitgalaxy/standards/language_standards/languages/m4.py index faf19390a2..3726fcdb40 100644 --- a/gitgalaxy/standards/language_standards/languages/m4.py +++ b/gitgalaxy/standards/language_standards/languages/m4.py @@ -41,6 +41,22 @@ "rules": { # Epic #3264: Explicitly declare the structural invocation paradigm "calls_out": CALLS_OUT_C_STYLE, + "_calls_out_ignore": frozenset( + { + "ifelse", + "ifdef", + "define", + "m4_define", + "m4_if", + "m4_ifdef", + "dnl", + "divert", + "undivert", + "m4_provide", + "include", + "sinclude", + } + ), # --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE --- # 1. branch (Control Flow / Branching) # M4 branching logic and Autoconf shell-generation branches. diff --git a/gitgalaxy/standards/language_standards/languages/matlab.py b/gitgalaxy/standards/language_standards/languages/matlab.py index 3b115e28dc..ca8fb1bda2 100644 --- a/gitgalaxy/standards/language_standards/languages/matlab.py +++ b/gitgalaxy/standards/language_standards/languages/matlab.py @@ -45,6 +45,23 @@ "rules": { # Epic #3264: Explicitly declare the structural invocation paradigm "calls_out": CALLS_OUT_C_STYLE, + "_calls_out_ignore": frozenset( + { + "zeros", + "ones", + "size", + "length", + "numel", + "disp", + "error", + "sprintf", + "fprintf", + "nargin", + "nargout", + "exist", + "strcmp", + } + ), # --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE --- # branch: MATLAB control flow. EXCLUDES 'error' and 'rethrow' (bailout_hits). "branch": re.compile(r"\b(?:if|elseif|else|switch|case|otherwise|for|while)\b|&&|\|\||~="), diff --git a/gitgalaxy/standards/language_standards/languages/perl.py b/gitgalaxy/standards/language_standards/languages/perl.py index 2a0c9ad9f0..204f8a6246 100644 --- a/gitgalaxy/standards/language_standards/languages/perl.py +++ b/gitgalaxy/standards/language_standards/languages/perl.py @@ -50,6 +50,28 @@ "rules": { # Epic #3264: Explicitly declare the structural invocation paradigm "calls_out": CALLS_OUT_C_STYLE, + "_calls_out_ignore": frozenset( + { + "my", + "our", + "local", + "unless", + "elsif", + "foreach", + "defined", + "wantarray", + "ref", + "bless", + "scalar", + "exists", + "delete", + "shift", + "unshift", + "chomp", + "die", + "warn", + } + ), # --- PHASE 1: LOGIC TOPOLOGY & STRUCTURE --- # 1. branch: Decisions that split the flow. Includes modern try/catch/finally and defer. "branch": re.compile( diff --git a/gitgalaxy/standards/language_standards/languages/ruby.py b/gitgalaxy/standards/language_standards/languages/ruby.py index a9703205fe..dfda778550 100644 --- a/gitgalaxy/standards/language_standards/languages/ruby.py +++ b/gitgalaxy/standards/language_standards/languages/ruby.py @@ -65,6 +65,21 @@ "rules": { # Epic #3264: Explicitly declare the structural invocation paradigm "calls_out": CALLS_OUT_C_STYLE, + "_calls_out_ignore": frozenset( + { + "elsif", + "module_function", + "attr_accessor", + "attr_reader", + "attr_writer", + "lambda", + "proc", + "puts", + "raise", + "loop", + "yield", + } + ), # 1. branch (Control Flow / Branching) # Decisions and logical jumps. EXCLUDES raise/throw (bailout_hits). "branch": re.compile(r"\b(if|unless|elsif|else|case|when|in|for|while|until)\b|&&|\|\||(?]])\n\t#include \n" assert not M4_RULES["_dependency_capture"].search(embedded) + +def test_m4_calls_out_strict(): + """Epic #3264 Phase 3: m4 calls_out extraction and ignore tuning.""" + from _strict_harness import assert_redos_immune + from gitgalaxy.core.detector import StructuralExtractor + from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS + + m4 = LANGUAGE_DEFINITIONS["m4"] + calls_out = m4["rules"]["calls_out"] + + assert calls_out.groups == 1 + + code = """ +define(`test_m4', ` + ifelse(A, B) + ifdef(C, D) + m4_define(E, F) + + probe_dispatch(1) + probe_branch(1) + probe_io(1) + syscmd(ls) + esyscmd(ls) +') +""" + extractor = StructuralExtractor("m4", {"m4": m4}) + res = extractor.splice(code, "") + calls = res["functions"][0]["calls_out_to"] + + assert "probe_dispatch" in calls + assert "probe_branch" in calls + assert "probe_io" in calls + assert "syscmd" in calls + assert "esyscmd" in calls + + assert "ifelse" not in calls + assert "ifdef" not in calls + assert "m4_define" not in calls + + payload = "ifelse" + (" " * 10000) + "(" + assert_redos_immune(calls_out, payload) diff --git a/tests/extraction/languages/test_matlab_strict.py b/tests/extraction/languages/test_matlab_strict.py index 7968656f84..b73aabeab5 100644 --- a/tests/extraction/languages/test_matlab_strict.py +++ b/tests/extraction/languages/test_matlab_strict.py @@ -698,3 +698,52 @@ def test_matlab_api_contract_2730(): # Not declarations -- must not match. assert not api.search(" function y = helper(x)"), "indented classdef method" + +def test_matlab_calls_out_strict(): + """Epic #3264 Phase 3: Matlab calls_out extraction and ignore tuning.""" + from _strict_harness import assert_redos_immune + from gitgalaxy.core.detector import StructuralExtractor + from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS + + matlab = LANGUAGE_DEFINITIONS["matlab"] + calls_out = matlab["rules"]["calls_out"] + + assert calls_out.groups == 1 + + code = """ +function test_matlab() + zeros(10) + ones(5) + size(A) + disp('hello') + error('err') + + probe_branch(1) + probe_io(1) + load('data') + save('data') + fopen('f') + system('ls') + dos('dir') +end +""" + extractor = StructuralExtractor("matlab", {"matlab": matlab}) + res = extractor.splice(code, "") + calls = res["functions"][0]["calls_out_to"] + + assert "probe_branch" in calls + assert "probe_io" in calls + assert "load" in calls + assert "save" in calls + assert "fopen" in calls + assert "system" in calls + assert "dos" in calls + + assert "zeros" not in calls + assert "ones" not in calls + assert "size" not in calls + assert "disp" not in calls + assert "error" not in calls + + payload = "zeros" + (" " * 10000) + "(" + assert_redos_immune(calls_out, payload) diff --git a/tests/extraction/languages/test_perl_strict.py b/tests/extraction/languages/test_perl_strict.py index 8c6f551a71..74cda3f06a 100644 --- a/tests/extraction/languages/test_perl_strict.py +++ b/tests/extraction/languages/test_perl_strict.py @@ -620,3 +620,44 @@ def test_perl_api_contract_2730(): # Not declarations -- must not match. assert not api.search("sub _private_helper {"), "underscore-private sub" + +def test_perl_calls_out_strict(): + """Epic #3264 Phase 3: Perl calls_out extraction and ignore tuning.""" + from _strict_harness import assert_redos_immune + from gitgalaxy.core.detector import StructuralExtractor + from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS + + perl = LANGUAGE_DEFINITIONS["perl"] + calls_out = perl["rules"]["calls_out"] + + assert calls_out.groups == 1 + + code = """ +sub my_func { + my ($x, $y) = @_; + elsif ($x == 1) { } + unless ($y) { } + foreach my $k (keys %h) { } + wantarray(); + + probe_branch($x); + probe_io($x); + open(my $fh, "<", "file.txt"); +} +""" + extractor = StructuralExtractor("perl", {"perl": perl}) + res = extractor.splice(code, "") + calls = res["functions"][0]["calls_out_to"] + + assert "probe_branch" in calls + assert "probe_io" in calls + assert "open" in calls + + assert "my" not in calls + assert "elsif" not in calls + assert "unless" not in calls + assert "foreach" not in calls + assert "wantarray" not in calls + + payload = "my " + (" " * 10000) + "(" + assert_redos_immune(calls_out, payload) diff --git a/tests/extraction/languages/test_ruby_strict.py b/tests/extraction/languages/test_ruby_strict.py index 7c891c7618..f1b271c303 100644 --- a/tests/extraction/languages/test_ruby_strict.py +++ b/tests/extraction/languages/test_ruby_strict.py @@ -266,3 +266,50 @@ def test_ruby_api_contract_2730(): # Not declarations -- must not match. assert not api.search('def probe_globals(env)'), 'top-level def is private on Object' + +def test_ruby_calls_out_strict(): + """Epic #3264 Phase 3: Ruby calls_out extraction and ignore tuning.""" + from _strict_harness import assert_redos_immune + from gitgalaxy.core.detector import StructuralExtractor + from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS + + ruby = LANGUAGE_DEFINITIONS["ruby"] + calls_out = ruby["rules"]["calls_out"] + + assert calls_out.groups == 1 + + code = """ +def test_method() + elsif (condition) + module_function (:sym) + attr_accessor (:prop) + lambda { } + puts("hello") + raise("error") + + probe_branch(1) + probe_io(1) + abort(1) + spawn(1) + describe("test") +end +""" + extractor = StructuralExtractor("ruby", {"ruby": ruby}) + res = extractor.splice(code, "") + calls = res["functions"][0]["calls_out_to"] + + assert "probe_branch" in calls + assert "probe_io" in calls + assert "abort" in calls + assert "spawn" in calls + assert "describe" in calls + + assert "elsif" not in calls + assert "module_function" not in calls + assert "attr_accessor" not in calls + assert "lambda" not in calls + assert "puts" not in calls + assert "raise" not in calls + + payload = "puts" + (" " * 10000) + "(" + assert_redos_immune(calls_out, payload)