fix(lsp/php): prevent OOM when a PHP trait uses itself#920
Merged
Conversation
flatten_trait_into_class() iterated reg->funcs with a live upper bound
(reg->func_count) while cbm_registry_add_func() appended to that same
array inside the loop. Each copied method is tagged with
receiver_type = class_qn, so when a trait adopts itself -- directly
(`trait T { use T; }`) or via an alias that resolves back to the trait
by short name (`use X\T as A; use A;`) -- class_qn equals
canonical_trait_qn and every appended method re-matches the loop filter,
extending the iteration. The loop never terminates: it arena-allocates a
fresh method each pass until the process exhausts all memory (observed:
40 GB+, freezing the host).
Fix:
- Short-circuit self-flattening: a trait cannot meaningfully use itself
(PHP itself rejects it), so return early when class_qn equals the
resolved trait QN.
- Snapshot reg->func_count before the loop so entries appended during
iteration are never revisited (defensive against the mutate-while-
iterating hazard in general).
Add regression test phplsp_trait_self_use_terminates: it hangs/OOMs on
the pre-fix code and passes after. Verified end to end -- the prod binary
now indexes the triggering file in <0.5s at ~6 MB RSS instead of running
away.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Mubariz <mubariz@sportimport.de>
Owner
|
Thanks for the PHP trait OOM fix and the minimal reproducer. Triage: high-priority parsing/stability bug. Review focus: the trait-copy loop must use a stable bound or visited guard so self-use and alias-back-to-self cases cannot grow the registry unboundedly, while ordinary trait flattening still works. The real-world OOM impact makes this release-sensitive. |
Owner
|
Merged — thank you for an outstanding report and fix. The differential reduction down to the 4-line reproducer, the two-layer fix (semantic short-circuit + snapshotted loop bound), and the confirmed-red-first regression test are model contributions. This closes a genuinely nasty OOM — much appreciated. |
This was referenced Jul 9, 2026
jfischer-motivity
pushed a commit
to jfischer-motivity/codebase-memory-mcp
that referenced
this pull request
Jul 9, 2026
Two reported indexer OOMs reduce to the same self-flattening loop the trait-uses-itself fix closed (self-flatten short-circuit + snapshotted loop bound in flatten_trait_into_class): - a trait whose name collides with an aliased `use ... as` import it composes (DeusData#765's exact single-file reproducer) — the alias canonicalizes onto the enclosing trait via the short-name fallback - a class sharing its short name with an aliased trait import (DeusData#951's reproducer, reduced to one file: the trait definition being unavailable is what forces the short-name fallback onto the class) Both fixtures were verified RED against the pre-guard code (each hangs the suite within the 90s alarm when the two guards are reverted, each independently as the first test run) and GREEN with the guards in place. Landing them as permanent regression guards; the issues close as fixed-by-the-DeusData#920-pair. Closes DeusData#765 Closes DeusData#951 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes an unbounded memory blow-up (observed 40 GB+, freezing the host)
when the PHP Hybrid-LSP pass indexes a file whose trait uses itself. It was
hit in a real project by a 4-line trait; the only workaround was to add the file
to
.cbmignore.Minimal reproducer (
index_repository,mode: "full"— runs away onmain):It also triggers via an alias that resolves back to the enclosing trait by short
name, which is how it appeared in the wild (
use Vendor\EnumTrait as X; use X;inside a trait that is itself named
EnumTrait).Root cause
flatten_trait_into_class()ininternal/cbm/lsp/php_lsp.ccopies a trait'smethods onto the using class by iterating the type registry with a live upper
bound:
cbm_registry_add_func()appends to the very array being iterated (no dedup) andincrements
reg->func_count. Normally the copies getreceiver_type = class_qn,which differs from the trait, so they don't re-match and the loop ends.
When a trait adopts itself,
class_qn == canonical_trait_qn, so every copiedmethod is tagged with the trait's own QN and re-matches the loop filter —
appending another method and pushing the bound out ahead of
i. The loop neverterminates, arena-allocating a fresh method each pass until memory is exhausted.
(Confirmed by differential reduction — self-adoption + ≥1 member + a trait
useare each necessary — and a live
sample(1)showingflatten_trait_into_class → cbm_arena_sprintfwith the footprint climbing through 6 GB.)Fix (
internal/cbm/lsp/php_lsp.c)(PHP raises "Trait X cannot use itself"), so return early when
class_qn == canonical_trait_qn. This removes the only path by which anappended func can re-match the filter.
loop are never revisited — closing the mutate-while-iterating hazard
structurally, independent of (1).
Checklist
git commit -s) — DCO trailer presentbuild/c/test-runner(ASan+UBSan): 5916 passed,1 skipped, 0 failures, 0 sanitizer errors;
php_lspsuite 279/279make -f Makefile.cbm lint-ci) — could not run locally(clang-format / clang-tidy / cppcheck not installed). Diff manually checked
against
.clang-format(LLVM, ColumnLimit 100: added lines ≤ 79, 4-spaceindent, attach braces) and mirrors the surrounding formatted code; please
let CI verify.
phplsp_trait_self_use_terminates, confirmed it hangs/OOMs on pre-fixcode and passes after the fix.
Verified end to end: the production binary now indexes the original triggering
file (and its real-world siblings) in < 0.5 s at ~6 MB RSS instead of running
away.