Skip to content

Do not narrow the falsey branch of a nested isset() at its intermediate offset - #6175

Merged
staabm merged 1 commit into
phpstan:2.2.xfrom
SanderMuller:fix-isset-nested-offset-leak
Aug 3, 2026
Merged

Do not narrow the falsey branch of a nested isset() at its intermediate offset#6175
staabm merged 1 commit into
phpstan:2.2.xfrom
SanderMuller:fix-isset-nested-offset-leak

Conversation

@SanderMuller

Copy link
Copy Markdown
Contributor

Since 2.1.40 (#4983), isset() on a nested array offset narrows the intermediate offset in the falsey branch, and that narrowing leaks the offset's existence into the enclosing scope. After isset($r['K']['Port']), $r['K'] is treated as definitely existing for the rest of the scope, so $r['K'] ?? null loses its |null.

The falsey-branch narrowing block in IssetHandler::specifyTypes() was written for the single-level case, where the isset target's var is a plain variable or property. For a nested isset like isset($r['K']['Port']), the target's var is itself an ArrayDimFetch ($r['K']), an offset that may not exist, and narrowing that intermediate offset in the falsey branch is what leaks its existence outward.

This skips the block when the target's var is itself an offset access, which is the case that leaks. Single-level isset (variable or property base) is unchanged, so the narrowing #4983 added still applies where it is sound.

This is the minimal fix. #6110 addresses the same issue by keeping the narrowing where the intermediate offset is known to exist (isAlwaysSet()); this PR is the smaller alternative, so you can compare the CI impact of both.

The regression test covers the reporter's cases (ternary and plain if), deeper nesting, a property base, and the ?? and single-level controls that were already correct.

Closes phpstan/phpstan#15005

…te offset

The falsey-branch dim-fetch narrowing in IssetHandler::specifyTypes() was
written for a single-level isset(), where the target's var is a plain variable
or property. For a nested isset() like isset($r['K']['Port']), the target's var
is itself an ArrayDimFetch ($r['K']) that may not exist, and narrowing that
intermediate offset in the falsey branch leaks its existence into the enclosing
scope, so $r['K'] ?? null lost its |null after the check.

Skip the block when the var is itself an offset access. Single-level isset()
keeps the narrowing phpstan#4983 added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@staabm
staabm requested a review from VincentLanglet August 3, 2026 09:11

@staabm staabm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the fix does not break exisiting tests and fixes a false positive, I think we should go for it

@staabm
staabm merged commit 6cd8f70 into phpstan:2.2.x Aug 3, 2026
742 of 747 checks passed
@staabm

staabm commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

thank you @SanderMuller

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.

isset() on a nested array offset narrows the falsey branch to never, leaking the intermediate offset's existence (regression in 2.1.40)

3 participants