Skip to content

Fixes GH-21768: ext/reflection: assertion failure on internal virtual p…#21769

Open
devnexen wants to merge 2 commits intophp:masterfrom
devnexen:gh21768
Open

Fixes GH-21768: ext/reflection: assertion failure on internal virtual p…#21769
devnexen wants to merge 2 commits intophp:masterfrom
devnexen:gh21768

Conversation

@devnexen
Copy link
Copy Markdown
Member

…roperties.

ReflectionProperty::isReadable() and isWritable() asserted prop->hooks being set whenever ZEND_ACC_VIRTUAL is set. Internal classes such as DOMDocument declare virtual properties backed by the object read/write handlers without hooks, tripping the assertion.

Assertion introduced in e4f727d.

@ndossche
Copy link
Copy Markdown
Member

ndossche commented Apr 16, 2026

So this solves the assertion failure. But I wonder what this new reflection method actually tests...

I.e. the following code will throw:

<?php
$dom = new DOMDocument;
$dom->doctype = null; // Error!

but it claims this property is writable:

<?php
$rc = new ReflectionClass($dom);
var_dump($rc->getProperty('doctype')->isWritable(null)); // bool(true)

@devnexen
Copy link
Copy Markdown
Member Author

Fair point, returning true for read-only props like doctype is misleading. I'm not sure what the right call is here.

Would it make more sense to fix this on the DOM side, giving these @virtual properties proper get-only hooks so reflection can answer correctly, rather than working
around it in reflection itself? Hooks were designed for this shape, and it'd also benefit anything else that introspects them. It's a bigger patch, but probably the
more correct direction.

As a narrow alternative for the crash-fix, we could throw ReflectionException whenever the property is ZEND_ACC_VIRTUAL without hooks, something like "Cannot
determine writability of virtual property DOMDocument::$doctype without property hooks." Keeps the method honest (correct bool or "don't know") without pretending to
know.

Happy to go either way, or hold off on this PR if you'd rather sort it at the DOM level.

@ndossche
Copy link
Copy Markdown
Member

ndossche commented Apr 16, 2026

The properties of dom that aren't writable are marked as readonly. This isn't really right as some dom manipulations can change those properties indirectly anyway. This is a leftover from back in the days.
It would be more correct to redeclare them as private(set) instead of readonly, in conjunction with this patch. This might break property redeclarations though so that's definitely master-only material.

devnexen added a commit to devnexen/php-src that referenced this pull request Apr 16, 2026
@readonly on DOM property stubs was documentation only and did not
translate to any runtime flag, so reflection reported the properties
as writable while the write_property handler threw on assignment.
Declaring them public private(set) lets the engine reject external
writes via the normal visibility check and lets ReflectionProperty::
isWritable() answer honestly.

Also drops @readonly from the three non-virtual HTMLCollection
$children properties (Element, DocumentFragment, Document) and
applies private(set) there as well.

Depends on the assertion removal from phpGH-21769 for isReadable() to
stop aborting on these properties.
@devnexen devnexen marked this pull request as ready for review April 16, 2026 16:04
Comment thread ext/reflection/php_reflection.c Outdated
if (prop->flags & ZEND_ACC_VIRTUAL) {
ZEND_ASSERT(prop->hooks);
if (!prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
if (prop->hooks && !prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
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.

I think the prop->hooks should be moved to the if (prop->flags & ZEND_ACC_VIRTUAL) { check, and then adjusting the prop->flags & ZEND_ACC_READONLY to return false for @readonly internal properties. I think it's safe to assume they will always be initialized internally. That should work for the existing cases like in ext-date.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks, that's a cleaner shape. Two questions before I go that way.

ndossche suggested on the same PR that DOM's formerly @readonly @virtual properties should be redeclared as public private(set) instead, and I prototyped that on a
companion branch (https://github.com/php/php-src/pull/XXXX if I open it, otherwise local for now). With private(set), isWritable(null) already returns false for
DOMDocument::$doctype without touching reflection, because the existing visibility check at ZEND_ACC_PPP_SET_MASK fires first.

Your approach and hers overlap in intent, so I'd rather pick one direction than merge both. Do you have a preference?

The second question is whether the @readonly doc tag should start emitting ZEND_ACC_READONLY for internal virtual properties in gen_stub.php, since currently it's
documentation only. Without that, your short-circuit wouldn't fire on DOM because the flag isn't set. If we go your way, I'd add that to gen_stub.php alongside the
reflection change, but it would also apply the flag to a few non-virtual HTMLCollection $children cases, which needs a look to confirm the runtime readonly check
doesn't surprise anything.

And yes, I'll add Fixes GH-21768 to the PR body, sorry about the missing link.

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.

There are legitimate uses of virtual+readonly, even if dom is not one. So I think both should be fixed.

Copy link
Copy Markdown
Member

@iluuu1994 iluuu1994 Apr 17, 2026

Choose a reason for hiding this comment

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

I don't know the historical reason for @readonly and whether they can just be converted to readonly. But if possible that sounds reasonable. (Edit: I guess a bit weird for virtual properties.)

@iluuu1994
Copy link
Copy Markdown
Member

@devnexen Btw, please also add "Fixes GH-xyz" to the description of your PRs. For some reason, GH does not properly link them when this text only appears in the title. See GH-21768, where this PR isn't listed under "Development".

…ual properties.

ReflectionProperty::isReadable() and isWritable() asserted prop->hooks
being set whenever ZEND_ACC_VIRTUAL is set. Internal classes such as
DOMDocument declare virtual properties backed by the object read/write
handlers without hooks, tripping the assertion.

Assertion introduced in e4f727d.
@devnexen devnexen changed the title Fix GH-21768: ext/reflection: assertion failure on internal virtual p… Fixes GH-21768: ext/reflection: assertion failure on internal virtual p… Apr 17, 2026
@devnexen devnexen linked an issue Apr 17, 2026 that may be closed by this pull request
…able().

Follow-up to review feedback from @iluuu1994 on phpGH-21769. Gate the
ZEND_ACC_VIRTUAL branch on prop->hooks being set, and short-circuit
virtual properties that also carry ZEND_ACC_READONLY to RETURN_FALSE
(no backing slot, treat as always initialized). Covers legitimate
virtual+readonly uses outside of DOM.
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.

Assertion failure at ext/reflection/php_reflection.c

3 participants