Skip to content

func_start missing the args regex's 'Invocation Shield': bare call statements false-positive as function definitions (7 languages) #1221

Description

@squid-protocol

What

func_start's method-shorthand branch (the one that matches ES6/Java/C#-style bare name(...) { method definitions with no leading keyword) has no requirement that the match is actually followed by {. Any statement that merely starts a line with identifier( -- i.e. any bare function call used as a statement -- matches as if it were a function/method definition.

This is the exact defect class the args regex's own comment already names and fixed for several of these same languages (see javascript's args rule in language_standards.py):

FIX 1 (Invocation Shield): Injected (?=[ \t\n]*\{) at the end of the class method branch, demanding structural proof that the signature opens a logic block.

func_start's equivalent branch never got the same treatment, so it's still open to the false-positive args was explicitly hardened against.

Repro (regex-level, works for any of the 7 affected languages)

from gitgalaxy.standards.language_standards import LANGUAGE_DEFINITIONS
fs = LANGUAGE_DEFINITIONS["javascript"]["rules"]["func_start"]
fs.search("next();").group(0)        # -> 'next'   (should be None -- this is a call, not a definition)
fs.search("  next();").group(0)      # -> '  next' (same, indented)

javascript's current func_start pattern (method-shorthand branch is the last alternative):

(?:\b(?:async\s+)?function\s*\*?\s+[a-zA-Z_$][\w$]*(?=\s*\()|\b[a-zA-Z_$][\w$]*(?=[ \t\n]*=[ \t\n]*(?:async\s*)?(?:function(?:\s*\*)?\b|\([^)]*\)[ \t\n]*=>|[a-zA-Z_$][\w$]*[ \t\n]*=>))|^[ \t]*[a-zA-Z_$][\w$]*(?=[ \t\n]*:[ \t\n]*(?:async\s*)?(?:function(?:\s*\*)?\b|\([^)]*\)[ \t\n]*=>|[a-zA-Z_$][\w$]*[ \t\n]*=>))|^[ \t]*(?:static[ \t\n]+)?(?:async[ \t\n]+)?(?:get\s+|set\s+)?\*?(?!(?:if|for|while|switch|catch|return|throw|new|typeof|jQuery|function)\b|\$)#?[a-zA-Z_$][\w$]*(?=\s*\())

Note the final alternative's lookahead is just (?=\s*\() -- no trailing { requirement, unlike args' equivalent branch which has (?=[ \t\n]*\{).

Confirmed on real production code, not just synthetic snippets

Building a tree-sitter-based ground-truth accuracy pass for JavaScript (mirroring ast_accuracy_audit.py's Python methodology, #1200) against expressjs/express (v5.2.1, full repo, 43 JS files): 9 of 59 "functions" GitGalaxy reported (≈15%) were phantom call-sites, not real definitions -- found_functions=50, extra_functions=9, i.e. function precision ≈84.7% on this corpus. Every single extra was a bare-call statement whose name happened to start a line:

  • benchmarks/middleware.js, examples/auth/index.js, examples/error/index.js, examples/params/index.js, examples/route-middleware/index.js, examples/route-separation/user.js -- all report a phantom function named next, from the extremely common Express middleware idiom next(); as a statement (see e.g. examples/auth/index.js:38,77).
  • examples/auth/index.js also reports a phantom function named hash, from hash({ password: 'foobar' }, function (err, pass, salt, hash) { (examples/auth/index.js:50) -- a call to a require()'d function that starts the line.
  • examples/view-constructor/index.js reports a phantom fn.

Because next(...)/similar callback-invocation idioms are near-universal in real JS/TS/Java/C#/Groovy/Dart/Apex code (any statement-position function or method call, not just Express middleware), this is not an edge case -- it's a systemic precision hit on any real corpus in the affected languages.

Affected languages (confirmed via the same regex-level repro)

Checked every language with a func_start rule; these 7 all match next(); where they shouldn't:

  • javascript, typescript, java, csharp, apex, dart, groovy

Not affected (their method-shorthand branch, if present, already requires more structural proof, or they don't have this branch shape at all): c, cpp, rust, go, php, ruby, perl, lua, solidity, powershell, kotlin, scala, swift.

Note this affected-language list is a different grouping than #1209's (which was about the args regex's missing capture group) -- e.g. ruby/powershell had the args bug but don't have this one; apex/dart didn't have the args bug in this form but do have this one. Different defect, different structural precondition (whether func_start's method-shorthand branch has a trailing-brace lookahead), just the same underlying category of bug (a signature-shaped call site vs. a real definition) and the same proven fix shape already sitting in this codebase's own args regex.

Suggested fix

For each of the 7 languages, add the same (?=[ \t\n]*\{) (or language-appropriate equivalent, e.g. Java/C# also allow a leading throws/generic-bounds clause before {) to the end of func_start's method-shorthand branch, mirroring args' own "Invocation Shield" fix in the same file. Needs the usual per-language care this repo's func_start changes get: verify against real one-liner/multi-line legitimate method definitions in each language aren't broken by the added lookahead (e.g. Java/C# methods can have a throws/where clause between ) and {), a ReDoS scaling sweep on the changed branch, and a golden-master diff via crucible_check.py per the Differential Scan protocol before merging.

Not in scope here

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions