E0045 reports loops that retain memory in proportion to an input-sized collection. It distinguishes cumulative allocation from live heap: allocating temporary values is not the same as keeping them reachable until the request or worker finishes.
array_unshift(), where the cumulative
allocation is quadratic even when the final array is linearThe finding reports the symbolic retained growth, cumulative allocation, and
peak live heap. Retained size covers memory added by the loop; pre-existing
input arrays are included in the peak estimate but not in the reported retained
growth. For example, a retained array may be described as
count($items) * bytes($items); this is a symbolic estimate, not a measurement
of PHP’s internal zval or hashtable layout.
E0045 does not run a garbage collector or know when PHP will free a value. It uses conservative reachability rules and the following simplifications:
fgets(), fgetcsv(), fgetss(), fgetc(), fread(),
fscanf(), and stream_get_line()) are treated as unbounded input. Streaming
without retention is safe; retaining stream values is reported as
input-dependent live-heap growthretained_element_bytes sets the minimum known element size that
is treated as large retentionunset() in dynamic scopes, and object lifetimes that cross
function boundaries are not modeled preciselyThe rule is deliberately conservative around unknown callees: an unknown call with a tracked argument may invalidate that value’s growth information instead of claiming that memory is released. A callee summary only contributes retained growth when the retained value is derived from one of the callee’s parameters. Helpers that throw fresh exceptions or return unrelated temporary objects do not make their callers retain input-sized data.
When retained growth is unknown, the rule requires direct local accumulation or
cumulative allocation at or above heap_alloc_threshold. Validation and
predicate loops that only call helpers and discard their results are therefore
not reported as live-heap growth. A discarded helper return value does not
contribute retained growth, but in-place mutators such as array_push() still
count as local accumulation.
rules:
E0045:
runtime_threshold: quadratic
heap_alloc_threshold: quadratic
retained_element_bytes: 32
report_input_cardinality: true
include_cli_input: false
max_findings_per_function: 20
heap_alloc_threshold controls when cumulative allocation is reported.
retained_element_bytes is the estimated per-element size used when the element
type is unknown. Lowering it makes retention findings more sensitive.