Skip to content

fix(#25): treat a forward into a PHP internal function as a use - #27

Merged
larspohlmann merged 1 commit into
developfrom
feature/25-internal-function-use
Sep 6, 2026
Merged

larspohlmann merged 1 commit into
developfrom
feature/25-internal-function-use

Conversation

@larspohlmann

Copy link
Copy Markdown
Owner

Closes #25

What & why

A method whose only touch of a parameter is handing it to a PHP built-in (trim, str_replace, parse_url, array_map, …) was scored as a pass-through hop, with the chain terminating external. But the value cannot travel any further inside user code once it enters a built-in — that method is the consumer. Counting it as a hop made every chain ending in a built-in one hop too long (see #25 for the measured impact).

The fix, in two layers:

  • CallResolver returns a new InternalFunctionUse resolution when a function is absent from the index and PHP-internal (function_exists + ReflectionFunction::isInternal). A function that is absent but not internal (vendor code, an unseen user helper) still terminates as external, since the value may forward on there.
  • ChainTraversal treats an InternalFunctionUse forward as a used terminal at the current method rather than a scored hop. An origin that only feeds its own parameter to a built-in is not tramp data and is dropped (no 0-hop findings).

Callback-taking built-ins (array_map, usort, call_user_func) are internal too, so the conservative "the argument is consumed" choice from #25 falls out for free.

Frozen-semantics flag: a forward into a PHP internal function is a use — one step stronger than the existing "external targets terminate" rule. Recorded by the internal-function-use fixture.

Real-world validation

On simple-feed-reader/backend (the codebase #25 measured), with its phptramp.dist.json (limit 4, warnLimit 3, minClasses 2):

findings
before (develop) 26
after (this branch) 13

Same result as --exclude-terminal external (13) — but by shortening chains, not dropping them. Verified every remaining finding's origin exists in the baseline: zero new chains introduced; the fix only shortens or removes.

Checklist

  • composer check is green (cs + stan + md + test)
  • composer infection:diff is green (MSI 95%; the one escaped diff-scoped mutant is a verified equivalent mutant — removing the guard continue leaves output byte-identical, confirmed by running the full suite with the mutation applied)
  • Semantic changes come with a fixture under tests/fixtures/ (internal-function-use)
  • Title is a conventional commit with the issue number
  • Targets develop (never main)

A method whose only touch of a parameter is handing it to a built-in
(trim, str_replace, parse_url, array_map, …) was scored as a pass-through
hop, with the chain terminating `external`. But the value cannot travel
further inside user code once it enters a built-in: that method is the
consumer. Counting it as a hop made every chain ending in a built-in one
hop too long.

CallResolver now returns a new InternalFunctionUse resolution when a
function is absent from the index *and* PHP-internal (function_exists +
ReflectionFunction::isInternal). A function that is absent but not
internal (vendor code, an unseen user helper) still terminates as
external, since the value may forward on there. ChainTraversal treats an
InternalFunctionUse forward as a `used` terminal at the current method
rather than a scored hop; an origin that only feeds its own parameter to
a built-in is not tramp data and is dropped. Callback-taking built-ins
(array_map, usort, call_user_func) are internal too, so the conservative
"argument is consumed" choice falls out for free.

Frozen-semantics flag: a forward into a PHP internal function is a use,
one step stronger than the existing "external targets terminate" rule.
Recorded by the internal-function-use fixture.

The lone escaped diff-scoped mutant (removing the guard `continue`) is a
verified equivalent mutant: the fall-through builds a discarded hop and
calls follow() with a resolution it ignores, so output is byte-identical
— confirmed by running the full suite with the mutation applied. MSI 95%.
@larspohlmann
larspohlmann merged commit 72c8d6c into develop Sep 6, 2026
5 checks passed
@larspohlmann
larspohlmann deleted the feature/25-internal-function-use branch September 6, 2026 05:47
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.

A call into a PHP internal function counts as a forward, so every chain ending in a built-in is one hop too long

1 participant