fix(lsp/php): prevent OOM when a PHP trait uses itself#920
Open
muba00 wants to merge 1 commit into
Open
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>
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.