Skip to content

Fix GH-12695: skip __get() when __isset() materialised the property#22181

Open
nicolas-grekas wants to merge 1 commit into
php:masterfrom
nicolas-grekas:gh12695-isset-materialization-fix
Open

Fix GH-12695: skip __get() when __isset() materialised the property#22181
nicolas-grekas wants to merge 1 commit into
php:masterfrom
nicolas-grekas:gh12695-isset-materialization-fix

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Contributor

Fixes #12695.

After __isset() returns true under ?? or empty(), the engine re-checks the property table before calling __get(). When __isset() has materialised the property (a pattern used by lazy proxies), its value is returned directly and __get() is skipped.

isset() is unchanged (it never called __get()).

Tests under Zend/tests/magic_methods/gh12695*.phpt cover:

  • the fix for ?? and empty()
  • non-regression: when __isset() doesn't materialise, __get() is still called and ??'s null-check still applies
  • isset()'s unchanged behaviour

Copy link
Copy Markdown
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

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

Looks functionally correct. I'd be happy merging this to master if the rest of internals are happy.

Comment thread Zend/zend_object_handlers.c Outdated
Comment thread Zend/zend_object_handlers.c Outdated
Comment thread Zend/zend_object_handlers.c Outdated
@nicolas-grekas nicolas-grekas force-pushed the gh12695-isset-materialization-fix branch from 1c11fe3 to 5d92fdc Compare May 29, 2026 14:17
Copy link
Copy Markdown
Member

@arnaud-lb arnaud-lb left a comment

Choose a reason for hiding this comment

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

This looks right to me otherwise

Comment thread Zend/zend_object_handlers.c
goto exit;
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: for clarity I suggest to add a ZEND_UNREACHABLE() branch here:

Suggested change
}
} else {
ZEND_UNREACHABLE();
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I checked and WRONG_PROPERTY_OFFSET can actually reach this point: the magic-isset block only filters on BP_VAR_IS and __isset being defined, not on the offset type, and IS_WRONG_PROPERTY_OFFSET is handled later in the call_getter branch (around line 1003). Adding ZEND_UNREACHABLE() here would assert in debug builds whenever someone uses $obj->privateField ?? $y on a class with __isset defined. So leaving the fall-through to retval = &EG(uninitialized_zval); as-is, which is correct for that case.

Comment thread Zend/zend_object_handlers.c Outdated
@nicolas-grekas nicolas-grekas force-pushed the gh12695-isset-materialization-fix branch from 5d92fdc to 0e4cd8a Compare May 29, 2026 14:33
After __isset() returns true under ?? or empty(), re-check the
property table before calling __get(). When __isset() materialised
the property (a pattern used by lazy proxies), its value is returned
directly. isset() itself is unchanged.

Closes phpGH-12695.
@nicolas-grekas nicolas-grekas force-pushed the gh12695-isset-materialization-fix branch from 0e4cd8a to c47e4ec Compare May 29, 2026 14:46
Copy link
Copy Markdown
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

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

Thanks! LGTM. Given this is on verge of bug and feature, it would be good to post on the list again in a separate thread about this PR, and clarify that this is proposed to be merged unless there are objections. We usually wait for at least 2 weeks for such changes to be merged. If there are concerns, this will need to go through the RFC process nonetheless.

Copy link
Copy Markdown
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

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

Only looked at the tests and NEWS/UPGRADING. I trust the implementation review of Arnaud and Ilija.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this also test the case where __isset() materializes to null for completeness?

Comment on lines +10 to +21
class C {
public function __get($n) {
echo " __get($n)\n";
return 'from-get';
}
public function __isset($n) {
echo " __isset($n)\n";
return true;
}
}

echo "1) `??` when __isset=true and __get returns a value: __get is called:\n";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
class C {
public function __get($n) {
echo " __get($n)\n";
return 'from-get';
}
public function __isset($n) {
echo " __isset($n)\n";
return true;
}
}
echo "1) `??` when __isset=true and __get returns a value: __get is called:\n";
echo "1) `??` when __isset=true and __get returns a value: __get is called:\n";
class C {
public function __get($n) {
echo " __get($n)\n";
return 'from-get';
}
public function __isset($n) {
echo " __isset($n)\n";
return true;
}
}

Minor formatting nit: The other cases define the class after the echo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrong magic methods sequence with ?? operator

4 participants