Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions gitgalaxy/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2018,11 +2018,12 @@ def _function_slice(
# which only "succeeds" when a `{` happens to appear by
# coincidence. This dropped ~97.4% of real matches (1 of 39
# raw matches reached the named list). Mode A's "greedy to
# the next func_start match" body heuristic is a correct fit.
"m4",
) or family in ("column_sensitive"):
mode_name = "Mode_A_Labels"
sats, impact = self._slice_by_labels(code, rules, offset, spatial_map)
elif lang_id == "m4":
mode_name = "Mode_F_M4_Brackets"
sats, impact = self._slice_by_m4_brackets(code, rules, offset, spatial_map)
elif family in ("single_line_only", "multi_style_dash") or lang_id in (
"python",
"yaml",
Expand Down Expand Up @@ -4414,6 +4415,93 @@ def preserve_newlines(m):
# SHARED FUNCTIONAL METRICS ENGINE
# ==============================================================================

def _slice_by_m4_brackets(
self,
code: str,
rules: dict[str, Any],
offset: int,
spatial_map: dict[str, list[int]],
) -> tuple[list[FunctionNode], float]:
"""[INTEGRATION MODE F] - M4 Bracket-Aware Slicing"""
satellites: list[FunctionNode] = []
sum_fxn_impact = 0.0
func_start = rules.get("func_start")

if not func_start:
return [], 0.0

matches = list(func_start.finditer(code))
if not matches:
return [], 0.0

for match in matches:
start_idx = match.start()

# Find the opening parenthesis for this macro invocation
paren_start = code.find("(", start_idx)
if paren_start == -1:
continue

depth_paren = 0
depth_bracket = 0
pos = paren_start

while pos < len(code):
ch = code[pos]
if ch == "[":
depth_bracket += 1
elif ch == "]":
depth_bracket = max(0, depth_bracket - 1)
elif ch == "(":
# In M4, brackets quote everything inside them, including parens
if depth_bracket == 0:
depth_paren += 1
elif ch == ")" and depth_bracket == 0:
depth_paren -= 1
if depth_paren == 0:
break
pos += 1

end_idx = min(pos + 1, len(code))

block = code[start_idx:end_idx].strip()
if not block:
continue

name = "unknown"
if match.group(1) is not None:
name = match.group(1).strip()
elif match.lastindex and match.group(match.lastindex) is not None:
name = match.group(match.lastindex).strip()
else:
# Fallback extraction if regex group failed
# The first argument in parentheses
args_text = code[paren_start + 1 : end_idx - 1]
# Regex will typically capture the name, but just in case:
m_name = re.search(r"^\s*(?:`([^']+)'|\[{1,2}([^\]]+)\]{0,2}|([A-Za-z_][A-Za-z0-9_]*))", args_text)
if m_name:
name = next(g for g in m_name.groups() if g is not None).strip()

start_line = offset + code.count("\n", 0, start_idx) + 1
loc = block.count("\n") + 1

sat, mag = self._calculate_block_metrics(
name,
block,
loc,
start_line,
start_line + loc - 1,
rules,
start_idx,
end_idx,
spatial_map,
)

satellites.append(sat)
sum_fxn_impact += mag

return satellites, sum_fxn_impact

# galaxyscope:ignore sec_high_risk_execution

def _matching_paren_end(self, text: str, open_idx: int) -> int:
Expand Down
42 changes: 42 additions & 0 deletions tests/core_engine/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -3380,3 +3380,45 @@ def test_detector_nested_functions_in_signature_dropped():
# should prevent it from being extracted as a real satellite function.
satellites, _ = opt._slice_by_braces(code, "typescript", opt.languages["typescript"]["rules"], 0, {})
assert len(satellites) == 0, "Nested parameter function `f` should have been skipped by signature_end logic!"

def test_detector_m4_bracket_slicing_with_unbalanced_quotes():
"""
Issue #2204: m4 macro bodies are bounded by `(` and `)`, but they can contain
shell fragments with unbalanced quotes (e.g. `"` and `'`) that break Mode B's
string shielding. They also use `[` and `]` to quote strings (which protects
internal parentheses). The custom `_slice_by_m4_brackets` mode handles this.
"""
from gitgalaxy.core.detector import StructuralExtractor
from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS

code = '''
AC_DEFUN([AC_PROG_F77], [])

AC_DEFUN([MY_MACRO], [
if test "$foo" = "bar("; then
echo "unbalanced quote here -> '"
fi
# [ ( nested bracket paren ignored ) ]
])

AC_DEFUN([ANOTHER],
[
echo "done"
])
'''
extractor = StructuralExtractor("m4", LANGUAGE_DEFINITIONS)
rules = LANGUAGE_DEFINITIONS["m4"]["rules"]

sats, _ = extractor._slice_by_m4_brackets(code, rules, 0, {})

assert len(sats) == 3

# Check names
assert sats[0]["name"] == "AC_PROG_F77"
assert sats[1]["name"] == "MY_MACRO"
assert sats[2]["name"] == "ANOTHER"

# Check exact lengths
assert sats[0]["loc"] == 1
assert sats[1]["loc"] == 6
assert sats[2]["loc"] == 4
Loading
Loading