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
47 changes: 42 additions & 5 deletions gitgalaxy/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,11 +1446,6 @@ def _slice_by_braces(
if not func_start:
return [], 0.0

try:
matches = list(func_start.finditer(code))
except Exception:
return [], 0.0

# Dynamically set scope bounds based on lexical family
# We now consistently use curly braces for standard block-style languages.
opener, closer = "{", "}"
Expand Down Expand Up @@ -1513,6 +1508,48 @@ def fast_shield(m):

safe_code = "".join(lines)

# BUG FIX (epic #813, extraction hardening, #814/#815): func_start
# used to be matched against the raw, unshielded `code` -- computed
# above, BEFORE `safe_code` existed. That let a single-line string
# literal or comment containing function-shaped text (e.g. `let
# query = "function Foo() {";`) false-positive-match, since
# javascript's/typescript's func_start regex is `\b`-anchored (not
# `^`-anchored) and has no way to know it's inside a string/comment
# on its own. `safe_code` already exists at this point specifically
# to solve this for the downstream brace search -- matching against
# it here instead of `code` closes the same gap for the match
# itself. `safe_code` is guaranteed the same length as `code`
# (shielding replaces matched spans with same-length whitespace), so
# every index computed from `matches` below remains valid against
# the original `code` for slicing.
#
# Gated to javascript/typescript only, NOT applied to every Mode B
# language: verifying this fix against the real crucible corpus
# surfaced a pre-existing, separate bug in `prism.py`'s comment/
# string stripping for PHP (#859) -- `combined_pattern`'s shielding
# already relies on `code_stream` being clean, and for at least two
# real PHP corpus files it isn't, causing a multi-thousand-character
# false shield match. That's harmless today because the brace
# search's blast radius is naturally bounded (a bounded window, one
# brace lookup) -- but matching *all* of func_start's positions
# against a corrupted `safe_code` (this fix's approach) turns that
# latent corruption into wholesale loss of real functions for those
# files (confirmed: one file dropped from 1 real function detected
# to 0, with a 17x structural-magnitude blowup). Broadening this fix
# to other Mode B languages should follow #859 (and an audit of
# whether other languages share the same class of prism.py gap),
# not precede it.
if lang_id in ("javascript", "typescript"):
try:
matches = list(func_start.finditer(safe_code))
except Exception:
return [], 0.0
else:
try:
matches = list(func_start.finditer(code))
except Exception:
return [], 0.0

last_end_idx = 0
current_line_count = offset + 1
last_counted_idx = 0
Expand Down
60 changes: 60 additions & 0 deletions tests/core_engine/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1950,3 +1950,63 @@ def test_detector_csharp_expression_body_fallback_gated_to_csharp_only():

satellites, _ = js_detector._slice_by_braces(code, "javascript", js_rules, 0, {})
assert satellites == [], "the csharp-only arrow fallback must not fire for other languages"


# ==============================================================================
# JAVASCRIPT/TYPESCRIPT STRING-LITERAL FALSE POSITIVE (epic #813, #814/#815)
# ==============================================================================
def test_detector_js_ts_string_literal_no_longer_hallucinated_as_function():
"""
Regression test for a real bug found while hardening the extraction
gauntlets (epic #813): func_start used to be matched against the raw,
unshielded `code` in _slice_by_braces, computed BEFORE the
string/comment-shielded `safe_code` existed (safe_code was only built
afterward, for the brace-search step). Since javascript's/typescript's
func_start regex is `\\b`-anchored (not `^`-anchored), a single-line
string literal containing function-shaped text false-positive-matched,
e.g. `let query = "function Foo() {";`. Fixed by matching against
`safe_code` instead for these two languages specifically.
"""
from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS

for lang in ("javascript", "typescript"):
detector = StructuralExtractor(lang, LANGUAGE_DEFINITIONS)
rules = LANGUAGE_DEFINITIONS[lang]["rules"]
code = 'let query = "function Foo() {";\nconst realFn = () => {\n return 1;\n};\n'

satellites, _ = detector._slice_by_braces(code, lang, rules, 0, {})
names = [s["name"] for s in satellites]
assert names == ["realFn"], f"[{lang}] string-literal lookalike still hallucinated a function: {names}"


def test_detector_string_literal_fix_gated_away_from_other_mode_b_languages():
"""
The safe_code-matching fix above is deliberately gated to
`lang_id in ("javascript", "typescript")` only -- NOT applied broadly to
every Mode-B (brace-slicing) language. Verifying it against the real
crucible corpus surfaced a separate, pre-existing bug in prism.py's
comment/string stripping for PHP (filed as #859): at least one real PHP
corpus file's `code_stream` already has corrupted docblock/string
content that confuses the same string/comment shielding step used here.
That's currently harmless because the brace-search step's blast radius
is naturally bounded -- but matching func_start's own positions against
a corrupted safe_code (this fix's approach) would turn that latent
corruption into wholesale loss of real functions for those files. This
test locks in the gate so a future edit doesn't "simplify" this by
removing the lang_id check before #859 is actually fixed.
"""
from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS

php_detector = StructuralExtractor("php", LANGUAGE_DEFINITIONS)
php_rules = LANGUAGE_DEFINITIONS["php"]["rules"]
# A php-shaped analogue of the same lookalike: if this language were
# matched against safe_code, this would correctly resolve to zero
# satellites (like javascript/typescript above) -- but the gate means
# php still uses the raw-code path, so this just proves php's own
# ordinary (already-existing) function detection is untouched by this
# fix rather than asserting on the string-literal case directly (php's
# own func_start is `^`-anchored, so it was never vulnerable to this
# specific bug shape in the first place).
code = "function realFn() {\n return 1;\n}\n"
satellites, _ = php_detector._slice_by_braces(code, "php", php_rules, 0, {})
assert [s["name"] for s in satellites] == ["realFn"], "php's ordinary function detection regressed"
10 changes: 5 additions & 5 deletions tests/ruff_audit_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
"gitgalaxy/core/aperture.py:502: SIM102": "Use a single `if` statement instead of nested `if` statements",
"gitgalaxy/core/aperture.py:514: SIM102": "Use a single `if` statement instead of nested `if` statements",
"gitgalaxy/core/detector.py:1002: PERF401": "Use `list.extend` to create a transformed list",
"gitgalaxy/core/detector.py:1497: SIM102": "Use a single `if` statement instead of nested `if` statements",
"gitgalaxy/core/detector.py:1504: SIM102": "Use a single `if` statement instead of nested `if` statements",
"gitgalaxy/core/detector.py:1678: SIM108": "Use ternary operator `line_end = len(safe_code) if next_nl == -1 else next_nl + 1` instead of `if`-`else`-block",
"gitgalaxy/core/detector.py:2119: SIM108": "Use ternary operator `args_count = args_str.count(\",\") + 1 if \",\" in args_str else len(args_str.strip().split())` instead of `if`-`else`-block",
"gitgalaxy/core/detector.py:2234: C403": "Unnecessary list comprehension (rewrite as a set comprehension)",
"gitgalaxy/core/detector.py:1492: SIM102": "Use a single `if` statement instead of nested `if` statements",
"gitgalaxy/core/detector.py:1499: SIM102": "Use a single `if` statement instead of nested `if` statements",
"gitgalaxy/core/detector.py:1715: SIM108": "Use ternary operator `line_end = len(safe_code) if next_nl == -1 else next_nl + 1` instead of `if`-`else`-block",
"gitgalaxy/core/detector.py:2156: SIM108": "Use ternary operator `args_count = args_str.count(\",\") + 1 if \",\" in args_str else len(args_str.strip().split())` instead of `if`-`else`-block",
"gitgalaxy/core/detector.py:2271: C403": "Unnecessary list comprehension (rewrite as a set comprehension)",
"gitgalaxy/core/detector.py:845: PERF401": "Use `list.extend` to create a transformed list",
"gitgalaxy/core/guidestar_lens.py:139: C401": "Unnecessary generator (rewrite as a set comprehension)",
"gitgalaxy/core/network_risk_sensor.py:183: C419": "Unnecessary list comprehension",
Expand Down
Loading