Skip to content

detector.py's class fallback doesn't skip languages with class_start=None, causing false-positive class extraction (confirmed: m4) #1925

Description

@squid-protocol

Description

Found investigating tri-comparison ledger shape m4/class/existence/agree[gitgalaxy]_vs[ctags] (4 occurrences).

gitgalaxy/standards/language_standards.py sets "class_start": None for 19 languages, a deliberate signal (per how_to_add_a_language.md's Rule 4) that the language genuinely has no class/OOP concept: shell, agc_assembly, makefile, xml, markdown, csv, pbtxt, yacc, m4, mlir, proto, hlo, td, plaintext, json, glsl, nix, blp, batch.

gitgalaxy/core/detector.py's class-extraction logic doesn't respect that signal, though:

class_start_pattern = (
    self.languages.get(self.primary_lang_id, {}).get("rules", {}).get("class_start")
    if self.primary_lang_id in _CLASS_START_NAMED_EXTRACTION_LANGS
    else None
)
if class_start_pattern is not None:
    class_matches = list(class_start_pattern.finditer(code_stream))
    ...
else:
    # Legacy fallback: hardcoded `class|struct|interface|trait|enum` regex
    class_start_pattern = re.compile(
        r"^\s*(?:export\s+|public\s+|abstract\s+)?(?:class|struct|interface|trait|enum)\s+([a-zA-Z0-9_]+)..."
    )
    class_matches = list(class_start_pattern.finditer(code_stream))

There's no check distinguishing "this language's own class_start is None because it genuinely has no class concept" from "this language just isn't in the allowlist yet, use the generic fallback as a stopgap." Both cases fall into the same else branch, so every one of the 19 class_start: None languages gets the generic C-family-shaped fallback regex applied to its raw text regardless, with no protection against that pattern coincidentally matching unrelated text (macro arguments, embedded foreign-language snippets, sample code in markup, etc.).

Steps to Reproduce (confirmed, not theoretical)

language-crucible/data/m4/curl/configure.ac:1358:

      struct SocketIFace *ISocket = NULL;

This is C source text embedded as a macro argument inside AC_LANG_PROGRAM([[ ... ]]) (an autoconf feature-test snippet) -- not real m4 structure, and not routed to a different language (detector.py's HANDSHAKE_REGISTRY mid-file language-switching only has triggers for <script>/<style>/inline-asm, nothing for autoconf's embedded-C convention). Because m4's class_start is None and m4 isn't in _CLASS_START_NAMED_EXTRACTION_LANGS either, the generic fallback regex runs over the whole file anyway and matches struct SocketIFace as if it were a real m4 class.

Confirmed directly against the live gatherer (not just the regex in isolation):

gather_language('m4')[...].gg_classes
# -> [('SocketIFace', None), ('Library', None), ('sockaddr_in6', None), ('timespec', None)]

4 false-positive "classes" across the m4 corpus, none of which are real m4 constructs -- ctags correctly reports none of them (ctags has no class-shaped kind for m4 at all).

Suggested fix

In detector.py's class-extraction branch, distinguish "this language's own class_start is explicitly None" (skip class extraction entirely, emit no classes) from "this language isn't in the allowlist but does have a real class_start regex not yet verified for named extraction" (current fallback behavior, unchanged) -- e.g. check self.languages.get(self.primary_lang_id, {}).get("rules", {}).get("class_start") is None directly, independent of allowlist membership, before falling through to the generic regex.

Scope note

Only m4 is confirmed as an active false-positive source right now (visible via the tri-comparison ledger, since it has ctags coverage to disagree with). The other 18 class_start: None languages listed above are NOT confirmed to be affected -- most don't have a comparison tool to catch this via the ledger, and the fallback regex's specific shape (class|struct|interface|trait|enum NAME) may simply never appear in their typical corpus content (e.g. json/csv/plaintext). Worth a quick corpus check per language once the fix lands, but that's a separate follow-up, not asserted here as confirmed.

Evidence

Full discrepancy record in docs/self_scan/tri_comparison_ledger.json, shape m4/class/existence/agree[gitgalaxy]_vs[ctags].

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugUnintended behavior or logic failure in the enginecore-engineModifications to the central physics and parsing enginetestingUnit, integration, and E2E pipeline verification

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions