Skip to content

fix(fortran): func_start could phantom-match across a subroutine's own END boundary - #1555

Merged
squid-protocol merged 1 commit into
mainfrom
fix/fortran-1531-end-boundary-crossing
Aug 14, 2026
Merged

fix(fortran): func_start could phantom-match across a subroutine's own END boundary#1555
squid-protocol merged 1 commit into
mainfrom
fix/fortran-1531-end-boundary-crossing

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Fixes #1531.

Summary

The RETURN TYPE section's lazy "Legacy Sizing/Attributes" class ([A-Za-z0-9_ \t\n&*,()=:]*?) had no length cap and includes letters/newlines. On a type-declaration line immediately preceding an unrelated later SUBROUTINE/FUNCTION (e.g. CHARACTER(len=*), INTENT(IN) :: message a few lines before that same subroutine's own END SUBROUTINE wrf_message), backtracking let the class swallow the entire intervening body -- including the trailing END statement -- and match keyword+identifier there instead. This produced a phantom duplicate function_data row whose start_line/args belonged to a type declaration, not any real definition.

Confirmed reproducing the issue's exact symptoms against language-crucible/data/fortran/wrf/module_initialize_real.F: wrf_message (phantom start_line 5581 vs. real 5580, args=1 vs. real 2) and const_module_initialize, plus a third previously-unreported instance of the same bug shape in the same file (wrf_error_fatal).

Fix

Bounded the attribute class to {0,40} chars.

A worthwhile detour on the way here: a per-character (?!\bEND\b) exclusion was tried first -- more semantically precise (targets the actual mechanism instead of an arbitrary length) -- but empirically disables Python re's internal fast path for what would otherwise be a plain bounded character class. Direct timing on a payload of ~3000 repeated type-declaration lines followed by one distant END (a realistic adversarial shape, not synthetic): 0.01s with a plain numeric bound vs. ~13s with the lookahead -- a real ReDoS regression that would have shipped if not caught by this PR's own probe before pushing. Went with the numeric bound instead, verified against the same payload.

Test plan

  • New regression test: exact reproduction of the corpus bug shape, confirms exactly one match
  • New ReDoS test: the ~13s-triggering payload, now well under timeout
  • pytest tests/extraction/languages/test_fortran.py -- 35 passed
  • tree_sitter_accuracy_audit.py --all --ci -- clean across all 31 languages
  • crucible_check.py --mode both -- PASS/PASS after blessing expected drift (3 phantom duplicates removed from the corpus)
  • audit_check.py --ci -- all clear

🤖 Generated with Claude Code

…n END boundary

#1531: the RETURN TYPE section's lazy "Legacy Sizing/Attributes" class
([A-Za-z0-9_ \t\n&*,()=:]*?, unbounded) includes letters and newlines with no
length cap. On a type-declaration line immediately preceding an unrelated
later SUBROUTINE/FUNCTION (e.g. `CHARACTER(len=*), INTENT(IN) :: message`
a few lines before that same subroutine's own `END SUBROUTINE wrf_message`),
backtracking let the class swallow the entire intervening body -- including
the trailing END statement -- and match keyword+identifier there instead.
This produced a phantom duplicate function_data row whose start_line/args
belonged to a type declaration, not any real definition -- confirmed
reproducing #1531's exact wrf_message (start_line drift 5581 vs real 5580,
args=1 vs real=2) and const_module_initialize symptoms, plus a third
previously-unreported case (wrf_error_fatal) in the same corpus file.

Bounded the class to {0,40} chars -- comfortably more than any real
single/continued attribute list needs, well short of the ~95+ chars needed
to reach a phantom match. A per-character `(?!\bEND\b)` exclusion was tried
first and is more semantically precise, but empirically disables `re`'s fast
path for a plain bounded character class: 0.01s vs. ~13s on a payload of
thousands of repeated type-declaration lines with one distant END -- a real
ReDoS regression on a plausible shape, not just a synthetic one. Caught by
this PR's own ReDoS probe before it shipped; a plain numeric bound has none
of that risk and was verified with the same probe.

language-crucible/data/fortran/wrf/*.F: 3 duplicate names eliminated
(wrf_message, wrf_error_fatal, const_module_initialize), each now resolving
to its single real occurrence with correct start_line/args.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@squid-protocol
squid-protocol merged commit c720ca1 into main Aug 14, 2026
27 checks passed
@squid-protocol
squid-protocol deleted the fix/fortran-1531-end-boundary-crossing branch August 14, 2026 03:23
@github-actions

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fortran: detector.py's start_line is wrong for some subroutines, ~370 lines off in module_initialize_real.F

1 participant